-------------201504161039更新-------------
更新內容:
- IWaitForm接口刪除System.Windows.Forms.DialogResult DialogResult屬性。即隱藏等待窗體的方式不再分為設置DialogResult和調用Hide()兩種,改為僅調用Hide()一種,簡化設計。由於Hide()屬於訪問控件,執行器需根據自身是否會跨線程調用該方法而做出相應處理
- WaitUI增加私有方法HideWaitForm,用於隱藏等待窗體(由於會在后台線程調用該方法,故內部有跨線程處理),替代原來的設置DialogResult的做法
- WaitForm的FormClosing事件由注冊該事件改為重寫OnFormClosing方法,對阻止窗體關閉的條件增加了Visible,即當窗體處於可見時,才會阻止窗體關閉和觸發UserCancelling事件,這是為了更准確的區分是執行器調用Hide()隱藏等待窗體,還是用戶關閉等待窗體,僅通過e.CloseReason是不可靠的,因為當用戶點過關閉按鈕后,e.CloseReason就會是UserClosing,稍后執行器在調用Hide隱藏窗體時,仍然會進入OnFormClosing,此時e.CloseReason仍然是UserClosing,就會再次觸發UserCancelling事件,雖然沒什么影響,但實屬不應該,加了Visible的話,執行器Hide窗體后,Visible就為false,就不會再次觸發UserCancelling事件。當然,仍然建議自定義等待窗體屏蔽關閉按鈕,讓用戶只能通過點擊取消控件來取消任務,就沒那么多事了。但不建議通過把ControlBox=false來整個隱藏右上角那仨按鈕,因為我始終認為要給用戶最小化的權利,我作為用戶使用其它軟件的時候,是很痛恨這種限制的
- 等待窗體的【取消】按鈕單擊后不再將Enabled置為false。原因是在基於BackgroundWorker的方案中,等待窗體Hide后有可能再次ShowDialog,也就是再次執行任務時依然要保證可取消
- 將WaitFormNullException異常的定義移至WaitForm.cs文件中(原先在WaitUI.cs中)。原因是等待窗體相關的東西應該與執行器保持獨立
-------------20150415原文(已更新)-------------
適用環境:.net 2.0+的Winform項目。
先解釋一下我所謂的【帶等待窗體的任務執行器】是個什么鬼,就是可以用該類執行任意耗時方法(下文將把被執行的方法稱為任務或任務方法),執行期間會顯示一個模式等待窗體,讓用戶知道任務正在得到執行,程序並沒有卡死。先看一下效果:
功能:
- 等待窗體可以使用執行器自帶的默認窗體(就上圖的樣子),嫌丑你也可以使用自己精心設計的窗體,甚至基於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; }
看完示例,熟悉套路的你可能都已經能洞悉實現細節了,不過作為方案分享文章,我還是照章講一下使用說明先,后面再掰扯設計說明,先看類圖:
使用說明:
- WaitUI通過RunAction、RunFunc、RunDelegate這3個基本方法和它們的重載執行任務,看名字就知道,它們依次是執行無返回值方法、有返回值方法和自定義委托,每個方法都有不指定等待窗體和指定等待窗體兩種重載形態,不指定時就使用方案自帶的WaitForm作為等待窗體。自定義等待窗體需實現IWaitForm接口,詳情在后面的設計說明部分有說。這里就表示等待窗體是在執行任務時才傳進去的,任務執行完成后,WaitUI會銷毀等待窗體,這是為了讓WaitUI作為一個靜態類,盡量短暫的持有對象,節約內存。所以如果傳入的是自定義等待窗體的變量,請注意不要在WaitRun之后再次使用該變量,因為它已經被銷毀,推薦的做法是直接在RunXXX中new一個自定義等待窗體。當然如果不嫌棄自帶等待窗體,直接就不用傳入,自然不會有這個問題。前兩種方法是泛型方法,根據Action和Func這倆泛型委托重載,這倆委托支持到最多16個參數,但為了節約篇幅,方案中只重載了0~8個參數的情況,用戶可以根據需要增加重載。它倆可以執行任意不多於8個參數的有返回或無返回方法,得益於編譯器的智能推斷,使用時非常方便,直接RunAction(Foo, arg1, arg2, ...)就好了,根本不用糾結到底要使用哪個重載。對於RunDelegate方法,接受的是一個委托實例,也就是不能直接傳入方法,必須要用委托把方法套上才行。任何委托都可以傳入,所以RunDelegate是最應萬變的方法,當你的方法存在ref/out參數,或者參數個數變態到超過16個時,你還可以也只可以選用RunDelegate。但有個限制,委托有且只有綁定一個方法,RunXXX拒絕執行委托鏈
- RunFunc和RunDelegate方法有返回值,前者的返回類型與任務方法的返回類型一致,后者則是object。它倆的返回值就是任務方法的返回值。同樣,任務拋出的異常一樣會在3種RunXXX方法中拋出來,等於用RunXXX執行任務與直接調用任務相比,除了寫法上有所不同:
int a = WaitUI.RunFunc(Foo,33); int b = Foo(33);
調用體驗是一樣一樣的,所以你拿去就可以用,不需要對任務方法做什么修改,帶個套就完事兒,除非任務方法存在下面這種情況
- 原理中說過,RunXXX方法實際上是調用任務所屬委托的BeginInvoke方法,也就是異步執行任務,也就是任務會在另一個線程執行。所以任務中不能訪問控件,這恐怕是該方案最大的不便,但確實原理所限,所以如果你的任務有訪問控件的代碼,還得做出改動才行。要問為什么非得讓任務在后台,而等待窗體在前台,不可以調換過來嗎?那樣不就沒這個不便了嗎?那是因為等待窗體如果不在主線程ShowDialog,它就達不到模式的效果,用戶仍然可以唱歌跳舞,這恐怕是你不願意的
- 任務中可以通過WaitUI的一組屬性和方法(WorkMessage、BarValue、BarPerformStep等)更新等待窗體中的文本呈現控件和進度指示控件(不限於Label和ProgressBar,取決於等待窗體的設計),用來向用戶報告任務執行進度。當然不想做任何報告也可以,就讓用戶面對一個“請稍候...”和循環滾動條也無不可,具體文字和滾動條樣式取決於等待窗體的默認設置
- WaitUI有個CancelControlVisible屬性,可以設置為true/false控制等待窗體上是否顯示【取消】按鈕之類的控件(不限於Button,取決於等待窗體的設計,所以下文不說取消按鈕,說取消控件)。這里需要詳細說一下該方案的取消任務的機制,其實與BackgroundWorker的機制一致(好吧是我借鑒了它),熟悉bgw的老鳥請略過。顯示取消控件只代表用戶可以請求終止任務,至於你(或者說任務)是否響應這個請求(同意終止與否)是另一回事。什么意思,就是用戶點擊取消控件后,不是說任務就會自動終止了~憑什么會終止嘛對吧,任務在線程池,又不可能Abort,所以任務是否終止完全取決你在任務代碼中的處理,比如你在任務中段就來個return或throw ex,這個就叫終止,任由代碼執行下去,就叫做沒終止,所以用戶請求終止與任務是不是真的終止了沒有必然聯系。說這么多是什么意思,就是如果你要讓用戶看到取消控件,那么你就應該響應用戶的請求,反過來如果不想任務被終止,那么就別讓用戶有發起請求的可能,當然這是與技術無關的純人機交互理念的東西,沒有對錯,反正我是建議不要欺騙用戶,下面說說如何響應終止請求。當用戶發起終止請求后,WaitUI的UserCancelling會變為true,在任務中你可以根據這個值來做出終止任務的處理,但是在終止之前,還得麻煩你設置一個標記,千萬別忘記,就是讓WaitUI.Cancelled = true,這等於告訴執行器任務確實終止了,在設置完標記后,最好緊跟終止代碼,不要再做其它事,讓Cancelled與事實一致。執行器中根據Cancelled來獲知任務是否已終止,進而做出相應的處理和返回。為什么不根據UserCancelling而是Cancelled相信你已經明白了,前者是用戶的意願,后者是開發者的決定,當然是決定靠譜。回到CancelControlVisible屬性,這個屬性建議在任務方法頂部就設置,因為一個任務是否可終止應該是確定的,通常來說,循環類任務是可以終止的,例如批量處理圖片,一圈處理一張,那這種任務是可以也應該允許用戶終止的;而非循環類任務,或者原子性比較強的任務,開始了就只能等待結果或報錯,這種任務一方面可能就不允許用戶終止,另一方面則是想終止都終止不了(比如WebRequest.GetResponse、SqlCommand.ExecuteNonQuery之類),這種任務最好就不提供取消控件給用戶。不過CancelControlVisible也像WorkMessage之類的屬性一樣,是可以在任務中隨時+反復設置的,所以你的任務可能有些階段可被終止,有時則不允許終止,開開合合都是可以的,as you wish
- RunXXX有3種執行結果:①成功執行任務,返回任務返回值~如果任務有返回值的話;②任務產生異常,RunXXX會原樣拋出該異常;③任務被終止,拋出WorkCancelledException異常(后面有關於為什么選擇拋異常這種方式的說明)。你自行根據不同結果做相應處理
- 對於有ref/out參數的任務方法,如果你想在任務執行后取回,請注意要這樣:
//正確 object[] prms = { a, b }; WaitUI.RunDelegate(new Action(Foo), prms); a = prms[0]; b = prms[1]; //錯誤 WaitUI.RunDelegate(new Action(Foo), a, b);
即要先構造好參數數組(哪怕只有1個參數),完了傳入數組,最后從數組中取出的元素才是被蹂躡過的。不能直接傳入單個參數變量,那樣你是不能通過該變量取回改動過的值的,具體原因自己悟
方案源碼:
WaitUI.cs包含class WaitUI和2個異常類WorkIsBusyException、WorkCancelledException

