本文轉自:http://www.cnblogs.com/sakiwer/archive/2009/12/15/1619281.html
-----------------------------------------------------------------------------------------------------------------------------------------------------
何謂訊息
滑鼠移動、按鍵被按下、視窗被關閉.,這些都會產生訊息。在Windows作業系統中,訊息是以下面的資料結構存在的(定義在WinUser.h檔案中):..
typedef struct tagMSG { HWND hwnd; UINT message; WPARAM wParam; LPARAM lParam; DWORD time; POINT pt; } MSG; |
訊息內有六個資訊,分別是:
. hwnd:視窗/控制項的唯一hwnd的編號。訊息迴圈會根據此資訊,將訊息送到正確目標。
. message:Windows預先定義的訊息種類的ID。
. wParam 與lParam:有些message本身需要攜帶更多的資訊,這些資訊就放在wParam與lParam中。
. time與pt:訊息發生當時的時間與滑鼠位置。
.NET Framework如何封裝訊息迴圈
.NET Framework的Windows Forms將訊息迴圈封裝起來,以方便我們使用。本節中所提到的類(class),都是屬於System.Windows.Forms名字空間(namespace)。
簡單歸納如下:訊息迴圈被封裝進了Application類的Run()靜態方法中;Windows Procedure被封裝進了NativeWindow 與Control 類中;個別的訊息處理動作被封裝進Control 類的OnXyz()(例如OnPaint())。我們可以覆蓋(override)OnXyz(),來提供我們自己的程式。也可以利用.NET的事件 (event)機制,在Xyz事件上,加入我們的事件處理函數(Event Handler)。Control類的OnXyz()會主動調用Xyz 事件的處理函數。
請注意,因為Xyz 的事件處理函數是由Control類的OnXyz()方法所調用的,所以當你覆寫OnXyz()方法時,不要忘了調用Control類的OnXyz() (除非你有特殊需求),否則Xyz事件處理函數將會沒有作用。只要調用base.OnXyz(),就可以調用到Control類的OnXyz()方法,如 下所示:
protected override void OnPaint(PaintEventArgs e){ base.OnPaint (e);// TODO: 加入 Form1.OnPaint 實作 } |
我們可以利用覆寫Control類的OnXyz(),來決定該訊息發生時要做些什麼。同理,我們甚至可以覆寫Control與NativeWindow類的WndProc(),來定義Windows Procedure。
再次提醒你,因為OnXyz()系列方法是由Control類的WndProc()所調用的,所以當你覆寫WndProc()時,不要忘了調用 Control類的WndProc()(除非你有特殊需求),否則OnXyz()系列方法(以及Xyz事件處理函數)將會沒有作用。只要調用 base.WndProc(),就可以調用到Control類的WndProc(),如下所示:
protected override void WndProc(ref Message m){ base.WndProc (ref m);//TODO: 加入Form1.WndProc 實作 } |
你可能也注意到了,WndProc()需要一個Message類的參數,這正是MSG被封裝成.NET版本的結果。
一個Windows Forms的範例
為了讓讀者更加瞭解實際的狀況,我用下面的執行個體範例作說明:
namespace WindowsApplication1{ /// Form1 的摘要描述。 public class Form1 : Form{ /// 設計工具所需的變數。 private Container components = null; public Form1(){ AutoScaleBaseSize = new Size(5, 15); ClientSize = new Size(292, 266); Name = "Form1"; Text = "Form1"; Paint += new PaintEventHandler(this.Form1_Paint); Paint += new PaintEventHandler(this.Form1_Paint2); } /// 應用程式的主進入點。 [STAThread] static void Main(){ Application.Run(new Form1()); } protected override void OnPaint(PaintEventArgs e){ base.OnPaint (e); // 2 } private void Form1_Paint(object sender, PaintEventArgs e){ // 3 } private void Form1_Paint2(object sender, PaintEventArgs e){ // 4 } protected override void WndProc(ref Message m){ base.WndProc (ref m); // 1 } } } |
1、在Main()中,利用Application.Run()來將Form1視窗顯示出來,並進入訊息迴圈。程式的執行過程中,Application.Run()一直未結束。
2、OS在此Process的訊息佇列內放進一個WM_PAINT訊息,好讓視窗被顯示出來。
3、WM_PAINT被Application.Run()內的訊息迴圈取出來,分發到WndProc()。由於多態(Polymorphism)的因 素,此次調用(invoke)到的WndProc()是屬於Form1的WndProc(),也就是上述程式中批註(comment)1的地方,而不是調 用到 Control.WndProc()。
4、在Form1.WndProc()的最後,有調用base.WndProc(),這實際上調用到Control.WndProc()。
5、Control.WndProc()從Message參數中得知此訊息是WM_PAINT,於是調用OnPaint()。由於多態的因素,此次調用 到的OnPaint()是屬於Form1的OnPaint(),也就是上述程式中批註2的地方,而不是調用到 Control.OnPaint()。
6、在Form1.OnPaint()的最後,有調用base.OnPaint(),這實際上調用到Control.OnPaint()。
7、我們曾經在Form1的建構函式(constructor)中將Form1_Paint()與Form1_Paint2()登記成為Paint事件處理函數
(Event Handler)。Control.OnPaint()會去依序去調用這兩個函數,也就是上述程式中批註3與4的地方。
幹嘛知道這麼多?拜工具之賜,現在的程式員很幸福,可以在糊裡胡塗的情況下寫出程式來。不過這樣的程式員恐怕競爭力不強,畢竟將組件 (component)拖放(drag and drop)到畫面上,再設定組件屬性的工作,稱不上有太大的難度。只有深入瞭解內部原理,才能讓自己對技術融會貫通,也才能讓程式員之路走得更穩健、更長 久。