带有进度条的通用桌面闪屏对话框






4.88/5 (4投票s)
一个简单的通用桌面闪屏对话框,
引言
在许多 Windows 应用程序中,经常需要使用耗时的过程(数据库查询、远程函数调用等)。为了向用户提供反馈,并在最佳情况下提供操作的进度状态,合理的方法是显示一种在耗时操作结束时自动关闭的消息对话框。
在我的某些项目中,我使用了一种带有进度条的启动对话框。这种启动对话框的巧妙之处在于:它可以通用地使用,适用于任何类型的操作。它可以作为欢迎屏幕,也可以作为长期操作的状态对话框。
使用委托(.NET 中函数指针的实现),可以相当容易地开发一个通用的启动屏幕,将指向耗时函数的指针作为其参数之一传递。
背景
启动对话框被定义为一个对话框窗体,具有自定义属性,属性类型为对象数组,还具有执行任务的委托函数以及对象类型的 Result 属性。委托回调实现了 好莱坞原则 设计模式,这对于 GUI 对话框非常有用。传递的函数作为 BackgroundWorker
任务执行,并启用了 WorkerSupportsCancellation 和 WorkerReportsProgress。
此示例应用程序使用 ContosoUniversity 数据库和从 ASP.NET 文章 "使用 ASP.NET MVC 应用程序中的实体框架进行代码优先迁移和部署" 中获取的数据访问层库的一部分。
耗时函数在这里实现为一个作业,用于收集用于详细学生列表的数据库项目。由于 ContosoUniversity 中的记录很少,因此工作线程在每次迭代中都会停止半秒钟,以模拟一个非常长的操作。
此外,我还使用了 Troy Goode 的 PagedList NuGet 包。
附带的源代码是 Microsoft Visual Studio 2013 C# 项目。
使用代码
耗时函数 GetDetailedStudentList
定义在名为 DataHelper
的类中
public class DataHelper : IDisposable
{
/// <summary>
/// DbContext from ContosoUniversity
/// </summary>
private SchoolContext ctx = new SchoolContext();
public DataHelper()
{
ctx = new SchoolContext();
}
/// <summary>
/// The time-consuming function
/// </summary>
/// <param name="size">Page size</param>
/// <param name="page">Current page number</param>
/// <param name="worker">Backgroundworker instance for reporting to</param>
/// <param name="e">Backgroundworker event to generate</param>
/// <returns></returns>
public PagedListResult<DetailedStudentInfo> GetDetailedStudentList(int size, int page, BackgroundWorker worker, DoWorkEventArgs e)
{
PagedListResult<DetailedStudentInfo> retValue = new PagedListResult<DetailedStudentInfo>();
try
{
int counter = 0;
int highestPercentageReached = 0;
retValue.Total = ctx.Students.Count();
var tmpList = ctx.Students
.OrderBy(o => o.LastName)
.ToPagedList(page, size)
.ToList()
.ConvertAll(new Converter<Student, DetailedStudentInfo>(
delegate(Student student)
{
return new DetailedStudentInfo(student);
}));
foreach (var student in tmpList)
{
//check for Cancel button has been clicked
if (worker.CancellationPending)
{
e.Cancel = true;
break;
}
else
{
//calculate current progress status
int perc = (int)((float)counter++*100/(float)retValue.Total);
if (perc > highestPercentageReached)
{
highestPercentageReached = perc;
worker.ReportProgress(perc);
}
student.StudentCourses = ctx.Enrollments
.Where(t => t.StudentID == student.StudentObject.ID)
.Select(c => c.Course).ToList();
foreach (var course in student.StudentCourses)
{
student.StudentInstructors.AddRange(course.Instructors);
}
}
//for simulating a very long operation
Thread.Sleep(500);
}
retValue.ResultPagedList = tmpList;
}
catch (Exception)
{
e.Cancel = true;
}
return retValue;
}
public void Dispose()
{
if (ctx != null)
{
ctx.Dispose();
}
}
}
SplashForm
实现为具有一些自定义属性的标准窗体。
using System;
using System.ComponentModel;
using System.Windows.Forms;
namespace SplashDemo
{
/// <summary>
/// The time-consuming function delegate
/// </summary>
/// <param name="state">The time-consuming function parameter array</param>
/// <param name="worker">The calling worker object</param>
/// <param name="e">The calling worker event</param>
/// <returns></returns>
public delegate object SplashCallBack(object[] state, BackgroundWorker worker, DoWorkEventArgs e);
/// <summary>
/// An universal Desktop Splash Dialog with Progressbar
/// </summary>
public partial class SplashForm : Form
{
private readonly SplashCallBack _splashCallback;
private readonly object[] _stateParams;
public object ResultObject { get; private set; }
public Exception ExError { get; private set; }
public SplashForm()
{
InitializeComponent();
}
/// <summary>
/// Contructor overload
/// </summary>
/// <param name="callback">The time-consuming function call</param>
/// <param name="state">The time-consuming function parameters</param>
/// <param name="title">Dialog title</param>
/// <param name="message">Text message to display</param>
public SplashForm(SplashCallBack callback, object[] state, string title, string message)
: this()
{
this.Text = title;
this.TextBoxMessage.Text = message;
this._splashCallback = callback;
this._stateParams = state;
}
private void SplashForm_Shown(object sender, EventArgs e)
{
this.SplashProgressBar.Visible = true;
backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
//call the callback function delegate
e.Result = this._splashCallback(_stateParams, worker, e);
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.SplashProgressBar.Value = e.ProgressPercentage;
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
this.SplashProgressBar.Visible = false;
if (e.Cancelled == true)
{
this.ExError = new Exception("Cancelled by User!");
this.DialogResult = System.Windows.Forms.DialogResult.Cancel;
}
else if (e.Error != null)
{
this.ExError = e.Error;
this.DialogResult = System.Windows.Forms.DialogResult.Abort;
}
else
{
this.ResultObject = e.Result;
this.ExError = null;
this.DialogResult = System.Windows.Forms.DialogResult.OK;
}
this.Close();
}
private void ButtonCancel_Click(object sender, EventArgs e)
{
if (backgroundWorker1.WorkerSupportsCancellation == true)
{
// Cancel the asynchronous operation.
backgroundWorker1.CancelAsync();
}
}
}
}
关键在于在命名空间 SplashDemo
中定义的委托定义以及自定义属性 _splashCallback
和 _stateParams
。耗时函数的返回值将存储在 ResultObject
属性中。发生的错误存储在 ExError
属性中。
public delegate object SplashCallBack(object[] state, BackgroundWorker worker, DoWorkEventArgs e);
public partial class SplashForm : Form
{
private readonly SplashCallBack _splashCallback;
private readonly object[] _stateParams;
public object ResultObject { get; private set; }
public Exception ExError { get; private set; }
私有属性以及标题和消息文本由构造函数重载设置。
public SplashForm(SplashCallBack callback, object[] state, string title, string message)
: this()
{
this.Text = title;
this.TextBoxMessage.Text = message;
this._splashCallback = callback;
this._stateParams = state;
}
耗时函数调用在主窗体中启动,通过创建 SplashForm
实例,将函数定义和所需的参数作为对象数组传递 - 仅通过调用重载的 SplashForm
构造函数即可。
/// <summary>
/// Execute the time-consuming function by SplashForm
/// </summary>
private void RefreshForm()
{
dataGridView1.DataSource = null;
this.dataGridView1.Refresh();
object[] stateParams = new object[] { this.PageSize, this.CurrentPage };
using (SplashForm splash = new SplashForm(RunSplashCallbackFunction,
stateParams, "Please wait", "Reading Data..."))
{
var dlgRetValue = splash.ShowDialog(this);
switch (dlgRetValue)
{
case DialogResult.OK:
var ret = (PagedListResult<DetailedStudentInfo>)splash.ResultObject;
this.StudentList = ret.ResultPagedList;
this.TotalPages = (int)Math.Ceiling((double)ret.Total / this.PageSize);
StatusLabel.Text = String.Format(
"Received a record list of {0} records. Displaying {1} rows of page {2}",
ret.Total,
ret.ResultPagedList.Count,
this.CurrentPage);
ReloadGridView();
break;
case DialogResult.Cancel:
StatusLabel.Text = "Operation cancelled by the user.";
break;
default:
StatusLabel.Text = String.Format(
"Error: {0}",
((Exception)splash.ExError).Message);
break;
}
}
}
/// <summary>
/// The function to be referred as a delegate
/// </summary>
/// <param name="state">Parameter array</param>
/// <returns>Object (as PagedListResult)</returns>
private static object RunSplashCallbackFunction(object[] state, BackgroundWorker worker, DoWorkEventArgs e)
{
if (state.Length != 2)
throw new Exception("Parameter count mismatch!");
int page = (int)state[1];
int size = (int)state[0];
using (DataHelper dbh = new DataHelper())
{
return dbh.GetDetailedStudentList(size, page, worker, e);
}
}
可以通过返回后将 ResultObject
属性转换为适当的类型来检索结果。
var ret = (PagedListResult<DetailedStudentInfo>)splash.ResultObject;
可以以相同的方式执行其他耗时作业,只需定义符合 SplashCallback
委托语法的新的函数即可。如果您的耗时函数没有迭代步骤,只需将 SplashProgressBar
样式更改为 Marquee 即可。
关注点
这是一个使用委托函数和好莱坞原则设计模式的有趣示例。
历史
- 2014 年 10 月 29 日:初始发布