久久精品国产精品国产精品污,男人扒开添女人下部免费视频,一级国产69式性姿势免费视频,夜鲁夜鲁很鲁在线视频 视频,欧美丰满少妇一区二区三区,国产偷国产偷亚洲高清人乐享,中文 在线 日韩 亚洲 欧美,熟妇人妻无乱码中文字幕真矢织江,一区二区三区人妻制服国产

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

MFC消息处理学习总结

發布時間:2025/4/14 编程问答 16 豆豆
生活随笔 收集整理的這篇文章主要介紹了 MFC消息处理学习总结 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Windows消息機制概述

http://www.cppblog.com/suiaiguo/archive/2009/07/18/90412.html

消息是指什么?
? ? ?消息系統對于一個win32程序來說十分重要,它是一個程序運行的動力源泉。一個消息,是系統定義的一個32位的值,他唯一的定義了一個事件,向 Windows發出一個通知,告訴應用程序某個事情發生了。例如,單擊鼠標、改變窗口尺寸、按下鍵盤上的一個鍵都會使Windows發送一個消息給應用程序。
? ? 消息本身是作為一個記錄傳遞給應用程序的,這個記錄中包含了消息的類型以及其他信息。例如,對于單擊鼠標所產生的消息來說,這個記錄中包含了單擊鼠標時的坐標。這個記錄類型叫做MSG,MSG含有來自windows應用程序消息隊列的消息信息,它在Windows中聲明如下:
typedef struct tagMsg
{
? ? ? ?HWND ? ?hwnd; ? ? ? //接受該消息的窗口句柄
? ? ? ?UINT ? ?message; ? ?//消息常量標識符,也就是我們通常所說的消息號
? ? ? ?WPARAM ?wParam; ? ? //32位消息的特定附加信息,確切含義依賴于消息值
? ? ? ?LPARAM ?lParam; ? ? //32位消息的特定附加信息,確切含義依賴于消息值
? ? ? ?DWORD ? time; ? ? ? //消息創建時的時間
? ? ? ?POINT ? pt; ? ? ? ? //消息創建時的鼠標/光標在屏幕坐標系中的位置
}MSG;

? ? 消息可以由系統或者應用程序產生。系統在發生輸入事件時產生消息。舉個例子, 當用戶敲鍵, 移動鼠標或者單擊控件。系統也產生消息以響應由應用程序帶來的變化, 比如應用程序改變系統字體改變窗體大小。應用程序可以產生消息使窗體執行任務,或者與其他應用程序中的窗口通訊。
消息中有什么?
? ?我們給出了上面的注釋,是不是會對消息結構有了一個比較清楚的認識?如果還沒有,那么我們再試著給出下面的解釋:
? ? ?hwnd 32位的窗口句柄。窗口可以是任何類型的屏幕對象,因為Win32能夠維護大多數可視對象的句柄(窗口、對話框、按鈕、編輯框等)。
? ? ?message用于區別其他消息的常量值,這些常量可以是Windows單元中預定義的常量,也可以是自定義的常量。消息標識符以常量命名的方式指出消息的含義。當窗口過程接收到消息之后,他就會使用消息標識符來決定如何處理消息。例如、WM_PAINT告訴窗口過程窗體客戶區被改變了需要重繪。符號常量指定系統消息屬于的類別,其前綴指明了處理解釋消息的窗體的類型。
? ? ?wParam 通常是一個與消息有關的常量值,也可能是窗口或控件的句柄。
? ? ?lParam 通常是一個指向內存中數據的指針。由于WParam、lParam和Pointer都是32位的,因此,它們之間可以相互轉換。

消息標識符的值
? ? ?系統保留消息標識符的值在0x0000在0x03ff(WM_USER-1)范圍。這些值被系統定義消息使用。應用程序不能使用這些值給自己的消息。應用程序消息從WM_USER(0X0400)到0X7FFF,或0XC000到0XFFFF;WM_USER到 0X7FFF范圍的消息由應用程序自己使用;0XC000到0XFFFF范圍的消息用來和其他應用程序通信,我們順便說一下具有標志性的消息值:
? ? ?WM_NULL---0x0000 ? ?空消息。
? ? ?0x0001----0x0087 ? ?主要是窗口消息。
? ? ?0x00A0----0x00A9 ? ?非客戶區消息?
? ? ?0x0100----0x0108 ? ?鍵盤消息
? ? ?0x0111----0x0126 ? ?菜單消息
? ? ?0x0132----0x0138 ? ?顏色控制消息
? ? ?0x0200----0x020A ? ?鼠標消息
? ? ?0x0211----0x0213 ? ?菜單循環消息
? ? ?0x0220----0x0230 ? ?多文檔消息
? ? ?0x03E0----0x03E8 ? ?DDE消息
? ? ?0x0400 ? ? ? ? ? ? ?WM_USER
? ? ?0x8000 ? ? ? ? ? ? ?WM_APP
? ? ?0x0400----0x7FFF ? ?應用程序自定義私有消息

消息有哪幾種?
? ?其實,windows中的消息雖然很多,但是種類并不繁雜,大體上有3種:窗口消息、命令消息和控件通知消息。
? ? ?窗口消息大概是系統中最為常見的消息,它是指由操作系統和控制其他窗口的窗口所使用的消息。例如CreateWindow、DestroyWindow和MoveWindow等都會激發窗口消息,還有我們在上面談到的單擊鼠標所產生的消息也是一種窗口消息。
? ? ?命令消息,這是一種特殊的窗口消息,他用來處理從一個窗口發送到另一個窗口的用戶請求,例如按下一個按鈕,他就會向主窗口發送一個命令消息。
? ? ?控件通知消息,是指這樣一種消息,一個窗口內的子控件發生了一些事情,需要通知父窗口。通知消息只適用于標準的窗口控件如按鈕、列表框、組合框、編輯框,以及Windows公共控件如樹狀視圖、列表視圖等。例如,單擊或雙擊一個控件、在控件中選擇部分文本、操作控件的滾動條都會產生通知消息。她類似于命令消息,當用戶與控件窗口交互時,那么控件通知消息就會從控件窗口發送到它的主窗口。但是這種消息的存在并不是為了處理用戶命令,而是為了讓主窗口能夠改變控件,例如加載、顯示數據。例如按下一個按鈕,他向父窗口發送的消息也可以看作是一個控件通知消息;單擊鼠標所產生的消息可以由主窗口直接處理,然后交給控件窗口處理。
? ? 其中窗口消息及控件通知消息主要由窗口類即直接或間接由CWND類派生類處理。相對窗口消息及控件通知消息而言,命令消息的處理對象范圍就廣得多,它不僅可以由窗口類處理,還可以由文檔類,文檔模板類及應用類所處理。
? ? 由于控件通知消息很重要的,人們用的也比較多,但是具體的含義往往令初學者暈頭轉向,所以我決定把常見的幾個列出來供大家參考:
按扭控件
BN_CLICKED ? ? ? ?用戶單擊了按鈕
?BN_DISABLE 按鈕被禁止
?BN_DOUBLECLICKED ?用戶雙擊了按鈕
?BN_HILITE ?用/戶加亮了按鈕
?BN_PAINT ?按鈕應當重畫
?BN_UNHILITE 加亮應當去掉

組合框控件
?CBN_CLOSEUP 組合框的列表框被關閉
?CBN_DBLCLK 用戶雙擊了一個字符串
?CBN_DROPDOWN 組合框的列表框被拉出
?CBN_EDITCHANGE 用戶修改了編輯框中的文本
?CBN_EDITUPDATE 編輯框內的文本即將更新
?CBN_ERRSPACE 組合框內存不足
?CBN_KILLFOCUS 組合框失去輸入焦點
?CBN_SELCHANGE 在組合框中選擇了一項
?CBN_SELENDCANCEL 用戶的選擇應當被取消
?CBN_SELENDOK 用戶的選擇是合法的
?CBN_SETFOCUS 組合框獲得輸入焦點

編輯框控件
?EN_CHANGE 編輯框中的文本己更新
?EN_ERRSPACE 編輯框內存不足
?EN_HSCROLL 用戶點擊了水平滾動條
?EN_KILLFOCUS 編輯框正在失去輸入焦點
?EN_MAXTEXT 插入的內容被截斷
?EN_SETFOCUS 編輯框獲得輸入焦點
?EN_UPDATE 編輯框中的文本將要更新
?EN_VSCROLL 用戶點擊了垂直滾動條消息含義

列表框控件
?LBN_DBLCLK 用戶雙擊了一項
?LBN_ERRSPACE 列表框內存不夠
?LBN_KILLFOCUS 列表框正在失去輸入焦點
?LBN_SELCANCEL 選擇被取消
?LBN_SELCHANGE 選擇了另一項
?LBN_SETFOCUS 列表框獲得輸入焦點

隊列消息和非隊列消息
? ?從消息的發送途徑來看,消息可以分成2種:隊列消息和非隊列消息。消息隊列由可以分成系統消息隊列和線程消息隊列。系統消息隊列由Windows維護,線程消息隊列則由每個GUI線程自己進行維護,為避免給non-GUI現成創建消息隊列,所有線程產生時并沒有消息隊列,僅當線程第一次調用GDI函數時系統才給線程創建一個消息隊列。隊列消息送到系統消息隊列,然后到線程消息隊列;非隊列消息直接送給目的窗口過程。
? ? ?對于隊列消息,最常見的是鼠標和鍵盤觸發的消息,例如WM_MOUSERMOVE,WM_CHAR等消息,還有一些其它的消息,例如:WM_PAINT、 WM_TIMER和WM_QUIT。當鼠標、鍵盤事件被觸發后,相應的鼠標或鍵盤驅動程序就會把這些事件轉換成相應的消息,然后輸送到系統消息隊列,由 Windows系統去進行處理。Windows系統則在適當的時機,從系統消息隊列中取出一個消息,根據前面我們所說的MSG消息結構確定消息是要被送往那個窗口,然后把取出的消息送往創建窗口的線程的相應隊列,下面的事情就該由線程消息隊列操心了,Windows開始忙自己的事情去了。線程看到自己的消息隊列中有消息,就從隊列中取出來,通過操作系統發送到合適的窗口過程去處理。
? ? ?一般來講,系統總是將消息Post在消息隊列的末尾。這樣保證窗口以先進先出的順序接受消息。然而,WM_PAINT是一個例外,同一個窗口的多個 WM_PAINT被合并成一個 WM_PAINT 消息, 合并所有的無效區域到一個無效區域。合并WM_PAIN的目的是為了減少刷新窗口的次數。

? ? 非隊列消息將會繞過系統隊列和消息隊列,直接將消息發送到窗口過程,。系統發送非隊列消息通知窗口,系統發送消息通知窗口。例如,當用戶激活一個窗口系統發送WM_ACTIVATE, WM_SETFOCUS, and WM_SETCURSOR。這些消息通知窗口它被激活了。非隊列消息也可以由當應用程序調用系統函數產生。例如,當程序調用SetWindowPos系統發送WM_WINDOWPOSCHANGED消息。一些函數也發送非隊列消息,例如下面我們要談到的函數。
? ? ?
消息的發送
? ? ?了解了上面的這些基礎理論之后,我們就可以進行一下簡單的消息發送與接收。
? ? ?把一個消息發送到窗口有3種方式:發送、寄送和廣播。
? ? ?發送消息的函數有SendMessage、SendMessageCallback、SendNotifyMessage、 SendMessageTimeout;寄送消息的函數主要有PostMessage、PostThreadMessage、 PostQuitMessage;廣播消息的函數我知道的只有BroadcastSystemMessage、 BroadcastSystemMessageEx。
? ? ?SendMessage的原型如下:LRESULT SendMessage(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam),這個函數主要是向一個或多個窗口發送一條消息,一直等到消息被處理之后才會返回。不過需要注意的是,如果接收消息的窗口是同一個應用程序的一部分,那么這個窗口的窗口函數就被作為一個子程序馬上被調用;如果接收消息的窗口是被另外的線程所創建的,那么窗口系統就切換到相應的線程并且調用相應的窗口函數,這條消息不會被放進目標應用程序隊列中。函數的返回值是由接收消息的窗口的窗口函數返回,返回的值取決于被發送的消息。
? ? ?PostMessage的原型如下:BOOL PostMessage(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam),該函數把一條消息放置到創建hWnd窗口的線程的消息隊列中,該函數不等消息被處理就馬上將控制返回。需要注意的是,如果hWnd參數為 HWND_BROADCAST,那么,消息將被寄送給系統中的所有的重疊窗口和彈出窗口,但是子窗口不會收到該消息;如果hWnd參數為NULL,則該函數類似于將dwThreadID參數設置成當前線程的標志來調用PostThreadMEssage函數。
  從上面的這2個具有代表性的函數,我們可以看出消息的發送方式和寄送方式的區別所在:被發送的消息是否會被立即處理,函數是否立即返回。被發送的消息會被立即處理,處理完畢后函數才會返回;被寄送的消息不會被立即處理,他被放到一個先進先出的隊列中,一直等到應用程序空線的時候才會被處理,不過函數放置消息后立即返回。

  實際上,發送消息到一個窗口處理過程和直接調用窗口處理過程之間并沒有太大的區別,他們直接的唯一區別就在于你可以要求操作系統截獲所有被發送的消息,但是不能夠截獲對窗口處理過程的直接調用。
  以寄送方式發送的消息通常是與用戶輸入事件相對應的,因為這些事件不是十分緊迫,可以進行緩慢的緩沖處理,例如鼠標、鍵盤消息會被寄送,而按鈕等消息則會被發送。
  廣播消息用得比較少,BroadcastSystemMessage函數原型如下:
? ? ? long BroadcastSystemMessage(DWORD dwFlags,LPDWORD lpdwRecipients,UINT uiMessage,WPARAM wParam,LPARAM lParam);該函數可以向指定的接收者發送一條消息,這些接收者可以是應用程序、可安裝的驅動程序、網絡驅動程序、系統級別的設備驅動消息和他們的任意組合。需要注意的是,如果dwFlags參數是BSF_QUERY并且至少一個接收者返回了BROADCAST_QUERY_DENY,則返回值為0,如果沒有指定BSF_QUERY,則函數將消息發送給所有接收者,并且忽略其返回值。
消息的接收
 消息的接收主要有3個函數:GetMessage、PeekMessage、WaitMessage。
  GetMessage原型如下:BOOL GetMessage(LPMSG lpMsg,HWND hWnd,UINT wMsgFilterMin,UINT wMsgFilterMax);該函數用來獲取與hWnd參數所指定的窗口相關的且wMsgFilterMin和wMsgFilterMax參數所給出的消息值范圍內的消息。需要注意的是,如果hWnd為NULL,則GetMessage獲取屬于調用該函數應用程序的任一窗口的消息,如果 wMsgFilterMin和wMsgFilterMax都是0,則GetMessage就返回所有可得到的消息。函數獲取之后將刪除消息隊列中的除 WM_PAINT消息之外的其他消息,至于WM_PAINT則只有在其處理之后才被刪除。
   PeekMessage原型如下:BOOL PeekMessage(LPMSG lpMsg,HWND hWnd,UINT wMsgFilterMin,UINT wMsgFilterMax,UINT wRemoveMsg);該函數用于查看應用程序的消息隊列,如果其中有消息就將其放入lpMsg所指的結構中,不過,與GetMessage不同的是,PeekMessage函數不會等到有消息放入隊列時才返回。同樣,如果hWnd為NULL,則PeekMessage獲取屬于調用該函數應用程序的任一窗口的消息,如果hWnd=-1,那么函數只返回把hWnd參數為NULL的PostAppMessage函數送去的消息。如果 wMsgFilterMin和wMsgFilterMax都是0,則PeekMessage就返回所有可得到的消息。函數獲取之后將視最后一個參數來決定是否刪除消息隊列中的除 WM_PAINT消息之外的其他消息,至于WM_PAINT則只有在其處理之后才被刪除。
   WaitMessage原型如下:BOOL WaitMessage();當一個應用程序無事可做時,該函數就將控制權交給另外的應用程序,同時將該應用程序掛起,直到一個新的消息被放入應用程序的隊列之中才返回。

消息的處理
  接下來我們談一下消息的處理,首先我們來看一下VC中的消息泵:


while(GetMessage(&msg, NULL, 0, 0))
{
? ? ? ?if(!TranslateAccelerator(msg.hWnd, hAccelTable, &msg))
? ? ? {?
? ? ? ? ? ? TranslateMessage(&msg);
? ? ? ? ? ? DispatchMessage(&msg);
? ? ? ?}
}
?
? ?首先,GetMessage從進程的主線程的消息隊列中獲取一個消息并將它復制到MSG結構,如果隊列中沒有消息,則GetMessage函數將等待一個消息的到來以后才返回。如果你將一個窗口句柄作為第二個參數傳入GetMessage,那么只有指定窗口的的消息可以從隊列中獲得。GetMessage也可以從消息隊列中過濾消息只接受消息隊列中落在范圍內的消息。這時候就要利用GetMessage/PeekMessage指定一個消息過濾器。這個過濾器是一個消息標識符的范圍或者是一個窗體句柄,或者兩者同時指定。當應用程序要查找一個后入消息隊列的消息是很有用。WM_KEYFIRST 和 WM_KEYLAST 常量用于接受所有的鍵盤消息。 WM_MOUSEFIRST 和 WM_MOUSELAST 常量用于接受所有的鼠標消息。?
 然后TranslateAccelerator判斷該消息是不是一個按鍵消息并且是一個加速鍵消息,如果是,則該函數將把幾個按鍵消息轉換成一個加速鍵消息傳遞給窗口的回調函數。處理了加速鍵之后,函數TranslateMessage將把兩個按鍵消息WM_KEYDOWN和WM_KEYUP轉換成一個 WM_CHAR,不過需要注意的是,消息WM_KEYDOWN,WM_KEYUP仍然將傳遞給窗口的回調函數。 ? ??
 處理完之后,DispatchMessage函數將把此消息發送給該消息指定的窗口中已設定的回調函數。如果消息是WM_QUIT,則 GetMessage返回0,從而退出循環體。應用程序可以使用PostQuitMessage來結束自己的消息循環。通常在主窗口的 WM_DESTROY消息中調用。
 下面我們舉一個常見的小例子來說明這個消息泵的運用:

if (::PeekMessage(&msg, m_hWnd, WM_KEYFIRST,WM_KEYLAST, PM_REMOVE))
{
? ? ? ? ? if (msg.message == WM_KEYDOWN && msg.wParam == VK_ESCAPE)...
}

? 這里我們接受所有的鍵盤消息,所以就用WM_KEYFIRST 和 WM_KEYLAST作為參數。最后一個參數可以是PM_NOREMOVE 或者 PM_REMOVE,表示消息信息是否應該從消息隊列中刪除。 ? ? ? ? ? ? ? ??
? ?所以這段小代碼就是判斷是否按下了Esc鍵,如果是就進行處理。

窗口過程
 窗口過程是一個用于處理所有發送到這個窗口的消息的函數。任何一個窗口類都有一個窗口過程。同一個類的窗口使用同樣的窗口過程來響應消息。系統發送消息給窗口過程將消息數據作為參數傳遞給他,消息到來之后,按照消息類型排序進行處理,其中的參數則用來區分不同的消息,窗口過程使用參數產生合適行為。
 一個窗口過程不經常忽略消息,如果他不處理,它會將消息傳回到執行默認的處理。窗口過程通過調用DefWindowProc來做這個處理。窗口過程必須 return一個值作為它的消息處理結果。大多數窗口只處理小部分消息和將其他的通過DefWindowProc傳遞給系統做默認的處理。窗口過程被所有屬于同一個類的窗口共享,能為不同的窗口處理消息。下面我們來看一下具體的實例:

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
?int wmId, wmEvent;
?PAINTSTRUCT ps;
?HDC hdc;
?TCHAR szHello[MAX_LOADSTRING];
?LoadString(hInst, IDS_HELLO, szHello, MAX_LOADSTRING);

?switch (message)?
?{
? case WM_COMMAND:
? ? ? ? ?wmId ? ?= LOWORD(wParam);?
? ? ? ? ?wmEvent = HIWORD(wParam);?
? ? ? ? ?// Parse the menu selections:
? ? ? ? ?switch (wmId)
? ? ? ? ?{
? ? ? ? ? case IDM_ABOUT:
? ? ? ? ? ? ?DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About);
? ? ? ? ? ? ?break;
? ? ? ? ? case IDM_EXIT:
? ? ? ? ? ? ?DestroyWindow(hWnd);
? ? ? ? ? ? ?break;
? ? ? ? ? default:
? ? ? ? ? ? ?return DefWindowProc(hWnd, message, wParam, lParam);
? ? ? ? ?}
? ?break;

? case WM_PAINT:
? ? ? ? ?hdc = BeginPaint(hWnd, &ps);
? ? ? ? ?// TODO: Add any drawing code here
? ? ? ? ?RECT rt;
? ? ? ? ?GetClientRect(hWnd, &rt);
? ? ? ? ?DrawText(hdc, szHello, strlen(szHello), &rt, DT_CENTER);
? ? ? ? ?EndPaint(hWnd, &ps);
? ? ? ? ?break;

? case WM_DESTROY:
? ? ? ? ?PostQuitMessage(0);
? ? ? ? ?break;
? default:
? ? ? ? ?return DefWindowProc(hWnd, message, wParam, lParam);
? }
? return 0;
}

消息分流器
  通常的窗口過程是通過一個switch語句來實現的,這個事情很煩,有沒有更簡便的方法呢?有,那就是消息分流器,利用消息分流器,我們可以把switch語句分成更小的函數,每一個消息都對應一個小函數,這樣做的好處就是對消息更容易管理。
  之所以被稱為消息分流器,就是因為它可以對任何消息進行分流。下面我們做一個函數就很清楚了:


void MsgCracker(HWND hWnd,int id,HWND hWndCtl,UINT codeNotify)
{
? ? ? switch(id)
? ? ? {
? ? ?case ID_A:
? ? ? ? ? ? ? ? ? if(codeNotify==EN_CHANGE)
? ? ? ? ? ? ? ? ? break;
? ? ?case ID_B:
? ? ? ? ? ? ? ? ? if(codeNotify==BN_CLICKED)
? ? ? ? ? ? ? ? ? break;
? ? ? ? ? ? ?.
? ? ? ?}
}

然后我們修改一下窗口過程:

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
? ? ? ?switch(message)
? ? ? {
? ? ? ? ? ? ?HANDLE_MSG(hWnd,WM_COMMAND,MsgCracker);
? ? ? ? ? ? ?HANDLE_MSG(hWnd,WM_DESTROY,MsgCracker);
? ? ? ? ? ?default:
? ? ? ? ? ? ? ? ? ? return DefWindowProc(hWnd, message, wParam, lParam);
?  }
? return 0;
}


在WindowsX.h中定義了如下的HANDLE_MSG宏:


   #define HANDLE_MSG(hwnd,msg,fn) \
? ? ? ? ? ? ?switch(msg): return HANDLE_##msg((hwnd),(wParam),(lParam),(fn));


實際上,HANDLE_WM_XXXX都是宏,例如:HANDLE_MSG(hWnd,WM_COMMAND,MsgCracker);將被轉換成如下定義:


   #define HANDLE_WM_COMMAND(hwnd,wParam,lParam,fn)\?
? ? ? ? ? ? ?((fn)((hwnd),(int)(LOWORD(wParam)),(HWND)(lParam),(UINT)HIWORD(wParam)),0L);


好了,事情到了這一步,應該一切都明朗了。
不過,我們發現在windowsx.h里面還有一個宏:FORWARD_WM_XXXX,我們還是那WM_COMMAND為例,進行分析:


   #define FORWARD_WM_COMMAND(hwnd, id, hwndCtl, codeNotify, fn) \
? ? ?(void)(fn)((hwnd), WM_COMMAND, MAKEWPARAM((UINT)(id),(UINT)(codeNotify)), (LPARAM)(HWND)(hwndCtl))


所以實際上,FORWARD_WM_XXXX將消息參數進行了重新構造,生成了wParam && lParam,然后調用了我們定義的函數。


前面,我們分析了消息的基本理論和基本的函數及用法,接下來,我們將進一步討論消息傳遞在MFC中的實現。
MFC消息的處理實現方式
  初看MFC中的各種消息,以及在頭腦中根深蒂固的C++的影響,我們可能很自然的就會想到利用C++的三大特性之一:虛擬機制來實現消息的傳遞,但是經過分析,我們看到事情并不是想我們想象的那樣,在MFC中消息是通過一種所謂的消息映射機制來處理的。
  為什么呢?在潘愛民老師翻譯的《Visual C++技術內幕》(第4版)中給出了詳細的原因說明,我再簡要的說一遍。在CWnd類中大約有110個消息,還有其它的MFC的類呢,算起來消息太多了,在C++中對程序中用到的每一個派生類都要有一個vtable,每一個虛函數在vtable中都要占用一個4字節大小的入口地址,這樣一來,對于每個特定類型的窗口或控件,應用程序都需要一個440KB大小的表來支持虛擬消息控件函數。
  如果說上面的窗口或控件可以勉強實現的話,那么對于菜單命令消息及按鈕命令消息呢?因為不同的應用程序有不同的菜單和按鈕,我們怎么處理呢?在MFC 庫的這種消息映射系統就避免了使用大的vtable,并且能夠在處理常規Windows消息的同時處理各種各樣的應用程序的命令消息。
  說白了,MFC中的消息機制其實質是一張巨大的消息及其處理函數的一一對應表,然后加上分析處理這張表的應用框架內部的一些程序代碼.這樣就可以避免在SDK編程中用到的繁瑣的CASE語句。


MFC的消息映射的基類CCmdTarget
  如果你想讓你的控件能夠進行消息映射,就必須從CCmdTarget類中派生。CCmdTarget類是MFC處理命令消息的基礎、核心。MFC為該類設計了許多成員函數和一些成員數據,基本上是為了解決消息映射問題的,所有響應消息或事件的類都從它派生,例如:應用程序類、框架類、文檔類、視圖類和各種各樣的控件類等等,還有很多。
不過這個類里面有2個函數對消息映射非常重要,一個是靜態成員函數DispatchCmdMsg,另一個是虛函數OnCmdMsg。
DispatchCmdMsg專門供MFC內部使用,用來分發Windows消息。OnCmdMsg用來傳遞和發送消息、更新用戶界面對象的狀態。
CCmdTarget對OnCmdMsg的默認實現:在當前命令目標(this所指)的類和基類的消息映射數組里搜索指定命令消息的消息處理函數。
  這里使用虛擬函數GetMessageMap得到命令目標類的消息映射入口數組_messageEntries,然后在數組里匹配命令消息ID相同、控制通知代碼也相同的消息映射條目。其中GetMessageMap是虛擬函數,所以可以確認當前命令目標的確切類。
如果找到了一個匹配的消息映射條目,則使用DispachCmdMsg調用這個處理函數;
如果沒有找到,則使用_GetBaseMessageMap得到基類的消息映射數組,查找,直到找到或搜尋了所有的基類(到CCmdTarget)為止;
如果最后沒有找到,則返回FASLE。
  每個從CCmdTarget派生的命令目標類都可以覆蓋OnCmdMsg,利用它來確定是否可以處理某條命令,如果不能,就通過調用下一命令目標的 OnCmdMsg,把該命令送給下一個命令目標處理。通常,派生類覆蓋OnCmdMsg時,要調用基類的被覆蓋的OnCmdMsg。
  在MFC框架中,一些MFC命令目標類覆蓋了OnCmdMsg,如框架窗口類覆蓋了該函數,實現了MFC的標準命令消息發送路徑。必要的話,應用程序也可以覆蓋OnCmdMsg,改變一個或多個類中的發送規定,實現與標準框架發送規定不同的發送路徑。例如,在以下情況可以作這樣的處理:在要打斷發送順序的類中把命令傳給一個非MFC默認對象;在新的非默認對象中或在可能要傳出命令的命令目標中。


