英文原文地址:http://msdn.microsoft.com/en-us/library/windows/desktop/ms644928(v=vs.85).aspx
创建一个消息循环
系统不会为每个线程自动创建一个消息队列。相反,只有线程操作需要一个消息队列时,系统才会创建一个消息队列。如果线程创建一个或者多个窗口,就需要消息循环了。消息循环从线程的消息队列中检索消息,然后分发这些消息到相应的窗体程序。
由于在应用中,系统发送消息到单独的窗体,所以在开始消息循环之前,线程必须创建至少一个窗体。大多是的应用包含一个创建窗体的线程。一个典型的应用为主窗体注册窗体类,创建并且显示主窗体,然后开始消息循环---所以以上操作都在WinMain函数中实现。
可以使用GetMessage和DispatchMessage函数创建一个消息循环。如果应用必须获得用户输入的字符,需要在循环中加入TranslateMessage函数(转换虚拟键消息到字符消息)。下面的例子显示消息循环在WinMain函数中的一个简单的基于窗体的应用。
HINSTANCE hinst;
HWND hwndMain;
int PASCALWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpszCmdLine, int nCmdShow)
{
MSG msg;
BOOL bRet;
WNDCLASS wc;
UNREFERENCED_PARAMETER(lpszCmdLine);
// Register the window class for the mainwindow.
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 possiblyexit
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
// Return the exit code to the system.
return msg.wParam;
}
下面的例子展示一个用快捷键和显示一个模态对话框线程的消息循环。当TranslateAccelerator或者IsDialogMessage函数返回TRUE(表示消息已经被处理),TranslateMessage和DispatchMessage不会被调用。原因是TranslateAccelerator和IsDialogMessage函数处理所有的转换和分发消息。
HWND hwndMain;
HWNDhwndDlgModeless = NULL;
MSG msg;
BOOL bRet;
HACCEL haccel;
// Performinitialization 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函数去检查消息队列是否有鼠标点击和键盘输入。
HWNDhwnd;
BOOLfDone;
MSGmsg;
//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 thequeue. 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 requiredcleanup.
//
fDone = TRUE;
}
}
}
其他函数,包括GetQueueStatus和GetInputState也允许检查线程的消息队列内容。GetQueueStatus函数返回指示消息队列中消息类型的标识数组;此方法是最快的方式发现消息队列中是否有消息。如果队列包括鼠标和键盘消息,GetInputStatus函数返回TRUE。这两个函数可以决定是否队列包括需要处理的消息。
PostMessage
PostMessage函数可以发送一个消息到消息队列,此函数把发送的消息放到线程的消息队列的末尾并且立即返回,不需要等待线程去处理这个消息。PostMessage函数参数包括:一个窗口句柄,一个消息标识和两个消息参数。系统拷贝这些参数到MSG结构体中,然后填充此结构体的time和pt成员,然后放此结构体到消息队列中。
系统用PostMessage函数参数中的窗体句柄决定哪个线程队列接收此消息。如果这个句柄是HWND_TOPMOST,系统发送消息到所有的最高水平的线程窗体消息队列。
可以使用PostThreadMessage函数发送消息到指定的线程消息队列。PostThreadMessage 和PostMessage比较相似,不同之处在于PostThreadMessage函数的第一个参数是一个线程标识而不是窗体句柄。可以通过GetCurrentThreadId函数检索这个线程标识。
用PostQuitMessage函数去退出消息循环。PostQuitMessage函数发送WM_QUIT消息到现在执行的线程。当接收到WM_QUIT消息时,线程消息循环终止,然后返回控制权给系统。应用通常调用PostQuitMessage函数作为WM_DESTROY消息的响应,如下例:
case WM_DESTROY:
// Perform cleanup tasks.
PostQuitMessage(0);
break;
SendMessage
SendMessage函数发送消息直接到窗口处理程序。SendMessage调用窗口处理程序,等待处理消息返回结果。
消息可以发送到任何系统中的窗体;仅仅需要一个窗体句柄。系统使用这个句柄决定哪个窗体程序可以接受消息。
在处理一个可能从其他线程发送的消息之前,窗体函数首先调用InSendMessage函数(此函数返回的结果为bool类型:返回是否现在的窗体程序处理从其他线程发送过来的消息结果),如果函数返回TRUE,窗体程序在其他函数之前调用ReplyMessage函数,这个函数让线程放弃控制权,如下实例:
case WM_USER + 5:
if (InSendMessage())
ReplyMessage(TRUE);
DialogBox(hInst, "MyDialogBox",hwndMain, (DLGPROC) MyDlgProc);
break;
许多消息可以发送到对话框中的控件。这些控件消息设置控件的外观、行为和内容或者检索控件的信息。例如CB_ADDSTRING消息可以增加一个字符串到组合框中,BM_SETCHECK消息可以设置一个复选框或者单选按钮的选择状态。
SendDlgItemMessage函数可以发送一个消息到控件,指定控件的标识和包含控件的对话框窗体句柄。下面的例子,摘录自一个对话框程序,从一个组合框的编辑控件到列表框拷贝一个字符串。这个例子用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 combobox and the
// length of the string in theedit control
// of the combo box.
hwndCombo = GetDlgItem(hwndDlg,IDD_COMBO);
cTxtLen =GetWindowTextLength(hwndCombo);
// Allocate memory for thestring 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 listbox of the
// combo box and remove thestring from the
// edit control of the combobox.
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 boxcommands.
//
}
//
// Process other dialog box messages.
//
}