创建消息循环

系统不会为每个线程自动创建消息循环。相反,系统只会为需要消息队列来执行操作的线程创建一个消息队列。如是一个线程创建了一个或多个窗口,那么它就必须提供一个消息循环。这个消息循环负责从线程消息队列中检索消息,然后将消息派发到合适的窗口过程中。

因为系统将消息定向到应用程序中的单个窗口,所以线程必须在启动其消息循环之前至少创建一个窗口。大多数应用程序都包含一个创建窗口的线程。一个典型的应用程序通常像这样,为主窗口注册窗口类,创建并显示主窗口,然后启动它的消息循环——所有这些都在WinMain函数中。

你可以通过调用GetMessage及DispatchMessage函数来创建一个消息循环。如果你的程序需要获取字符输入,则在消息循环中需要调用TranslateMessage. TranslateMessage将虚拟按键翻译成字符消息。以下代码演示了一个简单的基于Windows的应用程序WinMain函数中的消息循环。

HINSTANCE hinst; 
HWND hwndMain;

int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpszCmdLine, int nCmdShow)
{
MSG msg;
BOOL bRet;
WNDCLASS wc;
UNREFERENCED_PARAMETER(lpszCmdLine);

// Register the window class for the main window.

if (!hPrevInstance)
{
wc.style = 0;
wc.lpfnWndProc = (WNDPROC) WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon((HINSTANCE) NULL,
IDI_APPLICATION);
wc.hCursor = LoadCursor((HINSTANCE) NULL,
IDC_ARROW);
wc.hbrBackground = GetStockObject(WHITE_BRUSH);
wc.lpszMenuName = "MainMenu";
wc.lpszClassName = "MainWndClass";

if (!RegisterClass(&wc))
return FALSE;
}

hinst = hInstance; // save instance handle

// Create the main window.

hwndMain = CreateWindow("MainWndClass", "Sample",
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT, (HWND) NULL,
(HMENU) NULL, hinst, (LPVOID) NULL);

// If the main window cannot be created, terminate
// the application.

if (!hwndMain)
return FALSE;

// Show the window and paint its contents.

ShowWindow(hwndMain, nCmdShow);
UpdateWindow(hwndMain);

// Start the message loop.

while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
{
if (bRet == -1)
{
// handle the error and possibly exit
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}

// Return the exit code to the system.

return msg.wParam;
}

以下代码演示线程使用加速键及显示一个非模态对话框的消息循环。当TranslateAccelerator或IsDialogMessage返回TRUE(指示消息已被 处理)时, TranslateMessage及DispatchMessage不需要被调用。原因是TranslateAccelerator及IsDialogMessage执行了翻译及派发消息的所有工作。

HWND hwndMain; 
HWND hwndDlgModeless = NULL;
MSG msg;
BOOL bRet;
HACCEL haccel;
//
// Perform initialization and create a main window.
//

while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
{
if (bRet == -1)
{
// handle the error and possibly exit
}
else
{
if (hwndDlgModeless == (HWND) NULL ||
!IsDialogMessage(hwndDlgModeless, &msg) &&
!TranslateAccelerator(hwndMain, haccel,
&msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}

检查消息队列

有时,应用程序需要从线程的消息循环之外检查线程的消息队列的内容。例如,如果一个应用程序的窗口过程执行了一个较长的绘图操作,你可能希望该用户能够中断该操作。除非你的应用程序在操作期间,周期性地检查消息队列中的鼠标和键盘消息,否则在操作完成之前,它将不会响应用户的输入。这是因为DispatchMessage函数在窗口过程完成消息的处理后才会返回。

在执行长时间的操作时,你可以使用PeekMessage函数来检索消息队列。PeekMessage函数类似GetMessage函数,它们均会检查消息队列中满足过滤条件的消息,并将消息复制到MSG结构体中。这两个函数主要的不同是,GetMessage会阻塞,直到在消息队列中取到满足条件的消息,而PeekMessage不管有没有消息,均会立马返回。

以下代码演示了PeekMessage函数在一个长操作过程中从消息队列中检索鼠标点击及键盘输入消息的过程。

HWND hwnd; 
BOOL fDone;
MSG msg;

// Begin the operation and continue until it is complete
// or until the user clicks the mouse or presses a key.

fDone = FALSE;
while (!fDone)
{
fDone = DoLengthyOperation(); // application-defined function

// Remove any messages that may be in the queue. If the
// queue contains any mouse or keyboard
// messages, end the operation.

while (PeekMessage(&msg, hwnd, 0, 0, PM_REMOVE))
{
switch(msg.message)
{
case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_KEYDOWN:
//
// Perform any required cleanup.
//
fDone = TRUE;
}
}
}

其他一些函数,如GetQueueStatus及GetInputStates,也可以用来检查线程消息队列中的内容,
GetQueueStatus返回一个标志数组,指示消息队列中消息的类型;
使用它可以快速的发现消息队列中是否包含消息。如果队列中包含鼠标可键盘按钮消息,
GetInputStatus会返回TRUE.这两个函数均可以用于查明消息队列中
是否包含需要处理的消息。

Posting消息

你可以使用PostMessage函数Post一个消息到消息队列中。PostMessage将一个消息放置在线程消息队列的尾部,然后就立马返回,不会去等待线程处理消息。此函数可以传入一个窗口句柄,一个消息码以及两个消息参数。系统将这些参数复制到MSG结构体中,并且填充上time、pt成员,然后将此结构体放置到消息队列中。

系统根据传给PostMessage的窗口句柄来决定哪个线程消息队列可以收到这个消息。如果窗口句柄是HWND_TOPMOST,系统就会将此消息Post到所有顶层窗口的消息队列中。

你可以使用PostThreadMessage函数向指定的线程消息队列Post一个消息。PostThreadMessage函数类似PostMessage,除了第一个参数不是窗口句柄,而是线程标识。你可以通过GetCurrentThreadId函数获取当前线程标识。

使用PostQuitMessage函数可以退出消息循环。PostQuitMessage会向当前执行线程Post一个WM_QUIT消息。当遇到WM_QUIT消息时,消息循环会终止并将控制权返回给系统。应用程序通过响应WM_DESTROY消息中会调用PostQuitMessage,以下演示了这个情况:

case WM_DESTROY: 

// Perform cleanup tasks.

PostQuitMessage(0);
break;

Sending消息

SendMessage函数用于直接向窗口过程发送消息。SendMessage直接调用窗口过程,然后等待窗口过程处理完成,最后返回处理结果。

消息可以Send给系统中任意的窗口,只要提供给它窗口句柄。系统使用此句柄来决定哪个窗口过程可以接收到此消息。

在处理可能由其他线程发送来的消息前,窗口过程中应该先调用IsSendMessage来判断下当前是否在处理其他的SendMessage,如果此函数返回TRUE,窗口过程应该要调用ReplyMessage交出控制权给SendMessage,示例如下:

case WM_USER + 5: 
if (InSendMessage())
ReplyMessage(TRUE);

DialogBox(hInst, "MyDialogBox", hwndMain, (DLGPROC) MyDlgProc);
break;

许多消息可以发送给对话框中的控件。这些控件消息可以设置外观,行为以及控件中的内容或从控件中获取信息。例如,CB_ADDSTRING消息可以向一个组合框中增加字符串,BM_SETCHECK消息可以设置选择按钮或点选按钮的选择状态。

使用SendDlgItemMessage函数通过指定控件ID及其所属对话框句柄来向一个控件发送消息。以下例子来自一个对话框窗口过程,从一个组合框的编辑框中复制一个字符串到它的列表框中,例子中使用SendDlgItemMessage向组合框发送一个CB_ADDSTRING消息。

HWND hwndCombo; 
int cTxtLen;
PSTR pszMem;

switch (uMsg)
{
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDD_ADDCBITEM:
// Get the handle of the combo box and the
// length of the string in the edit control
// of the combo box.

hwndCombo = GetDlgItem(hwndDlg, IDD_COMBO);
cTxtLen = GetWindowTextLength(hwndCombo);

// Allocate memory for the string and copy
// the string into the memory.

pszMem = (PSTR) VirtualAlloc((LPVOID) NULL,
(DWORD) (cTxtLen + 1), MEM_COMMIT,
PAGE_READWRITE);
GetWindowText(hwndCombo, pszMem,
cTxtLen + 1);

// Add the string to the list box of the
// combo box and remove the string from the
// edit control of the combo box.

if (pszMem != NULL)
{
SendDlgItemMessage(hwndDlg, IDD_COMBO,
CB_ADDSTRING, 0,
(DWORD) ((LPSTR) pszMem));
SetWindowText(hwndCombo, (LPSTR) NULL);
}

// Free the memory and return.

VirtualFree(pszMem, 0, MEM_RELEASE);
return TRUE;
//
// Process other dialog box commands.
//

}
//
// Process other dialog box messages.
//

}