消息映射的內容
? ? 通過ClassWizard為我們生成的代碼,我們可以看到,消息映射基本上分為2大部分:
? ? 在頭文件(.h)中有一個宏DECLARE_MESSAGE_MAP(),他被放在了類的末尾,是一個public屬性的;與之對應的是在實現部分(.cpp)增加了一章消息映射表,內容如下:
? ? BEGIN_MESSAGE_MAP(當前類, 當前類的基類)
? ? ? ?//{{AFX_MSG_MAP(CMainFrame)
? ? ? ? ?消息的入口項
? ? ? ?//}}AFX_MSG_MAP
? ?END_MESSAGE_MAP()
? ?但是僅是這兩項還遠不足以完成一條消息,要是一個消息工作,必須有以下3個部分去協作:
1.在類的定義中加入相應的函數聲明;
2.在類的消息映射表中加入相應的消息映射入口項;
3.在類的實現中加入相應的函數體;


消息的添加
? ?有了上面的這些只是作為基礎,我們接下來就做我們最熟悉、最常用的工作:添加消息。MFC消息的添加主要有2種方法:自動/手動,我們就以這2種方法為例,說一下如何添加消息。
? ?1、利用Class Wizard實現自動添加
? ? ? 在菜單中選擇View-->Class Wizard,也可以用單擊鼠標右鍵,選擇Class Wizard,同樣可以激活Class Wizard。選擇Message Map標簽,從Class name組合框中選取我們想要添加消息的類。在Object IDs列表框中,選取類的名稱。此時, Messages列表框顯示該類的大多數(若不是全部的話)可重載成員函數和窗口消息。類重載顯示在列表的上部,以實際虛構成員函數的大小寫字母來表示。其他為窗口消息,以大寫字母出現,描述了實際窗口所能響應的消息ID。選中我們向添加的消息,單擊Add Function按鈕,Class Wizard自動將該消息添加進來。
? ? ? 有時候,我們想要添加的消息本應該出現在Message列表中,可是就是找不到,怎么辦?不要著急,我們可以利用Class Wizard上Class Info標簽以擴展消息列表。在該頁中,找到Message Filter組合框,通過它可以改變首頁中Messages列表框中的選項。這里,我們選擇Window,從而顯示所有的窗口消息,一把情況下,你想要添加的消息就可以在Message列表框中出現了,如果還沒有,那就接著往下看:)


? ?2、手動地添加消息處理函數
? ? 如果在Messages列表框中仍然看不到我們想要的消息,那么該消息可能是被系統忽略掉或者是你自己創建的,在這種情況下,就必須自己手工添加。根據我們前面所說的消息工作的3個部件,我們一一進行處理:
? ? ? 1) 在類的. h文件中添加處理函數的聲明,緊接在//}}AFX_MSG行之后加入聲明,注意:一定要以afx_msg開頭。
? ? ?通常,添加處理函數聲明的最好的地方是源代碼中Class Wizard維護的表下面,但是在它標記其領域的{{}}括弧外面。這些括弧中的任何東西都將會被Class Wizard銷毀。
? ? ? 2) 接著,在用戶類的.cpp文件中找到//}}AFX_MSG_MAP行,緊接在它之后加入消息入口項。同樣,也是放在{ {} }的外面
? ? ? 3) 最后,在該文件中添加消息處理函數的實體。
========

MFC的消息機制的實現原理和消息處理的過程



下面幾節將分析MFC的消息機制的實現原理和消息處理的過程。為此,首先要分析ClassWizard實現消息映射的內幕,然后討論MFC的窗口過程,分析MFC窗口過程是如何實現消息處理的。


消息映射的定義和實現
MFC處理的三類消息
根據處理函數和處理過程的不同,MFC主要處理三類消息:


Windows消息,前綴以“WM_”打頭,WM_COMMAND例外。Windows消息直接送給MFC窗口過程處理,窗口過程調用對應的消息處理函數。一般,由窗口對象來處理這類消息,也就是說,這類消息處理函數一般是MFC窗口類的成員函數。
控制通知消息,是控制子窗口送給父窗口的WM_COMMAND通知消息。窗口過程調用對應的消息處理函數。一般,由窗口對象來處理這類消息,也就是說,這類消息處理函數一般是MFC窗口類的成員函數。
需要指出的是,Win32使用新的WM_NOFITY來處理復雜的通知消息。WM_COMMAND類型的通知消息僅僅能傳遞一個控制窗口句柄(lparam)、控制窗ID和通知代碼(wparam)。WM_NOTIFY能傳遞任意復雜的信息。


命令消息,這是來自菜單、工具條按鈕、加速鍵等用戶接口對象的WM_COMMAND通知消息,屬于應用程序自己定義的消息。通過消息映射機制,MFC框架把命令按一定的路徑分發給多種類型的對象(具備消息處理能力)處理,如文檔、窗口、應用程序、文檔模板等對象。能處理消息映射的類必須從CCmdTarget類派生。
在討論了消息的分類之后,應該是討論各類消息如何處理的時候了。但是,要知道怎么處理消息,首先要知道如何映射消息。


MFC消息映射的實現方法
MFC使用ClassWizard幫助實現消息映射,它在源碼中添加一些消息映射的內容,并聲明和實現消息處理函數。現在來分析這些被添加的內容。


在類的定義(頭文件)里,它增加了消息處理函數聲明,并添加一行聲明消息映射的宏DECLARE_MESSAGE_MAP。


在類的實現(實現文件)里,實現消息處理函數,并使用IMPLEMENT_MESSAGE_MAP宏實現消息映射。一般情況下,這些聲明和實現是由MFC的ClassWizard自動來維護的。看一個例子:


在AppWizard產生的應用程序類的源碼中,應用程序類的定義(頭文件)包含了類似如下的代碼:


//{{AFX_MSG(CTttApp)


afx_msg void OnAppAbout();


//}}AFX_MSG


DECLARE_MESSAGE_MAP()


應用程序類的實現文件中包含了類似如下的代碼:


BEGIN_MESSAGE_MAP(CTApp, CWinApp)


//{{AFX_MSG_MAP(CTttApp)


ON_COMMAND(ID_APP_ABOUT, OnAppAbout)


//}}AFX_MSG_MAP


END_MESSAGE_MAP()


頭文件里是消息映射和消息處理函數的聲明,實現文件里是消息映射的實現和消息處理函數的實現。它表示讓應用程序對象處理命令消息ID_APP_ABOUT,消息處理函數是OnAppAbout。


為什么這樣做之后就完成了一個消息映射?這些聲明和實現到底作了些什么呢?接著,將討論這些問題。


在聲明與實現的內部
DECLARE_MESSAGE_MAP宏:
首先,看DECLARE_MESSAGE_MAP宏的內容:


#ifdef _AFXDLL


#define DECLARE_MESSAGE_MAP() \


private: \


static const AFX_MSGMAP_ENTRY _messageEntries[]; \


protected: \


static AFX_DATA const AFX_MSGMAP messageMap; \


static const AFX_MSGMAP* PASCAL _GetBaseMessageMap(); \


virtual const AFX_MSGMAP* GetMessageMap() const; \


#else


#define DECLARE_MESSAGE_MAP() \


private: \


static const AFX_MSGMAP_ENTRY _messageEntries[]; \


protected: \


static AFX_DATA const AFX_MSGMAP messageMap; \


virtual const AFX_MSGMAP* GetMessageMap() const; \


#endif


DECLARE_MESSAGE_MAP定義了兩個版本,分別用于靜態或者動態鏈接到MFC DLL的情形。


BEGIN_MESSAE_MAP宏
然后,看BEGIN_MESSAE_MAP宏的內容:


#ifdef _AFXDLL


#define BEGIN_MESSAGE_MAP(theClass, baseClass) \


const AFX_MSGMAP* PASCAL theClass::_GetBaseMessageMap() \


{ return &baseClass::messageMap; } \


const AFX_MSGMAP* theClass::GetMessageMap() const \


{ return &theClass::messageMap; } \


AFX_DATADEF const AFX_MSGMAP theClass::messageMap = \


{ &theClass::_GetBaseMessageMap,&theClass::_messageEntries[0] }; \


const AFX_MSGMAP_ENTRY theClass::_messageEntries[] = \


