【C#】带等待窗体的BackgroundWorker
---------------201504170911更新---------------
更新内容:删除bgwUI新增的Start方法,改为通过new修饰符+可选参数的方式同时覆盖基类(BackgroundWorker)的RunWorkerAsync有参和无参俩方法。所以执行任务仍旧使用熟悉的RunWorkerAsync即可,忘掉蹩脚的Start。在此要感谢园友【新的开始】在评论中的指点,非常感谢!
---------------20150416原文(已更新)---------------
适用环境:.net 2.0+的Winform项目
这是上一篇【分享带等待窗体的任务执行器一枚】的姊妹篇,建议先看看那篇文章了解一下相关背景。这里简单介绍一下,两个方案的共同目的都是在执行耗时任务时向用户显示一个模式窗体(我称等待窗体),通过该窗体,任务可以向用户报告执行进度,用户也可以通过它干预任务的执行(也就是取消~如果任务允许被终止的话),等于就是在任务与用户之间通过一个等待窗体来进行信息传递。这样的需求应该是很常见的,注重用户体验的开发者都不可能让用户眼巴巴的面对一个卡死掉的界面,所以相信在类似场景中,大家都有各自的处理手段,例如异步执行任务,同时在业务窗体上弄个滚动条什么的,比如这样:
这样的手段有的猿友可能已经形成了很完善的通用方案,比我这个好上百倍都不止(在此也恳请路过老鸟不吝分享自己的或自己知道的现成好方案),有的猿友则可能还是具体情况具体处理,没有一个通用方案,而我在做的,就是把我的方案分享出来,让还没有类似轮子的猿友拿去后,经过简单处理就能实现效果,同时,也希望得到老鸟的指点,不断完善。
上一篇分享的是一个叫做WaitUI的执行器,可以执行任何方法,使用简单。而这一篇分享的是一个叫做BackgroundWorkerUI的东东(下文简称bgwUI),看名字就知道它是基于BackgroundWorker(下文可能简称bgw)组件实现的,所以如果你更习惯bgw的使用方式,这个适合你。先看一下使用效果:
功能:
- 在bgwUI执行任务期间(DoWork事件)显示一个等待窗体,任务执行完成后自动消失。任务执行完是指DoWork事件跑完,而不是RunWorkerCompleted事件完,也就是RunWorkerCompleted执行期间已经没有等待窗体了
- 等待窗体可以自定义,但须实现IWaitForm接口
- 在DoWork事件中可以访问一组bgwUI提供的属性和方法更新等待窗体上的文本和进度,以及可以控制等待窗体上的【取消】按钮是否可见。是的,更新控件不需要再用ProgressChanged事件,事实上等待窗体实例(一个IWaitForm实例)对调用者是隐藏的,你不能也不需要直接对它操作,一切通过bgwUI进行
- 如果任务允许被终止,即bgw.WorkerSupportsCancellation为true,等待窗体会显示【取消】按钮,用户可以通过点击它发出终止任务的请求,你可以像老样子一样,在DoWork中访问CancellationPending获知该请求
- 其余功能与bgw一致
使用示例:
- private void button2_Click(object sender, EventArgs e)
- {
- //构造函数的另一个重载可传入自定义等待窗体的实例
- using (BackgroundWorkerUI bgwUI = new BackgroundWorkerUI(/*new MyWaitForm()*/))
- {
- bgwUI.WorkerSupportsCancellation = true;//允许取消任务
- bgwUI.DoWork += bgwUI_DoWork;
- //bgwUI.ProgressChanged += bgwUI_ProgressChanged;//虽然不需要,但仍可注册ProgressChanged事件做其它事
- bgwUI.RunWorkerCompleted += bgwUI_RunWorkerCompleted;//亦可注册RunWorkerCompleted事件
- bgwUI.RunWorkerAsync();
- }
- }
- void bgwUI_DoWork(object sender, DoWorkEventArgs e)
- {
- BackgroundWorkerUI bgwUI = sender as BackgroundWorkerUI;
- //可以通过bgwUI的一组公开属性和方法更新等待窗体
- //bgwUI.CancelControlVisible = true;//设置取消任务的控件的可见性,默认该属性会根据WorkerSupportsCancellation设置,但仍可以自由设置
- bgwUI.BarStyle = ProgressBarStyle.Continuous;//设置滚动条样式(默认是Marquee:循环梭动式)
- bgwUI.BarMaximum = ; //设置滚动条值上限(默认是100)
- bgwUI.BarMinimum = ; //设置滚动条值下限(默认是0)
- bgwUI.BarStep = ; //设置滚动条步进幅度(默认是10)
- bgwUI.BarVisible = true; //设置滚动条是否可见(默认是true:可见)
- int i;
- for (i = Convert.ToInt32(e.Argument); i <= ; i++)
- {
- if (bgwUI.CancellationPending)//老样子,访问CancellationPending获知用户是否取消任务
- {
- e.Cancel = true;
- return;
- }
- //更新等待窗体不需要调用ReportProgress(),也不需要WorkerReportsProgress支持
- bgwUI.WorkMessage = i.ToString();//设置任务进度描述
- bgwUI.BarValue = i; //设置任务进度值
- //CancelControlVisible可以反复设置,不受WorkerSupportsCancellation限制
- //if (i % 10 == 0) { bgw.CancelControlVisible = false; }
- //else if (i % 5 == 0) { bgw.CancelControlVisible = true; }
- Thread.Sleep();
- }
- e.Result = i;
- }
- void bgwUI_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
- {
- if (e.Cancelled)
- {
- MessageBox.Show("任务已取消!");
- }
- else if (e.Error != null)
- {
- MessageBox.Show("任务有异常!" + e.Error.Message);
- }
- else
- {
- MessageBox.Show("任务完成。" + e.Result);
- }
- }
使用示例
与BackgroundWorker的用法区别:
这里只讲区别,没讲到的表示与bgw一致,不熟悉bgw用法的猿友请MSDN。先看类图:
从类图可看出bgwUI是继承于bgw的子类。
- bgwUI重载了一个可传入IWaitForm实例的构造函数,就是可以传入自定义等待窗体,使用无参构造函数的话,就使用默认的等待窗体,即WaitForm
- DoWork事件中可以直接使用bgwUI的一组属性和方法(WorkMessage、BarValue、BarPerformStep等)更新等待窗体,不再需要注册ProgressChanged事件,完了在DoWork中bgw.ReportProgress,并且连WorkerReportsProgress属性都不需要置为true。但是虽然更新等待窗体不需要ProgressChanged事件,但如果你仍然需要该事件做一些其它事,仍然可以注册并照常使用
方案源码:
BackgroundWorkerUI.cs仅包含class BackgroundWorkerUI,它用到的WaitForm.cs请到上一篇文章取用,帮园子节约点空间~哈。
- using System;
- using System.ComponentModel;
- using System.Windows.Forms;
- namespace AhDung.WinForm
- {
- /// <summary>
- /// 带等待窗体的BackgroundWorker。报告进度用一组UI操作方法
- /// </summary>
- public class BackgroundWorkerUI : BackgroundWorker
- {
- readonly IWaitForm waitForm;//等待窗体
- Form activeForm;//等待窗体显示前的活动窗体
- bool formClosed;//指示等待窗体是否已被关闭
- #region 一组操作等候窗体UI的属性/方法
- /// <summary>
- /// 获取或设置进度描述
- /// </summary>
- public string WorkMessage
- {
- get
- {
- if (waitForm.InvokeRequired)
- {
- return waitForm.Invoke(new Func<string>(() => waitForm.WorkMessage)) as string;
- }
- return waitForm.WorkMessage;
- }
- set
- {
- if (waitForm.InvokeRequired)
- {
- waitForm.BeginInvoke(new Action(() => waitForm.WorkMessage = value));
- return;
- }
- waitForm.WorkMessage = value;
- }
- }
- /// <summary>
- /// 获取或设置进度条可见性
- /// </summary>
- public bool BarVisible
- {
- get
- {
- if (waitForm.InvokeRequired)
- {
- return Convert.ToBoolean(waitForm.Invoke(new Func<bool>(() => waitForm.BarVisible)));
- }
- return waitForm.BarVisible;
- }
- set
- {
- if (waitForm.InvokeRequired)
- {
- waitForm.BeginInvoke(new Action(() => waitForm.BarVisible = value));
- return;
- }
- waitForm.BarVisible = value;
- }
- }
- /// <summary>
- /// 获取或设置进度条动画样式
- /// </summary>
- public ProgressBarStyle BarStyle
- {
- get
- {
- if (waitForm.InvokeRequired)
- {
- return (ProgressBarStyle)(waitForm.Invoke(new Func<ProgressBarStyle>(() => waitForm.BarStyle)));
- }
- return waitForm.BarStyle;
- }
- set
- {
- if (waitForm.InvokeRequired)
- {
- waitForm.BeginInvoke(new Action(() => waitForm.BarStyle = value));
- return;
- }
- waitForm.BarStyle = value;
- }
- }
- /// <summary>
- /// 获取或设置进度值
- /// </summary>
- public int BarValue
- {
- get
- {
- if (waitForm.InvokeRequired)
- {
- return Convert.ToInt32(waitForm.Invoke(new Func<int>(() => waitForm.BarValue)));
- }
- return waitForm.BarValue;
- }
- set
- {
- if (waitForm.InvokeRequired)
- {
- waitForm.BeginInvoke(new Action(() => waitForm.BarValue = value));
- return;
- }
- waitForm.BarValue = value;
- }
- }
- /// <summary>
- /// 获取或设置进度条步进值
- /// </summary>
- public int BarStep
- {
- get
- {
- if (waitForm.InvokeRequired)
- {
- return Convert.ToInt32(waitForm.Invoke(new Func<int>(() => waitForm.BarStep)));
- }
- return waitForm.BarStep;
- }
- set
- {
- if (waitForm.InvokeRequired)
- {
- waitForm.BeginInvoke(new Action(() => waitForm.BarStep = value));
- return;
- }
- waitForm.BarStep = value;
- }
- }
- /// <summary>
- /// 使进度条步进
- /// </summary>
- public void BarPerformStep()
- {
- if (waitForm.InvokeRequired)
- {
- waitForm.BeginInvoke(new Action(() => waitForm.BarPerformStep()));
- return;
- }
- waitForm.BarPerformStep();
- }
- /// <summary>
- /// 获取或设置进度条上限值
- /// </summary>
- public int BarMaximum
- {
- get
- {
- if (waitForm.InvokeRequired)
- {
- return Convert.ToInt32(waitForm.Invoke(new Func<int>(() => waitForm.BarMaximum)));
- }
- return waitForm.BarMaximum;
- }
- set
- {
- if (waitForm.InvokeRequired)
- {
- waitForm.BeginInvoke(new Action(() => waitForm.BarMaximum = value));
- return;
- }
- waitForm.BarMaximum = value;
- }
- }
- /// <summary>
- /// 获取或设置进度条下限值
- /// </summary>
- public int BarMinimum
- {
- get
- {
- if (waitForm.InvokeRequired)
- {
- return Convert.ToInt32(waitForm.Invoke(new Func<int>(() => waitForm.BarMinimum)));
- }
- return waitForm.BarMinimum;
- }
- set
- {
- if (waitForm.InvokeRequired)
- {
- waitForm.BeginInvoke(new Action(() => waitForm.BarMinimum = value));
- return;
- }
- waitForm.BarMinimum = value;
- }
- }
- /// <summary>
- /// 获取或设置取消任务的控件的可见性
- /// </summary>
- public bool CancelControlVisible
- {
- get
- {
- if (waitForm.InvokeRequired)
- {
- return Convert.ToBoolean(waitForm.Invoke(new Func<bool>(() => waitForm.CancelControlVisible)));
- }
- return waitForm.CancelControlVisible;
- }
- set
- {
- if (waitForm.InvokeRequired)
- {
- waitForm.BeginInvoke(new Action(() => waitForm.CancelControlVisible = value));
- return;
- }
- waitForm.CancelControlVisible = value;
- }
- }
- #endregion
- /// <summary>
- /// 初始化组件
- /// </summary>
- public BackgroundWorkerUI()
- : this(new WaitForm())
- { }
- /// <summary>
- /// 初始化组件并指定等待窗体
- /// </summary>
- /// <param name="fmWait">等待窗体</param>
- public BackgroundWorkerUI(IWaitForm fmWait)
- {
- if (fmWait == null) { throw new WaitFormNullException(); }
- waitForm = fmWait;
- waitForm.UserCancelling += WaitForm_UserCancelling;//注册用户取消任务事件
- }
- /// <summary>
- /// 开始执行后台操作
- /// </summary>
- /// <param name="argument">要在DoWork事件处理程序中使用的参数</param>
- /// <remarks>通过可选参数可以同时覆盖基类无参RunWorkerAsync,一石二鸟</remarks>
- public new void RunWorkerAsync(object argument = null)
- {
- Form f;
- activeForm = (f = Form.ActiveForm) != null && f.IsMdiContainer ? f.ActiveMdiChild : f;//记录当时的活动窗体
- waitForm.CancelControlVisible = this.WorkerSupportsCancellation;
- formClosed = false;
- base.RunWorkerAsync(argument);
- //这里要判断一下,极端情况下有可能还没等ShowDialog,窗体就已经被关闭了
- if (!formClosed) { waitForm.ShowDialog(); }
- }
- /// <summary>
- /// 用户请求取消任务时
- /// </summary>
- private void WaitForm_UserCancelling(object sender, EventArgs e)
- {
- this.CancelAsync();
- }
- protected override void OnRunWorkerCompleted(RunWorkerCompletedEventArgs e)
- {
- waitForm.Hide();
- formClosed = true;
- //上面Hide后,原活动窗体会在该方法完成后才会重新获得焦点,所以必须加以干预让原窗体现在就获得焦点
- //否则随后的RunWorkerCompleted事件中弹出的模式窗体会有不正常的表现
- if (activeForm != null && !activeForm.IsDisposed) { activeForm.Activate(); }
- base.OnRunWorkerCompleted(e);
- }
- //资源释放
- protected override void Dispose(bool disposing)
- {
- IDisposable form;
- if (disposing && (form = waitForm as IDisposable) != null) { form.Dispose(); }
- base.Dispose(disposing);
- }
- }
- }
BackgroundWorkerUI.cs
-----------------分隔线-----------------
下面的内容对于方案使用来说不是必须的,赶时间你可以先闪。
实现说明:
- 之所以在构造时就要传入等待窗体,而且不提供WaitForm这样的属性让调用者随时能get/set等待窗体,是为了避免做一些蛋疼的控制,因为这样的话,当设置bgwUI.BarVisible这些属性的时候,等待窗体有可能是null,那显然就要增加null的判断,还有很多其它情况要考虑。就算是现在这样,调用者不小心传入一个已经Close/Dispose的等待窗体也没办法,这个问题WaitUI方案也同样存在,也许后面我会改为仅允许传入等待窗体的Type,完了在方案中全权负责等待窗体的从生到死,避免外部破坏
- 为什么有个activeForm字段。这个在源码里也有说明,就是要让等待窗体Hide后,base.OnRunWorkerCompleted执行前,让原先那个活动窗体立即获得焦点,activeForm就是用来记录原先那个活动窗体用的。至于为什么要做这个干预,是因为原活动窗体不会在等待窗体Hide后立即获得焦点,而是要等bgwUI.OnRunWorkerCompleted整个方法执行完才会获得,也就是说,base.OnRunWorkerCompleted执行期间是没有活动窗体的,base.OnRunWorkerCompleted执行的就是RunWorkerCompleted事件处理程序,换句话说,RunWorkerCompleted事件执行时没有活动窗体,那么在事件中弹出的模式窗体就不会有正常的表现,至于怎么个不正常,无法言表,自己体会。总之根本问题就是,当某个窗体在非活动状态下弹出模式窗体,那个模式窗体就会不正常,要问如何才能在非活动状态弹出模式窗体,这个可以自己用timer实现。而为什么会不正常,这个我也想知道,还请高人解答
- 有关IWaitForm和WaitForm的请参看上一篇
-文毕-
【C#】带等待窗体的BackgroundWorker的更多相关文章
- 【C#】分享带等待窗体的任务执行器一枚
-------------201504161039更新------------- 更新内容: IWaitForm接口删除System.Windows.Forms.DialogResult Dialog ...
- Devexpress 等待窗体
加载窗体以及等待窗体 程序加载时,需要等待加载完成后在显示 窗体显示顺序 1. 给用户看的等待窗体 2. 加载完成后的主窗体 代码如下: 1. 等待窗体代码 #region using using S ...
- c# winForm 等待窗体的实现
最近在做一个项目,需要用到等待窗体,在DevExpress下面有SplashScreen控件可以使用,同时也有ProgressIndicator控件能用,但是如果没有用Dev开发的话,我们就需要自定义 ...
- C#关于等待窗体(转)
c#.net 中如果想在主窗口A里点击打开新窗口B(因为要数据库操作,Bload需一小段时间)之前弹出带有滚动条等待子窗口C来提示用户没有死机,应该怎么做?我用多线程打开了c窗口,但是问题:1.C窗口 ...
- C#Winform之等待窗体
窗体主要代码: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 ...
- C#中的“等待窗体”对话框
这篇文章向您展示了如何在c#.net Windows窗体应用程序中创建一个等待窗体对话框.创建一个新表单,然后输入您的表单名称为frmWaitForm.接下来,将Label,Progress Bar控 ...
- (二十三)c#Winform自定义控件-等待窗体
前提 入行已经7,8年了,一直想做一套漂亮点的自定义控件,于是就有了本系列文章. 开源地址:https://gitee.com/kwwwvagaa/net_winform_custom_control ...
- winfrom创建转圈等待窗体
第一步:创建一个WaitForm public partial class WaitForm : Form { ; private ArrayList images = new ArrayList() ...
- DevExpress窗体加载等待
using DevExpress.XtraEditors; using DevExpress.XtraSplashScreen; using System; using System.Collecti ...
随机推荐
- Propagation of Visual Entity Properties Under Bandwidth Constraints
1. Introduction The Saga of Ryzom is a persistent massively-multiplayer online game (MMORPG) release ...
- 自制Unity小游戏TankHero-2D(5)声音+爆炸+场景切换+武器弹药
自制Unity小游戏TankHero-2D(5)声音+爆炸+场景切换+武器弹药 我在做这样一个坦克游戏,是仿照(http://game.kid.qq.com/a/20140221/028931.htm ...
- 缓存篇~第七回 Redis实现基于方法签名的数据集缓存(可控更新,分布式数据缓存)
返回目录 本篇文章可以说是第六回 Microsoft.Practices.EnterpriseLibrary.Caching实现基于方法签名的数据集缓存(可控更新,WEB端数据缓存)的续篇,事实上,有 ...
- Node.js入门:包结构
JavaScript缺少包结构.CommonJS致力于改变这种现状,于是定义了包的结构规范(http://wiki.commonjs.org/wiki/Packages/1.0 ).而NPM的 ...
- poj 3321 Apple Trie
/* poj 3321 Apple Trie 这道题的关键是如何将一个树建成一个一维数组利用树状数组来解题! 可以利用dfs()来搞定,我们在对一个节点深搜后,所经过的节点的数目就是该节点的子树的数目 ...
- Java多线程系列--“JUC集合”01之 框架
概要 之前,在"Java 集合系列目录(Category)"中,讲解了Java集合包中的各个类.接下来,将展开对JUC包中的集合进行学习.在学习之前,先温习一下"Java ...
- How Do Annotations Work in Java?--转
原文地址:https://dzone.com/articles/how-annotations-work-java Annotations have been a very important par ...
- Android悬浮窗口的实现
效果图:(悬浮框可拖动) 在项目开发中有一个需求:弹出悬浮窗后,响应悬浮窗的事件再弹出对话框,但是对话框怎么也不显示.也就是说在弹出悬浮框的同时,不能再弹出对话框,可能的原因: 1.悬浮框的焦点在最前 ...
- 学习网页制作中如何在正确选取和使用 CSS 单位
在 CSS 测量系统中,有好几种单位,如像素.百分比.英寸.厘米等等,Web 开发人员很难了解哪些单位在何处使用,如何使用.很多人习惯了总是使用同一种单位,但这一决定可能会严重限制你的设计的执行. 这 ...
- 170多个Ionic Framework学习资源(转载)
在Ionic官网找到的学习资源:http://blog.ionic.io/learning-ionic-in-your-living-room/ 网上的文章比较多,但是很多时候我们很难找到自己需要的. ...