【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; //設置滾動條值上限(默認是100)
bgwUI.BarMinimum = 0; //設置滾動條值下限(默認是0)
bgwUI.BarStep = 1; //設置滾動條步進幅度(默認是10)
bgwUI.BarVisible = true; //設置滾動條是否可見(默認是true:可見)
int i;
for (i = Convert.ToInt32(e.Argument); i <= 100; 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(50);
}
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的請參看上一篇
-文畢-