{ \


#else


#define BEGIN_MESSAGE_MAP(theClass, baseClass) \


const AFX_MSGMAP* theClass::GetMessageMap() const \


{ return &theClass::messageMap; } \


AFX_DATADEF const AFX_MSGMAP theClass::messageMap = \


{ &baseClass::messageMap,&theClass::_messageEntries[0] }; \


const AFX_MSGMAP_ENTRY theClass::_messageEntries[] = \


{ \


#endif


#define END_MESSAGE_MAP() \


{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \


}; \


對應地,BEGIN_MESSAGE_MAP定義了兩個版本,分別用于靜態或者動態鏈接到MFC DLL的情形。END_MESSAGE_MAP相對簡單,就只有一種定義。


ON_COMMAND宏
最后,看ON_COMMAND宏的內容:


#define ON_COMMAND(id, memberFxn) \


{\


WM_COMMAND,\


CN_COMMAND,\


(WORD)id,\


(WORD)id,\


AfxSig_vv,\


(AFX_PMSG)memberFxn\


};


消息映射聲明的解釋
在清楚了有關宏的定義之后,現在來分析它們的作用和功能。


消息映射聲明的實質是給所在類添加幾個靜態成員變量和靜態或虛擬函數,當然它們是與消息映射相關的變量和函數。


成員變量
有兩個成員變量被添加,第一個是_messageEntries,第二個是messageMap。


第一個成員變量的聲明:
AFX_MSGMAP_ENTRY_messageEntries[]


這是一個AFX_MSGMAP_ENTRY類型的數組變量,是一個靜態成員變量,用來容納類的消息映射條目。一個消息映射條目可以用AFX_MSGMAP_ENTRY結構來描述。


AFX_MSGMAP_ENTRY結構的定義如下:


struct AFX_MSGMAP_ENTRY


{


//Windows消息ID


UINT nMessage;


//控制消息的通知碼


UINT nCode;


//Windows Control的ID


UINT nID;


//如果是一定范圍的消息被映射,則nLastID指定其范圍


UINT nLastID;


UINT nSig;//消息的動作標識


//響應消息時應執行的函數(routine to call (orspecial value))


AFX_PMSG pfn;


};


從上述結構可以看出,每條映射有兩部分的內容:第一部分是關于消息ID的,包括前四個域;第二部分是關于消息對應的執行函數,包括后兩個域。


在上述結構的六個域中,pfn是一個指向CCmdTarger成員函數的指針。函數指針的類型定義如下:


typedef void (AFX_MSG_CALL CCmdTarget::*AFX_PMSG)(void);


當使用一條或者多條消息映射條目初始化消息映射數組時,各種不同類型的消息函數都被轉換成這樣的類型:不接收參數,也不返回參數的類型。因為所有可以有消息映射的類都是從CCmdTarge派生的,所以可以實現這樣的轉換。


nSig是一個標識變量,用來標識不同原型的消息處理函數,每一個不同原型的消息處理函數對應一個不同的nSig。在消息分發時,MFC內部根據nSig把消息派發給對應的成員函數處理,實際上,就是根據nSig的值把pfn還原成相應類型的消息處理函數并執行它。


第二個成員變量的聲明
AFX_MSGMAP messageMap;


這是一個AFX_MSGMAP類型的靜態成員變量,從其類型名稱和變量名稱可以猜出,它是一個包含了消息映射信息的變量。的確,它把消息映射的信息(消息映射數組)和相關函數打包在一起,也就是說,得到了一個消息處理類的該變量,就得到了它全部的消息映射數據和功能。AFX_MSGMAP結構的定義如下:


struct AFX_MSGMAP


{


//得到基類的消息映射入口地址的數據或者函數


#ifdef _AFXDLL


//pfnGetBaseMap指向_GetBaseMessageMap函數


const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();


#else


//pBaseMap保存基類消息映射入口_messageEntries的地址


const AFX_MSGMAP* pBaseMap;


#endif


//lpEntries保存消息映射入口_messageEntries的地址


const AFX_MSGMAP_ENTRY* lpEntries;


};


從上面的定義可以看出,通過messageMap可以得到類的消息映射數組_messageEntries和函數_GetBaseMessageMap的地址(不使用MFC DLL時,是基類消息映射數組的地址)。


成員函數
_GetBaseMessageMap()
用來得到基類消息映射的函數。


GetMessageMap()
用來得到自身消息映射的函數。


消息映射實現的解釋
消息映射實現的實質是初始化聲明中定義的靜態成員函數_messageEntries和messageMap,實現所聲明的靜態或虛擬函數GetMessageMap、_GetBaseMessageMap。


這樣,在進入WinMain函數之前,每個可以響應消息的MFC類都生成了一個消息映射表,程序運行時通過查詢該表判斷是否需要響應某條消息。


對消息映射入口表(消息映射數組)的初始化
如前所述,消息映射數組的元素是消息映射條目,條目的格式符合結構AFX_MESSAGE_ENTRY的描述。所以,要初始化消息映射數組,就必須使用符合該格式的數據來填充:如果指定當前類處理某個消息,則把和該消息有關的信息(四個)和消息處理函數的地址及原型組合成為一個消息映射條目,加入到消息映射數組中。


顯然,這是一個繁瑣的工作。為了簡化操作,MFC根據消息的不同和消息處理方式的不同,把消息映射劃分成若干類別,每一類的消息映射至少有一個共性:消息處理函數的原型相同。對每一類消息映射,MFC定義了一個宏來簡化初始化消息數組的工作。例如,前文提到的ON_COMMAND宏用來映射命令消息,只要指定命令ID和消息處理函數即可,因為對這類命令消息映射條目,其他四個屬性都是固定的。ON_COMMAND宏的初始化內容如下:


{WM_COMMAND,


CN_COMMAND,


(WORD)ID_APP_ABOUT,


(WORD)ID_APP_ABOUT,


AfxSig_vv,


(AFX_PMSG)OnAppAbout


}


這個消息映射條目的含義是:消息ID是ID_APP_ABOUT,OnAppAbout被轉換成AFX_PMSG指針類型,AfxSig_vv是MFC預定義的枚舉變量,用來標識OnAppAbout的函數類型為參數空(Void)、返回空(Void)。


在消息映射數組的最后,是宏END_MESSAGE_MAP的內容,它標識消息處理類的消息映射條目的終止。


對messageMap的初始化
如前所述,messageMap的類型是AFX_MESSMAP。


經過初始化,域lpEntries保存了消息映射數組_messageEntries的地址;如果動態鏈接到MFC DLL,則pfnGetBaseMap保存了_GetBaseMessageMap成員函數的地址;否則pBaseMap保存了基類的消息映射數組的地址。


對函數的實現
_GetBaseMessageMap()


它返回基類的成員變量messagMap(當使用MFC DLL時),使用該函數得到基類消息映射入口表。


GetMessageMap():


它返回成員變量messageMap,使用該函數得到自身消息映射入口表。


順便說一下,消息映射類的基類CCmdTarget也實現了上述和消息映射相關的函數,不過,它的消息映射數組是空的。


既然消息映射宏方便了消息映射的實現,那么有必要詳細的討論消息映射宏。下一節,介紹消息映射宏的分類、用法和用途。


消息映射宏的種類
為了簡化程序員的工作,MFC定義了一系列的消息映射宏和像AfxSig_vv這樣的枚舉變量,以及標準消息處理函數,并且具體地實現這些函數。這里主要討論消息映射宏,常用的分為以下幾類。


用于Windows消息的宏,前綴為“ON_WM_”。
這樣的宏不帶參數,因為它對應的消息和消息處理函數的函數名稱、函數原型是確定的。MFC提供了這類消息處理函數的定義和缺省實現。每個這樣的宏處理不同的Windows消息。


例如:宏ON_WM_CREATE()把消息WM_CREATE映射到OnCreate函數,消息映射條目的第一個成員nMessage指定為要處理的Windows消息的ID,第二個成員nCode指定為0。


用于命令消息的宏ON_COMMAND
這類宏帶有參數,需要通過參數指定命令ID和消息處理函數。這些消息都映射到WM_COMMAND上,也就是將消息映射條目的第一個成員nMessage指定為WM_COMMAND,第二個成員nCode指定為CN_COMMAND(即0)。消息處理函數的原型是void (void),不帶參數,不返回值。


除了單條命令消息的映射,還有把一定范圍的命令消息映射到一個消息處理函數的映射宏ON_COMMAND_RANGE。這類宏帶有參數,需要指定命令ID的范圍和消息處理函數。這些消息都映射到WM_COMMAND上,也就是將消息映射條目的第一個成員nMessage指定為WM_COMMAND,第二個成員nCode指定為CN_COMMAND(即0),第三個成員nID和第四個成員nLastID指定了映射消息的起止范圍。消息處理函數的原型是void (UINT),有一個UINT類型的參數,表示要處理的命令消息ID,不返回值。


(3)用于控制通知消息的宏


這類宏可能帶有三個參數,如ON_CONTROL,就需要指定控制窗口ID,通知碼和消息處理函數;也可能帶有兩個參數,如具體處理特定通知消息的宏ON_BN_CLICKED、ON_LBN_DBLCLK、ON_CBN_EDITCHANGE等,需要指定控制窗口ID和消息處理函數。


控制通知消息也被映射到WM_COMMAND上,也就是將消息映射條目的第一個成員的nMessage指定為WM_COMMAND,但是第二個成員nCode是特定的通知碼,第三個成員nID是控制子窗口的ID,第四個成員nLastID等于第三個成員的值。消息處理函數的原型是void (void),沒有參數,不返回值。


還有一類宏處理通知消息ON_NOTIFY,它類似于ON_CONTROL,但是控制通知消息被映射到WM_NOTIFY。消息映射條目的第一個成員的nMessage被指定為WM_NOTIFY,第二個成員nCode是特定的通知碼,第三個成員nID是控制子窗口的ID,第四個成員nLastID等于第三個成員的值。消息處理函數的原型是void (NMHDR*, LRESULT*),參數1是NMHDR指針,參數2是LRESULT指針,用于返回結果,但函數不返回值。


對應地,還有把一定范圍的控制子窗口的某個通知消息映射到一個消息處理函數的映射宏,這類宏包括ON__CONTROL_RANGE和ON_NOTIFY_RANGE。這類宏帶有參數,需要指定控制子窗口ID的范圍和通知消息,以及消息處理函數。


對于ON__CONTROL_RANGE,是將消息映射條目的第一個成員的nMessage指定為WM_COMMAND,但是第二個成員nCode是特定的通知碼,第三個成員nID和第四個成員nLastID等于指定了控制窗口ID的范圍。消息處理函數的原型是void (UINT),參數表示要處理的通知消息是哪個ID的控制子窗口發送的,函數不返回值。


對于ON__NOTIFY_RANGE,消息映射條目的第一個成員的nMessage被指定為WM_NOTIFY,第二個成員nCode是特定的通知碼,第三個成員nID和第四個成員nLastID指定了控制窗口ID的范圍。消息處理函數的原型是void (UINT, NMHDR*, LRESULT*),參數1表示要處理的通知消息是哪個ID的控制子窗口發送的,參數2是NMHDR指針,參數3是LRESULT指針,用于返回結果,但函數不返回值。


(4)用于用戶界面接口狀態更新的ON_UPDATE_COMMAND_UI宏


這類宏被映射到消息WM_COMMND上,帶有兩個參數,需要指定用戶接口對象ID和消息處理函數。消息映射條目的第一個成員nMessage被指定為WM_COMMAND,第二個成員nCode被指定為-1,第三個成員nID和第四個成員nLastID都指定為用戶接口對象ID。消息處理函數的原型是 void (CCmdUI*),參數指向一個CCmdUI對象,不返回值。


對應地,有更新一定ID范圍的用戶接口對象的宏ON_UPDATE_COMMAND_UI_RANGE,此宏帶有三個參數,用于指定用戶接口對象ID的范圍和消息處理函數。消息映射條目的第一個成員nMessage被指定為WM_COMMAND,第二個成員nCode被指定為-1,第三個成員nID和第四個成員nLastID用于指定用戶接口對象ID的范圍。消息處理函數的原型是 void (CCmdUI*),參數指向一個CCmdUI對象,函數不返回值。之所以不用當前用戶接口對象ID作為參數,是因為CCmdUI對象包含了有關信息。


(5)用于其他消息的宏


例如用于用戶定義消息的ON_MESSAGE。這類宏帶有參數,需要指定消息ID和消息處理函數。消息映射條目的第一個成員nMessage被指定為消息ID,第二個成員nCode被指定為0,第三個成員nID和第四個成員也是0。消息處理的原型是LRESULT (WPARAM, LPARAM),參數1和參數2是消息參數wParam和lParam,返回LRESULT類型的值。


(6)擴展消息映射宏


很多普通消息映射宏都有對應的擴展消息映射宏,例如:ON_COMMAND對應的ON_COMMAND_EX,ON_ONTIFY對應的ON_ONTIFY_EX,等等。擴展宏除了具有普通宏的功能,還有特別的用途。關于擴展宏的具體討論和分析,見4.4.3.2節。


作為一個總結,下表列出了這些常用的消息映射宏。


表4-1 常用的消息映射宏


消息映射宏


用途


ON_COMMAND


把command message映射到相應的函數


ON_CONTROL


把control notification message映射到相應的函數。MFC根據不同的控制消息,在此基礎上定義了更具體的宏,這樣用戶在使用時就不需要指定通知代碼ID,如ON_BN_CLICKED。


ON_MESSAGE


把user-defined message.映射到相應的函數


ON_REGISTERED_MESSAGE


把registered user-defined message映射到相應的函數,實際上nMessage等于0x0C000,nSig等于宏的消息參數。nSig的真實值為Afxsig_lwl。


ON_UPDATE_COMMAND_UI


把user interface user update command message映射到相應的函數上。


ON_COMMAND_RANGE


把一定范圍內的command IDs 映射到相應的函數上


ON_UPDATE_COMMAND_UI_RANGE


把一定范圍內的user interface user update command message映射到相應的函數上


ON_CONTROL_RANGE


把一定范圍內的control notification message映射到相應的函數上


在表4-1中,宏ON_REGISTERED_MESSAGE的定義如下:


#define ON_REGISTERED_MESSAGE(nMessageVariable, memberFxn) \


{ 0xC000, 0, 0, 0,\


(UINT)(UINT*)(&nMessageVariable), \


/*implied 'AfxSig_lwl'*/ \


(AFX_PMSG)(AFX_PMSGW)(LRESULT\


(AFX_MSG_CALL CWnd::*)\


(WPARAM, LPARAM))&memberFxn }


從上面的定義可以看出,實際上,該消息被映射到WM_COMMAND(0XC000),指定的registered消息ID存放在nSig域內,nSig的值在這樣的映射條目下隱含地定為AfxSig_lwl。由于ID和正常的nSig域存放的值范圍不同,所以MFC可以判斷出是否是registered消息映射條目。如果是,則使用AfxSig_lwl把消息處理函數轉換成參數1為Word、參數2為long、返回值為long的類型。


在介紹完了消息映射的內幕之后,應該討論消息處理過程了。由于CCmdTarge的特殊性和重要性,在4.3節先對其作一個大略的介紹。


CcmdTarget類
除了CObject類外,還有一個非常重要的類CCmdTarget。所有響應消息或事件的類都從它派生。例如,CWinapp,CWnd,CDocument,CView,CDocTemplate,CFrameWnd,等等。


CCmdTarget類是MFC處理命令消息的基礎、核心。MFC為該類設計了許多成員函數和一些成員數據,基本上是為了解決消息映射問題的,而且,很大一部分是針對OLE設計的。在OLE應用中,CCmdTarget是MFC處理模塊狀態的重要環節,它起到了傳遞模塊狀態的作用:其構造函數獲取當前模塊狀態,并保存在成員變量m_pModuleState里頭。關于模塊狀態,在后面章節講述。


CCmdTarget有兩個與消息映射有密切關系的成員函數:DispatchCmdMsg和OnCmdMsg。


靜態成員函數DispatchCmdMsg
CCmdTarget的靜態成員函數DispatchCmdMsg,用來分發Windows消息。此函數是MFC內部使用的,其原型如下:


static BOOL DispatchCmdMsg(


CCmdTarget* pTarget,


UINT nID,


int nCode,


AFX_PMSG pfn,


void* pExtra,


UINT nSig,


AFX_CMDHANDLERINFO* pHandlerInfo)


關于此函數將在4.4.3.2章節命令消息的處理中作更詳細的描述。


虛擬函數OnCmdMsg
CCmdTarget的虛擬函數OnCmdMsg,用來傳遞和發送消息、更新用戶界面對象的狀態,其原型如下:


OnCmdMsg(


UINT nID,


int nCode,


void* pExtra,


AFX_CMDHANDLERINFO* pHandlerInfo)


框架的命令消息傳遞機制主要是通過該函數來實現的。其參數描述參見4.3.3.2章節DispacthCMdMessage的參數描述。


在本書中,命令目標指希望或者可能處理消息的對象;命令目標類指命令目標的類。


CCmdTarget對OnCmdMsg的默認實現:在當前命令目標(this所指)的類和基類的消息映射數組里搜索指定命令消息的消息處理函數(標準Windows消息不會送到這里處理)。


這里使用虛擬函數GetMessageMap得到命令目標類的消息映射入口數組_messageEntries,然后在數組里匹配指定的消息映射條目。匹配標準:命令消息ID相同,控制通知代碼相同。因為GetMessageMap是虛擬函數,所以可以確認當前命令目標的確切類。


如果找到了一個匹配的消息映射條目,則使用DispachCmdMsg調用這個處理函數;


如果沒有找到,則使用_GetBaseMessageMap得到基類的消息映射數組,查找,直到找到或搜尋了所有的基類(到CCmdTarget)為止;


如果最后沒有找到,則返回FASLE。


每個從CCmdTarget派生的命令目標類都可以覆蓋OnCmdMsg,利用它來確定是否可以處理某條命令,如果不能,就通過調用下一命令目標的OnCmdMsg,把該命令送給下一個命令目標處理。通常,派生類覆蓋OnCmdMsg時,要調用基類的被覆蓋的OnCmdMsg。


在MFC框架中,一些MFC命令目標類覆蓋了OnCmdMsg,如框架窗口類覆蓋了該函數,實現了MFC的標準命令消息發送路徑。具體實現見后續章節。


必要的話,應用程序也可以覆蓋OnCmdMsg,改變一個或多個類中的發送規定,實現與標準框架發送規定不同的發送路徑。例如,在以下情況可以作這樣的處理:在要打斷發送順序的類中把命令傳給一個非MFC默認對象;在新的非默認對象中或在可能要傳出命令的命令目標中。


本節對CCmdTarget的兩個成員函數作一些討論,是為了對MFC的消息處理有一個大致印象。后面4.4.3.2節和4.4.3.3節將作進一步的討論。


MFC窗口過程
前文曾經提到,所有的消息都送給窗口過程處理,MFC的所有窗口都使用同一窗口過程,消息或者直接由窗口過程調用相應的消息處理函數處理,或者按MFC命令消息派發路徑送給指定的命令目標處理。


那么,MFC的窗口過程是什么?怎么處理標準Windows消息?怎么實現命令消息的派發?這些都將是下文要回答的問題。


MFC窗口過程的指定
從前面的討論可知,每一個“窗口類”都有自己的窗口過程。正常情況下使用該“窗口類”創建的窗口都使用它的窗口過程。


MFC的窗口對象在創建HWND窗口時,也使用了已經注冊的“窗口類”,這些“窗口類”或者使用應用程序提供的窗口過程,或者使用Windows提供的窗口過程(例如Windows控制窗口、對話框等)。那么,為什么說MFC創建的所有HWND窗口使用同一個窗口過程呢?


在MFC中,的確所有的窗口都使用同一個窗口過程:AfxWndProc或AfxWndProcBase(如果定義了_AFXDLL)。它們的原型如下:


LRESULT CALLBACK


AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAMlParam)


LRESULT CALLBACK


AfxWndProcBase(HWND hWnd, UINT nMsg, WPARAM wParam,LPARAM lParam)


這兩個函數的原型都如4.1.1節描述的窗口過程一樣。


如果動態鏈接到MFC DLL(定義了_AFXDLL),則AfxWndProcBase被用作窗口過程,否則AfxWndProc被用作窗口過程。AfxWndProcBase首先使用宏AFX_MANAGE_STATE設置正確的模塊狀態,然后調用AfxWndProc。


下面,假設不使用MFC DLL,討論MFC如何使用AfxWndProc取代各個窗口的原窗口過程。


窗口過程的取代發生在窗口創建的過程時,使用了子類化(Subclass)的方法。所以,從窗口的創建過程來考察取代過程。從前面可以知道,窗口創建最終是通過調用CWnd::CreateEx函數完成的,分析該函數的流程,如圖4-1所示。


圖4-1中的CREATESTRUCT結構類型的變量cs包含了傳遞給窗口過程的初始化參數。CREATESTRUCT結構描述了創建窗口所需要的信息,定義如下:


typedef struct tagCREATESTRUCT {


LPVOID lpCreateParams; //用來創建窗口的數據


HANDLE hInstance; //創建窗口的實例


HMENU hMenu; //窗口菜單


HWND hwndParent; //父窗口


int cy; //高度


int cx; //寬度


int y; //原點Y坐標


int x;//原點X坐標


LONG style; //窗口風格


LPCSTR lpszName; //窗口名


LPCSTR lpszClass; //窗口類


DWORD dwExStyle; //窗口擴展風格


} CREATESTRUCT;


cs表示的創建參數可以在創建窗口之前被程序員修改,程序員可以覆蓋當前窗口類的虛擬成員函數PreCreateWindow,通過該函數來修改cs的style域,改變窗口風格。這里cs的主要作用是保存創建窗口的各種信息,::CreateWindowEx函數使用cs的各個域作為參數來創建窗口,關于該函數見2.2.2節。


在創建窗口之前,創建了一個WH_CBT類型的鉤子(Hook)。這樣,創建窗口時所有的消息都會被鉤子過程函數_AfxCbtFilterHook截獲。


AfxCbtFilterHook函數首先檢查是不是希望處理的Hook──HCBT_CREATEWND。如果是,則先把MFC窗口對象(該對象必須已經創建了)和剛剛創建的Windows窗口對象捆綁在一起,建立它們之間的映射(見后面模塊-線程狀態);然后,調用::SetWindowLong設置窗口過程為AfxWndProc,并保存原窗口過程在窗口類成員變量m_pfnSuper中,這樣形成一個窗口過程鏈。需要的時候,原窗口過程地址可以通過窗口類成員函數GetSuperWndProcAddr得到。


這樣,AfxWndProc就成為CWnd或其派生類的窗口過程。不論隊列消息,還是非隊列消息,都送到AfxWndProc窗口過程來處理(如果使用MFC DLL,則AfxWndProcBase被調用,然后是AfxWndProc)。經過消息分發之后沒有被處理的消息,將送給原窗口過程處理。


最后,有一點可能需要解釋:為什么不直接指定窗口過程為AfxWndProc,而要這么大費周折呢?這是因為原窗口過程(“窗口類”指定的窗口過程)常常是必要的,是不可缺少的。


接下來,討論AfxWndProc窗口過程如何使用消息映射數據實現消息映射。Windows消息和命令消息的處理不一樣,前者沒有消息分發的過程。


對Windows消息的接收和處理
Windows消息送給AfxWndProc窗口過程之后,AfxWndProc得到HWND窗口對應的MFC窗口對象,然后,搜索該MFC窗口對象和其基類的消息映射數組,判定它們是否處理當前消息,如果是則調用對應的消息處理函數,否則,進行缺省處理。


下面,以一個應用程序的視窗口創建時,對WM_CREATE消息的處理為例,詳細地討論Windows消息的分發過程。


用第一章的例子,類CTview要處理WM_CREATE消息,使用ClassWizard加入消息處理函數CTview::OnCreate。下面,看這個函數怎么被調用:


視窗口最終調用::CreateEx函數來創建。由Windows系統發送WM_CREATE消息給視的窗口過程AfxWndProc,參數1是創建的視窗口的句柄,參數2是消息ID(WM_CREATE),參數3、4是消息參數。圖4-2描述了其余的處理過程。圖中函數的類屬限制并非源碼中所具有的,而是根據處理過程得出的判斷。例如,“CWnd::WindowProc”表示CWnd類的虛擬函數WindowProc被調用,并不一定當前對象是CWnd類的實例,事實上,它是CWnd派生類CTview類的實例;而“CTview::OnCreate”表示CTview的消息處理函數OnCreate被調用。下面描述每一步的詳細處理。


從窗口過程到消息映射
首先,分析AfxWndProc窗口過程函數。


AfxWndProc的原型如下:
LRESULT AfxWndProc(HWND hWnd,


UINT nMsg, WPARAM wParam, LPARAM lParam)


如果收到的消息nMsg不是WM_QUERYAFXWNDPROC(該消息被MFC內部用來確認窗口過程是否使用AfxWndProc),則從hWnd得到對應的MFC Windows對象(該對象必須已存在,是永久性<Permanent>對象)指針pWnd。pWnd所指的MFC窗口對象將負責完成消息的處理。這里,pWnd所指示的對象是MFC視窗口對象,即CTview對象。


然后,把pWnd和AfxWndProc接受的四個參數傳遞給函數AfxCallWndProc執行。


AfxCallWndProc原型如下:
LRESULT AFXAPI AfxCallWndProc(CWnd* pWnd, HWND hWnd,


UINT nMsg, WPARAM wParam = 0, LPARAM lParam = 0)


MFC使用AfxCallWndProc函數把消息送給CWnd類或其派生類的對象。該函數主要是把消息和消息參數(nMsg、wParam、lParam)傳遞給MFC窗口對象的成員函數WindowProc(pWnd->WindowProc)作進一步處理。如果是WM_INITDIALOG消息,則在調用WindowProc前后要作一些處理。


WindowProc的函數原型如下:


LRESULT CWnd::WindowProc(UINT message,


WPARAM wParam, LPARAM lParam)


這是一個虛擬函數,程序員可以在CWnd的派生類中覆蓋它,改變MFC分發消息的方式。例如,MFC的CControlBar就覆蓋了WindowProc,對某些消息作了自己的特別處理,其他消息處理由基類的WindowProc函數完成。


但是在當前例子中,當前對象的類CTview沒有覆蓋該函數,所以CWnd的WindowProc被調用。


這個函數把下一步的工作交給OnWndMsg函數來處理。如果OnWndMsg沒有處理,則交給DefWindowProc來處理。


OnWndMsg和DefWindowProc都是CWnd類的虛擬函數。


OnWndMsg的原型如下:
BOOL CWnd::OnWndMsg( UINT message,


WPARAM wParam, LPARAM lParam,RESULT*pResult );


該函數是虛擬函數。


和WindowProc一樣,由于當前對象的類CTview沒有覆蓋該函數,所以CWnd的OnWndMsg被調用。


在CWnd中,MFC使用OnWndMsg來分別處理各類消息:


如果是WM_COMMAND消息,交給OnCommand處理;然后返回。


如果是WM_NOTIFY消息,交給OnNotify處理;然后返回。


如果是WM_ACTIVATE消息,先交給_AfxHandleActivate處理(后面5.3.3.7節會解釋它的處理),再繼續下面的處理。


如果是WM_SETCURSOR消息,先交給_AfxHandleSetCursor處理;然后返回。


如果是其他的Windows消息(包括WM_ACTIVATE),則


首先在消息緩沖池進行消息匹配,


若匹配成功,則調用相應的消息處理函數;


若不成功,則在消息目標的消息映射數組中進行查找匹配,看它是否處理當前消息。這里,消息目標即CTview對象。


如果消息目標處理了該消息,則會匹配到消息處理函數,調用它進行處理;


否則,該消息沒有被應用程序處理,OnWndMsg返回FALSE。


關于Windows消息和消息處理函數的匹配,見下一節。


缺省處理函數DefWindowProc將在討論對話框等的實現時具體分析。


Windows消息的查找和匹配
CWnd或者派生類的對象調用OnWndMsg搜索本對象或者基類的消息映射數組,尋找當前消息的消息處理函數。如果當前對象或者基類處理了當前消息,則必定在其中一個類的消息映射數組中匹配到當前消息的處理函數。


消息匹配是一個比較耗時的任務,為了提高效率,MFC設計了一個消息緩沖池,把要處理的消息和匹配到的消息映射條目(條目包含了消息處理函數的地址)以及進行消息處理的當前類等信息構成一條緩沖信息,放到緩沖池中。如果以后又有同樣的消息需要同一個類處理,則直接從緩沖池查找到對應的消息映射條目就可以了。


MFC用哈希查找來查詢消息映射緩沖池。消息緩沖池相當于一個哈希表,它是應用程序的一個全局變量,可以放512條最新用到的消息映射條目的緩沖信息,每一條緩沖信息是哈希表的一個入口。


采用AFX_MSG_CACHE結構描述每條緩沖信息,其定義如下:


struct AFX_MSG_CACHE


{


UINT nMsg;


const AFX_MSGMAP_ENTRY* lpEntry;


const AFX_MSGMAP* pMessageMap;


};


nMsg存放消息ID,每個哈希表入口有不同的nMsg。


lpEnty存放和消息ID匹配的消息映射條目的地址,它可能是this所指對象的類的映射條目,也可能是這個類的某個基類的映射條目,也可能是空。


pMessageMap存放消息處理函數匹配成功時進行消息處理的當前類(this所指對象的類)的靜態成員變量messageMap的地址,它唯一的標識了一個類(每個類的messageMap變量都不一樣)。


this所指對象是一個CWnd或其派生類的實例,是正在處理消息的MFC窗口對象。


哈希查找:使用消息ID的值作為關鍵值進行哈希查找,如果成功,即可從lpEntry獲得消息映射條目的地址,從而得到消息處理函數及其原型。


如何判斷是否成功匹配呢?有兩條標準:


第一,當前要處理的消息message在哈希表(緩沖池)中有入口;第二,當前窗口對象(this所指對象)的類的靜態變量messageMap的地址應該等于本條緩沖信息的pMessagMap。MFC通過虛擬函數GetMessagMap得到messageMap的地址。


如果在消息緩沖池中沒有找到匹配,則搜索當前對象的消息映射數組,看是否有合適的消息處理函數。


如果匹配到一個消息處理函數,則把匹配結果加入到消息緩沖池中,即填寫該條消息對應的哈希表入口:


nMsg=message;


pMessageMap=this->GetMessageMap;


lpEntry=查找結果


然后,調用匹配到的消息處理函數。否則(沒有找到),使用_GetBaseMessageMap得到基類的消息映射數組,查找和匹配;直到匹配成功或搜尋了所有的基類(到CCmdTarget)為止。


如果最后沒有找到,則也把該條消息的匹配結果加入到緩沖池中。和匹配成功不同的是:指定lpEntry為空。這樣OnWndMsg返回,把控制權返還給AfxCallWndProc函數,AfxCallWndProc將繼續調用DefWndProc進行缺省處理。


消息映射數組的搜索在CCmdTarget::OnCmdMsg函數中也用到了,而且算法相同。為了提高速度,MFC把和消息映射數組條目逐一比較、匹配的函數AfxFindMessageEntry用匯編書寫。


const AFX_MSGMAP_ENTRY* AFXAPI


AfxFindMessageEntry(const AFX_MSGMAP_ENTRY* lpEntry,


UINT nMsg, UINT nCode, UINT nID)


第一個參數是要搜索的映射數組的入口;第二個參數是Windows消息標識;第三個參數是控制通知消息標識;第四個參數是命令消息標識。


對Windows消息來說,nMsg是每條消息不同的,nID和nCode為0。


對命令消息來說,nMsg固定為WM_COMMAND,nID是每條消息不同,nCode都是CN_COMMAND(定義為0)。


對控制通知消息來說,nMsg固定為WM_COMMAND或者WM_NOTIFY,nID和nCode是每條消息不同。


對于Register消息,nMsg指定為0XC000,nID和nCode為0。在使用函數AfxFindMessageEntry得到匹配結果之后,還必須判斷nSig是否等于message,只有相等才調用對應的消息處理函數。


Windows消息處理函數的調用
對一個Windows消息,匹配到了一個消息映射條目之后,將調用映射條目所指示的消息處理函數。


調用處理函數的過程就是轉換映射條目的pfn指針為適當的函數類型并執行它:MFC定義了一個成員函數指針mmf,首先把消息處理函數的地址賦值給該函數指針,然后根據消息映射條目的nSig值轉換指針的類型。但是,要給函數指針mmf賦值,必須使該指針可以指向所有的消息處理函數,為此則該指針的類型是所有類型的消息處理函數指針的聯合體。


對上述過程,MFC的實現大略如下:


union MessageMapFunctions mmf;


mmf.pfn = lpEntry->pfn;


swithc (value_of_nsig){





case AfxSig_is: //OnCreate就是該類型


lResult = (this->*mmf.pfn_is)((LPTSTR)lParam);


break;





default:


ASSERT(FALSE); break;


}





LDispatchRegistered: // 處理registered windows messages


ASSERT(message >= 0xC000);


mmf.pfn = lpEntry->pfn;


lResult = (this->*mmf.pfn_lwl)(wParam, lParam);





如果消息處理函數有返回值,則返回該結果,否則,返回TRUE。


對于圖4-1所示的例子,nSig等于AfxSig_is,所以將執行語句


(this->*mmf.pfn_is)((LPTSTR)lParam)


也就是對CTview::OnCreate的調用。


順便指出,對于Registered窗口消息,消息處理函數都是同一原型,所以都被轉換成lwl型(關于Registered窗口消息的映射,見4.4.2節)。


綜上所述,標準Windwos消息和應用程序消息中的Registered消息,由窗口過程直接調用相應的處理函數處理:


如果某個類型的窗口(C++類)處理了某條消息(覆蓋了CWnd或直接基類的處理函數),則對應的HWND窗口(Winodws window)收到該消息時就調用該覆蓋函數來處理;如果該類窗口沒有處理該消息,則調用實現該處理函數最直接的基類(在C++的類層次上接近該類)來處理,上述例子中如果CTview不處理WM_CREATE消息,則調用上一層的CWnd::OnCreate處理;


如果基類都不處理該消息,則調用DefWndProc來處理。


消息映射機制完成虛擬函數功能的原理
綜合對Windows消息的處理來看,MFC使用消息映射機制完成了C++虛擬函數的功能。這主要基于以下幾點:


所有處理消息的類從CCmdTarget派生。
使用靜態成員變量_messageEntries數組存放消息映射條目,使用靜態成員變量messageMap來唯一地區別和得到類的消息映射。
通過GetMessage虛擬函數來獲取當前對象的類的messageMap變量,進而得到消息映射入口。
按照先底層,后基層的順序在類的消息映射數組中搜索消息處理函數。基于這樣的機制,一般在覆蓋基類的消息處理函數時,應該調用基類的同名函數。
以上論斷適合于MFC其他消息處理機制,如對命令消息的處理等。不同的是其他消息處理有一個命令派發/分發的過程。


下一節,討論命令消息的接受和處理。


對命令消息的接收和處理
MFC標準命令消息的發送
在SDI或者MDI應用程序中,命令消息由用戶界面對象(如菜單、工具條等)產生,然后送給主邊框窗口。主邊框窗口使用標準MFC窗口過程處理命令消息。窗口過程把命令傳遞給MFC主邊框窗口對象,開始命令消息的分發。MFC邊框窗口類CFrameWnd提供了消息分發的能力。


下面,還是通過一個例子來說明命令消息的處理過程。


使用AppWizard產生一個單文檔應用程序t。從help菜單選擇“About”,就會彈出一個ABOUT對話框。下面,討論從命令消息的發出到對話框彈出的過程。


首先,選擇“ About”菜單項的動作導致一個Windows命令消息ID_APP_ABOUT的產生。Windows系統發送該命令消息到邊框窗口,導致它的窗口過程AfxWndProc被調用,參數1是邊框窗口的句柄,參數2是消息ID(即WM_COMMAND),參數3、4是消息參數,參數3的值是ID_APP_ABOUT。接著的系列調用如圖4-3所示。


下面分別講述每一層所調用的函數。


前4步同對Windows消息的處理。這里接受消息的HWND窗口是主邊框窗口,因此,AfxWndProc根據HWND句柄得到的MFC窗口對象是MFC邊框窗口對象。


在4.2.2節談到,如果CWnd::OnWndMsg判斷要處理的消息是命令消息(WM_COMMAND),就調用OnCommand進一步處理。由于OnCommand是虛擬函數,當前MFC窗口對象是邊框窗口對象,它的類從CFrameWnd類導出,沒有覆蓋CWnd的虛擬函數OnCommand,而CFrameWnd覆蓋了CWnd的OnCommand,所以,CFrameWnd的OnCommand被調用。換句話說,CFrameWnd的OnCommand被調用是動態約束的結果。接著介紹的本例子的有關調用,也是通過動態約束而實際發生的函數調用。


接著的有關調用,將不進行為什么調用某個類的虛擬或者消息處理函數的分析。


(1)CFrameWnd的OnCommand函數


BOOL CFrameWnd::OnCommand(WPARAM wParam, LPARAM lParam)


參數wParam的低階word存放了菜單命令nID或控制子窗口ID;如果消息來自控制窗口,高階word存放了控制通知消息;如果消息來自加速鍵,高階word值為1;如果消息來自菜單,高階word值為0。


如果是通知消息,參數lParam存放了控制窗口的句柄hWndCtrl,其他情況下lParam是0。


在這個例子里,低階word是ID_APP_ABOUT,高階word是1;lParam是0。


MFC對CFrameWnd的缺省實現主要是獲得一個機會來檢查程序是否運行在HELP狀態,需要執行上下文幫助,如果不需要,則調用基類的CWnd::OnCommand實現正常的命令消息發送。


(2)CWnd的OnCommand函數


BOOL CWnd::OnCommand(WPARAM wParam, LPARAM lParam)


它按一定的順序處理命令或者通知消息,如果發送成功,返回TRUE,否則,FALSE。處理順序如下:


如果是命令消息,則調用OnCmdMsg(nID, CN_UPDATE_COMMAND_UI, &state, NULL)測試nID命令是否已經被禁止,如果這樣,返回FALSE;否則,調用OnCmdMsg進行命令發送。關于CN_UPDATE_COMMAND_UI通知消息,見后面用戶界面狀態的更新處理。


如果是控制通知消息,則先用ReflectLastMsg反射通知消息到子窗口。如果子窗口處理了該消息,則返回TRUE;否則,調用OnCmdMsg進行命令發送。關于通知消息的反射見后面4.4.4.3節。OnCommand給OnCmdMsg傳遞四個參數:nID,即命令消息ID;nCode,如果是通知消息則為通知代碼,如果是命令消息則為NC_COMMAND(即0);其余兩個參數為空。


(3)CFrameWnd的OnCmdMsg函數


BOOL CFrameWnd::OnCmdMsg(UINT nID, int nCode, void* pExtra,


AFX_CMDHANDLERINFO* pHandlerInfo)


參數1是命令ID;如果是通知消息(WM_COMMAND或者WM_NOTIFY),則參數2表示通知代碼,如果是命令消息,參數2是0;如果是WM_NOTIFY,參數3包含了一些額外的信息;參數4在正常消息處理中應該是空。


在這個例子里,參數1是命令ID,參數2為0,參數3空。


OnCmdMsg是虛擬函數,CFrameWnd覆蓋了該函數,當前對象(this所指)是MFC單文檔的邊框窗口對象。故CFrameWnd的OnCmdMsg被調用。CFrameWnd::OnCmdMsg在MFC消息發送中占有非常重要的地位,MFC對該函數的缺省實現確定了MFC的標準命令發送路徑:


送給活動(Active)視處理,調用活動視的OnCmdMsg。由于當前對象是MFC視對象,所以,OnCmdMsg將搜索CTview及其基類的消息映射數組,試圖得到相應的處理函數。
如果視對象自己不處理,則視得到和它關聯的文檔,調用關聯文檔的OnCmdMsg。由于當前對象是MFC視對象,所以,OnCmdMsg將搜索CTdoc及其基類的消息映射數組,試圖得到相應的處理函數。
如果文檔對象不處理,則它得到管理文檔的文檔模板對象,調用文檔模板的OnCmdMsg。由于當前對象是MFC文檔模板對象,所以,OnCmdMsg將搜索文檔模板類及其基類的消息映射數組,試圖得到相應的處理函數。
如果文檔模板不處理,則把沒有處理的信息逐級返回:文檔模板告訴文檔對象,文檔對象告訴視對象,視對象告訴邊框窗口對象。最后,邊框窗口得知,視、文檔、文檔模板都沒有處理消息。
CFrameWnd的OnCmdMsg繼續調用CWnd::OnCmdMsg(斜體表示有類屬限制)來處理消息。由于CWnd沒有覆蓋OnCmdMsg,故實際上調用了函數CCmdTarget::OnCmdMsg。由于當前對象是MFC邊框窗口對象,所以OnCmdMsg函數將搜索CMainFrame類及其所有基類的消息映射數組,試圖得到相應的處理函數。CWnd沒有實現OnCmdMsg卻指定要執行其OnCmdMsg函數,可能是為了以后MFC給CWnd實現了OnCmdMsg之后其他代碼不用改變。
這一步是邊框窗口自己嘗試處理消息。


如果邊框窗口對象不處理,則送給應用程序對象處理。調用CTApp的OnCmdMsg,由于實際上CTApp及其基類CWinApp沒有覆蓋OnCmdMsg,故實際上調用了函數CCmdTarget::OnCmdMsg。由于當前對象是MFC應用程序對象,所以OnCmdMsg函數將搜索CTApp類及其所有基類的的消息映射入口數組,試圖得到相應的處理函數
如果應用程序對象不處理,則返回FALSE,表明沒有命令目標處理當前的命令消息。這樣,函數逐級別返回,OnCmdMsg告訴OnCommand消息沒有被處理,OnCommand告訴OnWndMsg消息沒有被處理,OnWndMsg告訴WindowProc消息沒有被處理,于是WindowProc調用DefWindowProc進行缺省處理。
本例子在第六步中,應用程序對ID_APP_ABOUT消息作了處理。它找到處理函數CTApp::OnAbout,使用DispatchCmdMsg派發消息給該函數處理。


如果是MDI邊框窗口,標準發送路徑還有一個環節,該環節和第二、三、四步所涉及的OnCmdMsg函數,將在下兩節再次具體分析。


命令消息的派發和消息的多次處理
命令消息的派發
如前3.1所述,CCmdTarget的靜態成員函數DispatchCmdMsg用來派發命令消息給指定的命令目標的消息處理函數。


static BOOL DispatchCmdMsg(CCmdTarget* pTarget,


UINT nID, int nCode,


AFX_PMSG pfn, void* pExtra, UINT nSig,


AFX_CMDHANDLERINFO* pHandlerInfo)


前面在講CCmdTarget時,提到了該函數。這里講述它的實現:


第一個參數指向處理消息的對象;第二個參數是命令ID;第三個是通知消息等;第四個是消息處理函數地址;第五個參數用于存放一些有用的信息,根據nCode的值表示不同的意義,例如當消息是WM_NOFITY,指向一個NMHDR結構(關于WM_NOTIFY,參見4.4.4.2節通知消息的處理);第六個參數標識消息處理函數原型;第七個參數是一個指針,指向AFX_CMDHANDLERINFO結構。前六個參數(除了第五個外)都是向函數傳遞信息,第五個和第七個參數是雙向的,既向函數傳遞信息,也可以向調用者返回信息。


關于AFX_CMDHANDLERINFO結構:


struct AFX_CMDHANDLERINFO


{


CCmdTarget* pTarget;


void (AFX_MSG_CALL CCmdTarget::*pmf)(void);


};


第一個成員是一個指向命令目標對象的指針,第二個成員是一個指向CCmdTarget成員函數的指針。


該函數的實現流程可以如下描述:


首先,它檢查參數pHandlerInfo是否空,如果不空,則用pTarget和pfn填寫其指向的結構,返回TRUE;通常消息處理時傳遞來的pHandlerInfo空,而在使用OnCmdMsg來測試某個對象是否處理某條命令時,傳遞一個非空的pHandlerInfo指針。若返回TRUE,則表示可以處理那條消息。


如果pHandlerInfo空,則進行消息處理函數的調用。它根據參數nSig的值,把參數pfn的類型轉換為要調用的消息處理函數的類型。這種指針轉換技術和前面講述的Windows消息的處理是一樣的。


消息的多次處理
如果消息處理函數不返回值,則DispatchCmdMsg返回TRUE;否則,DispatchCmdMsg返回消息處理函數的返回值。這個返回值沿著消息發送相反的路徑逐級向上傳遞,使得各個環節的OnCmdMsg和OnCommand得到返回的處理結果:TRUE或者FALSE,即成功或者失敗。


這樣就產生了一個問題,如果消息處理函數有意返回一個FALSE,那么不就傳遞了一個錯誤的信息?例如,OnCmdMsg函數得到FALSE返回值,就認為消息沒有被處理,它將繼續發送消息到下一環節。的確是這樣的,但是這不是MFC的漏洞,而是有意這么設計的,用來處理一些特別的消息映射宏,實現同一個消息的多次處理。


通常的命令或者通知消息是沒有返回值的(見4.4.2節的消息映射宏),僅僅一些特殊的消息處理函數具有返回值,這類消息的消息處理函數是使用擴展消息映射宏映射的,例如:


ON_COMMAND對應的ON_COMMAND_EX


擴展映射宏和對應的普通映射宏的參數個數相同,含義一樣。但是擴展映射宏的消息處理函數的原型和對應的普通映射宏相比,有兩個不同之處:一是多了一個UINT類型的參數,另外就是有返回值(返回BOOL類型)。回顧4.4.2章節,范圍映射宏ON_COMMAND_RANGE的消息處理函數也有一個這樣的參數,該參數在兩處的含義是一樣的,例如:命令消息擴展映射宏ON_COMMAND_EX定義的消息處理函數解釋該參數是當前要處理的命令消息ID。有返回值的意義在于:如果擴展映射宏的消息處理函數返回FALSE,則導致當前消息被發送給消息路徑上的下一個消息目標處理。


綜合來看,ON_COMMAND_EX宏有兩個功能:


一是可以把多個命令消息指定給一個消息處理函數處理。這類似于ON_COMMAND_RANGE宏的作用。不過,這里的多條消息的命令ID或者控制子窗口ID可以不連續,每條消息都需要一個ON_COMMAND_EX宏。


二是可以讓幾個消息目標處理同一個命令或者通知或者反射消息。如果消息發送路徑上較前的命令目標不處理消息或者處理消息后返回FALSE,則下一個命令目標將繼續處理該消息。


對于通知消息、反射消息,它們也有擴展映射宏,而且上述論斷也適合于它們。例如:


ON_NOTIFY對應的ON_NOTIFY_EX


ON_CONTROL對應的ON_CONTROL_EX


ON_CONTROL_REFLECT對應的ON_CONTROL_REFLECT_EX


等等。


范圍消息映射宏也有對應的擴展映射宏,例如:


ON_NOTIFY_RANGE對應的ON_NOTIFY_EX_RANGE


ON_COMMAND_RANGE對應的ON_COMMAND_EX_RANGE


使用這些宏的目的在于利用擴展宏的第二個功能:實現消息的多次處理。


關于擴展消息映射宏的例子,參見13.2..4.4節和13.2.4.6節。


一些消息處理類的OnCmdMsg的實現
從以上論述知道,OnCmdMsg虛擬函數在MFC命令消息的發送中扮演了重要的角色,CFrameWnd的OnCmdMsg實現了MFC的標準命令消息發送路徑。


那么,就產生一個問題:如果命令消息不送給邊框窗口對象,那么就不會有按標準命令發送路徑發送消息的過程?答案是肯定的。例如一個菜單被一個對話框窗口所擁有,那么,菜單命令將送給MFC對話框窗口對象處理,而不是MFC邊框窗口處理,當然不會和CFrameWnd的處理流程相同。


但是,有一點需要指出,一般標準的SDI和MDI應用程序,只有主邊框窗口擁有菜單和工具條等用戶接口對象,只有在用戶與用戶接口對象進行交互時,才產生命令,產生的命令必然是送給SDI或者MDI程序的主邊框窗口對象處理。


下面,討論幾個MFC類覆蓋OnCmdMsg虛擬函數時的實現。這些類的OnCmdMsg或者可能是標準MFC命令消息路徑的一個環節,或者可能是一個獨立的處理過程(對于其中的MFC窗口類)。


從分析CView的OnCmdMsg實現開始。


CView的OnCmdMsg
CView::OnCmdMsg(UINT nID, int nCode, void* pExtra,


AFX_CMDHANDLERINFO* pHandlerInfo)


首先,調用CWnd::OnCmdMsg,結果是搜索當前視的類和基類的消息映射數組,搜索順序是從下層到上層。若某一層實現了對命令消息nID的處理,則調用它的實現函數;否則,調用m_pDocument->OnCmdMsg,把命令消息送給文檔類處理。m_pDocument是和當前視關聯的文檔對象指針。如果文檔對象類實現了OnCmdMsg,則調用它的覆蓋函數;否則,調用基類(例如CDocument)的OnCmdMsg。


接著,討論CDocument的實現。


CDocument的 OnCmdMsg
BOOL CDocument::OnCmdMsg(UINT nID, int nCode, void* pExtra,


AFX_CMDHANDLERINFO* pHandlerInfo)


首先,調用CCmdTarget::OnCmdMsg,導致當前對象(this)的類和基類的消息映射數組被搜索,看是否有對應的消息處理函數可用。如果有,就調用它;如果沒有,則調用文檔模板的OnCmdMsg函數(m_pTemplate->OnCmdMsg)把消息送給文檔模板處理。


MFC文檔模板沒有覆蓋OnCmdMsg,導致基類CCmdTarget的OnCmdMsg被調用,看是否有文檔模板類或基類實現了對消息的處理。是的話,調用對應的消息處理函數,否則,返回FALSE。從前面的分析知道,CCmdTarget類的消息映射數組是空的,所以這里返回FALSE。


CDialog的OnCmdMsg
BOOL CDialog::OnCmdMsg(UINT nID, int nCode, void* pExtra,


AFX_CMDHANDLERINFO* pHandlerInfo)


調用CWnd::OnCmdMsg,讓對話框或其基類處理消息。
如果還沒有處理,而且是控制消息或系統命令或非命令按鈕,則返回FALSE,不作進一步處理。否則,調用父窗口的OnCmdmsg(GetParent()->OnCmdmsg)把消息送給父窗口處理。
如果仍然沒有處理,則調用當前線程的OnCmdMsg(GetThread()->OnCmdMsg)把消息送給線程對象處理。
如果最后沒有處理,返回FALSE。
CMDIFrameWnd的OnCmdMsg
對于MDI應用程序,MDI主邊框窗口首先是把命令消息發送給活動的MDI文檔邊框窗口進行處理。MDI主邊框窗口對OnCmdMsg的實現函數的原型如下:


BOOL CMDIFrameWnd::OnCmdMsg(UINT nID, int nCode, void* pExtra,


AFX_CMDHANDLERINFO* pHandlerInfo)


如果有激活的文檔邊框窗口,則調用它的OnCmdMsg(MDIGetActive()->OnCmdMsg)把消息交給它進行處理。MFC的文檔邊框窗口類并沒有覆蓋OnCmdMsg函數,所以基類CFrameWnd的函數被調用,導致文檔邊框窗口的活動視、文檔邊框窗口本身、應用程序對象依次來進行消息處理。
如果文檔邊框窗口沒有處理,調用CFrameWnd::OnCmdMsg把消息按標準路徑發送,重復第一次的步驟,不過對于MDI邊框窗口來說不存在活動視,所以省卻了讓視處理消息的必要;接著讓MDI邊框窗口本身來處理消息,如果它還沒有處理,則讓應用程序對象進行消息處理──雖然這是一個無用的重復。
除了CView、CDocument和CMDIFrameWnd類,還有幾個OLE相關的類覆蓋了OnCmdMsg函數。OLE的處理本書暫不涉及,CDialog::OnCmdMsg將在對話框章節專項討論其具體實現。


一些消息處理類的OnCommand的實現
除了虛擬函數OnCmdMsg,還有一個虛擬函數OnCommand在命令消息的發送中占有重要地位。在處理命令或者通知消息時,OnCommand被MFC窗口過程調用,然后它調用OnCmdMsg按一定路徑傳送消息。除了CWnd類和一些OLE相關類外,MFC里主要還有MDI邊框窗口實現了OnCommand。


BOOL CMDIFrameWnd::OnCommand(WPARAM wParam, LPARAM lParam)


第一,如果存在活動的文檔邊框窗口,則使用AfxCallWndProc調用它的窗口過程,把消息送給文檔邊框窗口來處理。這將導致文檔邊框窗口的OnCmdMsg作如下的處理:


活動視處理消息→與視關聯的文檔處理消息→本文檔邊框窗口處理消息→應用程序對象處理消息→文檔邊框窗口缺省處理


任何一個環節如果處理消息,則不再向下發送消息,處理終止。如果消息仍然沒有被處理,就只有交給主邊框窗口了。


第二,第一步沒有處理命令,繼續調用CFrameWnd::OnCommand,將導致CMDIFrameWnd的OnCmdMsg被調用。從前面的分析知道,將再次把消息送給MDI邊框窗口的活動文檔邊框窗口,第一步的過程除了文檔邊框窗口缺省處理外都將被重復。具體的處理過程見前文的CMDIFrameWnd::OnCmdMsg函數。


對于MDI消息,如果主邊框窗口還不處理的話,交給CMDIFrameWnd的DefWindowProc作缺省處理。
消息沒有處理,返回FALSE。
上述分析綜合了OnCommand和OnCmdMsg的處理,它們是在MFC內部MDI邊框窗口處理命令消息的完整的流程和標準的步驟。整個處理過程再次表明了邊框窗口在處理命令消息時的中心作用。從程序員的角度來看,可以認為整個標準處理路徑如下:


活動視處理消息→與視關聯的文檔處理消息→本文檔邊框窗口處理消息→應用程序對象處理消息→文檔邊框窗口缺省處理→MDI邊框窗口處理消息→MDI邊框窗口缺省處理


任何一個環節如果處理消息,不再向下發送消息,急處理終止。


對控制通知消息的接收和處理
WM_COMMAND控制通知消息的處理
WM_COMMAND控制通知消息的處理和WM_COMMAND命令消息的處理類似,但是也有不同之處。


首先,分析處理WM_COMMAND控制通知消息和命令消息的相似處。如前所述,命令消息和控制通知消息都是由窗口過程給OnCommand處理(參見CWnd::OnWndMsg的實現),OnCommand通過wParam和lParam參數區分是命令消息或通知消息,然后送給OnCmdMsg處理(參見CWnd::OnCommnd的實現)。


其次,兩者的不同之處是:


命令消息一般是送給主邊框窗口的,這時,邊框窗口的OnCmdMsg被調用;而控制通知消息送給控制子窗口的父窗口,這時,父窗口的OnCmdMsg被調用。
OnCmdMsg處理命令消息時,通過命令分發可以由多種命令目標處理,包括非窗口對象如文檔對象等;而處理控制通知消息時,不會有消息分發的過程,控制通知消息最終肯定是由窗口對象處理的。
不過,在某種程度上可以說,控制通知消息由窗口對象處理是一種習慣和約定。當使用ClassWizard進行消息映射時,它不提供把控制通知消息映射到非窗口對象的機會。但是,手工地添加消息映射,讓非窗口對象處理控制通知消息的可能是存在的。例如,對于CFormView,一方面它具備接受WM_COMMAND通知消息的條件,另一方面,具備把WM_COMMAND消息派發給關聯文檔對象處理的能力,所以給CFormView的通知消息是可以讓文檔對象處理的。


事實上,BN_CLICKED控制通知消息的處理和命令消息的處理完全一樣,因為該消息的通知代碼是0,ON_BN_CLICKED(id,memberfunction)和ON_COMMAND(id,memberfunction)是等同的。


此外,MFC的狀態更新處理機制就是建立在通知消息可以發送給各種命令目標的基礎之上的。關于MFC的狀態更新處理機制,見后面4.4.4.4節的討論。


控制通知消息可以反射給子窗口處理。OnCommand判定當前消息是WM_COMAND通知消息之后,首先它把消息反射給控制子窗口處理,如果子窗口處理了反射消息,OnCommand不會繼續調用OnCmdMsg讓父窗口對象來處理通知消息。
WM_NOTIFY消息及其處理:
(1)WM_NOTIFY消息


還有一種通知消息WM_NOTIFY,在Win32中用來傳遞信息復雜的通知消息。WM_NOTIFY消息怎么來傳遞復雜的信息呢?WM_NOTIFY的消息參數wParam包含了發送通知消息的控制窗口ID,另一個參數lParam包含了一個指針。該指針指向一個NMHDR結構,或者更大的結構,只要它的第一個結構成員是NMHDR結構。


NMHDR結構:


typedef struct tagNMHDR {


HWND hwndFrom;


UINT idFrom;


UINT code;


} NMHDR;


上述結構有三個成員,分別是發送通知消息的控制窗口的句柄、ID和通知消息代碼。


舉一個更大、更復雜的結構例子:列表控制窗發送LVN_KEYDOWN控制通知消息,則lParam包含了一個指向LV_KEYDOWN結構的指針。其結構如下:


typedef struct tagLV_KEYDOWN {


NMHDR hdr;


WORD wVKey;


UINT flags;


}LV_KEYDOWN;


它的第一個結構成員hdr就是NMHDR類型。其他成員包含了更多的信息:哪個鍵被按下,哪些輔助鍵(SHIFT、CTRL、ALT等)被按下。


(2)WM_NOTIFY消息的處理


在分析CWnd::OnWndMsg函數時,曾指出當消息是WM_NOTIFY時,它把消息傳遞給OnNotify虛擬函數處理。這是一個虛擬函數,類似于OnCommand,CWnd和派生類都可以覆蓋該函數。OnNotify的函數原型如下:


BOOL CWnd::OnNotify(WPARAM, LPARAM lParam, LRESULT* pResult)


參數1是發送通知消息的控制窗口ID,沒有被使用;參數2是一個指針;參數3指向一個long類型的數據,用來返回處理結果。


WM_NOTIFY消息的處理過程如下:


反射消息給控制子窗口處理。
如果子窗口不處理反射消息,則交給OnCmdMsg處理。給OnCmdMsg的四個參數分別如下:第一個是命令消息ID,第四個為空;第二個高階word是WM_NOTIFY,低階word是通知消息;第三個參數是指向AFX_NOTIFY結構的指針。第二、三個參數有別于OnCommand送給OnCmdMsg的參數。
AFX_NOTIFY結構:


struct AFX_NOTIFY


{


LRESULT* pResult;


NMHDR* pNMHDR;


};


pNMHDR的值來源于參數2 lParam,該結構的域pResult用來保存處理結果,域pNMHDR用來傳遞信息。


OnCmdMsg后續的處理和WM_COMMAND通知消息基本相同,只是在派發消息給消息處理函數時,DispatchMsdMsg的第五個參數pExtra指向OnCmdMsg傳遞給它的AFX_NOTIFY類型的參數,而不是空指針。這樣,處理函數就得到了復雜的通知消息信息。


消息反射
(1)消息反射的概念


前面討論控制通知消息時,曾經多次提到了消息反射。MFC提供了兩種消息反射機制,一種用于OLE控件,一種用于Windows控制窗口。這里只討論后一種消息反射。


Windows控制常常發送通知消息給它們的父窗口,通常控制消息由父窗口處理。但是在MFC里頭,父窗口在收到這些消息后,或者自己處理,或者反射這些消息給控制窗口自己處理,或者兩者都進行處理。如果程序員在父窗口類覆蓋了通知消息的處理(假定不調用基類的實現),消息將不會反射給控制子窗口。這種反射機制是MFC實現的,便于程序員創建可重用的控制窗口類。


MFC的CWnd類處理以下控制通知消息時,必要或者可能的話,把它們反射給子窗口處理:


WM_CTLCOLOR,


WM_VSCROLL,WM_HSCROLL,


WM_DRAWITEM,WM_MEASUREITEM,


WM_COMPAREITEM,WM_DELETEITEM,


WM_CHARTOITEM,WM_VKEYTOITEM,


WM_COMMAND、WM_NOTIFY。


例如,對WM_VSCROLL、WM_HSCROLL消息的處理,其消息處理函數如下:


void CWnd::OnHScroll(UINT, UINT, CScrollBar* pScrollBar)


{


//如果是一個滾動條控制,首先反射消息給它處理


if (pScrollBar != NULL &&pScrollBar->SendChildNotifyLastMsg())


return; //控制窗口成功處理了該消息


Default();


}


又如:在討論OnCommand和OnNofity函數處理通知消息時,都曾經指出,它們首先調用ReflectLastMsg把消息反射給控制窗口處理。


為了利用消息反射的功能,首先需要從適當的MFC窗口派生出一個控制窗口類,然后使用ClassWizard給它添加消息映射條目,指定它處理感興趣的反射消息。下面,討論反射消息映射宏。


上述消息的反射消息映射宏的命名遵循以下格式:“ON”前綴+消息名+“REFLECT”后綴,例如:消息WM_VSCROLL的反射消息映射宏是ON_WM_VSCROLL_REFECT。但是通知消息WM_COMMAND和WM_NOTIFY是例外,分別為ON_CONTROL_REFLECT和ON_NOFITY_REFLECT。狀態更新通知消息的反射消息映射宏是ON_UPDATE_COMMAND_UI_REFLECT。


消息處理函數的名字和去掉“WM_”前綴的消息名相同 ,例如WM_HSCROLL反射消息處理函數是Hscroll。


消息處理函數的原型這里不一一列舉了。


這些消息映射宏和消息處理函數的原型可以借助于ClassWizard自動地添加到程序中。ClassWizard添加消息處理函數時,可以處理的反射消息前面有一個等號,例如處理WM_HSCROLL的反射消息,選擇映射消息“=EN_HSC ROLL”。ClassWizard自動的添加消息映射宏和處理函數到框架文件。


(2)消息反射的處理過程


如果不考慮有OLE控件的情況,消息反射的處理流程如下圖所示:


首先,調用CWnd的成員函數SendChildNotifyLastMsg,它從線程狀態得到本線程最近一次獲取的消息(關于線程狀態,后面第9章會詳細介紹)和消息參數,并且把這些參數傳遞給函數OnChildNotify。注意,當前的CWnd對象就是MFC控制子窗口對象。


OnChlidNofify是CWnd定義的虛擬函數,不考慮OLE控制的話,它僅僅只調用ReflectChildNotify。OnChlidNofify可以被覆蓋,所以如果程序員希望處理某個控制的通知消息,除了采用消息映射的方法處理通知反射消息以外,還可以覆蓋OnChlidNotify虛擬函數,如果成功地處理了通知消息,則返回TRUE。


ReflectChildNotify是CWnd的成員函數,完成反射消息的派發。對于WM_COMMAND,它直接調用CWnd::OnCmdMsg派發反射消息WM_REFLECT_BASE+WM_COMMAND;對于WM_NOTIFY,它直接調用CWnd::OnCmdMsg派發反射消息WM_REFLECT_BASE+WM_NOFITY;對于其他消息,則直接調用CWnd::OnWndMsg(即CmdTarge::OnWndMsg)派發相應的反射消息,例如WM_REFLECT_BASE+WM_HSCROLL。


注意:ReflectChildNotify直接調用了CWnd的OnCmdMsg或OnWndMsg,這樣反射消息被直接派發給控制子窗口,省卻了消息發送的過程。


接著,控制子窗口如果處理了當前的反射消息,則返回反射消息被成員處理的信息。


(3)一個示例


如果要創建一個編輯框控制,要求它背景使用黃色,其他特性不變,則可以從CEdit派生一個類CYellowEdit,處理通知消息WM_CTLCOLOR的反射消息。CYellowEdit有三個屬性,定義如下:


CYellowEdit::CYellowEdit()


{


m_clrText = RGB( 0, 0, 0 );


m_clrBkgnd = RGB( 255, 255, 0 );


m_brBkgnd.CreateSolidBrush( m_clrBkgnd );


}


使用ClassWizard添加反射消息處理函數:


函數原型:


afx_msg void HScroll();


消息映射宏:


ON_WM_CTLCOLOR_REFLECT()


函數的框架


HBRUSH CYellowEdit::CtlColor(CDC* pDC, UINT nCtlColor)


{


// TODO:添加代碼改變設備描述表的屬性


// TODO: 如果不再調用父窗口的處理,則返回一個非空的刷子句柄


return NULL;


}


添加一些處理到函數CtlColor中,如下:


pDC->SetTextColor( m_clrText );//設置文本顏色


pDC->SetBkColor( m_clrBkgnd );//設置背景顏色


return m_brBkgnd; //返回背景刷


這樣,如果某個地方需要使用黃色背景的編輯框,則可以使用CYellowEdit控制。


對更新命令的接收和處理
用戶接口對象如菜單、工具條有多種狀態,例如:禁止,可用,選中,未選中,等等。這些狀態隨著運行條件的變化,由程序來進行更新。雖然程序員可以自己來完成更新,但是MFC框架為自動更新用戶接口對象提供了一個方便的接口,使用它對程序員來說可能是一個好的選擇。


實現方法
每一個用戶接口對象,如菜單、工具條、控制窗口的子窗口,都由唯一的ID號標識,用戶和它們交互時,產生相應ID號的命令消息。在MFC里,一個用戶接口對象還可以響應CN_UPDATE_COMMAND_UI通知消息。因此,對每個標號ID的接口對象,可以有兩個處理函數:一個消息處理函數用來處理該對象產生的命令消息ID,另一個狀態更新函數用來處理給該對象的CN_UPDATE_COMMAND_UID的通知消息。


使用ClassWizard可把狀態更新函數加入到某個消息處理類,其結果是:


在類的定義中聲明一個狀態函數;


在消息映射中使用ON_UPDATE_COMMAND_UI宏添加一個映射條目;


在類的實現文件中實現狀態更新函數的定義。


ON_UPDATE_COMMAND_UI給指定ID的用戶對象指定狀態更新函數,例如:


ON_UPDATE_COMMAND_UI(ID_EDIT_COPY, OnUpdateEditCopy)


映射標識號ID為ID_EDIT_COPY菜單的通知消息CN_UPDATE_COMMAND_UI到函數OnUpdateEditCopy。用于給EDIT(編輯菜單)的菜單項ID_EDIT_COPY(復制)添加一個狀態處理函數OnUpdateEditCopy,通過處理通知消息CN_UPDATE_COMMAND_UI實現該菜單項的狀態更新。


狀態處理函數的原型如下:


afxmsg void ClassName::OnUpdateEditPaste(CCmdUI* pCmdUI)


CCmdUI對象由MFC自動地構造。在完善函數的實現時,使用pCmdUI對象和CmdUI的成員函數實現菜單項ID_EDIT_COPY的狀態更新,讓它變灰或者變亮,也就是禁止或者允許用戶使用該菜單項。


狀態更新命令消息
要討論MFC的狀態更新處理,先得了解一條特殊的消息。MFC的消息映射機制除了處理各種Windows消息、控制通知消息、命令消息、反射消息外,還處理一種特別的“通知命令消息”,并通過它來更新菜單、工具欄(包括對話框工具欄)等命令目標的狀態。


這種“通知命令消息”是MFC內部定義的,消息ID是WM_COMMAND,通知代碼是CN_UPDATE_COMMAND_UI(0XFFFFFFFF)。


它不是一個真正意義上的通知消息,因為沒有控制窗口產生這樣的通知消息,而是MFC自己主動產生,用于送給工具條窗口或者主邊框窗口,通知它們更新用戶接口對象的狀態。


它和標準WM_COMMAND命令消息也不相同,因為它有特定的通知代碼,而命令消息通知代碼是0。


但是,從消息的處理角度,可以把它看作是一條通知消息。如果是工具條窗口接收該消息,則在發送機制上它和WM_COMMAND控制通知消息是相同的,相當于讓工具條窗口處理一條通知消息。如果是邊框窗口接收該消息,則在消息的發送機制上它和WM_COMMAND命令消息是相同的,可以讓任意命令目標處理該消息,也就是說邊框窗口可以把該條通知消息發送給任意命令目標處理。


從程序員的角度,可以把它看作一條“狀態更新命令消息”,像處理命令消息那樣處理該消息。每條命令消息都可以對應有一條“狀態更新命令消息”。ClassWizard也支持讓任意消息目標處理“狀態更新命令消息”(包括非窗口命令目標),實現用戶接口狀態的更新。


在這條消息發送時,通過OnCmdMsg的第三個參數pExtra傳遞一些信息,表示要更新的用戶接口對象。pExtra指向一個CCmdUI對象。這些信息將傳遞給狀態更新命令消息的處理函數。


下面討論用于更新用戶接口對象狀態的類CCmdUI。


類CCmdUI
CCmdUI不是從CObject派生,沒有基類。


成員變量
m_nID 用戶接口對象的ID


m_nIndex 用戶接口對象的index


m_pMenu 指向CCmdUI對象表示的菜單


m_pSubMenu 指向CCmdUI對象表示的子菜單


m_pOther 指向其他發送通知消息的窗口對象


m_pParentMenu 指向CCmdUI對象表示的子菜單


成員函數
Enable(BOOL bOn = TRUE ) 禁止用戶接口對象或者使之可用


SetCheck( int nCheck = 1) 標記用戶接口對象選中或未選中


SetRadio(BOOL bOn = TRUE)


SetText(LPCTSTR lpszText)


ContinueRouting()


還有一個MFC內部使用的成員函數:


DoUpdate(CCmdTarget* pTarget, BOOL bDisableIfNoHndler)


其中,參數1指向處理接收更新通知的命令目標,一般是邊框窗口;參數2指示如果沒有提供處理函數(例如某個菜單沒有對應的命令處理函數),是否禁止用戶對象。


DoUpdate作以下事情:


首先,發送狀態更新命令消息給參數1表示的命令目標:調用pTarget->OnCmdMsg(m_nID, CN_UPDATE_COMMAND_UI, this, NULL)發送m_nID對象的通知消息CN_UPDATE_COMMAND_UI。OnCmdMsg的參數3取值this,包含了當前要更新的用戶接口對象的信息。


然后,如果參數2為TRUE,調用pTarget->OnCmdMsg(m_nID,CN_COMMAND, this, &info)測試命令消息m_nID是否被處理。這時,OnCmdMsg的第四個參數非空,表示僅僅是測試,不是真的要派發消息。如果沒有提供命令消息m_nID的處理函數,則禁止用戶對象m_nID,否則使之可用。


從上面的討論可以知道:通過其結構,一個CCmdUI對象標識它表示了哪一個用戶接口對象,如果是菜單接口對象,pMenu表示了要更新的菜單對象;如果是工具條,pOther表示了要更新的工具條窗口對象,nID表示了工具條按鈕ID。


所以,由參數上狀態更新消息的消息處理函數就知道要更新什么接口對象的狀態。例如,第1節的函數OnUpdateEditPaste,函數參數pCmdUI表示一個菜單對象,需要更新該菜單對象的狀態。


通過其成員函數,一個CCmdUI可以更新、改變用戶接口對象的狀態。例如,CCmdUI可以管理菜單和對話框控制的狀態,調用Enable禁止或者允許菜單或者控制子窗口,等等。


所以,函數OnUpdateEditPaste可以直接調用參數的成員函數(如pCmdUI->Enable)實現菜單對象的狀態更新。


由于接口對象的多樣性,其他接口對象將從CCmdUI派生出管理自己的類來,覆蓋基類的有關成員函數如Enable等,提供對自身狀態更新的功能。例如管理狀態條和工具欄更新的CStatusCmdUI類和CToolCmdUI類。


自動更新用戶接口對象狀態的機制
MFC提供了分別用于更新菜單和工具條的兩種途徑。


更新菜單狀態
當用戶對菜單如File單擊鼠標時,就產生一條WM_INITMENUPOPUP消息,邊框窗口在菜單下拉之前響應該消息,從而更新該菜單所有項的狀態。


在應用程序開始運行時,邊框也會收到WM_INITMENUPOPUP消息。


更新工具條等狀態
當應用程序進入空閑處理狀態時,將發送WM_IDLEUPDATECMDUI消息,導致所有的工具條用戶對象的狀態處理函數被調用,從而改變其狀態。WM_IDLEUPDATECMDUI是MFC自己定義和使用的消息。


在窗口初始化時,工具條也會收到WM_IDLEUPDATECMDUI消息。


菜單狀態更新的實現
MFC讓邊框窗口來響應WM_INITMENUPOPUP消息,消息處理函數是OnInitMenuPopup,其原型如下:


afx_msg void CFrameWnd::OnInitMenuPopup( CMenu* pPopupMenu,


UINT nIndex, BOOL bSysMenu );


第一個參數指向一個CMenu對象,是當前按擊的菜單;第二個參數是菜單索引;第三個參數表示子菜單是否是系統控制菜單。


函數的處理:


如果是系統控制菜單,不作處理;否則,創建CCmdUI對象state,給它的各個成員如m_pMenu,m_pParentMenu,m_pOther等賦值。


對該菜單的各個菜單項,調函數state.DoUpdate,用CCmdUI的DoUpdate來更新狀態。DoUpdate的第一個參數是this,表示命令目標是邊框窗口;在CFrameWnd的成員變量m_bAutoMenuEnable為TRUE時(表示如果菜單m_nID沒有對應的消息處理函數或狀態更新函數,則禁止它),把DoUpdate的第二個參數bDisableIfNoHndler置為TRUE。


順便指出,m_bAutoMenuEnable缺省時為TRUE,所以,應用程序啟動時菜單經過初始化處理,沒有提供消息處理函數或狀態更新函數的菜單項被禁止。


工具條等狀態更新的實現
圖4-5表示了消息空閑時MFC更新用戶對象狀態的流程:


MFC提供的缺省空閑處理向頂層窗口(框架窗口)的所有子窗口發送消息WM_IDLEUPDATECMDUI;MFC的控制窗口(工具條、狀態欄等)實現了對該消息的處理,導致用戶對象狀態處理函數的調用。


雖然兩種途徑調用了同一狀態處理函數,但是傳遞的 CCmdUI參數從內部構成上是不一樣的:第一種傳遞的CCmdUI對象表示了一菜單對象,(pMenu域被賦值);第二種傳遞了一個窗口對象(pOther域被賦值)。同樣的狀態改變動作,如禁止、允許狀態的改變,前者調用了CMenu的成員函數EnableMenuItem,后者使用了CWnd的成員函數EnabelWindow。但是,這些不同由CCmdUI對象內部區分、處理,對用戶是透明的:不論菜單還是對應的工具條,用戶都用同一個狀態處理函數使用同樣的形式來處理。


 


這一節分析了用戶界面更新的原理和機制。在后面第13章討論工具條和狀態欄時,將詳細的分析這種機制的具體實現。


消息的預處理
到現在為止,詳細的討論了MFC的消息映射機制。但是,為了提高效率和簡化處理,MFC提供了一種消息預處理機制,如果一條消息在預處理時被過濾掉了(被處理),則不會被派發給目的窗口的窗口過程,更不會進入消息循環了。


顯然,能夠進行預處理的消息只可能是隊列消息,而且必須在消息派發之前進行預處理。因此,MFC在實現消息循環時,對于得到的每一條消息,首先送給目的窗口、其父窗口、其祖父窗口乃至最頂層父窗口,依次進行預處理,如果沒有被處理,則進行消息轉換和消息派發,如果某個窗口實現了預處理,則終止。有關實現見后面關于CWinThread線程類的章節,CWinThread的Run函數和PreTranslateMessage函數以及CWnd的函數WalkPreTranslateTree實現了上述要求和功能。這里要討論的是MFC窗口類如何進行消息預處理。


CWnd提供了虛擬函數PreTranslateMessage來進行消息預處理。CWnd的派生類可以覆蓋該函數,實現自己的預處理。下面,討論幾個典型的預處理。


首先,是CWnd的預處理:


預處理函數的原型為:


BOOL CWnd::PreTranslateMessage(MSG* pMsg)


CWnd類主要是處理和過濾Tooltips消息。關于該函數的實現和Tooltips消息,見后面第13章關于工具欄的討論。


然后,是CFrameWnd的預處理:


CFrameWnd除了調用基類CWnd的實現過濾Tooltips消息之外,還要判斷當前消息是否是鍵盤快捷鍵被按下,如果是,則調用函數::TranslateAccelerator(m_hWnd, hAccel, pMsg)處理快捷鍵。


接著,是CMDIChildWnd的預處理:


CMDIChildWnd的預處理過程和CFrameWnd的一樣,但是不能依靠基類CFrameWnd的實現,必須覆蓋它。因為MDI子窗口沒有菜單,所以它必須在MDI邊框窗口的上下文中來處理快捷鍵,它調用了函數::TranslateAccelerator(GetMDIFrame()->m_hWnd, hAccel,pMsg)。


討論了MDI子窗口的預處理后,還要討論MDI邊框窗口:


CMDIFrameWnd的實現除了CFrameWnd的實現的功能外,它還要處理MDI快捷鍵(標準MDI界面統一使用的系統快捷鍵)。


在后面,還會討論CDialog、CFormView、CToolBar等的消息預處理及其實現。


至于CWnd::WalkPreTranslateTree函數,它從接受消息的窗口開始,逐級向父窗回溯,逐一對各層窗口調用PreTranslateMessage函數,直到消息被處理或者到最頂層窗口為止。


MFC消息映射的回顧
從處理命令消息的過程可以看出,Windows消息和控制消息的處理要比命令消息的處理簡單,因為查找消息處理函數時,后者只要搜索當前窗口對象(this所指)的類或其基類的消息映射入口表。但是,命令消息就要復雜多了,它沿一定的順序鏈查找鏈上的各個命令目標,每一個被查找的命令目標都要搜索它的類或基類的消息映射入口表。


MFC通過消息映射的手段,以一種類似C++虛擬函數的概念向程序員提供了一種處理消息的方式。但是,若使用C++虛擬函數實現眾多的消息,將導致虛擬函數表極其龐大;而使用消息映射,則僅僅感興趣的消息才加入映射表,這樣就要節省資源、提高效率。這套消息映射機制的基礎包括以下幾個方面:


消息映射入口表的實現:采用了C++靜態成員和虛擬函數的方法來表示和得到一個消息映射類(CCmdTarget或派生類)的映射表。
消息查找的實現:從低層到高層搜索消息映射入口表,直至根類CCmdTarget。
消息發送的實現:主要以幾個虛擬函數為基礎來實現標準MFC消息發送路徑:OnComamnd、OnNotify、OnWndMsg和OnCmdMsg。、
OnWndMsg是CWnd類或其派生類的成員函數,由窗口過程調用。它處理標準的Windows消息。


OnCommand是CWnd類或其派生類的成員函數,由OnWndMsg調用來處理WM_COMMAND消息,實現命令消息或者控制通知消息的發送。如果派生類覆蓋該函數,則必須調用基類的實現,否則將不能自動的處理命令消息映射,而且必須使用該函數接受的參數(不是程序員給定值)調用基類的OnCommand。


OnNotify是CWnd類或其派生類的成員函數,由OnWndMsg調用來處理WM_NOTIFY消息,實現控制通知消息的發送。


OnCmdMsg是CCmdTarget類或其派生類的成員函數。被OnCommand調用,用來實現命令消息發送和派發命令消息到命令消息處理函數。


自動更新用戶對象狀態是通過MFC的命令消息發送機制實現的。


控制消息可以反射給控制窗口處理。


隊列消息在發送給窗口過程之前可以進行消息預處理,如果消息被MFC窗口對象預處理了,則不會進入消息發送過程
========

深度解析VC中的消息傳遞機制

?http://www.cnblogs.com/zhwx/archive/2012/05/16/2504323.html


摘要:Windows編程和Dos編程,一個很大的區別就是,Windows編程是事件驅動,消息傳遞的。所以,要學好Windows編程,必須


對消息機制有一個清楚的認識,本文希望能夠對消息的傳遞做一個全面的分析。


一、什么是消息?


消息系統對于一個win32程序來說十分重要,它是一個程序運行的動力源泉。一個消息,是系統定義的一個32位的值,他唯一的定


義了一個事件,向Windows發出一個通知,告訴應用程序某個事情發生了。例如,單擊鼠標、改變窗口尺寸、按下鍵盤上的一個鍵


都會使Windows發送一個消息給應用程序。


消息本身是作為一個記錄傳遞給應用程序的,這個記錄中包含了消息的類型以及其他信息。例如,對于單擊鼠標所產生的消息來


說,這個記錄中包含了單擊鼠標時的坐標。這個記錄類型叫做MSG,MSG含有來自windows應用程序消息隊列的消息信息,它在


Windows中聲明如下:?
typedef struct tagMsg?
{?
HWND hwnd; ? ? ? ? ?// 接受該消息的窗口句柄?
UINT message; ? ? ? ? // 消息常量標識符,也就是我們通常所說的消息號?
WPARAM wParam; ? ? // 32位消息的特定附加信息,確切含義依賴于消息值?
LPARAM lParam; ? ? ? // 32位消息的特定附加信息,確切含義依賴于消息值?
DWORD time; ? ? ? ? // 消息創建時的時間?
POINT pt; ? ? ? ? ? ? // 消息創建時的鼠標/光標在屏幕坐標系中的位置?
}MSG;?
消息可以由系統或者應用程序產生。系統在發生輸入事件時產生消息。舉個例子, 當用戶敲鍵, 移動鼠標或者單擊控件。系統也


產生消息以響應由應用程序帶來的變化, 比如應用程序改變系統字體,改變窗體大小。應用程序可以產生消息使窗體執行任務,


或者與其他應用程序中的窗口通訊。


二、消息中有什么??
我們給出了上面的注釋,是不是會對消息結構有了一個比較清楚的認識?如果還沒有,那么我們再試著給出下面的解釋:


hwnd 32位的窗口句柄。窗口可以是任何類型的屏幕對象,因為Win32能夠維護大多數可視對象的句柄(窗口、對話框、按鈕、編輯


框等)。?
message 用于區別其他消息的常量值。這些常量可以是Windows單元中預定義的常量,也可以是自定義的常量。消息標識符以常量


命名的方式指出消息的含義。當窗口過程接收到消息之后,他就會使用消息標識符來決定如何處理消息。例如、WM_PAINT告訴窗


口過程窗體客戶區被改變了需要重繪。符號常量指定系統消息屬于的類別,其前綴指明了處理解釋消息的窗體的類型。?
wParam 通常是一個與消息有關的常量值,也可能是窗口或控件的句柄。?
lParam 通常是一個指向內存中數據的指針。由于WParam、lParam和Pointer都是32位的,因此,它們之間可以相互轉換。


三、消息標識符的值


系統保留消息標識符的值在0x0000在0x03ff(WM_USER-1)范圍。這些值被系統定義消息使用。 應用程序不能使用這些值給自己的


消息。


應用程序消息從WM_USER(0X0400)到0X7FFF,或0XC000到0XFFFF;WM_USER到0X7FFF范圍的消息由應用程序自己使用;0XC000到


0XFFFF范圍的消息用來和其他應用程序通信,我們順便說一下具有標志性的消息值:?
WM_NULL---0x0000 空消息。?
0x0001----0x0087 主要是窗口消息。?
0x00A0----0x00A9 非客戶區消息 ?
0x0100----0x0108 鍵盤消息?
0x0111----0x0126 菜單消息?
0x0132----0x0138 顏色控制消息?
0x0200----0x020A 鼠標消息?
0x0211----0x0213 菜單循環消息?
0x0220----0x0230 多文檔消息?
0x03E0----0x03E8 DDE消息?
0x0400 WM_USER?
0x8000 WM_APP?
0x0400----0x7FFF 應用程序自定義私有消息


四、消息有的分類?
其實,windows中的消息雖然很多,但是種類并不繁雜,大體上有3種:窗口消息、命令消息和控件通知消息。?
窗口消息 大概是系統中最為常見的消息,它是指由操作系統和控制其他窗口的窗口所使用的消息。例如CreateWindow、


DestroyWindow和MoveWindow等都會激發窗口消息,還有我們在上面談到的單擊鼠標所產生的消息也是一種窗口消息。?
命令消息 這是一種特殊的窗口消息,他用來處理從一個窗口發送到另一個窗口的用戶請求,例如按下一個按鈕,他就會向主窗口


發送一個命令消息。?
控件通知消息 是指這樣一種消息,一個窗口內的子控件發生了一些事情,需要通知父窗口。通知消息只適用于標準的窗口控件如


按鈕、列表框、組合框、編輯框,以及Windows公共控件如樹狀視圖、列表視圖等。例如,單擊或雙擊一個控件、在控件中選擇部


分文本、操作控件的滾動條都會產生通知消息。 她類似于命令消息,當用戶與控件窗口交互時,那么控件通知消息就會從控件窗


口發送到它的主窗口。但是這種消息的存在并不是為了處理用戶命令,而是為了讓主窗口能夠改變控件,例如加載、顯示數據。


例如按下一個按鈕,他向父窗口發送的消息也可以看作是一個控件通知消息;單擊鼠標所產生的消息可以由主窗口直接處理,然


后交給控件窗口處理。?
其中窗口消息及控件通知消息主要由窗口類(即直接或間接由CWND類派生類)處理。相對窗口消息及控件通知消息而言,命令消


息的處理對象范圍就廣得多,它不僅可以由窗口類處理,還可以由文檔類,文檔模板類及應用類所處理。?
由于控件通知消息很重要的,人們用的也比較多,但是具體的含義往往令初學者暈頭轉向,所以我決定把常見的幾個列出來供大


家參考:?
按扭控件?
BN_CLICKED 用戶單擊了按鈕?
BN_DISABLE 按鈕被禁止?
BN_DOUBLECLICKED 用戶雙擊了按鈕?
BN_HILITE 用/戶加亮了按鈕?
BN_PAINT 按鈕應當重畫?
BN_UNHILITE 加亮應當去掉?
組合框控件?
CBN_CLOSEUP 組合框的列表框被關閉?
CBN_DBLCLK 用戶雙擊了一個字符串?
CBN_DROPDOWN 組合框的列表框被拉出?
CBN_EDITCHANGE 用戶修改了編輯框中的文本?
CBN_EDITUPDATE 編輯框內的文本即將更新?
CBN_ERRSPACE 組合框內存不足?
CBN_KILLFOCUS 組合框失去輸入焦點?
CBN_SELCHANGE 在組合框中選擇了一項?
CBN_SELENDCANCEL 用戶的選擇應當被取消?
CBN_SELENDOK 用戶的選擇是合法的?
CBN_SETFOCUS 組合框獲得輸入焦點?
編輯框控件?
EN_CHANGE 編輯框中的文本己更新?
EN_ERRSPACE 編輯框內存不足?
EN_HSCROLL 用戶點擊了水平滾動條?
EN_KILLFOCUS 編輯框正在失去輸入焦點?
EN_MAXTEXT 插入的內容被截斷?
EN_SETFOCUS 編輯框獲得輸入焦點?
EN_UPDATE 編輯框中的文本將要更新?
EN_VSCROLL 用戶點擊了垂直滾動條消息含義?
列表框控件?
LBN_DBLCLK 用戶雙擊了一項?
LBN_ERRSPACE 列表框內存不夠?
LBN_KILLFOCUS 列表框正在失去輸入焦點?
LBN_SELCANCEL 選擇被取消?
LBN_SELCHANGE 選擇了另一項?
LBN_SETFOCUS 列表框獲得輸入焦點


五、隊列消息和非隊列消息?
從消息的發送途徑來看,消息可以分成2種:隊列消息和非隊列消息。消息隊列又可以分成系統消息隊列和線程消息隊列。


系統消息隊列由Windows維護,線程消息隊列則由每個GUI線程自己進行維護,為避免給non-GUI現成創建消息隊列,所有線程產生


時并沒有消息隊列,僅當線程第一次調用GDI函數時,系統給線程創建一個消息隊列。隊列消息送到系統消息隊列,然后到線程消


息隊列;非隊列消息直接送給目的窗口過程。?
對于隊列消息,最常見的是鼠標和鍵盤觸發的消息,例如WM_MOUSERMOVE,WM_CHAR等消息,還有一些其它的消息,例如:WM_PAINT


、WM_TIMER和WM_QUIT。當鼠標、鍵盤事件被觸發后,相應的鼠標或鍵盤驅動程序就會把這些事件轉換成相應的消息,然后輸送到


系統消息隊列,由Windows系統去進行處理。Windows系統則在適當的時機,從系統消息隊列中取出一個消息,根據前面我們所說


的MSG消息結構確定消息是要被送往那個窗口,然后把取出的消息送往創建窗口的線程的相應隊列,下面的事情就該由線程消息隊


列操心了,Windows開始忙自己的事情去了。線程看到自己的消息隊列中有消息,就從隊列中取出來,通過操作系統發送到合適的


窗口過程去處理。?
一般來講,系統總是將消息Post在消息隊列的末尾。這樣保證窗口以先進先出的順序接受消息。然而,WM_PAINT是一個例外,同一


個窗口的多個 WM_PAINT被合并成一個 WM_PAINT 消息, 合并所有的無效區域到一個無效區域。合并WM_PAIN的目的是為了減少刷


新窗口的次數。?
非隊列消息將會繞過系統消息隊列和線程消息隊列,直接將消息發送到窗口過程。系統發送非隊列消息通知窗口。例如,當用戶激


活一個窗口,系統發送WM_ACTIVATE, WM_SETFOCUS, and WM_SETCURSOR。這些消息通知窗口它被激活了。非隊列消息也可以由當


應用程序調用系統函數產生。例如,當程序調用SetWindowPos,系統發送WM_WINDOWPOSCHANGED消息。一些函數也發送非隊列消息


,例如下面我們要談到的函數。


六、消息的發送?
了解了上面的這些基礎理論之后,我們就可以進行一下簡單的消息發送與接收。?
把一個消息發送到窗口有3種方式:發送、寄送和廣播。?
發送消息的函數有SendMessage、SendMessageCallback、SendNotifyMessage、SendMessageTimeout;寄送消息的函數主要有


PostMessage、PostThreadMessage、PostQuitMessage;廣播消息的函數我知道的只有BroadcastSystemMessage、


BroadcastSystemMessageEx。


SendMessage的原型如下:


LRESULT SendMessage(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)


這個函數主要是向一個或多個窗口發送一條消息,一直等到消息被處理之后才會返回。不過需要注意的是,如果接收消息的窗口


是同一個應用程序的一部分,那么這個窗口的窗口函數就被作為一個子程序馬上被調用;如果接收消息的窗口是被另外的線程所


創建的,那么窗口系統就切換到相應的線程并且調用相應的窗口函數,這條消息不會被放進目標應用程序隊列中。函數的返回值


是由接收消息的窗口的窗口函數返回,返回的值取決于被發送的消息。


PostMessage的原型如下:


BOOL PostMessage(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)該函數把一條消息放置到創建hWnd窗口的線程的消息


隊列中,該函數不等消息被處理就馬上將控制返回。需要注意的是,如果hWnd參數為HWND_BROADCAST,那么,消息將被寄送給系


統中的所有的重疊窗口和彈出窗口,但是子窗口不會收到該消息;如果hWnd參數為NULL,則該函數類似于將dwThreadID參數設置


成當前線程的標志來調用PostThreadMEssage函數。?
從上面的這2個具有代表性的函數,我們可以看出消息的發送方式和寄送方式的區別所在:被發送的消息是否會被立即處理,函數


是否立即返回。被發送的消息會被立即處理,處理完畢后函數才會返回;被寄送的消息不會被立即處理,他被放到一個先進先出


的隊列中,一直等到應用程序空閑的時候才會被處理,不過函數放置消息后立即返回。?
實際上,發送消息到一個窗口處理過程和直接調用窗口處理過程之間并沒有太大的區別,他們直接的唯一區別就在于你可以要求


操作系統截獲所有被發送的消息,但是不能夠截獲對窗口處理過程的直接調用。?
以寄送方式發送的消息通常是與用戶輸入事件相對應的,因為這些事件不是十分緊迫,可以進行緩慢的緩沖處理,例如鼠標、鍵


盤消息會被寄送,而按鈕等消息則會被發送。?
廣播消息用得比較少,BroadcastSystemMessage函數原型如下:?
long BroadcastSystemMessage(DWORD dwFlags, LPDWORD lpdwRecipients, UINT uiMessage, WPARAM wParam, LPARAM lParam);?
該函數可以向指定的接收者發送一條消息,這些接收者可以是應用程序、可安裝的驅動程序、網絡驅動程序、系統級別的設備驅


動消息和他們的任意組合。需要注意的是,如果dwFlags參數是BSF_QUERY并且至少一個接收者返回了BROADCAST_QUERY_DENY,則


返回值為0,如果沒有指定BSF_QUERY,則函數將消息發送給所有接收者,并且忽略其返回值。


七、消息的接收?
消息的接收主要有3個函數:GetMessage、PeekMessage、WaitMessage。?
GetMessage原型如下:BOOL GetMessage(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax);


該函數用來獲取與hWnd參數所指定的窗口相關的且wMsgFilterMin和wMsgFilterMax參數所給出的消息值范圍內的消息。需要注意


的是,如果hWnd為NULL,則GetMessage獲取屬于調用該函數應用程序的任一窗口的消息,如果wMsgFilterMin和wMsgFilterMax都


是0,則GetMessage就返回所有可得到的消息。函數獲取之后將刪除消息隊列中的除WM_PAINT消息之外的其他消息,至于


WM_PAINT則只有在其處理之后才被刪除。


PeekMessage原型如下:BOOL PeekMessage(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT


wRemoveMsg);?
該函數用于查看應用程序的消息隊列,如果其中有消息就將其放入lpMsg所指的結構中,不過,與GetMessage不同的是,


PeekMessage函數不會等到有消息放入隊列時才返回。同樣,如果hWnd為NULL,則PeekMessage獲取屬于調用該函數應用程序的任


一窗口的消息,如果hWnd=-1,那么函數只返回把hWnd參數為NULL的PostAppMessage函數送去的消息。如果wMsgFilterMin和


wMsgFilterMax都是0,則PeekMessage就返回所有可得到的消息。函數獲取之后將刪除消息隊列中的除WM_PAINT消息之外的其他


消息,至于WM_PAINT則只有在其處理之后才被刪除。?
WaitMessage原型如下:BOOL VaitMessage();


當一個應用程序無事可做時,該函數就將控制權交給另外的應用程序,同時將該應用程序掛起,直到一個新的消息被放入應用程


序的隊列之中才返回。


八、消息的處理?
接下來我們談一下消息的處理,首先我們來看一下VC中的消息泵:?
while(GetMessage(&msg, NULL, 0, 0))?
{?
? ? if(!TranslateAccelerator(msg.hWnd, hAccelTable, &msg))?
? ? { ?
? ? ? ? TranslateMessage(&msg);?
? ? ? ? DispatchMessage(&msg);?
? ? }?
}


首先,GetMessage從進程的主線程的消息隊列中獲取一個消息并將它復制到MSG結構,如果隊列中沒有消息,則GetMessage函數將


等待一個消息的到來以后才返回。 如果你將一個窗口句柄作為第二個參數傳入GetMessage,那么只有指定窗口的消息可以從隊列


中獲得。GetMessage也可以從消息隊列中過濾消息,只接受消息隊列中落在范圍內的消息。這時候就要利用GetMessage/


PeekMessage指定一個消息過濾器。這個過濾器是一個消息標識符的范圍或者是一個窗體句柄,或者兩者同時指定。當應用程序要


查找一個后入消息隊列的消息是很有用。WM_KEYFIRST 和 WM_KEYLAST 常量用于接受所有的鍵盤消息。 WM_MOUSEFIRST 和


WM_MOUSELAST 常量用于接受所有的鼠標消息。 ?
然后TranslateAccelerator判斷該消息是不是一個按鍵消息并且是一個加速鍵消息,如果是,則該函數將把幾個按鍵消息轉換成


一個加速鍵消息傳遞給窗口的回調函數。處理了加速鍵之后,函數TranslateMessage將把兩個按鍵消息WM_KEYDOWN和WM_KEYUP轉


換成一個WM_CHAR,不過需要注意的是,消息WM_KEYDOWN,WM_KEYUP仍然將傳遞給窗口的回調函數。


處理完之后,DispatchMessage函數將把此消息發送給該消息指定的窗口中已設定的回調函數。如果消息是WM_QUIT,則


GetMessage返回0,從而退出循環體。應用程序可以使用PostQuitMessage來結束自己的消息循環。通常在主窗口的WM_DESTROY消


息中調用。


下面我們舉一個常見的小例子來說明這個消息泵的運用:?
if (::PeekMessage(&msg, m_hWnd, WM_KEYFIRST,WM_KEYLAST, PM_REMOVE))?
{?
? ? if (msg.message == WM_KEYDOWN && msg.wParam == VK_ESCAPE)...?
}


這里我們接受所有的鍵盤消息,所以就用WM_KEYFIRST 和 WM_KEYLAST作為參數。最后一個參數可以是PM_NOREMOVE 或者


PM_REMOVE,表示消息信息是否應該從消息隊列中刪除。 ?
所以這段小代碼就是判斷是否按下了Esc鍵,如果是就進行處理。


九、窗口過程?
窗口過程是一個用于處理所有發送到這個窗口的消息的函數。任何一個窗口類都有一個窗口過程。同一個類的窗口使用同樣的窗


口過程來響應消息。


系統發送消息給窗口過程將消息數據作為參數傳遞給他,消息到來之后,按照消息類型排序進行處理,其中的參數則用來區分不


同的消息,窗口過程使用參數產生合適行為。?
一個窗口過程不經常忽略消息,如果他不處理,它會將消息傳回到執行默認的處理。窗口過程通過調用DefWindowProc來做這個處


理。窗口過程必須return一個值作為它的消息處理結果。大多數窗口只處理小部分消息和將其他的通過DefWindowProc傳遞給系統


做默認的處理。窗口過程被所有屬于同一個類的窗口共享,能為不同的窗口處理消息。下面我們來看一下具體的實例:


LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)?
{?
? ? int wmId, wmEvent;?
? ? PAINTSTRUCT ps;?
? ? HDC hdc;?
? ?TCHAR szHello[MAX_LOADSTRING];?
? ? LoadString(hInst, IDS_HELLO, szHello, MAX_LOADSTRING);


switch (message) ?
{?
? ? case WM_COMMAND:?
? ? ? ? wmId = LOWORD(wParam); ?
? ? ? ? wmEvent = HIWORD(wParam); ?
? ? ? ? // Parse the menu selections:?
? ? ? ? switch (wmId)?
? ? ? ? {?
? ? ? ? case IDM_ABOUT:?
? ? ? ? ? ? DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About);?
? ? ? ? break;?
? ? ? ? case IDM_EXIT:?
? ? ? ? ? ? DestroyWindow(hWnd);?
? ? ? ? break;?
? ? ? ? default:?
? ? ? ? ? ? return DefWindowProc(hWnd, message, wParam, lParam);?
? ? ? ? }?
? ? break;?
? ? case WM_PAINT:?
? ? ? ? hdc = BeginPaint(hWnd, &ps);?
? ? ? ? // TODO: Add any drawing code here...?
? ? ? ? RECT rt;?
? ? ? ? GetClientRect(hWnd, &rt);?
? ? ? ? DrawText(hdc, szHello, strlen(szHello), &rt, DT_CENTER);?
? ? ? ? EndPaint(hWnd, &ps);?
? ? break;?
? ? case WM_DESTROY:?
? ? ? ? PostQuitMessage(0);?
? ? break;?
? ? default:?
? ? ? ? return DefWindowProc(hWnd, message, wParam, lParam);?
}?
return 0;


}


