-------------201504161039更新-------------

更新内容:

  1. IWaitForm接口删除System.Windows.Forms.DialogResult DialogResult属性。即隐藏等待窗体的方式不再分为设置DialogResult调用Hide()两种,改为仅调用Hide()一种,简化设计。由于Hide()属于访问控件,执行器需根据自身是否会跨线程调用该方法而做出相应处理
  2. WaitUI增加私有方法HideWaitForm,用于隐藏等待窗体(由于会在后台线程调用该方法,故内部有跨线程处理),替代原来的设置DialogResult的做法
  3. WaitForm的FormClosing事件由注册该事件改为重写OnFormClosing方法,对阻止窗体关闭的条件增加了Visible,即当窗体处于可见时,才会阻止窗体关闭和触发UserCancelling事件,这是为了更准确的区分是执行器调用Hide()隐藏等待窗体,还是用户关闭等待窗体,仅通过e.CloseReason是不可靠的,因为当用户点过关闭按钮后,e.CloseReason就会是UserClosing,稍后执行器在调用Hide隐藏窗体时,仍然会进入OnFormClosing,此时e.CloseReason仍然是UserClosing,就会再次触发UserCancelling事件,虽然没什么影响,但实属不应该,加了Visible的话,执行器Hide窗体后,Visible就为false,就不会再次触发UserCancelling事件。当然,仍然建议自定义等待窗体屏蔽关闭按钮,让用户只能通过点击取消控件来取消任务,就没那么多事了。但不建议通过把ControlBox=false来整个隐藏右上角那仨按钮,因为我始终认为要给用户最小化的权利,我作为用户使用其它软件的时候,是很痛恨这种限制的
  4. 等待窗体的【取消】按钮单击后不再将Enabled置为false。原因是在基于BackgroundWorker的方案中,等待窗体Hide后有可能再次ShowDialog,也就是再次执行任务时依然要保证可取消
  5. WaitFormNullException异常的定义移至WaitForm.cs文件中(原先在WaitUI.cs中)。原因是等待窗体相关的东西应该与执行器保持独立

-------------20150415原文(已更新)-------------

适用环境:.net 2.0+的Winform项目。

先解释一下我所谓的【带等待窗体的任务执行器】是个什么鬼,就是可以用该类执行任意耗时方法(下文将把被执行的方法称为任务任务方法),执行期间会显示一个模式等待窗体,让用户知道任务正在得到执行,程序并没有卡死。先看一下效果:

【C#】分享带等待窗体的任务执行器一枚

功能:

  • 等待窗体可以使用执行器自带的默认窗体(就上图的样子),嫌丑你也可以使用自己精心设计的窗体,甚至基于Devexpress、C1等第三方漂亮窗体打造也是完全可以的
  • 在任务中可以更新等待窗体上的Label、ProgressBar之类的控件以提供进度反馈。懒得反馈的话,就默认“请稍候...”+Marquee式滚动
  • 如果任务允许被终止,用户可以通过某些操作终止任务执行(例如点击上图中的【取消】按钮);如果不允许,你可以把取消按钮隐藏了,或者在任务中不响应用户的终止请求就好
  • 任务的执行结果(包括ref/out参数)、是否出现异常、是否被取消等情况都可以得到

原理:

  • 调用任务所属委托的BeginInvoke,让任务在后台线程执行,随即在UI线程(通常就是主线程)调用等待窗体的ShowDialog弹出模式窗体,让用户知道任务正在执行的同时阻止用户进行其他操作。由于任务和等待窗体分别在不同的线程跑,所以等待窗体不会被卡住
  • 任务执行期间可以通过执行器提供的一组属性和方法操作等待窗体上的控件,这组属性和方法内部是通过调用等待窗体的Invoke或BeginInovke对控件进行操作,实现跨线程访问控件
  • 任务执行期间用户可以通过点击等待窗体上的【取消】按钮(如果你让它显示的话)或点击右上角关闭按钮发出终止任务的请求(等待窗体会拦截关闭操作),其结果是执行器的UserCancelling属性会置为true,所以在任务中可以访问该属性得知用户是否请求了取消操作,如果你同意终止的话,需设置执行器的Cancelled=true,并随即return出任务方法
  • 任务执行完后(无论成功、异常、取消)会自动进入异步回调方法,回调方法中会首先访问Cancelled获知任务是否已取消,如果已取消,则直接return出回调方法;如果未取消,则调用任务所属委托的EndInvoke得到任务执行结果或异常。最后不管取消与否,finally块中会调用HideWaitForm(),以确保关闭等待窗体
  • 等待窗体关闭后,执行器会继续执行ShowDialog后面的语句。如果任务已取消,则抛出特定异常报告调用者;如果任务存在异常,则抛出该异常;以上情况都不存在的话,返回任务结果

