探討WinForm不同代碼的實現
WinForm看上去有些深奧,在這里我們將探討WinForm二三事,希望通過以下的文字,能讓大家在開發過程中對WinForm有更深入的了解和認識。
在進入正文之前,想請大家先欣賞下面兩段代碼:
- //這是一個控制臺程序,請先添加System.Windows.Form.dll的引用
- using System.Windows.Form;
- public class ConsoleApplicationShowDialog
- {
- static void Main()
- {
- Form frm = new Form();
- frm.ShowDialog();
- }
- }
兩個代碼片段都是控制臺程序(編譯的時候,請選擇ConsoleApplication類型編譯)。這兩段程序***的區別就在于顯示窗體的時候***個使用ShowDialog(就是所謂的模態窗體),第二個使用Show(也就是所謂的非模態窗體)。
經過測試我們發現,使用Show顯示出來的窗體一顯示就死在那里了,不響應用戶的輸入,如果你在窗體上放一個按鈕,甚至發現按鈕都無法顯示,點擊也無任何響應。而是用ShowDialog顯示出來的窗體卻不一樣,可以響應用戶的輸入。這是什么原因呢?
為了找到問題的根源,我們來看看Show方法和ShowDialog方法實現的區別。Show方法是在Control里定義的,Form間接的派生自Control類(看起來這里是一個組合模式哦),Show方法代碼:
- public void Show()
- {
- this.Visible = true;
- }
Show方法的代碼相當的簡單,做的工作僅僅就是將窗體顯示出來,那前面第二段代碼應該與下面的代碼作用是一樣的:
- //這是一個控制臺程序,請先添加System.Windows.Form.dll的引用
- using System.Windows.Form;
- public class ConsoleApplicationShow
- {
- static void Main()
- {
- Form frm = new Form();
- frm.Visible = true;
- }
- }
現在再來看看ShowDialog方法,ShowDialog方法有些復雜,但是在這百來行代碼中,應該有一條你很熟悉:
- public DialogResult ShowDialog(IWin32Window owner)
- {
- //...省略
- Application.RunDialog(this);
- //...省略
- }
哦,這行代碼跟我們千千萬萬個WinForm程序的啟動部分相當類似:
- public class Program
- {
- static void Main()
- {
- Form frm = new Form();
- Application.Run(frm);
- }
- }
MSDN對Application.Run的說明是:
Begins running a standard application message loop on the current thread, and makes the specified form visible.在當前的線程上啟動一個標準的應用程序“消息循環”,并且顯示指定的窗體,下面是Application.Run的代碼:
- public static void Run(Form mainForm){
- ThreadContext.FromCurrent().RunMessageLoop(-1, new
- ApplicationContext(mainForm));
- }
哦?什么是消息循環?如果你是直接進入.Net開發的,沒有經過Win32時代的洗禮,那可能對這個消息循環并不是很清楚,在你眼里只有注冊事件,處理事件。雖然.Net通過封裝,簡化了消息循環這種處理用戶點擊等事件的編程模型,但是.Net底下還是Win32,有的時候我們還是得了解一下,對理解有些問題可能有幫助(后面會提到)。
消息循環(Message Loop)
說Application.Run啟動一個消息循環,那么什么是消息循環呢?看下面的代碼:
- MSG msg;
- while(GetMessage(&msg,NULL,0,0)){
- TranslateMessage(&msg);
- DispatchMessage(&msg);
- }
這是一段幾乎所有使用Win32 API編寫Windows Application的程序里都有的代碼。這就是一個消息循環。你不需要透徹的理解上面這段代碼,你只需要了解這么一個意思:
Windows為每個Windows程序都維護了一個消息隊列,當有用戶輸入事件的時候,Windows就把這個事件轉換為一個稱之為“消息”的東東(也就是上面代碼中的MSG結構),在這個消息里包含有一些信息,比如鼠標點擊的點啊,消息的類型啊等等。而上面的while循環中的GetMessage方法就是不斷的從這個消息隊列里取消息出來,然后處理,這樣窗體就能響應用戶的輸入了。
通過上面的討論,我們現在大概明白了為啥Show和ShowDialog區別這么大呢,原來ShowDialog啟動了一個消息循環,這樣用ShowDialog顯示出來的窗體就能響應用戶的輸入事件了,而Show僅僅是設置一下窗體的Visible屬性,并沒有啟動一個消息循環,使用Show顯示出來的窗體也就無法響應用戶的輸入事件了,也就是死在那里了。
上面說,GetMessage取出消息,然后處理,那在哪兒處理呢?在Win32程序中我們還可以看到這樣的片段:
- LRESULT CALLBACK WndProc(HWND hwnd,UINT message,WPARAM
- wParam,LPARAM lParam){
- switch(message) {
- case WM_CREATE: //處理窗體創建事件
- return 0;
- case WM_PAINT: //處理窗體繪制事件
- return 0; //更多事件,比如按鈕點擊等
- }
- }
啊,好丑陋的處理方式。原來是根據message的類型,做出不同的處理,而Windows定義了一大堆WM_開頭的東東。可我們可愛的.Net,WinForm里面優美的事件處理模型就是基于這個之上的,通過上面的代碼,和你在.Net里使用事件的感觸你是否能想象出.Net是如何封裝這個過程的?
WinForm中的消息處理
實際上在.Net的WinForm中,消息處理的影子還是存在的,并沒有消失得無影無蹤,在Form中還有這么一個protected的方法:
- protected override void WndProc(ref Message m){
- switch (m.Msg) {
- case 0x10://......
- case 0x11: //....
- }
- base.WndProc(ref m);}
哦,原來與Win32里面的那個一模一樣。實際上通過重寫這個方法我們可以實現一些正常做法難以實現的東東。
為什么耗時操作要異步
談了這么多,我們來談一點我們身邊的事情。你應該碰到過這樣一個場景:編寫一個程序,點擊一個按鈕之后要做一個比較耗時的操作,比如要更新一大批數據到數據庫,這個時候程序就像本文開頭那個程序一樣,死掉了。用戶不管怎么點擊,程序變成灰色,標題欄上還顯示一個“沒有響應”,有的程序甚至連個提示都不給,用戶以為真的死掉了,氣急敗壞的啪嚓一下把程序關了,耗時操作進行到一半就這樣被無情終止。這是為什么呢?
通過前面的討論,我們知道,響應用戶的輸入就是靠消息循環,而消息循環就是在當前的線程上,也就是我們所謂的那個UI線程,如果一個耗時操作也同在UI線程上,那么消息循環就“卡著”了,也就無法處理后續的消息,程序也就假死了。
那我們如何處理這種耗時操作呢?當然就是將這個耗時操作放到另外一個線程中,不占用UI線程,讓消息循環得以繼續的進行下去。
原文標題:WinForm二三事
鏈接:http://www.cnblogs.com/yuyijq/archive/2009/11/04/1595775.html