消息分流器?
通常的窗口過程是通過一個switch語句來實現的,這個事情很煩,有沒有更簡便的方法呢?有,那就是消息分流器,利用消息分


流器,我們可以把switch語句分成更小的函數,每一個消息都對應一個小函數,這樣做的好處就是對消息更容易管理。?
之所以被稱為消息分流器,就是因為它可以對任何消息進行分流。下面我們做一個函數就很清楚了:?
void MsgCracker(HWND hWnd,int id,HWND hWndCtl,UINT codeNotify)?
{?
? ? switch(id)?
? ? {?
? ? ? ? case ID_A:?
? ? ? ? ? ? if(codeNotify==EN_CHANGE)...?
? ? ? ?break;?
? ? ? ? case ID_B:?
? ? ? ? ? ? if(codeNotify==BN_CLICKED)...?
? ? ? ? break;?
? ? ? ? ....?
? ? }?
}?
然后我們修改一下窗口過程:


LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)?
{?
? ? switch(message)?
? ? {?
? ? ? ? HANDLE_MSG(hWnd,WM_COMMAND,MsgCracker);?
? ? ? ? HANDLE_MSG(hWnd,WM_DESTROY,MsgCracker);?
? ? ? ? default:?
? ? ? ? ? ? return DefWindowProc(hWnd, message, wParam, lParam);?
}?
? ? return 0;?
}?
在WindowsX.h中定義了如下的HANDLE_MSG宏:?
#define HANDLE_MSG(hwnd,msg,fn) \?
switch(msg): return HANDLE_##msg((hwnd),(wParam),(lParam),(fn));?
實際上,HANDLE_WM_XXXX都是宏,例如:HANDLE_MSG(hWnd,WM_COMMAND,MsgCracker);將被轉換成如下定義:?
#define HANDLE_WM_COMMAND(hwnd,wParam,lParam,fn)\ ?
((fn)((hwnd),(int)(LOWORD(wParam)),(HWND)(lParam),(UINT)HIWORD(wParam)),0L);?
好了,事情到了這一步,應該一切都明朗了。?
不過,我們發現在windowsx.h里面還有一個宏:FORWARD_WM_XXXX,我們還是那WM_COMMAND為例,進行分析:?
#define FORWARD_WM_COMMAND(hwnd, id, hwndCtl, codeNotify, fn) \?
(void)(fn)((hwnd), WM_COMMAND, MAKEWPARAM((UINT)(id),(UINT)(codeNotify)), (LPARAM)(HWND)(hwndCtl))?
所以實際上,FORWARD_WM_XXXX將消息參數進行了重新構造,生成了wParam && lParam,然后調用了我們定義的函數。


