WIN32GUI消息机制简单探索
利用现有图形库可以做到快速开发一个桌面程序,但当你不满足于库产生的控件想自己定义控件或者想了解图形系统运作时,却不能随心所欲。图形库把底层接口封装,固然大大简化了开发的工作量,但其也把具体实现隐与表层之下,WIN32API是操作系统留给我们的接口,直接用API进行GUI的简单设计无疑能够极大的贴近图形库运转的底层面貌,去了解消息循环的真正面貌。
以下为MSDN的创建一个主窗口的例子:
#include <windows.h>
// Global variable
HINSTANCE hinst;
// Function prototypes.
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int);
InitApplication(HINSTANCE);
InitInstance(HINSTANCE, int);
LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);
// Application entry point.
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
MSG msg;
if (!InitApplication(hinstance))
return FALSE;
if (!InitInstance(hinstance, nCmdShow))
return FALSE;
BOOL fGotMessage;
while ((fGotMessage = GetMessage(&msg, (HWND) NULL, 0, 0)) != 0 && fGotMessage != -1)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
UNREFERENCED_PARAMETER(lpCmdLine);
}
BOOL InitApplication(HINSTANCE hinstance)
{
WNDCLASSEX wcx;
// Fill in the window class structure with parameters
// that describe the main window.
wcx.cbSize = sizeof(wcx); // size of structure
wcx.style = CS_HREDRAW |
CS_VREDRAW; // redraw if size changes
wcx.lpfnWndProc = MainWndProc; // points to window procedure
wcx.cbClsExtra = 0; // no extra class memory
wcx.cbWndExtra = 0; // no extra window memory
wcx.hInstance = hinstance; // handle to instance
wcx.hIcon = LoadIcon(NULL,
IDI_APPLICATION); // predefined app. icon
wcx.hCursor = LoadCursor(NULL,
IDC_ARROW); // predefined arrow
wcx.hbrBackground = GetStockObject(
WHITE_BRUSH); // white background brush
wcx.lpszMenuName = "MainMenu"; // name of menu resource
wcx.lpszClassName = "MainWClass"; // name of window class
wcx.hIconSm = LoadImage(hinstance, // small class icon
MAKEINTRESOURCE(5),
IMAGE_ICON,
GetSystemMetrics(SM_CXSMICON),
GetSystemMetrics(SM_CYSMICON),
LR_DEFAULTCOLOR);
// Register the window class.
return RegisterClassEx(&wcx);
}
BOOL InitInstance(HINSTANCE hinstance, int nCmdShow)
{
HWND hwnd;
// Save the application-instance handle.
hinst = hinstance;
// Create the main window.
hwnd = CreateWindow(
"MainWClass", // name of window class
"Sample", // title-bar string
WS_OVERLAPPEDWINDOW, // top-level window
CW_USEDEFAULT, // default horizontal position
CW_USEDEFAULT, // default vertical position
CW_USEDEFAULT, // default width
CW_USEDEFAULT, // default height
(HWND) NULL, // no owner window
(HMENU) NULL, // use class menu
hinstance, // handle to application instance
(LPVOID) NULL); // no window-creation data
if (!hwnd)
return FALSE;
// Show the window and send a WM_PAINT message to the window
// procedure.
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
return TRUE;
}
一。注册窗口类
这里有一个结构体WNDCLASSEX:
typedef struct tagWNDCLASSEX {
UINT cbSize;
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCTSTR lpszMenuName;
LPCTSTR lpszClassName;
HICON hIconSm;
} WNDCLASSEX, *PWNDCLASSEX;
在Window Procedures的使用中,MSDN给出了其例子:
LRESULT CALLBACK MainWndProc(
HWND hwnd, // handle to window
UINT uMsg, // message identifier
WPARAM wParam, // first message parameter
LPARAM lParam) // second message parameter
{
switch (uMsg)
{
case WM_CREATE:
// Initialize the window.
return 0;
case WM_PAINT:
// Paint the window's client area.
return 0;
case WM_SIZE:
// Set the size and position of the window.
return 0;
case WM_DESTROY:
// Clean up window-specific data objects.
return 0;
//
// Process other messages.
//
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return 0;
}
注册窗口类的函数为RegisterClassEx :
ATOM WINAPI RegisterClassEx(
_In_ const WNDCLASSEX *lpwcx
);
二。创建主窗口
创建主窗口用的是CreatWindows函数,返回的是指向窗口的指针。调用ShowWindow就可以显示主窗口了
三。建立消息循环
一般消息循环创建如下:
/* Run the message loop. It will run until GetMessage() returns 0 */
while (GetMessage (&messages, NULL, 0, 0))
{
/* Translate virtual-key messages into character messages */
TranslateMessage(&messages);
/* Send message to WindowProcedure */
DispatchMessage(&messages);
}
上面简单说了创建一个主窗口,但我想要的是消息是如何循环的。现在知道的是程序会将消息队列中的消息一条条抓取并分派到相应窗口的处理函数让其对这条消息进行处理,我现在想知道的是消息的产生及分派:消息如何产生,并且还能标记出要发往的窗口,是操作系统完成了这一切吗?还有就是消息如何分派,是抓取之后直接传给控件所在主窗口(假设按下了主窗口的一个按钮),还是说传给了按钮,再由按钮把消息给主窗口?下面进行实验:
创建按钮:
hwndButton=CreateWindow(
"BUTTON", // Predefined class; Unicode assumed
"OK", // Button text
WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, // Styles
10, // x position
10, // y position
100, // Button width
100, // Button height
hwnd, // Parent window
(HMENU)520, // No menu.
(HINSTANCE)GetWindowLong(hwnd, GWL_HINSTANCE),
NULL); // Pointer not needed.
LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message) /* handle the messages */
{
case WM_DESTROY:
PostQuitMessage (0); /* send a WM_QUIT to the message queue */
break;
case WM_COMMAND:
if (LOWORD(wParam) == 520 && HIWORD(wParam) == BN_CLICKED)
{
MessageBox(hwnd, TEXT("22222"), TEXT("11111"), MB_OK);
}
break;
default: /* for messages that we don't deal with */
return DefWindowProc (hwnd, message, wParam, lParam);
}
return 0;
}
下面获取按钮的处理函数并设置新的处理函数:
SetWindowLong函数返回一个指向原始窗口过程的指针; 使用此指针将消息传递到原始过程。子类窗口过程必须使用CallWindowProc函数调用原始窗口过程。
LRESULT CALLBACK ButtonWinProc(HWND, UINT, WPARAM, LPARAM);//新的按键回调函数
WNDPROC OldButtonwinProc;//保存旧回调函数的指针
OldButtonwinProc=(WNDPROC)SetWindowLong(hwndButton,GWL_WNDPROC,(LONG)ButtonWinProc);//设置新的回调函数保存旧的
LRESULT CALLBACK ButtonWinProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)//新的回调函数
{
if(message==WM_LBUTTONDOWN) cout<<"down ";
if(message==WM_LBUTTONUP) cout<<"up ";
if(message==BM_CLICK) cout<<"click ";
switch (message) /* handle the messages */
{
default:
return CallWindowProc(OldButtonwinProc,hwnd,message,wParam,lParam);//默认调用原有处理函数
}
}
SendMessage(hwndButton,BM_CLICK,520,0);
LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message) /* handle the messages */
{
case WM_DESTROY:
PostQuitMessage (0); /* send a WM_QUIT to the message queue */
break;
case WM_COMMAND:
if (LOWORD(wParam) == 520 && HIWORD(wParam) == BN_CLICKED)
{
cout<<"inmainframe ";
}
break;
default: /* for messages that we don't deal with */
return DefWindowProc (hwnd, message, wParam, lParam);
}
return 0;
}
而如果按键的回调函数改为如下:
LRESULT CALLBACK ButtonWinProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
if(message==WM_LBUTTONDOWN) cout<<"down ";
if(message==WM_LBUTTONUP) cout<<"up ";
if(message==BM_CLICK) cout<<"click ";
switch (message) /* handle the messages */
{
default:;
//return CallWindowProc(OldButtonwinProc,hwnd,message,wParam,lParam);
}
}
这就能够有一定可信度的说明,当按键处理函数接到BM_CLICK后,会给按键发送WM_LBUTTONDOWN和WM_LBUTTONUP,然后按键给主窗口发送了按键点击的消息,也就完成了一次按键点击的模拟。另外类似BM_CLICK中的M表示向控件发送的消息,也就是命令控件做事情,而类似BN_CLICKED中的N则表明是控件向外部(父窗口)发出的的通知,表示自己的状态。
再将WinMian中消息循环改为如下:
while (GetMessage (&messages, NULL, 0, 0))
{
/* Translate virtual-key messages into character messages */
TranslateMessage(&messages);
/* Send message to WindowProcedure */
if(messages.hwnd==hwndButton&&messages.message==WM_LBUTTONDOWN) cout<<"111";
if(messages.hwnd==hwnd&&messages.message==WM_COMMAND) cout<<"222";
DispatchMessage(&messages);
}
这说明按键按下确实是WinMain循环里抓取并直接送往按键的,而按键则是负责按键点击消息的发送,且不会再次进入WinMain消息循换了。
由此可总结,操作系统将各种消息放入应用程序队列,而应用程序抓取消息驱动整个程序一步步响应。一次点击按键,WinMain里面抓取并派送给按键鼠标左键按下与抬起这两个消息,而后按键把自身“点击”这个状态发送给父窗口,从而使父窗口能够处理鼠标点击事件。至于消息的产生,如何标记出要发往的窗口?谁标记的?这些还有待探索。。。。