使用消息和消息队列
参考:http://msdn.microsoft.com/en-us/library/windows/desktop/ms644928(v=vs.85).aspx
下面的代码示例演示如何执行与窗口消息和消息队列有关的任务:
一 创建消息循环
系统不会自动为每个线程创建消息队列,而是只在线程执行那些需要消息队列的操作时才会为其创建消息队列。如果线程创建了一个或多个窗口则必须提供消息循环来接收线程消息队列中接收消息并分派到适当的窗口过程。由于系统会直接送消息到个别窗口,所以线程在开始其消息循环之前至少要创建一个窗口,多数应用程序都包含一个创建窗口的简单线程。一个典型的应用程序其WinMain 函数中应包括为主窗口注册窗口类,创建和显示主窗口,然后开始其消息循环。
你可以通过GetMessage和DispatchMessage函数创建(译:此处应该说“开始”更合适)消息循环,如果你的应用程序必须包含用户的字符输入,循环中要包括TranslateMessage函数。TranslateMessage 将虚拟键消息转换为字符消息。下面的示例展示位于简单窗口应用程序的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函数检查消息队列,该函数与 GetMessage 函数类似:都检查消息队列中符合过滤器规则的消息然后复制到一个 MSG 结构,主要的区别是后者直到在消息队列中找到符合过滤器规则的消息才返回,而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 和 GetInputState, 也可以检查线程消息队列的内容。 GetQueueStatus 返回一个标志位的数组以指示队列中消息的类型,这是检查队列是否包含任意类型消息最快的方法。GetInputState 在队列包含鼠标或键盘消息时返回 TRUE 。它们都可以用于检查队列是否需要处理的消息。
三 寄送消息
调用PostMessage函数可以寄送消息到(线程)消息队列, PostMessage 放置一个消息到线程消息队列的末尾并立即返回,不会等待线程处理该消息。函数的参数包括一个窗口句柄,一个消息标识和两个消息参数,系统考贝这些参数到一个MSG结构,设置结构的 time 和 pt 成员后放置到消息队列。系统根据随 PostMessage 一起传递的窗口句柄来判断哪个线程消息队列来接收消息,如果句柄为HWND_TOPMOST ,系统会寄送消息到所有顶级窗口的线程消息队列。
可以用 PostThreadMessage 函数寄送消息到指定的线程消息队列,PostThreadMessage 与 PostMessage 类似, 只是第一个参数是线程标识而非窗口句柄,线程标识可以通过GetCurrentThreadId函数获得。
PostQuitMessage 函数可以结束一个消息循环,PostQuitMessage 寄送WM_QUIT 消息到当前正在执行的线程,消息的消息循环遇到WM_QUIT 消息后会结束并返回控制权给系统,通常用来响应 WM_DESTROY消息,像下面的示例:
case WM_DESTROY: // Perform cleanup tasks. PostQuitMessage(0); break;
四 发送消息
SendMessage 函数直接发送消息到窗口函数,SendMessage 调用一个窗口函数并等待该过程处理该消息并返回一个结果。消息可以发送到系统中的任意一个窗口,仅需要一个窗口句柄。系统使用该句柄来决定将消息发送到哪个窗口函数。处理一个可能发送自另一个线程的消息之前,窗口函数应先调用InSendMessage函数。如果函数返回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 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. // }