十、MFC消息的處理實現方式?
初看MFC中的各種消息,以及在頭腦中根深蒂固的C++的影響,我們可能很自然的就會想到利用C++的三大特性之一:虛擬機制來實


現消息的傳遞,但是經過分析,我們看到事情并不是想我們想象的那樣,


在MFC中消息是通過一種所謂的消息映射機制來處理的。?
為什么呢?在潘愛民老師翻譯的《Visual C++技術內幕》(第4版)中給出了詳細的原因說明,我再簡要的說一遍。在CWnd類中大


約有110個消息,還有其它的MFC的類呢,算起來消息太多了,在C++中對程序中用到的每一個派生類都要有一個vtable,每一個虛


函數在vtable中都要占用一個4字節大小的入口地址,這樣一來,對于每個特定類型的窗口或控件,應用程序都需要一個440KB大


小的表來支持虛擬消息控件函數。?
如果說上面的窗口或控件可以勉強實現的話,那么對于菜單命令消息及按鈕命令消息呢?因為不同的應用程序有不同的菜單和按


鈕,我們怎么處理呢?在MFC庫的這種消息映射系統就避免了使用大的vtable,并且能夠在處理常規Windows消息的同時處理各種


各樣的應用程序的命令消息。?
說白了,MFC中的消息機制其實質是一張巨大的消息及其處理函數的一一對應表,然后加上分析處理這張表的應用框架內部的一些


