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