《編程思想之消息機制》一文中我們講了消息的相關概念和消息機制的模擬,本文將進一步聊聊C++中的消息機制。
在講之前,我們先看一個簡單例子:創建一個窗口和兩個按鈕,用來控制窗口的背景顏色。其效果如下:
Win32Test.h
#pragma once
#include
#include
#include
//資源ID
#define ID_BUTTON_DRAW 1000
#define ID_BUTTON_SWEEP 1001
// 注冊窗口類
ATOM AppRegisterClass(HINSTANCE hInstance);
// 初始化窗口
BOOL InitInstance(HINSTANCE, int);
// 消息處理函數(又叫窗口過程)
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
// (白色背景)按鈕事件
void OnButtonWhite();
// (灰色背景)按鈕事件
void OnButtonGray();
// 繪制事件
void OnDraw(HDC hdc);
Win32Test.cpp
#include stdafx.h
#include Win32Test.h
//字符數組長度
#define MAX_LOADSTRING 100
//全局變量
HINSTANCE hInst; // 當前實例
TCHAR g_szTitle[MAX_LOADSTRING] = TEXT(Message process); // 窗口標題
TCHAR g_szWindowClass[MAX_LOADSTRING] = TEXT(AppTest); // 窗口類的名稱
HWND g_hWnd; // 窗口句柄
bool g_bWhite = false; // 是否為白色背景
//WinMain入口函數
int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// 注冊窗口類
if(!AppRegisterClass(hInstance))
{
return (FALSE);
}
// 初始化應用程序窗口
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
// 消息循環
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int) msg.wParam;
}
// 注冊窗口類
ATOM AppRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = g_szWindowClass;
wcex.hIconSm = NULL;
return RegisterClassEx(&wcex);
}
// 保存實例化句柄並創建主窗口
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
hInst = hInstance; // 保存handle到全局變量
g_hWnd = CreateWindow(g_szWindowClass, g_szTitle, WS_OVERLAPPEDWINDOW, 0, 0, 400, 300, NULL, NULL, hInstance, NULL);
// 創建按鈕
HWND hBtWhite = CreateWindowEx(0, LButton, L白色, WS_CHILD | WS_VISIBLE | BS_TEXT, 100, 100, 50, 20, g_hWnd, (HMENU)ID_BUTTON_DRAW, hInst, NULL);
HWND hBtGray = CreateWindowEx(0, LButton, L灰色, WS_CHILD | WS_VISIBLE | BS_CENTER, 250, 100, 50, 20, g_hWnd, (HMENU)ID_BUTTON_SWEEP, hInst, NULL);
if (!g_hWnd)
{
return FALSE;
}
ShowWindow(g_hWnd, nCmdShow);
UpdateWindow(g_hWnd);
return TRUE;
}
// (窗口)消息處理
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
switch (message)
{
case WM_COMMAND:
wmId = LOWORD(wParam);
//wmEvent = HIWORD(wParam);
switch (wmId)
{
case ID_BUTTON_DRAW:
OnButtonWhite();
break;
case ID_BUTTON_SWEEP:
OnButtonGray();
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
OnDraw(hdc);
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
//事件處理
//按下hBtWhite時的事件
void OnButtonWhite()
{
g_bWhite = true;
InvalidateRect(g_hWnd, NULL, FALSE); //刷新窗口
}
//按下hBtGray時的事件
void OnButtonGray()
{
g_bWhite = false;
InvalidateRect(g_hWnd, NULL, FALSE); //刷新窗口
}
//繪制事件(每次刷新時重新繪制圖像)
void OnDraw(HDC hdc)
{
POINT oldPoint;
SetViewportOrgEx(hdc, 0, 0, &oldPoint);
RECT rcView;
GetWindowRect(g_hWnd, &rcView); // 獲得句柄的畫布大小
HBRUSH hbrWhite = (HBRUSH)GetStockObject(WHITE_BRUSH);
HBRUSH hbrGray = (HBRUSH)GetStockObject(GRAY_BRUSH);
if (g_bWhite)
{
FillRect(hdc, &rcView, hbrWhite);
} else
{
FillRect(hdc, &rcView, hbrGray);
}
SetViewportOrgEx(hdc, oldPoint.x, oldPoint.y, NULL);
}
在上面這個例子中,消息的流經過程如下:
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
這個while循環就是消息循環,不斷地從消息隊列中獲取消息,並通過DispatchMessage(&msg)將消息分發出去。消息隊列是在Windows操作系統中定義的(我們無法看到對應定義的代碼),對於每一個正在執行的Windows應用程序,系統為其建立一個“消息隊列”,即應用程序隊列,用來存放該程序可能創建的各種窗口的消息。DispatchMessage會將消息傳給窗口函數(即消息處理函數)去處理,也就是WndProc函數。WndProc是一個回調函數,在注冊窗口時通過wcex.lpfnWndProc將其傳給了操作系統,所以DispatchMessage分發消息後,操作系統會調用窗口函數(WndProc)去處理消息。關於回調函數可參考:回調函數。
每一個窗口都應該有一個函數負責消息處理,程序員必須負責設計這個所謂的窗口函數WndProc。LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
中的四個參數就是消息的相關信息(消息來自的句柄、消息類型等),函數中通過switch/case根據不同的消息類型分別進行不同的處理。在收到相應類型的消息之後,可調用相應的函數去處理,如OnButtonWhite、OnButtonGray、OnDraw,這就是事件處理的雛形。 在default中調用了DefWindowProc,DefWindowProc是操作系統定義的默認消息處理函數,這是因為所有的消息都必須被處理,應用程序不處理的消息需要交給操作系統處理。
Windows消息都以WM_為前綴,意思是”Windows Message”,如WM_CREATE、WM_PAINT等。消息的定義如下:
typedef struct tagMsg
{
HWND hwnd; //接受該消息的窗口句柄
UINT message; //消息常量標識符,也就是我們通常所說的消息號
WPARAM wParam; //32位消息的特定附加信息,確切含義依賴於消息值
LPARAM lParam; //32位消息的特定附加信息,確切含義依賴於消息值
DWORD time; //消息創建時的時間
POINT pt; //消息創建時的鼠標/光標在屏幕坐標系中的位置
}MSG;
消息主要有三種類型:
1. 命令消息(WM_COMMAND):命令消息是程序員需要程序做某些操作的命令。凡UI對象產生的消息都是這種命令消息,可能來自菜單、加速鍵或工具欄按鈕等,都以WM_COMMAND呈現。
2. 標准窗口消息:除WM_COMMAND之處,任何以WM_開頭的消息都是這一類。標准窗口消息是系統中最為常見的消息,它是指由操作系統和控制其他窗口的窗口所使用的消息。例如CreateWindow、DestroyWindow和MoveWindow等都會激發窗口消息,以及鼠標移動、點擊,鍵盤輸入都是屬於這種消息。
3. Notification:這種消息由控件產生,為的是向其父窗口(通常是對話框窗口)通知某種情況。當一個窗口內的子控件發生了一些事情,而這些是需要通知父窗口的,此刻它就上場啦。通知消息只適用於標准的窗口控件如按鈕、列表框、組合框、編輯框,以及Windows公共控件如樹狀視圖、列表視圖等。
Windows中有一個系統消息隊列,對於每一個正在執行的Windows應用程序,系統為其建立一個“消息隊列”,即應用程序隊列,用來存放該程序可能創建的各種窗口的消息。
(1)隊列消息(Queued Messages)
消息會先保存在消息隊列中,通過消息循環從消息隊列中獲取消息並分發到各窗口函數去處理,如鼠標、鍵盤消息就屬於這類消息。
(2)非隊列消息(NonQueued Messages)
就是消息會直接發送到窗口函數處理,而不經過消息隊列。 如: WM_ACTIVATE, WM_SETFOCUS, WM_SETCURSOR, WM_WINDOWPOSCHANGED就屬於此類。
PostMessage發送的消息是隊列消息,它會把消息Post到消息隊列中; SendMessage發送的消息是非隊列消息, 被直接送到窗口過程處理,等消息被處理後才返回。
<喎?http://www.Bkjia.com/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPs6q1qTD99Xi0ru5/bPMo6zO0sPHv8nS1LjEtq/Su8/Cyc/D5rXE1eK49sD919OhozxiciAvPg0KMS7U2ldpbjMyVGVzdC5o1tDM7bzTSURfQlVUVE9OX1RFU1S1xLao0uU8L3A+DQo8cHJlIGNsYXNzPQ=="brush:java;">
#define ID_BUTTON_TEST 1002
2.在OnButtonWhite中分別用SendMessage和PostMessage發送消息
//按下hBtWhite時的事件
void OnButtonWhite()
{
g_bWhite = true;
InvalidateRect(g_hWnd, NULL, FALSE); //刷新窗口
SendMessage(g_hWnd, WM_COMMAND, ID_BUTTON_TEST, 0);
//PostMessage(g_hWnd, WM_COMMAND, ID_BUTTON_TEST, 0);
}
3.在消息循環中增加ID_BUTTON_TEST的判斷
while (GetMessage(&msg, NULL, 0, 0))
{
if (LOWORD(msg.wParam) == ID_BUTTON_TEST)
{
OutputDebugString(LThis is a ID_BUTTON_TEST message.); // [BreakPoint1]
}
TranslateMessage(&msg);
DispatchMessage(&msg);
}
4.在窗口處理函數WndProc增加ID_BUTTON_TEST的判斷
case ID_BUTTON_TEST:
{
OutputDebugString(LThis is a ID_BUTTON_TEST message.); // [BreakPoint2]
}
break;
case ID_BUTTON_DRAW:
OnButtonWhite();
break;
case ID_BUTTON_SWEEP:
OnButtonGray();
break;
用斷點調試的方式我們發現,用SendMessage發送的ID_BUTTON_TEST消息只會進入BreakPoint2,而PostMessage發送的ID_BUTTON_TEST會進入到BreakPoint1和BreakPoint2。