程序代碼。這樣就可以避免在SDK編程中用到的繁瑣的CASE語句。


MFC的消息映射的基類CCmdTarget?
如果你想讓你的控件能夠進行消息映射,就必須從CCmdTarget類中派生。CCmdTarget類是MFC處理命令消息的基礎、核心。MFC為


該類設計了許多成員函數和一些成員數據,基本上是為了解決消息映射問題的,所有響應消息或事件的類都從它派生,例如:應


用程序類、框架類、文檔類、視圖類和各種各樣的控件類等等,還有很多。?
不過這個類里面有2個函數對消息映射非常重要,一個是靜態成員函數DispatchCmdMsg,另一個是虛函數OnCmdMsg。?
DispatchCmdMsg專門供MFC內部使用,用來分發Windows消息。OnCmdMsg用來傳遞和發送消息、更新用戶界面對象的狀態。?
CCmdTarget對OnCmdMsg的默認實現:在當前命令目標(this所指)的類和基類的消息映射數組里搜索指定命令消息的消息處理函數


。?
這里使用虛擬函數GetMessageMap得到命令目標類的消息映射入口數組_messageEntries,然后在數組里匹配命令消息ID相同、控


制通知代碼也相同的消息映射條目。其中GetMessageMap是虛擬函數,所以可以確認當前命令目標的確切類。?
如果找到了一個匹配的消息映射條目,則使用DispachCmdMsg調用這個處理函數;?
如果沒有找到,則使用_GetBaseMessageMap得到基類的消息映射數組,查找,直到找到或搜尋了所有的基類(到CCmdTarget)為


止;如果最后沒有找到,則返回FASLE。?
每個從CCmdTarget派生的命令目標類都可以覆蓋OnCmdMsg,利用它來確定是否可以處理某條命令,如果不能,就通過調用下一命


令目標的OnCmdMsg,把該命令送給下一個命令目標處理。通常,派生類覆蓋OnCmdMsg時 ,要調用基類的被覆蓋的OnCmdMsg。?
在MFC框架中,一些MFC命令目標類覆蓋了OnCmdMsg,如框架窗口類覆蓋了該函數,實現了MFC的標準命令消息發送路徑。必要的話


,應用程序也可以覆蓋OnCmdMsg,改變一個或多個類中的發送規定,實現與標準框架發送規定不同的發送路徑。例如,在以下情


況可以作這樣的處理:在要打斷發送順序的類中把命令傳給一個非MFC默認對象;在新的非默認對象中或在可能要傳出命令的命令


目標中。


消息映射的內容?
通過ClassWizard為我們生成的代碼,我們可以看到,消息映射基本上分為2大部分:


在頭文件(.h)中有一個宏DECLARE_MESSAGE_MAP(),他被放在了類的末尾,是一個public屬性的;與之對應的是在實現部分


(.cpp)增加了一張消息映射表,內容如下:?
BEGIN_MESSAGE_MAP(當前類, 當前類的基類)?
file://{{AFX_MSG_MAP(CMainFrame)?
消息的入口項?
file://}}AFX_MSG_MAP?
END_MESSAGE_MAP()?
但是僅有這兩項還遠不足以完成一條消息,要使一個消息工作,必須有以下3個部分去協作:?
1.在類的定義中加入相應的函數聲明;?
2.在類的消息映射表中加入相應的消息映射入口項;?
3.在類的實現中加入相應的函數體;


消息的添加?
有了上面的這些只是作為基礎,我們接下來就做我們最熟悉、最常用的工作:添加消息。


MFC消息的添加主要有2種方法:自動/手動,我們就以這2種方法為例,說一下如何添加消息。?
1、利用Class Wizard實現自動添加?
在菜單中選擇View-->Class Wizard,也可以用單擊鼠標右鍵,選擇Class Wizard,同樣可以激活Class Wizard。選擇Message


Map標簽,從Class name組合框中選取我們想要添加消息的類。在Object IDs列表框中,選取類的名稱。此時, Messages列表框


顯示該類的大多數(若不是全部的話)可重載成員函數和窗口消息。類重載顯示在列表的上部,以實際虛構成員函數的大小寫字母


來表示。其他為窗口消息,以大寫字母出現,描述了實際窗口所能響應的消息ID。選中我們向添加的消息,單擊Add Function按


鈕,Class Wizard自動將該消息添加進來。?
有時候,我們想要添加的消息本應該出現在Message列表中,可是就是找不到,怎么辦?不要著急,我們可以利用Class Wizard上


Class Info標簽以擴展消息列表。在該頁中,找到Message Filter組合框,通過它可以改變首頁中Messages列表框中的選項。這


里,我們選擇Window,從而顯示所有的窗口消息,一把情況下,你想要添加的消息就可以在Message列表框中出現了,如果還沒有


,那就接著往下看:)


2、手動地添加消息處理函數?
如果在Messages列表框中仍然看不到我們想要的消息,那么該消息可能是被系統忽略掉或者是你自己創建的,在這種情況下,就


必須自己手工添加。根據我們前面所說的消息工作的3個部件,我們一一進行處理:?
1) 在類的. h文件中添加處理函數的聲明,緊接在//}}AFX_MSG行之后加入聲明,注意:一定要以afx_msg開頭。?
通常,添加處理函數聲明的最好的地方是源代碼中Class Wizard維護的表下面,但是在它標記其領域的{{}}括弧外面。這些


括弧中的任何東西都將會被Class Wizard銷毀。?
2) 接著,在用戶類的.cpp文件中找到//}}AFX_MSG_MAP行,緊接在它之后加入消息入口項。同樣,也是放在{ {} }的外面


3) 最后,在該文件中添加消息處理函數的實體。
========

VS2010/MFC編程入門之五(MFC消息映射機制概述)

http://www.jizhuomi.com/software/147.html
? ? ??
? ? ? ?前面已經說過,Windows應用程序是消息驅動的。在MFC軟件開發中,界面操作或者線程之間通信都會經常用到消息,通過對消息的處理實現相應的操作。比較典型的過程是,用戶操作窗口,然后有消息產生,送給窗口的消息處理函數處理,對用戶的操作做出響應。


? ? ? ?什么是消息


? ? ? ?窗口消息一般由三個部分組成:1.一個無符號整數,是消息值;(2)消息附帶的WPARAM類型的參數;(3)消息附帶的LPARAM類型的參數。其實我們一般所說的消息是狹義上的消息值,也就是一個無符號整數,經常被定義為宏。


? ? ? ?什么是消息映射機制


? ? ? ?MFC使用一種消息映射機制來處理消息,在應用程序框架中的表現就是一個消息與消息處理函數一一對應的消息映射表,以及消息處理函數的聲明和實現等代碼。當窗口接收到消息時,會到消息映射表中查找該消息對應的消息處理函數,然后由消息處理函數進行相應的處理。SDK編程時需要在窗口過程中一一判斷消息值進行相應的處理,相比之下MFC的消息映射機制要方便好用的多。


? ? ? ?Windows消息分類


? ? ? ?先講下Windows消息的分類。Windows消息分為系統消息和用戶自定義消息。Windows系統消息有三種:


? ? ? ?1.標準Windows消息。除WM_COMMAND外以WM_開頭的消息是標準消息。例如,WM_CREATE、WM_CLOSE。


? ? ? ?2.命令消息。消息名為WM_COMMAND,消息中附帶了標識符ID來區分是來自哪個菜單、工具欄按鈕或加速鍵的消息。


? ? ? ?3.通知消息。通知消息一般由列表框等子窗口發送給父窗口,消息名也是WM_COMMAND,其中附帶了控件通知碼來區分控件。


? ? ? ?CWnd的派生類都可以接收到標準Windows消息、通知消息和命令消息。命令消息還可以由文檔類等接收。


? ? ? ?用戶自定義消息是實際上就是用戶定義一個宏作為消息,此宏的值應該大于等于WM_USER,然后此宏就可以跟系統消息一樣使用,窗口類中可以定義它的處理函數。


? ? ? ?消息映射表


? ? ? ?除了一些沒有基類的類或CObject的直接派生類外,其他的類都可以自動生成消息映射表。下面的講解都以前面例程HelloWorld的CMainFrame為例。消息映射表如下:


C++代碼
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWndEx) ??
? ? ON_WM_CREATE() ??
? ? ON_COMMAND(ID_VIEW_CUSTOMIZE, &CMainFrame::OnViewCustomize) ??
? ? ON_REGISTERED_MESSAGE(AFX_WM_CREATETOOLBAR, &CMainFrame::OnToolbarCreateNew) ??
? ? ON_COMMAND_RANGE(ID_VIEW_APPLOOK_WIN_2000, ID_VIEW_APPLOOK_WINDOWS_7, &CMainFrame::OnApplicationLook) ??
? ? ON_UPDATE_COMMAND_UI_RANGE(ID_VIEW_APPLOOK_WIN_2000, ID_VIEW_APPLOOK_WINDOWS_7, &CMainFrame::OnUpdateApplicationLook) ??
? ? ON_WM_SETTINGCHANGE() ??
END_MESSAGE_MAP() ?
? ? ? ?在BEGIN_MESSAG_MAP和END_MESSAGE_MAP之間的內容成為消息映射入口項。消息映射除了在CMainFrame的實現文件中添加消息映射表外,在類的定義文件MainFrm.h中還會添加一個宏調用:


? ? ? ?DECLARE_MESSAGE_MAP()


? ? ? ?一般這個宏調用寫在類定義的結尾處。


VS2010/MFC編程入門之五(MFC消息映射機制概述)


? ? ? ?添加消息處理函數


? ? ? ?如何添加消息處理函數呢?不管是自動還是手動添加都有三個步驟:


? ? ? ?1.在類定義中加入消息處理函數的函數聲明,注意要以afx_msg打頭。例如MainFrm.h中WM_CREATE的消息處理函數的函數聲明:afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);。


? ? ? ?2.在類的消息映射表中添加該消息的消息映射入口項。例如WM_CREATE的消息映射入口項:ON_WM_CREATE()。


? ? ? ?3.在類實現中添加消息處理函數的函數實現。例如,MainFrm.cpp中WM_CREATE的消息處理函數的實現:


? ? ? ? ? int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
? ? ? ? ?{
? ? ? ? ? ? ? ? ? ......
? ? ? ? ?}


? ? ? ?通過以上三個步驟以后,WM_CREATE等消息就可以在窗口類中被消息處理函數處理了。


? ? ? ?各種Windows消息的消息處理函數


? ? ? ?標準Windows消息的消息處理函數都與WM_CREATE消息類似。


? ? ? ?命令消息的消息映射入口項形式如:ON_COMMAND(ID_VIEW_CUSTOMIZE, &CMainFrame::OnViewCustomize),消息為ID_VIEW_CUSTOMIZE,消息處理函數為OnViewCustomize。


? ? ? ?如果想要使用某個處理函數批量處理某些命令消息,則可以像CMainFrame消息映射表中的ON_COMMAND_RANGE(ID_VIEW_APPLOOK_WIN_2000, ID_VIEW_APPLOOK_WINDOWS_7, &CMainFrame::OnApplicationLook)一樣添加消息映射入口項,這樣值在ID_VIEW_APPLOOK_WIN_2000到ID_VIEW_APPLOOK_WINDOWS_7之間的菜單項等的命令消息都由CMainFrame的OnApplicationLook函數處理。函數原型為afx_msg void OnApplicationLook(UINT id);,參數id為用戶操作的菜單項等的ID。


? ? ? ?在操作列表框等控件時往往會給父窗口發送WM_NOTIFY通知消息。WM_NOTIFY消息的wParam參數為發送通知消息的控件的ID,lParam參數指向一個結構體,可能是NMHDR結構體,也可能是第一個元素為NMHDR結構體變量的其他結構體。NMHDR結構體的定義如下(僅作了解):


? ? ? ?Typedef sturct tagNMHDR{
? ? ? ? ? ? ? ? HWND hwndFrom;
? ? ? ? ? ? ? ? UINT idFrom;
? ? ? ? ? ? ? ? UINT code;
? ? ? ?} NMHDR;


? ? ? ?hwndFrom為發送通知消息控件的句柄,idFrom為控件ID,code為要處理的通知消息的通知碼,例如NM_CLICK。


? ? ? ?通知消息的消息映射入口項形式如:


? ? ? ?ON_NOTIFY(wNotifyCode,id,memberFxn)


? ? ? ?wNotifyCode為要處理的通知消息通知碼,例如:NM_CLICK。id為控件標識ID。MemberFxn為此消息的處理函數。


? ? ? ?通知消息的處理函數的原型為:


? ? ? ?afx_msg void memberFxn( NMHDR * pNotifyStruct, LRESULT * result);


? ? ? ?如果需要使用用戶自定義消息,首先要定義消息宏,如:#define WM_UPDATE_WND (WM_USER+1),再到消息映射表中添加消息映射入口項:ON_MESSAGE(WM_UPDATE_WND, &CMainFrame::OnUpdateWnd),然后在MainFrm.h中添加消息處理函數的函數聲明:afx_msg LRESULT OnUpdateWnd(WPARAM wParam, LPARAM lParam);,最后在MainFrm.cpp中實現此函數。


========

總結

以上是生活随笔為你收集整理的MFC消息处理学习总结的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

