在winform中使用进度条
- 被操作对象如何提供其处理进度
- 窗体与线程问题
- 回归UI线程
详细说明
- 被操作对象如何提供其处理进度
一般来说,对于任务一般都是一个方法,一旦其执行完了则也就结束。但是如果需要在其执行过程中将执行的进度汇报给UI线程其相当于是在执行过程中不段的去引发一个事件,通过事件的执行来讲任务进度汇报给UI主线程。
定义事件参数类
事件参数类主要是将任务的进度变量传递给事件进行处理
// 定义事件的参数类 public class ValueEventArgs : EventArgs { public int Value { set; get;} }
定义事件委托
通过将事件参数类传递给事件委托。
// 定义事件使用的委托 public delegate void ValueChangedEventHandler( object sender, ValueEventArgs e);
定义业务对象,包含需要长时间执行的方法
class LongTimeWork
{
// 定义一个事件来提示界面工作的进度
public event ValueChangedEventHandler ValueChanged;// 触发事件的方法
protected void OnValueChanged( ValueEventArgs e)
{
if( this.ValueChanged != null)
{
this.ValueChanged( this, e);
}
}public void LongTimeMethod()
{
for (int i = 0; i < 100; i++)
{
// 进行工作
System.Threading.Thread.Sleep(1000);// 触发事件 ValueEventArgs e = new ValueEventArgs() { Value = i+1}; this.OnValueChanged(e); }
}
}
在业务对象定义中包括一个事件(ValueChanged),包括引发事件的方法(OnValueChanged),和需要长时间执行的方法(LongTimeMethod)。在长时间执行方法中通过OnChanged来引发事件,将事件参数类传递给事件处理方法。
窗体与线程问题
如果仅仅通过上的处理实际操作中会阻塞UI主线程,因为界面的操作实际是运行在一个线程上。在这个线程上进行消息循环,不断的响应外部的操作。但是如果在响应过程中始终仅仅只是执行一个方法则会导致无法继续进行消息循环。比如,执行上面的LongTimeMethod方法,需要花费相当长的时间,这样就会导致UI主线程被阻塞,无法响应外部的操作。
解决这种情况的办法就是采用异步编程的方法,将LongTimeMethod这个方法放在另外开放的线程中去执行,而不是放在UI主线程中。在.net中实现异步操作的基本方法就是使用委托,通过委托的BeginInvok来另外开辟线程去执行委托方法。
//开始异步操作 private void button1_Click(object sender, EventArgs e) { // 禁用按钮 this.button1.Enabled = false; // 实例化业务对象 LongTime.Business.LongTimeWork workder = new Business.LongTimeWork(); workder.ValueChanged += new Business.ValueChangedEventHandler(workder_ValueChanged); // 使用异步方式调用长时间的方法 Action handler = new Action(workder.LongTimeMethod); handler.BeginInvoke( new AsyncCallback(this.AsyncCallback), handler ); }
// 结束异步操作 private void AsyncCallback(IAsyncResult ar) { // 标准的处理步骤 Action handler = ar.AsyncState as Action; handler.EndInvoke(ar); MessageBox.Show("工作完成!"); this.button1.Enabled = true; }
上面介绍异步委托的方法是直接在BeginInvoke中传入用户自定义变量(此次的自定义变量直接就传入了调用委托),然后在回调函数中通过ar的AsyncState属性获取开始委托中的用户自定义变量,使用委托的EndInvoke方法结束异步操作。
另外的结束异步委托的方法是直接通过使用回调函数的输入参数IAsyncResult接口,将IAsyncResult接口转换成AsyncResult类,调用AsyncResult的AsyncDelegate委托可以获取引发异步操作开始的委托,通过这个委托来结束异步调用。如下代码所示:
// 结束异步操作 private void AsyncCallback(IAsyncResult ar) { // 标准的处理步骤 AsyncResult handler_Result = ar as AsyncResult; Action handler=handler_Result as AsyncResult.AsyncDelegate; handler.EndInvoke(ar); MessageBox.Show("工作完成!"); this.button1.Enabled = true; }
- 回到UI主线程
在运行程序过程中,会报异常。具体原因为:操作进度条的线程并不是UI主线程,winform规定对于在UI显示界面的操作必须是在UI主线程中进行操作,不允许进行跨线程操作UI界面元素。所以需要使用委托的方式进行,使用什么委托呢?是委托都可以,Windows Forms 中提供了一个专用的委托,可以考虑使用一下。
public delegate void MethodInvoker()
不过,也有可能我们的线程与 UI 的线程正好是同一个线程,那我们就没有必要这么麻烦了,Control 还定义了一个属性 InvokeRequired 用来检查是否在同一个线程之上,不是则返回真,需要使用委托进行,否则返回假,可以直接处理控件。
[BrowsableAttribute(false)] public bool InvokeRequired { get; }
具体操作如下:
// 进度发生变化之后的回调方法 private void workder_ValueChanged(object sender, Business.ValueEventArgs e) { System.Windows.Forms.MethodInvoker invoker = ()=>this.progressBar1.Value = e.Value; if (this.progressBar1.InvokeRequired) { this.progressBar1.Invoke(invoker); } else { invoker(); } }
// 结束异步操作 private void AsyncCallback(IAsyncResult ar) { // 标准的处理步骤 Action handler = ar.AsyncState as Action; handler.EndInvoke(ar); MessageBox.Show("工作完成!"); // 重新启用按钮 System.Windows.Forms.MethodInvoker invoker = ()=>this.button1.Enabled = true; if (this.InvokeRequired) { this.Invoke(invoker); } else { invoker(); } }