利用現有圖形庫可以做到快速開發一個桌面程序,但當你不滿足於庫產生的控件想自己定義控件或者想了解圖形系統運作時,卻不能隨心所欲。圖形庫把底層接口封裝,固然大大簡化了開發的工作量,但其也把具體實現隱與表層之下,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;
}
總的來說創建一個主窗口大致分爲三個部分:1.註冊窗口類2.創建主窗口3.建立消息循環。
一。註冊窗口類
這裏有一個結構體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;
這個結構體包含了窗口類的信息,被用於 RegisterClassEx (即註冊窗口類的函數)和GetClassInfoEx這兩個函數。這個結構體比較重要的一個參數是lpfnWndProc,lpfn即long
pointer to function,是一個指向函數的長指針,指向窗口的處理函數。
在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
);
它是以指向結構體WNDCLASSEX的指針爲參數的。
二。創建主窗口
創建主窗口用的是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);
}
GetMessage不斷抓取消息,轉換後分派給相應的窗口處理函數(窗口過程Window Procedure)。每當用戶移動鼠標,單擊鼠標按鈕或敲擊鍵盤時,鼠標或鍵盤的設備驅動程序將輸入轉換爲消息並將其放置在系統消息隊列中。系統從系統消息隊列中刪除一個消息,檢查它們以確定目標窗口,然後將其發佈到創建目標窗口的線程的消息隊列中。線程的消息隊列接收線程創建的窗口的所有鼠標和鍵盤消息(GetMessage)。該線程從其隊列中刪除消息,並引導系統將其發送到適當的窗口過程(TranslateMessage DispatchMessage)進行處理。
-------------------------------------------------------我是帥氣的分割線--------------------------------------------------------------------------
上面簡單說了創建一個主窗口,但我想要的是消息是如何循環的。現在知道的是程序會將消息隊列中的消息一條條抓取並分派到相應窗口的處理函數讓其對這條消息進行處理,我現在想知道的是消息的產生及分派:消息如何產生,並且還能標記出要發往的窗口,是操作系統完成了這一切嗎?還有就是消息如何分派,是抓取之後直接傳給控件所在主窗口(假設按下了主窗口的一個按鈕),還是說傳給了按鈕,再由按鈕把消息給主窗口?下面進行實驗:
創建按鈕:
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.
主窗口的Window Procedures:
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;
}
其中WM_COMMAND即爲處理按鍵按下消息的處理分支。按鍵能正常工作,如下:
下面獲取按鈕的處理函數並設置新的處理函數:
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);//默認調用原有處理函數
}
}
在WinMain裏發送模擬按鍵點擊的消息給按鍵:
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);
}
}
則只會打印click。
這就能夠有一定可信度的說明,當按鍵處理函數接到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裏面抓取並派送給按鍵鼠標左鍵按下與擡起這兩個消息,而後按鍵把自身“點擊”這個狀態發送給父窗口,從而使父窗口能夠處理鼠標點擊事件。至於消息的產生,如何標記出要發往的窗口?誰標記的?這些還有待探索。。。。