如述,功能简单,实现容易,我只是把这种需求通用化了一下,让还没有类似轮子的朋友可以拿去就用。另外还有个基于BackgroundWorker的实现方式,我可能会在下一篇文章分享。

先看一下大致的使用示例:

//WaitUI就是执行器
private void button1_Click(object sender, EventArgs es)
{
    //可检测执行器是否正在执行另一个任务。其实基本不可能出现IsBusy=true,因为执行器工作时,用户做不了其它事
    //老实说这个IsBusy要不要公开我还纠结了一下,因为公开了没什么用,但也没什么坏处,因为setter是private的
    //Whatever~最后我还是选择公开,可能~因为爱情
    //if (WaitUI.IsBusy) { return; }

    try
    {
        //WaitUI.RunXXX方法用于执行任务
        //该方法的返回值就是任务的返回值
        //任务抛出的异常会通过RunXXX方法抛出

        //WaitUI.RunAction(Foo, 33, 66);                                      //执行无返回值的方法
        int r = WaitUI.RunFunc(Foo, 33, 66);                                  //执行有返回值的方法
        //object r = WaitUI.RunDelegate(new Func<int, int, int>(Foo), 33, 66);//执行委托
        //WaitUI.RunAction(new MyWaitForm(), Foo);//指定自定义等待窗体执行任务,几个RunXXX方法都有可指定自定义窗体的重载

        MessageBox.Show("任务完成。" + r);
    }
    catch (WorkCancelledException)//任务被取消是通过抛出该异常来报告
    {
        MessageBox.Show("任务已取消!");
    }
    catch (Exception ex)//任务抛出的异常
    {
        MessageBox.Show("任务出现异常!" + ex.Message);
    }
}

//耗时任务。因为该方法会在后台线程执行,所以方法中不可以有访问控件的代码
int Foo(int a, int b)
{
    //可以通过执行器的一系列公开属性和方法间接操作等待窗体的UI元素
    WaitUI.CancelControlVisible = true;//设置取消任务的控件的可见性,即是否允许用户取消任务(默认是false:不可见)
    WaitUI.BarStyle = ProgressBarStyle.Continuous;//设置滚动条样式(默认是Marquee:循环梭动式)
    WaitUI.BarMaximum = 100;      //设置滚动条值上限(默认是100)
    WaitUI.BarMinimum = 0;        //设置滚动条值下限(默认是0)
    WaitUI.BarStep = 1;           //设置滚动条步进幅度(默认是10)
    WaitUI.BarVisible = true;     //设置滚动条是否可见(默认是true:可见)

    int i;
    for (i = a; i < b; i++)
    {
        if (WaitUI.UserCancelling)//响应用户的取消请求
        {
            WaitUI.Cancelled = true;//告诉执行器任务已取消
            return 0;
        }

        //可以抛个异常试试
        //if (i == 43) { throw new NotSupportedException("异常测试"); }

        //可以随时再次操作等待窗体的各种UI元素
        //if (i % 10 == 0) { WaitUI.CancelControlVisible = false; }   //隐藏取消控件
        //else if (i % 5 == 0) { WaitUI.CancelControlVisible = true; }//显示取消控件
        WaitUI.WorkMessage = "正在XXOO,已完成 " + i + " 下..."; //更新进度描述
        WaitUI.BarValue = i;//更新进度值
        //WaitUI.BarPerformStep();//步进进度条

        Thread.Sleep(50);
    }
    return i;
}
使用示例

相关文章:

  • 2022-12-23
  • 2022-12-23
  • 2022-02-03
  • 2021-06-02
  • 2022-12-23
  • 2021-12-21
  • 2022-12-23
  • 2021-12-24
猜你喜欢
  • 2022-12-23
  • 2022-12-23
  • 2021-06-10
  • 2022-12-23
  • 2022-12-23
  • 2022-01-18
  • 2022-01-19
相关资源
相似解决方案