窗體(windows),事件(event),消息(message)
我們大部分人用的操作系統都是微軟的windows系列.windows顧名思義是窗口,窗體.它最大的特點就是幾乎所有操作都是圖形界面.在面向對象開發中是一切皆對象,那在windows的操作中是一切皆窗體.窗體背後對應的是消息機制.是以消息以基礎,以事件為驅動.所謂窗體就是我們能用眼睛看得到的一個個圖形界面,幾乎任何可顯示的實體都是窗口,有時彈出的對話框也算個小窗體,一個按鈕也算窗體.一個界面可以由多個窗體組合而成.事件就代表著所有的操作,比如鼠標動一下,點一下,窗口拉大拉小,文本框輸入文字等...而事件發生後是以消息的形式發給應用程序對應的函數去處理.所以窗體,事件,消息三者的關系是我們對窗體的操作就是一個事件,事件會產生消息.
windows操作系統是事件驅動,那運行在它上面的絕大部分應用程序自然也是事件驅動.應用程序大部分時候是傻傻的呆那不動的,只有你去點下它才會動起來,做相應的操作.當然了有些應用程序可能會有些後台操作,比如設個時鐘,每過多久自動刷新下或做些其他啥操作.
我覺得人其實也有點像個事件驅動的機器了.我們接受各種內部或外部的刺激,然後產生各種反應.就算在睡覺時也在接受刺激做些本能的反應.行為主義心理學派就認為人根本沒有所謂的自由意志,就只知道通過一些刺激做些反應.只不過各種刺激非常復雜,並且反應也是一系列的連鎖反應,所以我們不容易感覺的到.當然各大 心理學派所持的觀點不一樣,那些理論我們也不容易證否.跟算法裡面著名的NP難問題有點類似,我們不知道NP難問題是不是能解決.不能證明它能解決,也不能證明它不能解決.
消息控件一般分三層結構.
第一層,windows內核.裡面維護一個消息隊列,像鼠標,鍵盤等一些IO設備產生事件後不是直接傳給應用程序,而是先被操作系統處理,轉換成一個個的"消息".由於創建窗體時會指定一個應用程序句柄,所以當操作某個窗體產生時操作系統知道哪些消息是屬於哪個應用程序的,操作系統為每個應用程序維護一個消息隊列.
第二層,應用程序.在windows API的編程中一般通過一個while循環,用GetMessage或PeekMessage來獲取消息隊列中的消息.不過這些信息還只相當於是原材料,需要進一步加工.通過TranslateMessage把它進一步加工,做一些轉換,或者如果是些無用的消息就會被直接忽略掉.加工完了後又通過DispatchMessage把信息重新傳回操作系統.每一個消息實際上是一個一個結構體,裡面有些相關信息,裡面有一個窗體句柄.這樣我們就知道這個消息是哪個窗體產生的.
第三層,窗體過程(windows procedure),創建窗體時會指定一個窗體過程,實際上就是窗體對應的一些處理消息的函數.當應用程序把消息重新傳回操作系統後.操作系統再把消息發送給窗口過程,窗口過程就根據消息中的一些信息做些判斷做些不同的處理.
C#中的事件處理機制
在C#中做了很多的封裝,我們根本看不到消息的處理過程.通過層層封裝最後我們看到的只是事件(event)這個概念,在C#中event是個關鍵字,也可以看做某種類型.當對窗體做一些操作時可以生成一個個的事件.比如點擊按鍵btnOK,它對應的事件是btnOK.Click.(Click是C#中事先定義好了的事件,我們只要拿來用就行.)其實我們可以這樣簡單的理解,消息控制的三層結構中,前面所有操作都給你封裝了,直到最後指定一個個的窗體過程,而且不同的消息對應的窗體過程都指定好的了.只不過那個過程為空,有點相當於個函數指針,要你自己去指定一個具體的函數.在C#中沒有指針,但有個概念叫代理(delegate),它類似於函數指針.而事件相當於是對代理的封裝,或者說是代理的一種應用.代理還可以用在其他很多地方.
比如定義事件Click是這樣定義的.
(1)public event EventHandler Click; // 其中EventHandler是一個代理類型.定義事件的規范格式就是用關鍵字event然後加代理類型,然後再加變量名.定義代理EventHandler的格式是這樣的.
(2)public delegate void EventHandler(object sender, EventArgs e);//它表示EventHandler可以指向任意以object,EventArgs為參數,返回類型是void的函數.我們前面說了事件Click還只相當於一個空的窗體過程.那我們怎麼去指定具體的函數來.我們可以通過+=這樣的關鍵字來指定一個事件處理程序.比如
(3)btnOK.Click += new System.EventHandler(btnOK_Click); //其中btnOK_Click是函數名,你也可以取其他任何名字.然後在class中任何地方定義函數
而且綁定多個像+=添加字符串一樣直添加.當一個事件綁定了多個函數時,事件觸發時多個函數會全部執行.當然我們還能通過-=這樣的關鍵字解除某個函數的綁定.
void btnOK_Click(object sender, EventArgs e)
{
//操作相應操作的代碼
}
反正C#中所有與窗口或控件相關的事件都給你封裝好了,你直接拿來用,只要綁定個具體的函數就行.另外事件綁定的函數類型一般是兩個參數,其中第一個是object sender,它表明是哪個窗體對象觸發的這個事件.如果是按鈕的話可以把把這個object對象轉換成button對象,然後獲取按鈕其他信息(button)sender.另外一個參數是EventArgs,其實這個參數有時會不一樣,如果是鍵盤觸發的事件它就是KeyPressEventArgs了,它是附帶有其他一些信息,比如鍵盤按的哪個按鈕啊.
不過實際上我們也很少用到這兩參數,讓它們擺在那不管,自己在函數中寫其他代碼就行了.
自定義事件
另外除了預定義好的事件可以直接拿來用外,我們還可以自己定義些事件,使用的語法格式完全一樣.自已定義的事件不一定得是窗體事件,可以是其他任何東東.比如某個變量值達到多少時觸發個事件之類的。
自定義的事件具體是怎麼觸發它執行的由於已經封裝了我們也看不到.不過如果是自定義事件肯定得指定怎麼去執行.舉個簡單的例子看下吧.
public delegate void SayHello(string str); //聲明一個代理
public event SayHello onSayHello; //定義一個事件onSayHello
觸發事件執行的代碼就是
string msg = "arwen";
onSayHello(msg); //看起來跟調用一個函數沒啥區別了.當然你調用它之前還得先綁定一個函數給它,不然會出錯的.所以一般用if(onSayHello != null)這樣判斷一下,如果事件還沒綁定啥函數上去就為null
那既然事件都跟調用函數都差不多了,還不如直接調用函數或者調用代理,為啥搞得多此一舉,再整個事件來啊.
用自定義事件可能基於兩種原因吧,當然這是我自己瞎猜的啊
一,是體現一種設計模式,訂閱者模式.
二,是當體現封裝原則.
比如一個class想調用另外一個class中某個函數時,不想讓直接調用,或者函數聲明為private的,就可通過event間接的調用.因為delegate相當於指向函數的指針,而event又相當於是對delegate的封裝.那你可能又問封裝有啥好的啊,這可能是體現一種設計思想,對以後擴充功能或做一些額外的處理有用吧.不然像像我們把所以類中的字段都給封裝成一個個的屬性(property),大部分時候是一點都看不出來有啥用,反而覺得有點多此一舉,還不如直接用字段別用屬性呢.但如果我們有時想在屬性中做些額外的處理時特別有用.比如只讓你讀取字段值不讓賦值,或者反過來.
舉個用自定義事件調用其他類中的private函數的簡單例子吧.
在class A中有private的函數sayHello.那class B中肯定不能直接調用sayHello了.當然我們另外還要假設在A中用到了B.
delegate void DelegateSayHello(string name); //在類外面某個地方,它其實也可以看成一種特殊的類了.當然也可以放在某個類裡面定義
public class A
{
B sb;
sb.HowAreYou += new DelegateSayHello(sayHello);
private void sayHello(string name)
{
Console.WriteLine("Hello," + name);
}
}
public class B
{
public event DelegateSayHello HowAreYou;
string name = "arwen";
private void DoSomething()
{
HowAreYou(name); //這裡就調用了class A中的private函數sayHello了啊
}
}