1.什么是句柄.
句柄是應用程序建立或使用的對象所使用的一個唯一的整數值(通常是32位),Windows要使用各種各樣的句柄來標識諸如應用程序實例,窗口,圖標,菜單,輸出設備,文件等對象.
Windows是一個以虛擬內存為基礎的操作系統,這種環境下,Windows內存管理器經常在內存中來回移動對象,依次滿足各種應用程序的需要,對象被移動了,意味着地址就變.因此,Windows操作系統用一塊內存地址,用來專門登記各種應用對象在內存中的地址變化,而這個地址(存儲單元的位置)本身是不變的,Windows內存管理器在移動對象在內存的位置后,把對象新的地址告知這個句柄保存,這樣我們只需記住這個句柄地址就可以間接地知道對象具體的內存中什么位置.這個地址是對象裝載時有系統分配的,當系統卸載時又釋放系統.
句柄地址(不變) |
記載對象內存的地址 |
記載實際對象 |
句柄是指針的指針.記載對象在內存中的地址.
2. Windows的消息機制
Windows是一個消息驅動式系統,Windows中有兩種消息隊列,一種是系統消息隊列,另一種是應用程序消息隊列,計算機中所有的輸入設備由Windows監控,當一個事件發生時,Windows先將輸入的消息放入系統的消息隊列中,再將輸入的消息拷貝到相應的應用程序隊列中.應用程序的消息循環從他的消息隊列中檢索每個消息並且發送給相應的窗口函數中.注意:消息是非搶先式,不論事件急或不急,總是按到達先后排隊(一些系統消息除外).
系統的消息隊列中 |
應用程序的消息隊列中 |
窗口函數 |
Windows中消息結構的原型
typedef struct tagMSG
{
HWND hwnd; //消息將要發送到的那個窗口的句柄,用這個參數可以決定讓哪個窗口接收消息
UINT message; //消息號,它唯一標識了一種消息類型。
WPARAM wParam; //一個32位的消息參數,這個值的確切意義取決於消息本身
LPARAM lParam; //一個32位的消息參數,這個值的確切意義取決於消息本身
DWORD time; //消息放入消息隊列中的時間, 從Windows啟動后所測量的時間值
POINT pt; //消息放入消息隊列時的鼠標坐標
} MSG, *PMSG;
2.1 消息隊列
(1) 系統消息隊列(System Message Queue)
設備驅動(mouse, keyboard)會把操作輸入轉化成消息存在系統隊列中,然后系統會把此消息放到目標窗口所在的線程的消息隊列(thread-specific message queue)中等待處理.
(2) 線程消息隊列(Thread-specific Message Queue)
每一個GUI線程都會維護這樣一個線程消息隊列。(這個隊列只有在線程調用GDI函數時才會創建,默認不創建)。然后線程消息隊列中的消息會被送到相應的窗口過程(WndProc)處理, 注意: 線程消息隊列中WM_PAINT,WM_TIMER只有在Queue中沒有其他消息的時候才會被處理,WM_PAINT消息還會被合並以提高效率。其他所有消息以先進先出(FIFO)的方式被處理.
2.2 隊列消息(Queued Messages)和非消息隊列消息(Non – Queued Messages)
(1) 隊列消息
消息會先保存在消息隊列中,消息循環會從此隊列中取消息並分發到各窗口處理
(2) 非隊列消息
如: WM_ACTIVATE, WM_SETFOCUS, WM_SETCURSOR, WM_WINDOWPOSCHANGED
注意: postMessage發送的消息是隊列消息,它會把消息Post到消息隊列中;
SendMessage發送的消息是非隊列消息, 被直接送到窗口過程處理
使用:MFC 寄送一個消息,首先獲取CWnd類對象的指針.
然后調用CWnd的成員函數PostMessage()
2.3 窗口過程
每個窗口會有一個稱為窗口過程的回調函數(WndProc),它帶有四個參數,分別為:窗口句柄(Window Handle),消息ID(Message ID),和兩個消息參數(wParam, lParam), 當窗口收到消息時系統就會調用此窗口過程來處理消息。(所以叫回調函數)
消息類型:
(1)系統定義消息(Sysetem-Defined Messages)
在SDK中事先定義好的消息,非用戶定義的,其范圍在[0x0000, 0x03ff]之間, 可以分為以下三類:
1> 窗口消息(Windows Message)
與窗口的內部運作有關,如創建窗口,繪制窗口,銷毀窗口等。可以是一般的窗口,也可以是Dialog,控件等。
如:WM_CREATE, WM_PAINT, WM_MOUSEMOVE, WM_CTLCOLOR, WM_HSCROLL...
2> 命令消息(Command Message)
與處理用戶請求有關, 如單擊菜單項或工具欄或控件時, 就會產生命令消息。
WM_COMMAND, LOWORD(wParam)表示菜單項,工具欄按鈕或控件的ID。
如果是控件, HIWORD(wParam)表示控件消息類型
wNotifyCode = HIWORD(wParam); //通告代碼
wID = LOWORD(wParam); //菜單條目,控件或快捷鍵的標示符
hwndCtl = (HWND)lParam; //控件句柄
3> 控件通知(Notify Message)
控件通知消息, 這是最靈活的消息格式, 其Message, wParam, lParam分別為:WM_NOTIFY, 控件ID,指向NMHDR的指針。NMHDR包含控件通知的內容, 可以任意擴展。
(2)用戶自定義消息
對於其范圍有如下規定:
WM_USER: 0x0400-0x7FFF (ex. WM_USER+10)
WM_APP(winver> 4.0): 0x8000-0xBFFF (ex.WM_APP+4)
RegisterWindowMessage: 0xC000-0xFFFF
4. WM_COMMAND消息
WM_COMMAND消息
當用戶點擊菜單、按鈕、下拉列表框等控件時候,會觸發WM_COMMAND
LOWORD(wParam) 是控件或菜單或加速鍵的ID,菜單的sparator的ID為0
如果LOWORD(wParam) 是控件ID,HIWORD(wParam)是notification code, 比如BN_CLICKED, BN_DBLCLK等,標志用戶對控件的操作,雙擊,單擊之類。
如果LOWORD(wParam) 是菜單ID,HIWORD(wParam)是0。
如果LOWORD(wParam) 是加速符ID,HIWORD(wParam)是1。
如果LOWORD(wParam) 是控件ID,lParam是控件的句柄值,否則為NULL。其實,GetDlgItem(hWnd, LOWORD(wParam)) == lParam。
Notification Code的命名規律:
列表框: LBN_*****
組合框: CBN_****
Tab框: TBN_****
按鈕: BN_*****
Edit : EN_*****
對於WM_SYSCOMMAND 中如果是系統菜單的消息,都必須要交給DefWindowProc 來處理,並且將返回值返回給Windows ,不然你會發現不能拖動窗體、改變大小、最大最小化操作等。因為你如果不交給DefWindowProc 處理,相當於屏蔽了SC_RESTORE、SC_MOVE、SC_MAXIMIZE、SC_MINIMIZE、SC_CLOSE 等等操作了。這些命令都是通過Windows 投遞WM_SYSCOMMAND 消息,在DefWindowProc 中進行處理的。
WM_COMMAND產生的條件:點擊菜單,點擊加速鍵,點擊子窗口按鈕,點擊工具欄按鈕。這些時候都有command消息產生。
WM_COMMAND消息中有兩個參數,wparam、lparam,定義如下:
wParam 高兩個字節 通知碼
wParam 低兩字節 命令ID
lParam 發送命令消息的子窗體句柄。
對於菜單和加速鍵來說,lParam為0,只有控件此項才非0。命令ID也就是資源腳本中定義的菜單項的命令ID或者加速鍵的命令ID;菜單的通知碼為0;加速鍵的通知碼為1。
對於Windows菜單中菜單項和加速鍵,點擊后,Windows會向所屬的窗體發送WM_SYSCOMMAND,而不是WM_COMMAND消息。注意,WINDOWS菜單是系統菜單,也就是在標題欄點擊鼠標左鍵的時候彈出的菜單。我們可以捕獲WM_CREATE消息,加入自己的操作:GetSysMenu獲取系統菜單句柄,然后對系統菜單進行操作,並且捕獲添加菜單項(根據菜單命令ID)ID對應的WM_SYSCOMMAND消息進行處理。修改系統默認的菜單行為。
子窗體和父窗體:
子窗體被觸發時,向父窗體發送一個WM_COMMAND消息,父窗體的窗口函數處理這個消息,進行相關的處理。lParam表示子窗口句柄,LOWORD(wParam)表示子窗口ID,HIWORD (wParam)表示通知碼(例如單擊,雙擊,SETFOCUS等)。
WM_MESSAGE、WM_COMMAND、WM_NOTIFY等消息有什么不同?
WM_MESSAGE是最普通的WINDOWS消息,對於這種類型的消息沒什么好說的。那WM_COMMAND和WM_NOTIFY消息都是WINDOWS CONTROL給它的父窗體發的消息,那這兩種消息有什么不同呢?WM_COMMAND消息其實是早期的(WIN3.X時代)子窗體消息,子窗體給父窗體發送消息,父窗體就捕獲WM_COMMAND來處理子窗體的消息。但是這個消息只包括了有限的信息,例如wParam包括了子窗口ID和通知碼,lParam則包括了子窗口句柄,就這點信息了,如果想知道一些額外的信息的話(例如,鼠標點在了子控件的位置)就要借助於其他的WM_*消息。所以對於新型的WIN32控件,微軟就增加了一個新的NOTIFICATION消息,這個消息的參數是這樣的:wParam包含了控件ID,而lParam則包含了一個結構體的指針,這個結構體是NMHDR結構或者以NMHDR結構為第一項的一個更大的結構體。這樣就可以包含了很多的子控件想給父窗體提供的信息了,甚至可以自己去定義這種的結構體。這就是這幾種消息的差別點了。
控件的自畫:
首先在創建控件的時候當然就是指定BS_OWNERDRAW的STYLE,這個STYLE是告訴控件,別自己處理外觀,讓主程序來處理你的外觀,這時你就有權決定這個控件是畫成什么樣子了。然后就是處理WM_DRAWITEM的消息,利用 LPDRAWITEMSTRUCT pdis = (LPDRAWITEMSTRUCT) lParam; 來取得一些必要的信息,如按鈕的DC,位置等。這才能對這個DC的內容進行繪畫啊。COMMON CTRL的STYLE都在COMMCTRL.H頭文件里。
按鈕在以下狀態時會對它的父窗口發送WM_COMMAND的消息:
按了一次(BN_CLICKED),取得焦點(BN_SETFOCUS),失去焦點(BN_KILLFOCUS)等。
這個是按鈕的發送WM_COMMAND的條件,其他的控件什么時候會發送WM_COMMAND消息可查看該控件的通知碼(在wParam的高位HIWORD)。例如,滾動條控件在被滾動的時候會向它的父窗體發送消息,但是不是WM_COMMAND消息,而是WM_VSCROLL和WM_HSCROLL消息。這只是為了說明凡是子控件,都會在適當的條件下向它的父窗體發送消息。無論是WM_COMMAND還是WM_NOTIFY或是WM_VSCROLL消息等。MoveWindow會產生WM_SIZE消息。
在Windows3.1里,控件會將mouse, keybord等等的消息通知它的父窗口, 使用的消息就只有WM_COMMAND, 事件種類和控件ID被包含在wParam中, 控件的句柄包含在lParam中。由於wParam和 lParam已經滿了,當控件要向父窗口發送其它特殊消息同時附帶很多信息的時候就沒有地方可以存放它們了。所以Windows3.1中定義了許多其它的消息種類,比如WM_VSCROLL, WM_CTLCOLOR等等,每種消息wParam,lParam中附帶的信息是不同的。
當到了Win32后,控件的種類越來越多,當然不可以為每一個控件都定義一套消息,這樣也不利於系統的擴充。所以在Win32中定義了唯一一個強大的消息 WM_NOTIFY。當然WM_NOTIFY也遵守原來的消息規則,既只帶參數wParam和lParam。唯一不同處在於,此時的lParam中傳送的是一個NMHDR指針。不同的控件可以按照規則對NMHDR進行擴充,因此WM_NOTIFY消息傳送的信息量可以相當的大,這個可以看看MSDN中的相關說明,TreeControl中就有很多這種消息。
現在就可以知道為什么有ON_MESSAGE ,ON_COMMAND, , ON_NOTIFY了。
ON_MESSAGE是處理所有的Windows的消息的,因為所有的消息都以相同的格式傳送,也就是ID, WPARAM, LPARAM.
ON_COMMAND是專門處理WM_COMMAND消息的,這樣我們就不用自己解開WM_COMMAND中wParam和lParam中傳送的控件ID, 事件種類…,所有的都在MFC內部解決了:),當然方便了。
ON_NOTIFY更是不用說了,看看他的處理函數,是不是把NMHDR解出來了。
這樣一樣就一目了然了,ON_COMMAND和ON_NOTIFY都可以用ON_MESSAGE來處理,只不過自己要多做很多事情。ON_COMMAND和ON_NOTIFY最好就不要互換了!