在线欧美精品一区二区三区 | 女人被男人躁得好爽免费视频 | 精品无码av一区二区三区 | 最近中文2019字幕第二页 | 久久久www成人免费毛片 | 日本精品少妇一区二区三区 | 人妻插b视频一区二区三区 | 亚洲中文字幕无码一久久区 | 欧美兽交xxxx×视频 | 中文字幕av伊人av无码av | 欧美freesex黑人又粗又大 | 午夜嘿嘿嘿影院 | 精品欧美一区二区三区久久久 | 久久精品女人天堂av免费观看 | 麻花豆传媒剧国产免费mv在线 | 精品人妻中文字幕有码在线 | 欧美性猛交内射兽交老熟妇 | 国产区女主播在线观看 | 一本色道久久综合亚洲精品不卡 | 97久久国产亚洲精品超碰热 | 狠狠色噜噜狠狠狠7777奇米 | 真人与拘做受免费视频一 | √8天堂资源地址中文在线 | 成人一在线视频日韩国产 | 99国产精品白浆在线观看免费 | 国产激情精品一区二区三区 | 日本一区二区更新不卡 | 国语自产偷拍精品视频偷 | 国产熟妇高潮叫床视频播放 | 国产艳妇av在线观看果冻传媒 | 中国女人内谢69xxxxxa片 | 久久精品人人做人人综合 | 日本大乳高潮视频在线观看 | 国产xxx69麻豆国语对白 | 国产精品亚洲综合色区韩国 | 99久久精品日本一区二区免费 | 人妻体内射精一区二区三四 | 无码人妻精品一区二区三区下载 | 中文字幕无码人妻少妇免费 | аⅴ资源天堂资源库在线 | 在线看片无码永久免费视频 | 丁香啪啪综合成人亚洲 | 在线观看欧美一区二区三区 | 精品偷自拍另类在线观看 | 男女猛烈xx00免费视频试看 | 人人澡人摸人人添 | 亚洲熟悉妇女xxx妇女av | 国产精品a成v人在线播放 | 一本久久a久久精品亚洲 | 娇妻被黑人粗大高潮白浆 | 欧美精品无码一区二区三区 | 无码人妻精品一区二区三区下载 | 亚洲人成网站色7799 | 在线成人www免费观看视频 | 国内精品人妻无码久久久影院蜜桃 | 国产免费久久久久久无码 | 波多野结衣高清一区二区三区 | 99riav国产精品视频 | 中文久久乱码一区二区 | 国产精品久久国产精品99 | 在线亚洲高清揄拍自拍一品区 | 美女扒开屁股让男人桶 | 日本一区二区三区免费播放 | 亚洲欧美日韩综合久久久 | 天天综合网天天综合色 | 久久久国产一区二区三区 | 午夜熟女插插xx免费视频 | 国产精品18久久久久久麻辣 | 亚洲综合无码一区二区三区 | 亚洲日韩中文字幕在线播放 | www国产亚洲精品久久久日本 | 亚洲性无码av中文字幕 | 国产精品a成v人在线播放 | 无码精品国产va在线观看dvd | 日韩av无码一区二区三区 | 久久久久久久女国产乱让韩 | 日日躁夜夜躁狠狠躁 | 成人精品视频一区二区 | a片免费视频在线观看 | 久久久久久国产精品无码下载 | 欧美放荡的少妇 | 亚洲精品成人av在线 | 欧美丰满老熟妇xxxxx性 | 亚洲呦女专区 | 一本精品99久久精品77 | 亚洲欧美国产精品久久 | 蜜桃av蜜臀av色欲av麻 999久久久国产精品消防器材 | 内射欧美老妇wbb | √8天堂资源地址中文在线 | 无码免费一区二区三区 | 无套内谢的新婚少妇国语播放 | 欧美 日韩 亚洲 在线 | 国产亚洲视频中文字幕97精品 | 国产内射爽爽大片视频社区在线 | 噜噜噜亚洲色成人网站 | 亚洲欧美色中文字幕在线 | 国产 浪潮av性色四虎 | 国产亚洲精品久久久久久 | 国产精品美女久久久 | 亚洲va中文字幕无码久久不卡 | 国产午夜手机精彩视频 | 国产精品久久久久久亚洲影视内衣 | av无码不卡在线观看免费 | 国产精品香蕉在线观看 | 一区二区传媒有限公司 | 人妻有码中文字幕在线 | 大屁股大乳丰满人妻 | 免费观看又污又黄的网站 | 精品亚洲韩国一区二区三区 | 亚洲熟熟妇xxxx | 99国产精品白浆在线观看免费 | 四虎影视成人永久免费观看视频 | 国产精华av午夜在线观看 | 色婷婷av一区二区三区之红樱桃 | 精品欧美一区二区三区久久久 | 亚洲国产精品一区二区美利坚 | 国产suv精品一区二区五 | 青春草在线视频免费观看 | 一二三四社区在线中文视频 | 在线天堂新版最新版在线8 | 日本va欧美va欧美va精品 | 日韩成人一区二区三区在线观看 | 欧美成人高清在线播放 | 美女毛片一区二区三区四区 | 日产精品99久久久久久 | 亚洲精品成人av在线 | 无码人妻av免费一区二区三区 | 亚洲成av人综合在线观看 | 日韩av无码一区二区三区 | 国产偷抇久久精品a片69 | 亚洲中文字幕乱码av波多ji | 精品无码成人片一区二区98 | 激情亚洲一区国产精品 | 久久综合九色综合97网 | 亚洲色欲色欲天天天www | 亚洲精品午夜国产va久久成人 | 国产av无码专区亚洲a∨毛片 | 中文字幕精品av一区二区五区 | 少妇性l交大片 | 麻豆国产丝袜白领秘书在线观看 | 小鲜肉自慰网站xnxx | 久久久久久久久蜜桃 | 美女极度色诱视频国产 | 99久久久无码国产精品免费 | 性欧美牲交在线视频 | 亚洲欧美日韩成人高清在线一区 | 午夜精品一区二区三区的区别 | 亚洲精品久久久久avwww潮水 | 国产成人精品久久亚洲高清不卡 | 欧美丰满熟妇xxxx性ppx人交 | 天堂а√在线地址中文在线 | 国产极品美女高潮无套在线观看 | 亚洲高清偷拍一区二区三区 | 特级做a爰片毛片免费69 | 亚洲中文字幕在线观看 | 成人动漫在线观看 | 国産精品久久久久久久 | 久久国产自偷自偷免费一区调 | 黑人粗大猛烈进出高潮视频 | 色欲av亚洲一区无码少妇 | 日本va欧美va欧美va精品 | 男女爱爱好爽视频免费看 | 亚洲精品国偷拍自产在线观看蜜桃 | 激情内射亚州一区二区三区爱妻 | 1000部啪啪未满十八勿入下载 | 亚洲午夜福利在线观看 | 精品偷拍一区二区三区在线看 | 在线播放亚洲第一字幕 | 精品国产av色一区二区深夜久久 | 精品国产一区av天美传媒 | 人人爽人人爽人人片av亚洲 | 撕开奶罩揉吮奶头视频 | 色诱久久久久综合网ywww | 亚洲日韩av一区二区三区四区 | 亚洲乱码中文字幕在线 | а√资源新版在线天堂 | 欧美猛少妇色xxxxx | 国内少妇偷人精品视频 | 日本护士毛茸茸高潮 | 成人无码精品1区2区3区免费看 | 成人免费视频视频在线观看 免费 | 亚拍精品一区二区三区探花 | aⅴ亚洲 日韩 色 图网站 播放 | 色情久久久av熟女人妻网站 | 国产亚洲精品久久久久久久 | 又黄又爽又色的视频 | 2019午夜福利不卡片在线 | 2019nv天堂香蕉在线观看 | 精品国产成人一区二区三区 | 无码一区二区三区在线 | 激情内射日本一区二区三区 | 日韩精品无码一区二区中文字幕 | 亚洲精品一区三区三区在线观看 | 内射老妇bbwx0c0ck | 麻豆人妻少妇精品无码专区 | 亚洲va欧美va天堂v国产综合 | 性色欲情网站iwww九文堂 | 国产99久久精品一区二区 | a在线亚洲男人的天堂 | 亚洲精品鲁一鲁一区二区三区 | 国产乱码精品一品二品 | 成人性做爰aaa片免费看不忠 | 国产精品人人爽人人做我的可爱 | 影音先锋中文字幕无码 | 久久久久人妻一区精品色欧美 | 欧美一区二区三区 | 自拍偷自拍亚洲精品被多人伦好爽 | 国产做国产爱免费视频 | 精品成在人线av无码免费看 | 国产偷自视频区视频 | 亚洲精品午夜无码电影网 | 亚洲精品欧美二区三区中文字幕 | 国产精品无码久久av | 欧美精品一区二区精品久久 | 国产精品国产自线拍免费软件 | 婷婷丁香五月天综合东京热 | 久久精品中文闷骚内射 | 久久国产精品二国产精品 | 300部国产真实乱 | 97精品国产97久久久久久免费 | 久久久www成人免费毛片 | 一个人看的视频www在线 | 天天拍夜夜添久久精品大 | 亚洲人成网站在线播放942 | 久久99国产综合精品 | 99精品久久毛片a片 | 装睡被陌生人摸出水好爽 | 亚洲中文字幕无码一久久区 | 亚洲国产精品久久久久久 | 精品无码国产一区二区三区av | 久久国产自偷自偷免费一区调 | 久久久精品456亚洲影院 | 婷婷五月综合缴情在线视频 | 无码av中文字幕免费放 | 久久精品无码一区二区三区 | 男女超爽视频免费播放 | 成人无码精品一区二区三区 | 国产亚洲tv在线观看 | 国产av久久久久精东av | 久久亚洲精品成人无码 | 国产乱人伦app精品久久 国产在线无码精品电影网 国产国产精品人在线视 | 水蜜桃亚洲一二三四在线 | 国产亚洲精品久久久久久大师 | 亚洲中文字幕无码中字 | 丰满岳乱妇在线观看中字无码 | 日日天日日夜日日摸 | 亚洲色在线无码国产精品不卡 | 国产农村妇女aaaaa视频 撕开奶罩揉吮奶头视频 | 亚洲一区二区三区 | 中文字幕av无码一区二区三区电影 | 狠狠色色综合网站 | 色综合久久久无码中文字幕 | 丝袜 中出 制服 人妻 美腿 | 综合激情五月综合激情五月激情1 | 少妇人妻偷人精品无码视频 | 日本xxxx色视频在线观看免费 | 日韩少妇白浆无码系列 | 我要看www免费看插插视频 | 久久99精品国产.久久久久 | 色欲综合久久中文字幕网 | 久久久久久久人妻无码中文字幕爆 | 亚洲一区二区三区 | 亚洲综合色区中文字幕 | 亚洲爆乳精品无码一区二区三区 | 1000部啪啪未满十八勿入下载 | 国产av无码专区亚洲a∨毛片 | 国产精品久久久久久亚洲影视内衣 | 高潮毛片无遮挡高清免费视频 | 97精品国产97久久久久久免费 | 色婷婷欧美在线播放内射 | 国产亚洲精品久久久久久 | 国产国产精品人在线视 | 俺去俺来也在线www色官网 | 久久亚洲精品成人无码 | 亚洲色欲久久久综合网东京热 | 国产成人无码区免费内射一片色欲 | 少妇激情av一区二区 | 性色欲网站人妻丰满中文久久不卡 | 欧美性生交xxxxx久久久 | 久久无码中文字幕免费影院蜜桃 | 国产人成高清在线视频99最全资源 | 精品人妻人人做人人爽 | 丰满妇女强制高潮18xxxx | 丰满少妇熟乱xxxxx视频 | 欧美日韩一区二区综合 | 天堂久久天堂av色综合 | 少妇高潮喷潮久久久影院 | 在线视频网站www色 | 亚洲乱码国产乱码精品精 | 亚洲精品www久久久 | 日日天干夜夜狠狠爱 | 国产精品高潮呻吟av久久 | 国产欧美熟妇另类久久久 | 精品国产成人一区二区三区 | 午夜熟女插插xx免费视频 | 国产一区二区三区四区五区加勒比 | 99久久人妻精品免费二区 | 国内揄拍国内精品少妇国语 | 亚洲精品国产a久久久久久 | 高潮毛片无遮挡高清免费视频 | 国产综合色产在线精品 | 国产精品久免费的黄网站 | 小sao货水好多真紧h无码视频 | 欧洲熟妇精品视频 | 精品久久久久久人妻无码中文字幕 | 人人澡人人妻人人爽人人蜜桃 | 亚洲日韩av一区二区三区中文 | 久久人人爽人人爽人人片ⅴ | 精品无码一区二区三区爱欲 | 131美女爱做视频 | 亚洲中文字幕无码中字 | 国产成人精品一区二区在线小狼 | 欧美真人作爱免费视频 | √8天堂资源地址中文在线 | 成人毛片一区二区 | 影音先锋中文字幕无码 | 国产成人无码av一区二区 | 天天躁夜夜躁狠狠是什么心态 | 无人区乱码一区二区三区 | 人妻夜夜爽天天爽三区 | 欧美日韩综合一区二区三区 | 又粗又大又硬又长又爽 | 人妻尝试又大又粗久久 | 色婷婷欧美在线播放内射 | 日韩欧美群交p片內射中文 | 骚片av蜜桃精品一区 | 色欲综合久久中文字幕网 | 99久久人妻精品免费一区 | 日韩欧美中文字幕公布 | 欧美zoozzooz性欧美 | 亚洲娇小与黑人巨大交 | 奇米影视7777久久精品 | 无人区乱码一区二区三区 | 久久精品成人欧美大片 | 在线播放亚洲第一字幕 | 老子影院午夜伦不卡 | 国产精品亚洲专区无码不卡 | 妺妺窝人体色www婷婷 | 久久五月精品中文字幕 | 久久精品国产一区二区三区 | 日本一区二区三区免费播放 | 国产国语老龄妇女a片 | 精品欧洲av无码一区二区三区 | 色综合久久中文娱乐网 | 在线视频网站www色 | 色情久久久av熟女人妻网站 | 波多野结衣乳巨码无在线观看 | 国产精品久久久一区二区三区 | 国产精品亚洲综合色区韩国 | 在线观看免费人成视频 | 玩弄人妻少妇500系列视频 | 精品无码成人片一区二区98 | 国产电影无码午夜在线播放 | 国产成人精品一区二区在线小狼 | 亚洲精品美女久久久久久久 | 久久久www成人免费毛片 | 1000部啪啪未满十八勿入下载 | 国产成人人人97超碰超爽8 | 亚洲国产午夜精品理论片 | 国产亚洲欧美在线专区 | 亚洲欧洲中文日韩av乱码 | 精品国产成人一区二区三区 | 亚洲成av人影院在线观看 | 欧美野外疯狂做受xxxx高潮 | 无码国产色欲xxxxx视频 | 黑人玩弄人妻中文在线 | 最新国产乱人伦偷精品免费网站 | 亚洲小说春色综合另类 | 成年美女黄网站色大免费视频 | 婷婷丁香六月激情综合啪 | 日本又色又爽又黄的a片18禁 | 日本护士xxxxhd少妇 | 精品无码一区二区三区的天堂 | 日韩精品久久久肉伦网站 | www国产亚洲精品久久网站 | 在线 国产 欧美 亚洲 天堂 | aⅴ亚洲 日韩 色 图网站 播放 | 日本乱偷人妻中文字幕 | 一本久久伊人热热精品中文字幕 | 日韩视频 中文字幕 视频一区 | 国产在线无码精品电影网 | 东京热无码av男人的天堂 | 国産精品久久久久久久 | 亚洲爆乳大丰满无码专区 | 永久免费观看国产裸体美女 | 欧美日韩久久久精品a片 | 少妇性荡欲午夜性开放视频剧场 | 最近中文2019字幕第二页 | 人妻无码αv中文字幕久久琪琪布 | 久久久精品人妻久久影视 | 久久久久成人片免费观看蜜芽 | 一本大道伊人av久久综合 | 玩弄中年熟妇正在播放 | 亚洲色偷偷男人的天堂 | 欧美大屁股xxxxhd黑色 | 国产精品久免费的黄网站 | 99精品国产综合久久久久五月天 | 97se亚洲精品一区 | 日韩人妻系列无码专区 | 成年女人永久免费看片 | 久久99国产综合精品 | 国内综合精品午夜久久资源 | 麻豆精产国品 | 国产色精品久久人妻 | 国精产品一区二区三区 | 午夜福利一区二区三区在线观看 | 久久久久久久人妻无码中文字幕爆 | 18精品久久久无码午夜福利 | 国内少妇偷人精品视频 | 伊人色综合久久天天小片 | 国产精品久久久久久无码 | 色五月丁香五月综合五月 | 国产福利视频一区二区 | 无码av中文字幕免费放 | 精品久久久中文字幕人妻 | 亚洲国产精品毛片av不卡在线 | 骚片av蜜桃精品一区 | 牲欲强的熟妇农村老妇女视频 | 一本久道高清无码视频 | 国产精品人妻一区二区三区四 | www国产亚洲精品久久久日本 | 午夜嘿嘿嘿影院 | 人妻少妇精品无码专区动漫 | 色欲综合久久中文字幕网 | 人妻尝试又大又粗久久 | 国产成人一区二区三区别 | 欧美人与牲动交xxxx | 日韩 欧美 动漫 国产 制服 | 一个人看的视频www在线 | 国产亲子乱弄免费视频 | 国产sm调教视频在线观看 | 一本无码人妻在中文字幕免费 | 天天拍夜夜添久久精品大 | 中文字幕乱妇无码av在线 | 在线成人www免费观看视频 | 宝宝好涨水快流出来免费视频 | 精品久久久中文字幕人妻 | 久久精品一区二区三区四区 | 毛片内射-百度 | 中文字幕精品av一区二区五区 | 精品欧洲av无码一区二区三区 | 久久久成人毛片无码 | 国产成人无码av片在线观看不卡 | 久久精品人妻少妇一区二区三区 | 亚洲国产成人av在线观看 | 无码人妻精品一区二区三区下载 | 丝袜美腿亚洲一区二区 | 亚洲欧洲无卡二区视頻 | 久久人人爽人人爽人人片av高清 | 又大又黄又粗又爽的免费视频 | 精品 日韩 国产 欧美 视频 | 无码人妻少妇伦在线电影 | 欧美精品免费观看二区 | 亚洲另类伦春色综合小说 | 亚洲色欲色欲欲www在线 | 无码帝国www无码专区色综合 | 精品人妻人人做人人爽 | 西西人体www44rt大胆高清 | 丰满人妻精品国产99aⅴ | 国产麻豆精品精东影业av网站 | 午夜精品一区二区三区在线观看 | 日韩人妻系列无码专区 | 黑人大群体交免费视频 | 日韩在线不卡免费视频一区 | 一本久道久久综合狠狠爱 | 亚洲精品午夜无码电影网 | 在线观看国产一区二区三区 | 亚洲爆乳大丰满无码专区 | 国产人妖乱国产精品人妖 | 最近中文2019字幕第二页 | 欧美老人巨大xxxx做受 | 国产成人无码av片在线观看不卡 | 亚洲中文字幕乱码av波多ji | 青青青手机频在线观看 | 欧美高清在线精品一区 | 人人爽人人澡人人高潮 | 国产av无码专区亚洲a∨毛片 | 精品国产aⅴ无码一区二区 | 蜜桃视频插满18在线观看 | 水蜜桃av无码 | 日本一区二区三区免费高清 | 搡女人真爽免费视频大全 | 国产精品成人av在线观看 | 狠狠色欧美亚洲狠狠色www | 岛国片人妻三上悠亚 | 野外少妇愉情中文字幕 | 国产激情艳情在线看视频 | 牲欲强的熟妇农村老妇女 | 免费人成网站视频在线观看 | 九九久久精品国产免费看小说 | 麻豆果冻传媒2021精品传媒一区下载 | 国产精品无码久久av | 欧美日本精品一区二区三区 | 中文字幕 亚洲精品 第1页 | 国产成人精品三级麻豆 | 亚洲精品综合一区二区三区在线 | 国产精品视频免费播放 | 97久久超碰中文字幕 | 思思久久99热只有频精品66 | 双乳奶水饱满少妇呻吟 | 亚洲啪av永久无码精品放毛片 | 男女性色大片免费网站 | 熟妇人妻无码xxx视频 | 97夜夜澡人人爽人人喊中国片 | 两性色午夜视频免费播放 | 日韩在线不卡免费视频一区 | 成人欧美一区二区三区黑人免费 | 六十路熟妇乱子伦 | 久久精品人人做人人综合 | 免费网站看v片在线18禁无码 | 精品无码成人片一区二区98 | 人妻少妇精品视频专区 | 97夜夜澡人人双人人人喊 | 水蜜桃亚洲一二三四在线 | 欧美黑人巨大xxxxx | 300部国产真实乱 | 久久无码中文字幕免费影院蜜桃 | 性史性农村dvd毛片 | 亚洲日韩乱码中文无码蜜桃臀网站 | 久久精品女人天堂av免费观看 | 色婷婷香蕉在线一区二区 | 高潮毛片无遮挡高清免费视频 | 国产精品久久久av久久久 | 欧美人与禽猛交狂配 | 国产熟妇另类久久久久 | 2020最新国产自产精品 | 少妇被黑人到高潮喷出白浆 | 成人精品视频一区二区 | 精品夜夜澡人妻无码av蜜桃 | 亚洲精品久久久久久久久久久 | 在线 国产 欧美 亚洲 天堂 | 无码人妻少妇伦在线电影 | 亚洲爆乳大丰满无码专区 | 少妇性俱乐部纵欲狂欢电影 | 老熟妇乱子伦牲交视频 | 亚洲色偷偷偷综合网 | 国产成人无码区免费内射一片色欲 | 无码乱肉视频免费大全合集 | 亚洲午夜久久久影院 | 国产极品视觉盛宴 | 亚洲 日韩 欧美 成人 在线观看 | 狂野欧美性猛交免费视频 | 欧美熟妇另类久久久久久多毛 | 无人区乱码一区二区三区 | 最新国产乱人伦偷精品免费网站 | 亚洲大尺度无码无码专区 | 欧美 丝袜 自拍 制服 另类 | 亚洲精品美女久久久久久久 | 色一情一乱一伦一视频免费看 | 图片小说视频一区二区 | 午夜精品久久久内射近拍高清 | 欧美日韩人成综合在线播放 | 日日噜噜噜噜夜夜爽亚洲精品 | 亚洲精品午夜无码电影网 | 精品日本一区二区三区在线观看 | 亚洲成av人影院在线观看 | 玩弄中年熟妇正在播放 | 精品一区二区三区无码免费视频 | 国色天香社区在线视频 | 午夜不卡av免费 一本久久a久久精品vr综合 | 亚洲国产精华液网站w | 无码毛片视频一区二区本码 | 亚洲自偷自偷在线制服 | 四虎永久在线精品免费网址 | 久久久久久九九精品久 | 少妇高潮一区二区三区99 | 一本久久a久久精品亚洲 | 精品国偷自产在线 | 日本爽爽爽爽爽爽在线观看免 | 18禁黄网站男男禁片免费观看 | 亚洲精品久久久久中文第一幕 | 日日噜噜噜噜夜夜爽亚洲精品 | 久久精品99久久香蕉国产色戒 | 超碰97人人射妻 | 国产另类ts人妖一区二区 | 亚洲精品无码国产 | 午夜成人1000部免费视频 | 漂亮人妻洗澡被公强 日日躁 | 亚洲中文字幕乱码av波多ji | 在线观看国产午夜福利片 | 精品久久久无码人妻字幂 | 67194成是人免费无码 | 在线播放亚洲第一字幕 | 欧美三级不卡在线观看 | 欧美高清在线精品一区 | 一本无码人妻在中文字幕免费 | 大胆欧美熟妇xx | 丰满人妻被黑人猛烈进入 | 狠狠色噜噜狠狠狠狠7777米奇 | 久久zyz资源站无码中文动漫 | 爆乳一区二区三区无码 | 欧美兽交xxxx×视频 | 成在人线av无码免观看麻豆 | 欧美xxxx黑人又粗又长 | 亚洲精品一区二区三区大桥未久 | 欧美一区二区三区 | 大胆欧美熟妇xx | 日韩精品一区二区av在线 | 国产超碰人人爽人人做人人添 | 午夜丰满少妇性开放视频 | 久久精品人人做人人综合 | 日韩欧美成人免费观看 | 亚洲人成影院在线无码按摩店 | 亚洲综合无码一区二区三区 | 日本熟妇大屁股人妻 | 丰满护士巨好爽好大乳 | 国产在线精品一区二区高清不卡 | 97无码免费人妻超级碰碰夜夜 | 亚洲欧洲无卡二区视頻 | 中文字幕无码免费久久99 | 亚洲国产精品毛片av不卡在线 | 国产亚洲精品久久久ai换 | 国产香蕉97碰碰久久人人 | 一本无码人妻在中文字幕免费 | 日本又色又爽又黄的a片18禁 | 日韩精品a片一区二区三区妖精 | 国产人妻大战黑人第1集 | 国产va免费精品观看 | 国产黑色丝袜在线播放 | 国产人妻精品一区二区三区 | 在线看片无码永久免费视频 | 老熟妇仑乱视频一区二区 | 东北女人啪啪对白 | 久热国产vs视频在线观看 | 中文字幕乱妇无码av在线 | www成人国产高清内射 | 两性色午夜免费视频 | 亚洲综合另类小说色区 | 在线播放亚洲第一字幕 | 亚洲午夜无码久久 | aa片在线观看视频在线播放 | 野狼第一精品社区 | 色婷婷综合激情综在线播放 | 人妻无码久久精品人妻 | 色欲人妻aaaaaaa无码 | 久久精品中文闷骚内射 | 国产精品久久久久7777 | 玩弄人妻少妇500系列视频 | 99精品无人区乱码1区2区3区 | 国产av剧情md精品麻豆 | 亚洲国产精品一区二区美利坚 | 亚洲日韩乱码中文无码蜜桃臀网站 | 亚洲欧美精品伊人久久 | 国产无遮挡吃胸膜奶免费看 | 久久久成人毛片无码 | 97夜夜澡人人爽人人喊中国片 | 午夜精品久久久久久久 | 亚洲国产高清在线观看视频 | 欧美熟妇另类久久久久久多毛 | 俄罗斯老熟妇色xxxx | 久久久www成人免费毛片 | 亚洲成av人综合在线观看 | 人妻互换免费中文字幕 | 久久国产36精品色熟妇 | 国产真实乱对白精彩久久 | 7777奇米四色成人眼影 | 国产综合色产在线精品 | 国产三级久久久精品麻豆三级 | 日产精品高潮呻吟av久久 | 熟女俱乐部五十路六十路av | 国产精品亚洲综合色区韩国 | 久久国产精品精品国产色婷婷 | 日韩人妻系列无码专区 | 波多野结衣高清一区二区三区 | 国产亚洲视频中文字幕97精品 | 亚洲最大成人网站 | 欧美日韩一区二区综合 | 国产精品无码永久免费888 | 狠狠cao日日穞夜夜穞av | 西西人体www44rt大胆高清 | 亚洲成色在线综合网站 | 国产亚洲美女精品久久久2020 | 精品人妻人人做人人爽 | 男人扒开女人内裤强吻桶进去 | 丰满人妻被黑人猛烈进入 | 人人妻人人澡人人爽欧美一区 | 国产小呦泬泬99精品 | a在线亚洲男人的天堂 | 夫妻免费无码v看片 | 任你躁在线精品免费 | 国产成人无码区免费内射一片色欲 | 性啪啪chinese东北女人 | 十八禁视频网站在线观看 | 曰韩少妇内射免费播放 | 亚洲国产精品无码久久久久高潮 | 任你躁国产自任一区二区三区 | 国产成人无码av在线影院 | 日韩少妇内射免费播放 | 中国女人内谢69xxxxxa片 | 永久免费观看国产裸体美女 | 扒开双腿吃奶呻吟做受视频 | 亚洲精品欧美二区三区中文字幕 | 中文字幕人成乱码熟女app | 亚洲一区二区三区香蕉 | 伊人久久大香线蕉av一区二区 | 美女黄网站人色视频免费国产 | 国内精品人妻无码久久久影院蜜桃 | 又色又爽又黄的美女裸体网站 | 老熟妇乱子伦牲交视频 | 国产美女极度色诱视频www | 国产av久久久久精东av | 国产精品无码成人午夜电影 | 国产内射老熟女aaaa | 午夜不卡av免费 一本久久a久久精品vr综合 | 日日躁夜夜躁狠狠躁 | 人人妻人人澡人人爽欧美一区九九 | 日本熟妇人妻xxxxx人hd | 77777熟女视频在线观看 а天堂中文在线官网 | 国产福利视频一区二区 | 成人片黄网站色大片免费观看 | 国产乱人偷精品人妻a片 | 亚洲日韩一区二区三区 | 中文字幕色婷婷在线视频 | 一本久道高清无码视频 | 国内少妇偷人精品视频免费 | 日本熟妇乱子伦xxxx | 国产精品久久久久久无码 | 中文精品久久久久人妻不卡 | 波多野结衣av在线观看 | 男女超爽视频免费播放 | 一个人看的视频www在线 | 日韩成人一区二区三区在线观看 | 精品一区二区不卡无码av | 欧美熟妇另类久久久久久不卡 | 久久久国产一区二区三区 | 色婷婷欧美在线播放内射 | 丁香啪啪综合成人亚洲 | 黑人粗大猛烈进出高潮视频 | 日本熟妇人妻xxxxx人hd | 好爽又高潮了毛片免费下载 | 欧洲美熟女乱又伦 | 亚洲一区二区三区在线观看网站 | 国产亚av手机在线观看 | 好屌草这里只有精品 | 无码精品国产va在线观看dvd | 99久久99久久免费精品蜜桃 | 在线播放免费人成毛片乱码 | 午夜男女很黄的视频 | 激情五月综合色婷婷一区二区 | 精品久久久无码中文字幕 | 欧洲精品码一区二区三区免费看 | 老熟女重囗味hdxx69 | 55夜色66夜色国产精品视频 | 国产在线精品一区二区三区直播 | 国产人妻大战黑人第1集 | 久久久成人毛片无码 | 国精品人妻无码一区二区三区蜜柚 | 日日躁夜夜躁狠狠躁 | 国产人妻人伦精品1国产丝袜 | 兔费看少妇性l交大片免费 | 国产精品.xx视频.xxtv | 亚洲成a人片在线观看无码3d | 曰本女人与公拘交酡免费视频 | 国产av一区二区精品久久凹凸 | 无码av免费一区二区三区试看 | 久久99精品久久久久久 | 人人澡人人妻人人爽人人蜜桃 | 久久久久99精品成人片 | 人人妻人人澡人人爽精品欧美 | 红桃av一区二区三区在线无码av | 国产精品成人av在线观看 | 久久久久久九九精品久 | 日欧一片内射va在线影院 | 亚洲 高清 成人 动漫 | 夜先锋av资源网站 | 欧美人与禽zoz0性伦交 | 国产福利视频一区二区 | 成人性做爰aaa片免费看不忠 | 亚洲s码欧洲m码国产av | 少妇高潮一区二区三区99 | 久久视频在线观看精品 | 久久综合色之久久综合 | 欧美熟妇另类久久久久久不卡 | 日韩精品成人一区二区三区 | 正在播放东北夫妻内射 | 亚洲成av人片在线观看无码不卡 | 亚洲国产高清在线观看视频 | 2020最新国产自产精品 | 国产av一区二区三区最新精品 | 婷婷丁香六月激情综合啪 | 欧美精品免费观看二区 | 伊人久久婷婷五月综合97色 | 又色又爽又黄的美女裸体网站 | 日本精品久久久久中文字幕 | 天下第一社区视频www日本 | 日产国产精品亚洲系列 | 日产精品99久久久久久 | 亚洲国产av美女网站 | 午夜精品久久久久久久 | 无码人妻久久一区二区三区不卡 | 动漫av网站免费观看 | 国产激情综合五月久久 | 国产真实夫妇视频 | 欧美日本免费一区二区三区 | аⅴ资源天堂资源库在线 | 欧美性色19p | 久久综合色之久久综合 | 无码精品人妻一区二区三区av | 色一情一乱一伦 | 国产成人无码av片在线观看不卡 | 日韩少妇内射免费播放 | 国产免费观看黄av片 | 丰满岳乱妇在线观看中字无码 | 天干天干啦夜天干天2017 | 日本高清一区免费中文视频 | 无码毛片视频一区二区本码 | 国产97人人超碰caoprom | 亚洲日本va中文字幕 | 欧美老人巨大xxxx做受 | 国产一区二区三区四区五区加勒比 | 国产成人午夜福利在线播放 | 免费播放一区二区三区 | 国产热a欧美热a在线视频 | 亚洲国产成人av在线观看 | 欧美成人家庭影院 | 国产成人精品一区二区在线小狼 | 一本大道久久东京热无码av | 在线天堂新版最新版在线8 | 丰满岳乱妇在线观看中字无码 | 中文精品无码中文字幕无码专区 | 国产精品手机免费 | 99国产欧美久久久精品 | 精品国产精品久久一区免费式 | 亚洲日韩av一区二区三区四区 | 四虎永久在线精品免费网址 | 精品久久综合1区2区3区激情 | 青青青爽视频在线观看 | 在线播放无码字幕亚洲 | 日韩欧美成人免费观看 | 亚洲中文字幕无码一久久区 | 亚洲经典千人经典日产 | 国产人成高清在线视频99最全资源 | 乱人伦人妻中文字幕无码 | 秋霞成人午夜鲁丝一区二区三区 | 亚洲一区二区三区播放 | 精品无码国产自产拍在线观看蜜 | 无码人妻丰满熟妇区五十路百度 | 国产性生大片免费观看性 | 亚洲欧美国产精品专区久久 | 免费无码的av片在线观看 | 欧美三级不卡在线观看 | 麻豆蜜桃av蜜臀av色欲av | 99久久婷婷国产综合精品青草免费 | 99re在线播放 | 国产精品无码成人午夜电影 | 国产精品办公室沙发 | www国产亚洲精品久久久日本 | 久久99精品国产麻豆蜜芽 | 国产精品亚洲lv粉色 | 人妻少妇精品视频专区 | 亚洲自偷精品视频自拍 | 欧美第一黄网免费网站 | 动漫av一区二区在线观看 | 97se亚洲精品一区 | 天堂无码人妻精品一区二区三区 | 一区二区三区乱码在线 | 欧洲 | 国产人妻久久精品二区三区老狼 | 日韩亚洲欧美中文高清在线 | 国产特级毛片aaaaaaa高清 | 麻豆国产人妻欲求不满谁演的 | 人人妻人人澡人人爽欧美精品 | 激情内射亚州一区二区三区爱妻 | 日日麻批免费40分钟无码 | 亚洲成熟女人毛毛耸耸多 | 97精品国产97久久久久久免费 | 亚洲天堂2017无码 | 中国女人内谢69xxxxxa片 | 性生交片免费无码看人 | аⅴ资源天堂资源库在线 | 日本免费一区二区三区最新 | 亚洲精品一区二区三区大桥未久 | 色情久久久av熟女人妻网站 | 色婷婷香蕉在线一区二区 | 51国偷自产一区二区三区 | 成人无码视频免费播放 | 狠狠色噜噜狠狠狠7777奇米 | 人妻少妇被猛烈进入中文字幕 | 国产成人综合在线女婷五月99播放 | 丰满人妻一区二区三区免费视频 | 秋霞成人午夜鲁丝一区二区三区 | 无码乱肉视频免费大全合集 | 午夜精品久久久内射近拍高清 | 欧美兽交xxxx×视频 | 亚洲小说春色综合另类 | 高潮毛片无遮挡高清免费 | 亚洲精品美女久久久久久久 | 亚洲日韩乱码中文无码蜜桃臀网站 | 妺妺窝人体色www婷婷 | 欧美自拍另类欧美综合图片区 | 国产真实乱对白精彩久久 | 色欲av亚洲一区无码少妇 | 水蜜桃色314在线观看 | 亚洲欧美中文字幕5发布 | 日韩精品乱码av一区二区 | 欧美性黑人极品hd | 天天拍夜夜添久久精品 | 高潮毛片无遮挡高清免费视频 | 欧美三级a做爰在线观看 | 国产精品国产三级国产专播 | 动漫av网站免费观看 | 久久久久se色偷偷亚洲精品av | 中文久久乱码一区二区 | 日本va欧美va欧美va精品 | 日本va欧美va欧美va精品 | 在线精品国产一区二区三区 | 青春草在线视频免费观看 | 亚洲va欧美va天堂v国产综合 | 亚洲日本一区二区三区在线 | 亚洲成av人片天堂网无码】 | 中文字幕乱码亚洲无线三区 | 国产色xx群视频射精 | 国内少妇偷人精品视频 | 亚洲毛片av日韩av无码 | 国产精品无码一区二区三区不卡 | 亚洲精品午夜无码电影网 | 成人影院yy111111在线观看 | www一区二区www免费 | 初尝人妻少妇中文字幕 | 亚洲精品综合五月久久小说 | 欧美日韩一区二区免费视频 | 久久精品人人做人人综合 | 亚洲精品国偷拍自产在线麻豆 | 国产成人综合美国十次 | 日韩av无码一区二区三区 | 爽爽影院免费观看 | 高清无码午夜福利视频 | 国产美女极度色诱视频www | 少妇性荡欲午夜性开放视频剧场 | 麻豆国产人妻欲求不满 | 亚洲精品国产精品乱码不卡 | 久久人人97超碰a片精品 | 国产在线一区二区三区四区五区 | 午夜精品久久久久久久 | 精品少妇爆乳无码av无码专区 | 午夜时刻免费入口 | 午夜时刻免费入口 | 欧美性生交活xxxxxdddd | 国产性生大片免费观看性 | 狠狠噜狠狠狠狠丁香五月 | 成人精品一区二区三区中文字幕 | 久久久久久a亚洲欧洲av冫 | 久久久无码中文字幕久... | 国产真人无遮挡作爱免费视频 | aⅴ亚洲 日韩 色 图网站 播放 | 精品欧美一区二区三区久久久 | 性生交片免费无码看人 | 国产午夜亚洲精品不卡 | 国产成人无码一二三区视频 | 日本一卡二卡不卡视频查询 | 精品国产一区二区三区四区在线看 | 国产精品无码永久免费888 | 免费看少妇作爱视频 | 少妇性俱乐部纵欲狂欢电影 | 久久久久久久女国产乱让韩 | 亚洲欧美国产精品专区久久 | 亚洲国产成人av在线观看 | 亚洲综合在线一区二区三区 | 久久国内精品自在自线 | 日日橹狠狠爱欧美视频 | 六月丁香婷婷色狠狠久久 | 久久久婷婷五月亚洲97号色 | 日本免费一区二区三区最新 | 欧美精品在线观看 | 中文字幕无线码 | 日本精品久久久久中文字幕 | 亚洲狠狠色丁香婷婷综合 | 狠狠亚洲超碰狼人久久 | 久久精品国产一区二区三区 | 亚洲国产高清在线观看视频 | 风流少妇按摩来高潮 | 亚洲国产精品久久久天堂 | 国产偷自视频区视频 | 亚洲成av人在线观看网址 | 呦交小u女精品视频 | 亚洲爆乳无码专区 | 老太婆性杂交欧美肥老太 | 亚洲熟女一区二区三区 | 永久免费观看美女裸体的网站 | 午夜无码人妻av大片色欲 | 国内少妇偷人精品视频免费 | 乱人伦人妻中文字幕无码久久网 | 色一情一乱一伦一视频免费看 | 伊人久久大香线蕉午夜 | 国产亚洲精品久久久久久久久动漫 | 最近中文2019字幕第二页 | 乱中年女人伦av三区 | 妺妺窝人体色www婷婷 | 国产又爽又黄又刺激的视频 | 小鲜肉自慰网站xnxx | 中文字幕av无码一区二区三区电影 | 蜜桃av蜜臀av色欲av麻 999久久久国产精品消防器材 | 麻豆md0077饥渴少妇 | 欧美自拍另类欧美综合图片区 | 亚洲国产一区二区三区在线观看 | 精品无人国产偷自产在线 | 午夜福利一区二区三区在线观看 | 亚洲啪av永久无码精品放毛片 | 性欧美大战久久久久久久 | 国产成人人人97超碰超爽8 | 美女扒开屁股让男人桶 | 天堂а√在线中文在线 | 亚洲七七久久桃花影院 | 亚洲啪av永久无码精品放毛片 | 黄网在线观看免费网站 | 乱码av麻豆丝袜熟女系列 | 国产成人久久精品流白浆 | 精品乱子伦一区二区三区 | 大肉大捧一进一出视频出来呀 | 国产色xx群视频射精 | 亚洲一区二区三区在线观看网站 | 99久久精品日本一区二区免费 | 噜噜噜亚洲色成人网站 | 亚洲国产成人a精品不卡在线 | 丰满少妇熟乱xxxxx视频 | 亚洲热妇无码av在线播放 | 在线欧美精品一区二区三区 | 久久午夜无码鲁丝片 | 国产成人精品久久亚洲高清不卡 | 亚洲成av人片天堂网无码】 | 国内精品久久久久久中文字幕 | 日本一卡2卡3卡四卡精品网站 | 日韩人妻少妇一区二区三区 | 婷婷丁香五月天综合东京热 | 少妇一晚三次一区二区三区 | www成人国产高清内射 | 人人爽人人澡人人人妻 | 久久久国产一区二区三区 | av在线亚洲欧洲日产一区二区 | 亚洲色在线无码国产精品不卡 | 国产精品自产拍在线观看 | 亚洲s色大片在线观看 | 国产黑色丝袜在线播放 | 久激情内射婷内射蜜桃人妖 | 国产成人久久精品流白浆 | 18黄暴禁片在线观看 | 18黄暴禁片在线观看 | 国产午夜福利亚洲第一 | 亚洲一区av无码专区在线观看 | 亚洲国产精品久久久久久 | 国产国语老龄妇女a片 | 午夜肉伦伦影院 | 人妻尝试又大又粗久久 | 97久久国产亚洲精品超碰热 | 人妻互换免费中文字幕 | 两性色午夜免费视频 | 精品偷拍一区二区三区在线看 | 免费中文字幕日韩欧美 | 国产区女主播在线观看 | 色一情一乱一伦一区二区三欧美 | 欧美日韩亚洲国产精品 | 天天躁夜夜躁狠狠是什么心态 | 亚洲人成影院在线无码按摩店 | 一本一道久久综合久久 | 少妇性俱乐部纵欲狂欢电影 | 日韩人妻无码中文字幕视频 | 女人被男人爽到呻吟的视频 | 波多野结衣乳巨码无在线观看 | 色婷婷欧美在线播放内射 | 国产成人精品一区二区在线小狼 | 全黄性性激高免费视频 | 中文字幕av日韩精品一区二区 | 欧美放荡的少妇 | 成人免费视频视频在线观看 免费 | 国产成人无码一二三区视频 | 成在人线av无码免费 | 无码人妻丰满熟妇区五十路百度 | 久久天天躁夜夜躁狠狠 | 久久综合给合久久狠狠狠97色 | 亚洲日韩av片在线观看 | 久久99国产综合精品 | 99riav国产精品视频 | 天堂一区人妻无码 | 无码成人精品区在线观看 | 国产国语老龄妇女a片 | 亚洲色欲久久久综合网东京热 | 两性色午夜免费视频 | 99精品国产综合久久久久五月天 | 天干天干啦夜天干天2017 | 欧美精品国产综合久久 | 2020久久超碰国产精品最新 | 国产婷婷色一区二区三区在线 | 色窝窝无码一区二区三区色欲 | 精品久久久无码人妻字幂 | 久久久国产一区二区三区 | 曰韩无码二三区中文字幕 | 亚拍精品一区二区三区探花 | 人人妻人人澡人人爽精品欧美 | 97久久国产亚洲精品超碰热 | 麻豆果冻传媒2021精品传媒一区下载 | 人人妻人人澡人人爽人人精品浪潮 | 在线精品国产一区二区三区 | 日韩无码专区 | 永久免费精品精品永久-夜色 | 成熟人妻av无码专区 | 九九在线中文字幕无码 | 成人片黄网站色大片免费观看 | 中国女人内谢69xxxx | 亲嘴扒胸摸屁股激烈网站 | 日日碰狠狠丁香久燥 | 日产精品99久久久久久 | 色欲久久久天天天综合网精品 | 日韩人妻系列无码专区 | 久久人人爽人人人人片 | 又大又硬又黄的免费视频 | 亚洲精品中文字幕久久久久 | 国精产品一品二品国精品69xx | 夜夜躁日日躁狠狠久久av | 国产午夜精品一区二区三区嫩草 | 国产精品va在线观看无码 | 亚洲精品午夜国产va久久成人 | 乱人伦人妻中文字幕无码 | 国产婷婷色一区二区三区在线 | 国内老熟妇对白xxxxhd | 日本精品高清一区二区 | 国产精品igao视频网 | 日产精品高潮呻吟av久久 | 在线 国产 欧美 亚洲 天堂 | 亚洲中文无码av永久不收费 | 亚洲色在线无码国产精品不卡 | 伊人久久大香线蕉午夜 | 51国偷自产一区二区三区 | 亚洲啪av永久无码精品放毛片 | 国产sm调教视频在线观看 | 成人免费视频一区二区 | 亚洲日韩一区二区 | 狠狠cao日日穞夜夜穞av | 乱人伦人妻中文字幕无码 | 熟女少妇在线视频播放 | 久久久婷婷五月亚洲97号色 | 丝袜人妻一区二区三区 | 国内精品人妻无码久久久影院蜜桃 | 亚洲va欧美va天堂v国产综合 | 性生交大片免费看l | 亚洲乱码中文字幕在线 | 亚洲人成网站在线播放942 | 人妻少妇被猛烈进入中文字幕 | 久久99精品国产.久久久久 | 国产超级va在线观看视频 | 欧美性猛交内射兽交老熟妇 | 国产手机在线αⅴ片无码观看 | 撕开奶罩揉吮奶头视频 | 丰满人妻一区二区三区免费视频 | 欧美丰满熟妇xxxx性ppx人交 | 亚洲精品无码人妻无码 | 荫蒂添的好舒服视频囗交 | 中文精品久久久久人妻不卡 | 久久精品中文字幕大胸 | 欧美成人家庭影院 | 成人免费无码大片a毛片 | 亚洲乱码中文字幕在线 | 妺妺窝人体色www在线小说 | 亚洲日本在线电影 | 真人与拘做受免费视频一 | 性欧美牲交xxxxx视频 | 亚洲午夜福利在线观看 | 久久久久久久久888 | 国产乱人伦偷精品视频 | 日韩欧美中文字幕在线三区 | 亚洲日韩av一区二区三区四区 | 中文字幕无码日韩欧毛 | www成人国产高清内射 | 亚洲日韩av片在线观看 | 亚洲中文字幕无码中字 | 成人女人看片免费视频放人 | 国产性生大片免费观看性 | 67194成是人免费无码 | 日日鲁鲁鲁夜夜爽爽狠狠 | 亚洲 欧美 激情 小说 另类 | 成 人影片 免费观看 | 精品国产aⅴ无码一区二区 | 亚洲大尺度无码无码专区 | 亚洲性无码av中文字幕 | 性色av无码免费一区二区三区 | 小泽玛莉亚一区二区视频在线 | 国产亚av手机在线观看 | 久久精品一区二区三区四区 | 97无码免费人妻超级碰碰夜夜 | 日韩精品成人一区二区三区 | 国产农村妇女高潮大叫 | 久久国语露脸国产精品电影 | 精品一区二区不卡无码av | 国产内射爽爽大片视频社区在线 | 欧美激情内射喷水高潮 | 久久久久99精品国产片 | 国产又爽又猛又粗的视频a片 | 久久无码中文字幕免费影院蜜桃 | 国产成人精品一区二区在线小狼 | 国内精品人妻无码久久久影院 | 亚洲国产日韩a在线播放 | 亚洲性无码av中文字幕 | 亚洲日本va中文字幕 | 国产网红无码精品视频 | 亚洲区欧美区综合区自拍区 | 亚洲一区二区三区播放 | 99久久婷婷国产综合精品青草免费 | 1000部啪啪未满十八勿入下载 | 久久婷婷五月综合色国产香蕉 | 窝窝午夜理论片影院 | 欧美精品免费观看二区 | 99久久精品国产一区二区蜜芽 | 国产激情综合五月久久 | 久久久久久久人妻无码中文字幕爆 | 日日摸夜夜摸狠狠摸婷婷 | 国产精品香蕉在线观看 | 亚洲大尺度无码无码专区 | 奇米综合四色77777久久 东京无码熟妇人妻av在线网址 | 久久久精品欧美一区二区免费 | 亚洲春色在线视频 | 两性色午夜免费视频 | 国产激情无码一区二区 | 亚洲大尺度无码无码专区 | 四虎永久在线精品免费网址 | 日韩亚洲欧美精品综合 | 国产精品国产自线拍免费软件 | 久久久www成人免费毛片 | 风流少妇按摩来高潮 | 久久久久av无码免费网 | 日本乱偷人妻中文字幕 | 婷婷综合久久中文字幕蜜桃三电影 | 无码午夜成人1000部免费视频 | 亚洲精品www久久久 | 欧美成人家庭影院 | 亚洲成色在线综合网站 | 无码人妻av免费一区二区三区 | 99精品国产综合久久久久五月天 | 亚洲一区av无码专区在线观看 | 国产精品亚洲综合色区韩国 | 欧美色就是色 | 日日摸夜夜摸狠狠摸婷婷 | 国产精品二区一区二区aⅴ污介绍 | 国产精品对白交换视频 | 性欧美牲交xxxxx视频 | 青春草在线视频免费观看 | 性欧美牲交xxxxx视频 | 强辱丰满人妻hd中文字幕 | 动漫av一区二区在线观看 | 精品偷拍一区二区三区在线看 | 成人女人看片免费视频放人 | 久久久久久av无码免费看大片 | 亚洲熟妇色xxxxx欧美老妇y | 午夜肉伦伦影院 | 日本www一道久久久免费榴莲 | 成人欧美一区二区三区 | 免费观看激色视频网站 | 成人影院yy111111在线观看 | 2020久久香蕉国产线看观看 | 国内精品久久久久久中文字幕 | 最近中文2019字幕第二页 | 日韩精品乱码av一区二区 | 国产精品高潮呻吟av久久4虎 | 又粗又大又硬毛片免费看 | 天天躁夜夜躁狠狠是什么心态 | 蜜臀av无码人妻精品 | 日日噜噜噜噜夜夜爽亚洲精品 | 一二三四社区在线中文视频 | 真人与拘做受免费视频 | 国产区女主播在线观看 | 樱花草在线播放免费中文 | 久久久久人妻一区精品色欧美 | 99riav国产精品视频 | 日韩少妇白浆无码系列 | 大色综合色综合网站 | 妺妺窝人体色www在线小说 | 久久精品人人做人人综合 | 澳门永久av免费网站 | 熟妇人妻无乱码中文字幕 | 老子影院午夜伦不卡 | 97夜夜澡人人双人人人喊 | 中文字幕乱妇无码av在线 | 欧美日韩综合一区二区三区 | 国产情侣作爱视频免费观看 | 精品久久久久久亚洲精品 | 蜜臀av无码人妻精品 | 超碰97人人做人人爱少妇 | 国内少妇偷人精品视频免费 | 大屁股大乳丰满人妻 | 亚洲欧美色中文字幕在线 | 性欧美videos高清精品 | a在线观看免费网站大全 | 丰满人妻翻云覆雨呻吟视频 | 亚洲熟女一区二区三区 | 一个人免费观看的www视频 | 日本一区二区三区免费高清 | 日本精品少妇一区二区三区 | 国产深夜福利视频在线 | 麻豆国产人妻欲求不满谁演的 | 7777奇米四色成人眼影 | 无码人妻出轨黑人中文字幕 | 中文字幕无码视频专区 | 亚洲精品一区三区三区在线观看 | 国产特级毛片aaaaaa高潮流水 | 精品久久久久久人妻无码中文字幕 | 国产成人综合在线女婷五月99播放 | 成在人线av无码免观看麻豆 | 久久久久久av无码免费看大片 | 熟妇激情内射com | 日韩成人一区二区三区在线观看 | 精品欧洲av无码一区二区三区 | 国产又爽又猛又粗的视频a片 | 国产无遮挡吃胸膜奶免费看 | 亚洲毛片av日韩av无码 | 亚洲精品一区二区三区婷婷月 | 成人免费视频视频在线观看 免费 | 中文字幕无线码 | 伊人久久大香线蕉亚洲 | 亚洲国产成人a精品不卡在线 | 一本久道久久综合狠狠爱 | 日韩人妻无码中文字幕视频 | 国内揄拍国内精品少妇国语 | 图片小说视频一区二区 | www国产亚洲精品久久久日本 | 精品少妇爆乳无码av无码专区 | 性色欲网站人妻丰满中文久久不卡 | 天堂在线观看www | 国产精品久久久午夜夜伦鲁鲁 | 成年美女黄网站色大免费全看 | 国产亚洲精品久久久久久 | 久久久婷婷五月亚洲97号色 | 99久久精品午夜一区二区 | 国产精品内射视频免费 | 精品国精品国产自在久国产87 | 国产亚洲精品久久久久久久 | 欧美日韩视频无码一区二区三 | 大肉大捧一进一出视频出来呀 | 国产精品鲁鲁鲁 | 欧美成人高清在线播放 | 日本一卡2卡3卡4卡无卡免费网站 国产一区二区三区影院 | 久久zyz资源站无码中文动漫 | a国产一区二区免费入口 | 日产精品99久久久久久 | 亚洲日韩av一区二区三区中文 | 亚洲啪av永久无码精品放毛片 | 久久亚洲精品成人无码 | 熟女体下毛毛黑森林 | 内射后入在线观看一区 | 国产凸凹视频一区二区 | 亚洲人亚洲人成电影网站色 | 中文字幕人妻丝袜二区 | 水蜜桃av无码 | 人妻少妇精品无码专区动漫 | 中文字幕av无码一区二区三区电影 | 中文字幕乱码人妻二区三区 | 色偷偷人人澡人人爽人人模 | 亚洲国产欧美日韩精品一区二区三区 | 精品无码av一区二区三区 | 扒开双腿疯狂进出爽爽爽视频 | 国产人妻人伦精品1国产丝袜 | 日韩亚洲欧美中文高清在线 | 欧美色就是色 | 精品一区二区三区无码免费视频 | 伊在人天堂亚洲香蕉精品区 | 伊人久久大香线蕉午夜 | 亚洲色www成人永久网址 | 国产一区二区三区日韩精品 | 免费中文字幕日韩欧美 | 99精品视频在线观看免费 | 久精品国产欧美亚洲色aⅴ大片 | 成人免费无码大片a毛片 | 久久综合九色综合97网 | 国内精品人妻无码久久久影院 | 99久久无码一区人妻 | 亚洲爆乳精品无码一区二区三区 | 人妻插b视频一区二区三区 | 国产精华av午夜在线观看 | 丝袜人妻一区二区三区 | 亚洲aⅴ无码成人网站国产app | а天堂中文在线官网 | 亚洲自偷精品视频自拍 | 亚洲国产精品无码久久久久高潮 | 麻豆成人精品国产免费 | 人人超人人超碰超国产 | 久久这里只有精品视频9 | 国产乡下妇女做爰 | 青青青手机频在线观看 | 久久精品成人欧美大片 | 色一情一乱一伦一区二区三欧美 | 精品久久久久久人妻无码中文字幕 | 免费无码一区二区三区蜜桃大 | 国产精品无码一区二区桃花视频 | 无码纯肉视频在线观看 | 亚洲小说春色综合另类 | 久久精品国产99精品亚洲 | 午夜福利不卡在线视频 | 国产亚洲人成在线播放 | 88国产精品欧美一区二区三区 | 亚洲欧洲无卡二区视頻 | 亚洲精品午夜国产va久久成人 | 色综合久久久无码中文字幕 | 扒开双腿吃奶呻吟做受视频 | 久久亚洲中文字幕无码 | 久久久久久久女国产乱让韩 | 99久久久国产精品无码免费 | 玩弄少妇高潮ⅹxxxyw | 青青久在线视频免费观看 | 内射后入在线观看一区 | 亚洲欧美精品伊人久久 | 综合激情五月综合激情五月激情1 | 人妻体内射精一区二区三四 | 极品嫩模高潮叫床 | 欧美日韩一区二区免费视频 | 国产精品第一区揄拍无码 | 国产人妻精品一区二区三区 | 欧洲熟妇精品视频 | 亚洲呦女专区 | 亚洲中文字幕va福利 | 欧美自拍另类欧美综合图片区 | 国产精品久久久久久久影院 | 国产色视频一区二区三区 | 国内丰满熟女出轨videos | 乌克兰少妇性做爰 | 久久精品99久久香蕉国产色戒 | 国产超级va在线观看视频 | 东京热无码av男人的天堂 | 俄罗斯老熟妇色xxxx | 亚洲 a v无 码免 费 成 人 a v | 99riav国产精品视频 | 女人被男人爽到呻吟的视频 | 青青草原综合久久大伊人精品 | 一本大道伊人av久久综合 | 永久免费精品精品永久-夜色 | 亚洲精品国产第一综合99久久 | 一本久道久久综合婷婷五月 | 无遮挡啪啪摇乳动态图 | 国产亚洲tv在线观看 | 青青久在线视频免费观看 | 亚洲色欲久久久综合网东京热 | 国产熟女一区二区三区四区五区 | 日本精品人妻无码免费大全 | 大屁股大乳丰满人妻 | 精品人妻人人做人人爽 | 免费无码肉片在线观看 | 亚洲第一无码av无码专区 | 国产在线aaa片一区二区99 | 日韩精品a片一区二区三区妖精 | 国产精品久久福利网站 | 日本精品少妇一区二区三区 | 国产精品人人爽人人做我的可爱 | 亚洲小说图区综合在线 | 中文无码精品a∨在线观看不卡 | 成人片黄网站色大片免费观看 | 国产精品办公室沙发 | 亚洲娇小与黑人巨大交 | 亚洲日韩av片在线观看 | 日日碰狠狠躁久久躁蜜桃 | 熟妇人妻无乱码中文字幕 | 狠狠cao日日穞夜夜穞av | 精品厕所偷拍各类美女tp嘘嘘 | 国产高清av在线播放 | 在线播放无码字幕亚洲 | 国产肉丝袜在线观看 | 成人免费视频视频在线观看 免费 | 高清不卡一区二区三区 | 妺妺窝人体色www在线小说 | 日本熟妇大屁股人妻 | 亚洲 高清 成人 动漫 | 国产疯狂伦交大片 | 日本免费一区二区三区最新 | 巨爆乳无码视频在线观看 | 国产猛烈高潮尖叫视频免费 | 内射爽无广熟女亚洲 | 人妻无码αv中文字幕久久琪琪布 | 国产精品多人p群无码 | 国产精品久久国产精品99 | 国产综合在线观看 | 丰满少妇人妻久久久久久 | 国精产品一品二品国精品69xx | 扒开双腿吃奶呻吟做受视频 | 内射后入在线观看一区 | 1000部啪啪未满十八勿入下载 | 学生妹亚洲一区二区 | 亚洲一区二区三区在线观看网站 | 丰满岳乱妇在线观看中字无码 | 国产亚洲人成在线播放 | 国产特级毛片aaaaaaa高清 | 亚洲小说图区综合在线 | 一本色道久久综合狠狠躁 | 人人妻人人澡人人爽人人精品浪潮 | 欧美性猛交内射兽交老熟妇 | 精品久久久久香蕉网 | 特大黑人娇小亚洲女 | 无码人妻少妇伦在线电影 | 丝袜美腿亚洲一区二区 | 亚洲男人av香蕉爽爽爽爽 | 人妻无码久久精品人妻 | 国产精品久久精品三级 | 蜜桃av抽搐高潮一区二区 | 日韩在线不卡免费视频一区 | 欧美肥老太牲交大战 | 丰满护士巨好爽好大乳 | 国内精品人妻无码久久久影院 | 午夜精品一区二区三区在线观看 | 无码人妻久久一区二区三区不卡 | 伊人久久大香线焦av综合影院 | 99久久精品无码一区二区毛片 | 九九热爱视频精品 | 狠狠色欧美亚洲狠狠色www | 未满成年国产在线观看 | 国产午夜亚洲精品不卡 | 久久精品国产日本波多野结衣 | 亚洲乱码国产乱码精品精 | 荫蒂被男人添的好舒服爽免费视频 | 对白脏话肉麻粗话av | 国产办公室秘书无码精品99 | 久久zyz资源站无码中文动漫 | 欧美怡红院免费全部视频 | 国产网红无码精品视频 | 又大又硬又爽免费视频 | 久久人人爽人人人人片 | 日本丰满熟妇videos | 18精品久久久无码午夜福利 | 亚洲第一无码av无码专区 | 久久精品中文闷骚内射 | 午夜精品一区二区三区在线观看 | 蜜臀av无码人妻精品 | 网友自拍区视频精品 | 精品一区二区三区波多野结衣 | 国产av剧情md精品麻豆 | 国产精品久久久久7777 | 国产偷自视频区视频 | 亚洲高清偷拍一区二区三区 | 国产激情无码一区二区 | 无码毛片视频一区二区本码 | 乱人伦人妻中文字幕无码 | 亚洲国产av精品一区二区蜜芽 | 日韩欧美群交p片內射中文 | 天天躁日日躁狠狠躁免费麻豆 | 一本加勒比波多野结衣 | 妺妺窝人体色www在线小说 | 一本久久伊人热热精品中文字幕 | 亚洲中文字幕无码中文字在线 | 久久亚洲国产成人精品性色 | 久久人人爽人人爽人人片av高清 | 国产成人综合色在线观看网站 | 久久综合久久自在自线精品自 | 欧美xxxxx精品 | 国产一精品一av一免费 | 国产午夜亚洲精品不卡 | 狠狠色丁香久久婷婷综合五月 | 亚洲国产精品无码久久久久高潮 | 无码一区二区三区在线 | 欧美喷潮久久久xxxxx | 亚洲午夜福利在线观看 | 人妻互换免费中文字幕 | 中文字幕av日韩精品一区二区 | 中文字幕日韩精品一区二区三区 | 色综合久久久无码中文字幕 | 亚无码乱人伦一区二区 | 日日摸日日碰夜夜爽av | 亚洲中文字幕在线无码一区二区 | 中文字幕久久久久人妻 | 欧美人妻一区二区三区 | 久久精品人妻少妇一区二区三区 | 丰满人妻翻云覆雨呻吟视频 | 国产香蕉尹人综合在线观看 | 久久久久人妻一区精品色欧美 | 国产麻豆精品精东影业av网站 | 好屌草这里只有精品 | 亚洲の无码国产の无码影院 | 一区二区三区高清视频一 | 亚洲成av人综合在线观看 | 精品无码一区二区三区爱欲 | 强开小婷嫩苞又嫩又紧视频 | 又色又爽又黄的美女裸体网站 | 欧美野外疯狂做受xxxx高潮 | 亚洲精品国产a久久久久久 | 鲁鲁鲁爽爽爽在线视频观看 | 亚洲の无码国产の无码影院 | 亚洲熟妇自偷自拍另类 | a片在线免费观看 | 欧美国产日产一区二区 | 帮老师解开蕾丝奶罩吸乳网站 | 日韩av无码中文无码电影 | 99久久精品日本一区二区免费 | 六十路熟妇乱子伦 | 亚洲自偷自偷在线制服 | 国产精品福利视频导航 | 亚洲无人区一区二区三区 | 亚洲s码欧洲m码国产av | 乌克兰少妇xxxx做受 | 少妇久久久久久人妻无码 | 麻豆国产97在线 | 欧洲 | 天天做天天爱天天爽综合网 | 国产成人无码区免费内射一片色欲 | 亚洲国产精品无码久久久久高潮 | 国产精品久久久久久亚洲毛片 | 97久久超碰中文字幕 | 免费播放一区二区三区 | 俺去俺来也www色官网 | 久久久久国色av免费观看性色 | 国产成人综合美国十次 | 亚洲国产欧美在线成人 | 欧美亚洲日韩国产人成在线播放 | 红桃av一区二区三区在线无码av | 性史性农村dvd毛片 | 在线成人www免费观看视频 | 牲欲强的熟妇农村老妇女 | 超碰97人人射妻 | 欧美精品无码一区二区三区 | 性生交片免费无码看人 | 国产精品美女久久久久av爽李琼 | 99久久亚洲精品无码毛片 | 一本大道伊人av久久综合 | а天堂中文在线官网 | 美女极度色诱视频国产 | 国产乱人无码伦av在线a | 亚洲日韩av一区二区三区中文 | 嫩b人妻精品一区二区三区 | 欧美野外疯狂做受xxxx高潮 | 欧美老妇交乱视频在线观看 | 国产精品99久久精品爆乳 | 少妇被黑人到高潮喷出白浆 | 好爽又高潮了毛片免费下载 | 少妇被粗大的猛进出69影院 | 色妞www精品免费视频 | 中文字幕乱码人妻二区三区 | 国产精品高潮呻吟av久久4虎 | 国产69精品久久久久app下载 | а√资源新版在线天堂 | 男女猛烈xx00免费视频试看 | 乌克兰少妇xxxx做受 | 男人和女人高潮免费网站 | 初尝人妻少妇中文字幕 | 亚洲精品国偷拍自产在线观看蜜桃 | 久久人人爽人人爽人人片ⅴ | 色综合久久久无码中文字幕 | 2020最新国产自产精品 | 国产熟妇高潮叫床视频播放 | 熟妇人妻无码xxx视频 | 亚洲乱码中文字幕在线 | 久久亚洲国产成人精品性色 | 国产麻豆精品一区二区三区v视界 | 一本久久a久久精品vr综合 | 日韩欧美群交p片內射中文 | 欧美一区二区三区视频在线观看 | 俺去俺来也www色官网 | 亚洲国产av精品一区二区蜜芽 | 亚洲精品一区二区三区在线 | 欧美熟妇另类久久久久久不卡 | 精品亚洲成av人在线观看 | 亚洲成av人片天堂网无码】 | 亚洲综合伊人久久大杳蕉 | 久久综合给合久久狠狠狠97色 | 亚洲综合伊人久久大杳蕉 | 又色又爽又黄的美女裸体网站 | 乌克兰少妇xxxx做受 | 欧美日韩人成综合在线播放 | 红桃av一区二区三区在线无码av | 无码精品人妻一区二区三区av | 人人妻人人藻人人爽欧美一区 | 欧美三级不卡在线观看 | 国产农村妇女高潮大叫 | 无码精品国产va在线观看dvd | 国产成人精品无码播放 | 永久黄网站色视频免费直播 | 国模大胆一区二区三区 | 精品亚洲韩国一区二区三区 | 无码人妻精品一区二区三区不卡 | 国产亚洲精品精品国产亚洲综合 | 人人超人人超碰超国产 | 在线 国产 欧美 亚洲 天堂 | 亚洲成av人片在线观看无码不卡 | 色婷婷久久一区二区三区麻豆 | 男人和女人高潮免费网站 | 国产精品无码久久av | 日韩av无码一区二区三区 | 无码任你躁久久久久久久 | 巨爆乳无码视频在线观看 | 国产福利视频一区二区 | 1000部啪啪未满十八勿入下载 | 性欧美牲交xxxxx视频 | 图片区 小说区 区 亚洲五月 | 中文字幕人妻无码一区二区三区 | 伦伦影院午夜理论片 | 国产亚洲精品久久久久久 | 精品久久久无码中文字幕 | 国产三级精品三级男人的天堂 |