using System; using System.Reflection; using System.Windows.Forms; namespace AhDung.WinForm { /// <summary> /// 執行任務並顯示等候窗體 /// </summary> public static class WaitUI { static IWaitForm waitForm; //等待窗體 static object result; //任務返回結果 static Exception exception;//任務執行異常 static object[] parmsInput; //調用者傳入的參數 static ParameterInfo[] parmsMethod;//任務所需的參數 static bool isCallBackCompleted; //指示回調方法是否已執行完畢 /// <summary> /// 指示用戶是否已請求取消任務 /// </summary> public static bool UserCancelling { get; private set; } /// <summary> /// 指示任務是否已取消 /// </summary> public static bool Cancelled { private get; set; } /// <summary> /// 指示任務是否正在執行中 /// </summary> public static bool IsBusy { get; private set; } #region 一組操作等候窗體UI的屬性/方法 /// <summary> /// 獲取或設置進度描述 /// </summary> public static string WorkMessage { get { if (waitForm.InvokeRequired) { return waitForm.Invoke(new Func<string>(() => waitForm.WorkMessage)) as string; } return waitForm.WorkMessage; } set { if (waitForm == null) { return; } if (waitForm.InvokeRequired) { waitForm.BeginInvoke(new Action(() => waitForm.WorkMessage = value)); return; } waitForm.WorkMessage = value; } } /// <summary> /// 獲取或設置進度條可見性 /// </summary> public static bool BarVisible { get { if (waitForm.InvokeRequired) { return Convert.ToBoolean(waitForm.Invoke(new Func<bool>(() => waitForm.BarVisible))); } return waitForm.BarVisible; } set { if (waitForm == null) { return; } if (waitForm.InvokeRequired) { waitForm.BeginInvoke(new Action(() => waitForm.BarVisible = value)); return; } waitForm.BarVisible = value; } } /// <summary> /// 獲取或設置進度條動畫樣式 /// </summary> public static ProgressBarStyle BarStyle { get { if (waitForm.InvokeRequired) { return (ProgressBarStyle)(waitForm.Invoke(new Func<ProgressBarStyle>(() => waitForm.BarStyle))); } return waitForm.BarStyle; } set { if (waitForm == null) { return; } if (waitForm.InvokeRequired) { waitForm.BeginInvoke(new Action(() => waitForm.BarStyle = value)); return; } waitForm.BarStyle = value; } } /// <summary> /// 獲取或設置進度值 /// </summary> public static int BarValue { get { if (waitForm.InvokeRequired) { return Convert.ToInt32(waitForm.Invoke(new Func<int>(() => waitForm.BarValue))); } return waitForm.BarValue; } set { if (waitForm == null) { return; } if (waitForm.InvokeRequired) { waitForm.BeginInvoke(new Action(() => waitForm.BarValue = value)); return; } waitForm.BarValue = value; } } /// <summary> /// 獲取或設置進度條步進值 /// </summary> public static int BarStep { get { if (waitForm.InvokeRequired) { return Convert.ToInt32(waitForm.Invoke(new Func<int>(() => waitForm.BarStep))); } return waitForm.BarStep; } set { if (waitForm == null) { return; } if (waitForm.InvokeRequired) { waitForm.BeginInvoke(new Action(() => waitForm.BarStep = value)); return; } waitForm.BarStep = value; } } /// <summary> /// 使進度條步進 /// </summary> public static void BarPerformStep() { if (waitForm == null) { return; } if (waitForm.InvokeRequired) { waitForm.BeginInvoke(new Action(() => waitForm.BarPerformStep())); return; } waitForm.BarPerformStep(); } /// <summary> /// 獲取或設置進度條上限值 /// </summary> public static int BarMaximum { get { if (waitForm.InvokeRequired) { return Convert.ToInt32(waitForm.Invoke(new Func<int>(() => waitForm.BarMaximum))); } return waitForm.BarMaximum; } set { if (waitForm == null) { return; } if (waitForm.InvokeRequired) { waitForm.BeginInvoke(new Action(() => waitForm.BarMaximum = value)); return; } waitForm.BarMaximum = value; } } /// <summary> /// 獲取或設置進度條下限值 /// </summary> public static int BarMinimum { get { if (waitForm.InvokeRequired) { return Convert.ToInt32(waitForm.Invoke(new Func<int>(() => waitForm.BarMinimum))); } return waitForm.BarMinimum; } set { if (waitForm == null) { return; } if (waitForm.InvokeRequired) { waitForm.BeginInvoke(new Action(() => waitForm.BarMinimum = value)); return; } waitForm.BarMinimum = value; } } /// <summary> /// 獲取或設置取消任務的控件的可見性 /// </summary> public static bool CancelControlVisible { get { if (waitForm.InvokeRequired) { return Convert.ToBoolean(waitForm.Invoke(new Func<bool>(() => waitForm.CancelControlVisible))); } return waitForm.CancelControlVisible; } set { if (waitForm == null) { return; } if (waitForm.InvokeRequired) { waitForm.BeginInvoke(new Action(() => waitForm.CancelControlVisible = value)); return; } waitForm.CancelControlVisible = value; } } #endregion #region 公共方法:無返回值+默認窗體 /// <summary> /// 執行方法並顯示默認等候窗體 /// </summary> public static void RunAction(Action method) { RunDelegate(method); } /// <summary> /// 執行方法並顯示默認等候窗體 /// </summary> public static void RunAction<T>(Action<T> method, T arg) { RunDelegate(method, arg); } /// <summary> /// 執行方法並顯示默認等候窗體 /// </summary> public static void RunAction<T1, T2>(Action<T1, T2> method, T1 arg1, T2 arg2) { RunDelegate(method, arg1, arg2); } /// <summary> /// 執行方法並顯示默認等候窗體 /// </summary> public static void RunAction<T1, T2, T3>(Action<T1, T2, T3> method, T1 arg1, T2 arg2, T3 arg3) { RunDelegate(method, arg1, arg2, arg3); } /// <summary> /// 執行方法並顯示默認等候窗體 /// </summary> public static void RunAction<T1, T2, T3, T4>(Action<T1, T2, T3, T4> method, T1 arg1, T2 arg2, T3 arg3, T4 arg4) { RunDelegate(method, arg1, arg2, arg3, arg4); } /// <summary> /// 執行方法並顯示默認等候窗體 /// </summary> public static void RunAction<T1, T2, T3, T4, T5>(Action<T1, T2, T3, T4, T5> method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) { RunDelegate(method, arg1, arg2, arg3, arg4, arg5); } /// <summary> /// 執行方法並顯示默認等候窗體 /// </summary> public static void RunAction<T1, T2, T3, T4, T5, T6>(Action<T1, T2, T3, T4, T5, T6> method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) { RunDelegate(method, arg1, arg2, arg3, arg4, arg5, arg6); } /// <summary> /// 執行方法並顯示默認等候窗體 /// </summary> public static void RunAction<T1, T2, T3, T4, T5, T6, T7>(Action<T1, T2, T3, T4, T5, T6, T7> method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) { RunDelegate(method, arg1, arg2, arg3, arg4, arg5, arg6, arg7); } /// <summary> /// 執行方法並顯示默認等候窗體 /// </summary> public static void RunAction<T1, T2, T3, T4, T5, T6, T7, T8>(Action<T1, T2, T3, T4, T5, T6, T7, T8> method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) { RunDelegate(method, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); } #endregion #region 公共方法:無返回值+自定義窗體 /// <summary> /// 執行方法並顯示自定義等候窗體 /// </summary> public static void RunAction(IWaitForm fmWait, Action method) { RunDelegate(fmWait, method); } /// <summary> /// 執行方法並顯示自定義等候窗體 /// </summary> public static void RunAction<T>(IWaitForm fmWait, Action<T> method, T arg) { RunDelegate(fmWait, method, arg); } /// <summary> /// 執行方法並顯示自定義等候窗體 /// </summary> public static void RunAction<T1, T2>(IWaitForm fmWait, Action<T1, T2> method, T1 arg1, T2 arg2) { RunDelegate(fmWait, method, arg1, arg2); } /// <summary> /// 執行方法並顯示自定義等候窗體 /// </summary> public static void RunAction<T1, T2, T3>(IWaitForm fmWait, Action<T1, T2, T3> method, T1 arg1, T2 arg2, T3 arg3) { RunDelegate(fmWait, method, arg1, arg2, arg3); } /// <summary> /// 執行方法並顯示自定義等候窗體 /// </summary> public static void RunAction<T1, T2, T3, T4>(IWaitForm fmWait, Action<T1, T2, T3, T4> method, T1 arg1, T2 arg2, T3 arg3, T4 arg4) { RunDelegate(fmWait, method, arg1, arg2, arg3, arg4); } /// <summary> /// 執行方法並顯示自定義等候窗體 /// </summary> public static void RunAction<T1, T2, T3, T4, T5>(IWaitForm fmWait, Action<T1, T2, T3, T4, T5> method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) { RunDelegate(fmWait, method, arg1, arg2, arg3, arg4, arg5); } /// <summary> /// 執行方法並顯示自定義等候窗體 /// </summary> public static void RunAction<T1, T2, T3, T4, T5, T6>(IWaitForm fmWait, Action<T1, T2, T3, T4, T5, T6> method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) { RunDelegate(fmWait, method, arg1, arg2, arg3, arg4, arg5, arg6); } /// <summary> /// 執行方法並顯示自定義等候窗體 /// </summary> public static void RunAction<T1, T2, T3, T4, T5, T6, T7>(IWaitForm fmWait, Action<T1, T2, T3, T4, T5, T6, T7> method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) { RunDelegate(fmWait, method, arg1, arg2, arg3, arg4, arg5, arg6, arg7); } /// <summary> /// 執行方法並顯示自定義等候窗體 /// </summary> public static void RunAction<T1, T2, T3, T4, T5, T6, T7, T8>(IWaitForm fmWait, Action<T1, T2, T3, T4, T5, T6, T7, T8> method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) { RunDelegate(fmWait, method, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); } #endregion #region 公共方法:有返回值+默認窗體 /// <summary> /// 執行方法並顯示默認等候窗體 /// </summary> public static TResult RunFunc<TResult>(Func<TResult> method) { return (TResult)RunDelegate(method); } /// <summary> /// 執行方法並顯示默認等候窗體 /// </summary> public static TResult RunFunc<T, TResult>(Func<T, TResult> method, T arg) { return (TResult)RunDelegate(method, arg); } /// <summary> /// 執行方法並顯示默認等候窗體 /// </summary> public static TResult RunFunc<T1, T2, TResult>(Func<T1, T2, TResult> method, T1 arg1, T2 arg2) { return (TResult)RunDelegate(method, arg1, arg2); } /// <summary> /// 執行方法並顯示默認等候窗體 /// </summary> public static TResult RunFunc<T1, T2, T3, TResult>(Func<T1, T2, T3, TResult> method, T1 arg1, T2 arg2, T3 arg3) { return (TResult)RunDelegate(method, arg1, arg2, arg3); } /// <summary> /// 執行方法並顯示默認等候窗體 /// </summary> public static TResult RunFunc<T1, T2, T3, T4, TResult>(Func<T1, T2, T3, T4, TResult> method, T1 arg1, T2 arg2, T3 arg3, T4 arg4) { return (TResult)RunDelegate(method, arg1, arg2, arg3, arg4); } /// <summary> /// 執行方法並顯示默認等候窗體 /// </summary> public static TResult RunFunc<T1, T2, T3, T4, T5, TResult>(Func<T1, T2, T3, T4, T5, TResult> method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) { return (TResult)RunDelegate(method, arg1, arg2, arg3, arg4, arg5); } /// <summary> /// 執行方法並顯示默認等候窗體 /// </summary> public static TResult RunFunc<T1, T2, T3, T4, T5, T6, TResult>(Func<T1, T2, T3, T4, T5, T6, TResult> method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) { return (TResult)RunDelegate(method, arg1, arg2, arg3, arg4, arg5, arg6); } /// <summary> /// 執行方法並顯示默認等候窗體 /// </summary> public static TResult RunFunc<T1, T2, T3, T4, T5, T6, T7, TResult>(Func<T1, T2, T3, T4, T5, T6, T7, TResult> method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) { return (TResult)RunDelegate(method, arg1, arg2, arg3, arg4, arg5, arg6, arg7); } /// <summary> /// 執行方法並顯示默認等候窗體 /// </summary> public static TResult RunFunc<T1, T2, T3, T4, T5, T6, T7, T8, TResult>(Func<T1, T2, T3, T4, T5, T6, T7, T8, TResult> method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) { return (TResult)RunDelegate(method, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); } #endregion #region 公共方法:有返回值+自定義窗體 /// <summary> /// 執行方法並顯示自定義等候窗體 /// </summary> public static TResult RunFunc<TResult>(IWaitForm fmWait, Func<TResult> method) { return (TResult)RunDelegate(fmWait, method); } /// <summary> /// 執行方法並顯示自定義等候窗體 /// </summary> public static TResult RunFunc<T, TResult>(IWaitForm fmWait, Func<T, TResult> method, T arg) { return (TResult)RunDelegate(fmWait, method, arg); } /// <summary> /// 執行方法並顯示自定義等候窗體 /// </summary> public static TResult RunFunc<T1, T2, TResult>(IWaitForm fmWait, Func<T1, T2, TResult> method, T1 arg1, T2 arg2) { return (TResult)RunDelegate(fmWait, method, arg1, arg2); } /// <summary> /// 執行方法並顯示自定義等候窗體 /// </summary> public static TResult RunFunc<T1, T2, T3, TResult>(IWaitForm fmWait, Func<T1, T2, T3, TResult> method, T1 arg1, T2 arg2, T3 arg3) { return (TResult)RunDelegate(fmWait, method, arg1, arg2, arg3); } /// <summary> /// 執行方法並顯示自定義等候窗體 /// </summary> public static TResult RunFunc<T1, T2, T3, T4, TResult>(IWaitForm fmWait, Func<T1, T2, T3, T4, TResult> method, T1 arg1, T2 arg2, T3 arg3, T4 arg4) { return (TResult)RunDelegate(fmWait, method, arg1, arg2, arg3, arg4); } /// <summary> /// 執行方法並顯示自定義等候窗體 /// </summary> public static TResult RunFunc<T1, T2, T3, T4, T5, TResult>(IWaitForm fmWait, Func<T1, T2, T3, T4, T5, TResult> method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) { return (TResult)RunDelegate(fmWait, method, arg1, arg2, arg3, arg4, arg5); } /// <summary> /// 執行方法並顯示自定義等候窗體 /// </summary> public static TResult RunFunc<T1, T2, T3, T4, T5, T6, TResult>(IWaitForm fmWait, Func<T1, T2, T3, T4, T5, T6, TResult> method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) { return (TResult)RunDelegate(fmWait, method, arg1, arg2, arg3, arg4, arg5, arg6); } /// <summary> /// 執行方法並顯示自定義等候窗體 /// </summary> public static TResult RunFunc<T1, T2, T3, T4, T5, T6, T7, TResult>(IWaitForm fmWait, Func<T1, T2, T3, T4, T5, T6, T7, TResult> method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) { return (TResult)RunDelegate(fmWait, method, arg1, arg2, arg3, arg4, arg5, arg6, arg7); } /// <summary> /// 執行方法並顯示自定義等候窗體 /// </summary> public static TResult RunFunc<T1, T2, T3, T4, T5, T6, T7, T8, TResult>(IWaitForm fmWait, Func<T1, T2, T3, T4, T5, T6, T7, T8, TResult> method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) { return (TResult)RunDelegate(fmWait, method, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); } #endregion /// <summary> /// 執行委托並顯示默認等候窗體 /// </summary> public static object RunDelegate(Delegate del, params object[] args) { return RunDelegate(new WaitForm(), del, args); } /// <summary> /// 執行委托並顯示自定義等候窗體 /// </summary> public static object RunDelegate(IWaitForm fmWait, Delegate del, params object[] args) { if (IsBusy) { throw new WorkIsBusyException(); } if (fmWait == null) { throw new WaitFormNullException(); } if (del == null || del.GetInvocationList().Length != 1) { throw new ApplicationException("委托不能為空,且只能綁定1個方法!"); } if (args == null) { throw new ArgumentNullException("args"); } MethodInfo beginInvoke = del.GetType().GetMethod("BeginInvoke"); object[] parmsBeginInvoke = new object[beginInvoke.GetParameters().Length]; if (args.Length > parmsBeginInvoke.Length - 2) { throw new ArgumentException("提供的參數超過了方法所需的參數!"); } parmsMethod = del.Method.GetParameters();//假定GetParameters總是返回按參數Position排序的數組,如果將來有問題,要查驗這個假設 parmsInput = args; try { //賦值BeginInvoke參數 parmsInput.CopyTo(parmsBeginInvoke, 0); //塞入傳入的參數 for (int i = parmsInput.Length; i < parmsMethod.Length; i++) //對未傳入的參數賦予默認值 { ParameterInfo p = parmsMethod[i]; object pVal; if ((pVal = p.DefaultValue) == DBNull.Value) //若參數不具有默認值則拋異常 { throw new ArgumentException(string.Format("方法所需的參數{0}沒有定義默認值,必須傳入!", p.Name)); } parmsBeginInvoke[i] = pVal; } parmsBeginInvoke[parmsBeginInvoke.Length - 2] = new AsyncCallback(Callback);//倒數第2個參數 parmsBeginInvoke[parmsBeginInvoke.Length - 1] = del; //倒數第1個參數 //重置狀態 IsBusy = true; Cancelled = false; exception = null; isCallBackCompleted = false; waitForm = fmWait; fmWait.UserCancelling += WaitForm_UserCancelling;//注冊用戶取消任務事件 beginInvoke.Invoke(del, parmsBeginInvoke); if (!isCallBackCompleted)//這里要判斷一下,極端情況下有可能還沒等ShowDialog,回調就執行完了 { fmWait.ShowDialog(); //務必確保ShowDialog不會拋異常 } //返回 if (Cancelled) { throw new WorkCancelledException(); } if (exception != null) { throw exception; } return result; } finally { Release(); UserCancelling = false; IsBusy = false; } } /// <summary> /// 回調方法 /// </summary> private static void Callback(IAsyncResult ar) { try { if (Cancelled) { return; } //若任務取消就不必EndInvoke了 MethodInfo endInvoke = ar.AsyncState.GetType().GetMethod("EndInvoke"); object[] parmsEndInvoke = new object[endInvoke.GetParameters().Length]; if (parmsEndInvoke.Length != 1)//若方法存在ref或out參數,賦值給endInvoke參數 { int i = 0; foreach (ParameterInfo p in parmsMethod) { if (p.ParameterType.IsByRef) { parmsEndInvoke[i++] = parmsInput[p.Position]; } } } parmsEndInvoke[parmsEndInvoke.Length - 1] = ar; result = endInvoke.Invoke(ar.AsyncState, parmsEndInvoke); if (parmsEndInvoke.Length != 1)//從endInvoke參數取出值返給輸入參數 { int i = 0; foreach (ParameterInfo p in parmsMethod) { if (p.ParameterType.IsByRef) { parmsInput[p.Position] = parmsEndInvoke[i++]; } } } } catch (TargetInvocationException ex) { exception = ex.InnerException; } catch (Exception ex) { exception = ex; } finally { HideWaitForm(); isCallBackCompleted = true; } } /// <summary> /// 隱藏等待窗體 /// </summary> /// <remarks>因為該方法會在回調中調用,所以要做跨線程處理</remarks> static void HideWaitForm() { if (waitForm == null) { return; } if (waitForm.InvokeRequired) { waitForm.BeginInvoke(new Action(() => waitForm.Hide())); return; } waitForm.Hide(); } /// <summary> /// 用戶請求取消任務時 /// </summary> private static void WaitForm_UserCancelling(object sender, EventArgs e) { UserCancelling = true; } /// <summary> /// 釋放資源 /// </summary> private static void Release() { parmsInput = null;//這里不會影響調用者傳入的object[]實例,因為不是ref進來的 parmsMethod = null; IDisposable disp; if ((disp = waitForm as IDisposable) != null) { disp.Dispose(); } } } /// <summary> /// 任務正在執行 /// </summary> public class WorkIsBusyException : InvalidOperationException { public WorkIsBusyException() : base("任務正在執行!") { } } /// <summary> /// 任務已被取消 /// </summary> public class WorkCancelledException : ApplicationException { public WorkCancelledException() : base("任務已被取消!") { } } }
WaitForm.cs包含interface IWaitForm、class WaitForm和異常類WaitFormNullException,其中WaitForm為了屏蔽關閉按鈕,使用了WinFormHelper.cs

using System; using System.Windows.Forms; namespace AhDung.WinForm { /// <summary> /// 等待窗體 /// </summary> ///<remarks>IWaitForm的默認實現</remarks> public class WaitForm : Form, IWaitForm { /// <summary> /// Required designer variable. /// </summary> private readonly System.ComponentModel.IContainer components = null; /// <summary> /// Clean up any resources being used. /// </summary> /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> protected override void Dispose(bool disposing) { if (disposing && (components != null)) { components.Dispose(); } base.Dispose(disposing); } #region Windows Form Designer generated code /// <summary> /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// </summary> private void InitializeComponent() { this.lbMsg = new System.Windows.Forms.Label(); this.bar = new System.Windows.Forms.ProgressBar(); this.btnCancel = new System.Windows.Forms.Button(); this.SuspendLayout(); // // lbMsg // this.lbMsg.Location = new System.Drawing.Point(10, 20); this.lbMsg.Name = "lbMsg"; this.lbMsg.Size = new System.Drawing.Size(386, 55); this.lbMsg.TabIndex = 0; this.lbMsg.Text = "正在處理,請稍候..."; // // bar // this.bar.Location = new System.Drawing.Point(12, 78); this.bar.Name = "bar"; this.bar.Step = 1; this.bar.Size = new System.Drawing.Size(384, 16); this.bar.Style = System.Windows.Forms.ProgressBarStyle.Marquee; this.bar.TabIndex = 1; // // btnCancel // this.btnCancel.Location = new System.Drawing.Point(321, 109); this.btnCancel.Name = "btnCancel"; this.btnCancel.Size = new System.Drawing.Size(75, 23); this.btnCancel.TabIndex = 2; this.btnCancel.Text = "取消"; this.btnCancel.UseVisualStyleBackColor = true; this.btnCancel.Visible = false; // // FmWaitForDesign // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(408, 155); this.Controls.Add(this.btnCancel); this.Controls.Add(this.bar); this.Controls.Add(this.lbMsg); this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; this.MaximizeBox = false; this.Name = "FmWaitForDesign"; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; this.Text = "請稍候..."; this.ResumeLayout(false); } #endregion System.Windows.Forms.Label lbMsg; System.Windows.Forms.Button btnCancel; System.Windows.Forms.ProgressBar bar; public WaitForm() { InitializeComponent(); btnCancel.Click += btnCancel_Click;//注冊取消按鈕單擊事件 } #region 將【取消】按鈕點擊、窗體關閉等行為視為觸發【取消任務】事件 protected override void OnFormClosing(FormClosingEventArgs e) { //阻止用戶關閉窗體並觸發UserCancelling事件 //加Visible是因為調用Hide()也會觸發該事件,為了避免再次OnUserCancelling為之 if (e.CloseReason == CloseReason.UserClosing && this.Visible) { e.Cancel = true; this.OnUserCancelling(); } base.OnFormClosing(e); } private void btnCancel_Click(object sender, EventArgs e) { this.OnUserCancelling(); } protected virtual void OnUserCancelling() { if (UserCancelling != null) { UserCancelling(this, EventArgs.Empty); } } //屏蔽窗體關閉按鈕 protected override void OnVisibleChanged(EventArgs e) { base.OnVisibleChanged(e); if (this.Visible) { AhDung.WinForm.WinFormHelper.DisableCloseButton(this); } } #endregion #region 實現接口 public string WorkMessage { get { return lbMsg.Text; } set { lbMsg.Text = value; } } public bool BarVisible { get { return bar.Visible; } set { bar.Visible = value; } } public ProgressBarStyle BarStyle { get { return bar.Style; } set { bar.Style = value; } } public int BarValue { get { return bar.Value; } set { bar.Value = value; } } public int BarStep { get { return bar.Step; } set { bar.Step = value; } } public void BarPerformStep() { bar.PerformStep(); } public bool CancelControlVisible { get { return btnCancel.Visible; } set { btnCancel.Visible = value; } } public int BarMaximum { get { return bar.Maximum; } set { bar.Maximum = value; } } public int BarMinimum { get { return bar.Minimum; } set { bar.Minimum = value; } } public event EventHandler UserCancelling; #endregion } /// <summary> /// 等待窗體規范 /// </summary> public interface IWaitForm { #region 用於操作等待窗體UI表現的屬性和方法,實現時不用操心線程問題,讓客戶端(任務執行器)去操心 /// <summary> /// 獲取或設置進度描述 /// </summary> /// <remarks>建議默認值為“請稍候...”之類的字眼</remarks> string WorkMessage { get; set; } /// <summary> /// 獲取或設置進度條的可見性 /// </summary> /// <remarks>建議默認值為true</remarks> bool BarVisible { get; set; } /// <summary> /// 獲取或設置進度條的動畫樣式 /// </summary> /// <remarks>建議默認值為Marquee</remarks> ProgressBarStyle BarStyle { get; set; } /// <summary> /// 獲取或設置進度條的值 /// </summary> /// <remarks>建議默認值為0</remarks> int BarValue { get; set; } /// <summary> /// 獲取或設置進度條的步進幅度 /// </summary> int BarStep { get; set; } /// <summary> /// 使進度條步進 /// </summary> void BarPerformStep(); /// <summary> /// 獲取或設置取消任務的控件的可見性 /// </summary> /// <remarks>建議默認值為false</remarks> bool CancelControlVisible { get; set; } /// <summary> /// 獲取或設置進度條的值上限 /// </summary> /// <remarks>建議默認值為100</remarks> int BarMaximum { get; set; } /// <summary> /// 獲取或設置進度條的值下限 /// </summary> /// <remarks>建議默認值為0</remarks> int BarMinimum { get; set; } #endregion /// <summary> /// 顯示模式等待窗體 /// </summary> /// <remarks>建議使用Form類的默認實現</remarks> DialogResult ShowDialog(); #region Invoke相關,供客戶端在跨線程操作窗體UI /// <summary> /// 指示是否需要使用Invoke操作窗體控件 /// </summary> /// <remarks>建議使用Form類的默認實現</remarks> bool InvokeRequired { get; } /// <summary> /// 窗體Invoke方法 /// </summary> /// <remarks>建議使用Form類的默認實現</remarks> object Invoke(Delegate method); /// <summary> /// 窗體BeginInvoke方法 /// </summary> /// <remarks>建議使用Form類的默認實現</remarks> IAsyncResult BeginInvoke(Delegate method); #endregion /// <summary> /// 隱藏等待窗體 /// </summary> /// <remarks>建議使用Form類的默認實現</remarks> void Hide(); /// <summary> /// 當用戶請求取消任務時 /// </summary> /// <remarks>應在用戶交互取消控件、關閉窗體時觸發該事件</remarks> event EventHandler UserCancelling; } /// <summary> /// 等候窗體為空 /// </summary> public class WaitFormNullException : ApplicationException { public WaitFormNullException() : base("等待窗體不能為null!") { } } }

using System; using System.Runtime.InteropServices; using System.Windows.Forms; namespace AhDung.WinForm { public static class WinFormHelper { [DllImport("User32.dll ")] private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert); [DllImport("User32.dll ")] private static extern int RemoveMenu(IntPtr hMenu, int nPosition, int wFlags); const int MF_REMOVE = 0x1000; //const int SC_RESTORE = 0xF120; //還原 //const int SC_MOVE = 0xF010; //移動 //const int SC_SIZE = 0xF000; //大小 //const int SC_MINIMIZE = 0xF020; //最小化 //const int SC_MAXIMIZE = 0xF030; //最大化 const int SC_CLOSE = 0xF060; //關閉 /// <summary> /// 屏蔽窗體關閉功能 /// </summary> public static void DisableCloseButton(IWin32Window form) { IntPtr hMenu = GetSystemMenu(form.Handle, false); RemoveMenu(hMenu, SC_CLOSE, MF_REMOVE); } } }
可憐的.net 2.0~3.5沒有足夠的內置委托,所以你可能還需要Delegates.cs

namespace System { //無返回委托 public delegate void Action(); //public delegate void Action<in T>(T arg);//這個2.0是有的 public delegate void Action<in T1, in T2>(T1 arg1, T2 arg2); public delegate void Action<in T1, in T2, in T3>(T1 arg1, T2 arg2, T3 arg3); public delegate void Action<in T1, in T2, in T3, in T4>(T1 arg1, T2 arg2, T3 arg3, T4 arg4); public delegate void Action<in T1, in T2, in T3, in T4, in T5>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5); public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6); public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7); public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8); //public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9); //public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10); //public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11); //public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12); //public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13); //public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13, in T14>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14); //public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13, in T14, in T15>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15); //public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13, in T14, in T15, in T16>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16); //有返回委托 public delegate TResult Func<out TResult>(); public delegate TResult Func<in T, out TResult>(T arg); public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2); public delegate TResult Func<in T1, in T2, in T3, out TResult>(T1 arg1, T2 arg2, T3 arg3); public delegate TResult Func<in T1, in T2, in T3, in T4, out TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4); public delegate TResult Func<in T1, in T2, in T3, in T4, in T5, out TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5); public delegate TResult Func<in T1, in T2, in T3, in T4, in T5, in T6, out TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6); public delegate TResult Func<in T1, in T2, in T3, in T4, in T5, in T6, in T7, out TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7); public delegate TResult Func<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, out TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8); //public delegate TResult Func<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, out TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9); //public delegate TResult Func<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, out TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10); //public delegate TResult Func<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, out TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11); //public delegate TResult Func<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, out TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12); //public delegate TResult Func<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13, out TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13); //public delegate TResult Func<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13, in T14, out TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14); //public delegate TResult Func<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13, in T14, in T15, out TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15); //public delegate TResult Func<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13, in T14, in T15, in T16, out TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16); }
--------------華麗麗的分隔線--------------
下面的內容獻給閑的蛋疼的童鞋,又或者你想鄙視、教育我這里不該怎樣怎樣,那里應該怎樣怎樣的話,請看完再繼續。
設計說明:
- 為什么要把WaitUI弄成靜態類。new一個執行器執行任務,完了銷毀執行器不是很自然的事嗎,弄成靜態類就不怕資源占用、狀態管理不好造成BUG嗎。的確我有考慮過弄成實例類,但思考之后還是決定靜態之,原因有二:①使用簡單。這是從考慮做這個東西之初到實施過程中都始終優先考慮的原則,new一下是費不了多少事,但new完以后是不是得設置某些屬性,得Run,得釋放,像這樣:
using(WaitUI w = new WaitUI()) { w.CancelControlVisible = true; w.RunAction(Foo); }
怎么都不如一句WaitUI.RunAction(Foo)來的簡單;②不必改造任務方法。想象一下,實例類的話,任務中想更新等待窗體,是不是得獲得執行器實例的引用,或是某個包裝了執行器實例的類的實例,怎么獲得,自然是通過任務方法的參數傳進去,像這樣:
int Foo(int a, int b, WaitUI w) { w.WorkMessage = ""; ...
其結果就是必然要改造任務方法,而我的目的一是讓用戶拿去就能用,結果還要改這改那的我都閑害臊;二是讓任務方法既可以套上執行器執行,也可以不帶套照常執行,所以WorkMessage那些屬性都是寫成綠色無公害的,不管有沒有等待窗體,都不會拋異常,目的就是即便任務方法中增加了這些語句,也可以在不帶套的情況下照常執行
- 為什么要弄個IWaitForm這樣的胖子接口。的確這個地方我承認弄成基類比較合適,除了不至於弄出個胖接口之外,更重要的是可以做一些基礎實現,不至於什么都交給自定義等待窗體編寫者去實現,省事都是其次,關鍵是能做一些必要的控制,比如UserCancelling事件,要求用戶在點擊取消按鈕和關閉窗體時觸發,但編寫者只在其中一種操作時觸發或根本不觸發那也沒辦法,一個道理,過分靈活不是好事。而為什么我仍然選擇接口,也恰恰是因為要保證靈活,就是要允許編寫者從其它第三方Form繼承,設計美觀的等待窗體,如果設計為基類,那就堵死了這種可能,等於我在靈活性和健壯性之間選擇了前者。當然編寫者可以把WaitFormBase的基類改為目標第三方Form,還有Label、ProgressBar、Button都改為三方控件,反正源碼都在,愛怎么玩怎么玩
- WaitForm重寫了Form.OnVisibleChanged方法,是為了屏蔽右上角關閉按鈕,不屏蔽也是可以的,但必須在FormClosing事件中阻止窗體關閉,同時觸發UserCancelling事件,我兩樣的做了,也建議自定義等待窗體編寫者做足全套,因為不屏蔽關閉按鈕的話,用戶點了卻關不掉,感覺怪怪的。另外說說為什么UserCancelling要弄成事件,而不是弄成一個bool屬性,當用戶取消時置該屬性為true,完了讓WaitUI.UserCancelling直接訪問該屬性,為什么?原因是這個IWaitForm,我不希望它專供WaitUI使用,其它執行器或類似方案也可以用,那其他方案請求取消任務的操作未必是通過給UserCancelling類似的屬性做標記,人家有可能是執行一個方法,比如BackgroundWorker就是調用CancelAsync()(雖然它內部也是設置標記),那IWaitForm如果是設置某個屬性,就等於要執行器主動來獲取標記,而不是等待窗體主動通知執行器,顯然對BackgroundWorker這樣的就不好使了,總不能單獨弄個timer循環獲取標記。弄成事件就靈活多了,等待窗體只負責在適當的時候觸發這個事件,至於執行器如何響應這個事件,自行處理,WaitUI可以設置UserCancelling,而BackgroundWorker可以調用CancelAsync(),各找各媽。另外,WaitForm作為IWaitForm的默認實現,它可以作為自定義等待窗體的實現參考
- 任務取消為什么要采用拋出異常的方式,為什么不是讓調用者判斷Cancelled屬性就好。這里我也糾結了一下,最后決定選擇前者的理由是,RunXXX是要負責返回任務結果的,如果任務被取消,那么RunXXX的返回值就是不可靠的,如果任由調用者接到這個不可靠的值,這是對調用者不負責的,所以必須拋出異常,阻止調用者獲得不可靠的返回值。BackgroundWorker之所以采用Cancelled方式,是因為它已經通過另一種方式阻止調用者獲得錯誤結果,就是當e.Cancelled為true時,訪問e.Result會拋異常,等於理念是一樣的,效果也是一樣的
- 關於WaitUI.IsBusy,這貨請參看使用示例中的注釋,作為公開成員可有可無
最后真心希望路過大蝦對方案不妥之處指點一二,在此謝過。
-文畢-