使用消息和消息队列

以下代码示例演示如何执行与 Windows 消息和消息队列关联的以下任务。

创建消息循环

系统不会为每个线程自动创建消息队列。 相反,系统仅为执行需要消息队列的操作的线程创建消息队列。 如果线程创建一个或多个窗口,则必须提供消息循环;此消息循环从线程的消息队列中检索消息,并将其调度到相应的窗口过程。

由于系统会将消息定向到应用程序中的各个窗口,因此线程在启动其消息循环之前必须至少创建一个窗口。 大多数应用程序都包含创建窗口的单个线程。 典型的应用程序为其main窗口注册窗口类,创建并显示main窗口,然后启动其消息循环 - 这一切都在 WinMain 函数中。

使用 GetMessageDispatchMessage 函数创建消息循环。 如果应用程序必须从用户获取字符输入,请在 循环中包含 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; 
} 

以下示例演示了使用加速器并显示无模式对话框的线程的消息循环。 当 TranslateAcceleratorIsDialogMessage 返回 TRUE (指示消息已) 处理时,不会调用 TranslateMessageDispatchMessage 。 原因是 TranslateAcceleratorIsDialogMessage 执行所有必需的消息翻译和调度。

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 结构。 两个函数之间的main区别在于,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; 
        } 
    } 
} 

其他函数(包括 GetQueueStatusGetInputState)也可用于检查线程消息队列的内容。 GetQueueStatus 返回指示队列中消息类型的标志数组;使用它是发现队列是否包含任何消息的最快方法。 如果队列包含鼠标或键盘消息,则 GetInputState 返回 TRUE。 这两个函数都可用于确定队列是否包含需要处理的消息。

发布消息

可以使用 PostMessage 函数将消息发布到消息队列。 PostMessage 将消息放置在线程的消息队列的末尾,并立即返回,而无需等待线程处理消息。 函数的参数包括窗口句柄、消息标识符和两个消息参数。 系统将这些参数复制到 MSG 结构,填充 结构的时间和pt 成员,并将该结构置于消息队列中。

系统使用 随 PostMessage 函数一起传递的窗口句柄来确定哪个线程消息队列应接收消息。 如果句柄 HWND_TOPMOST,系统会将消息发布到所有顶级窗口的线程消息队列。

可以使用 PostThreadMessage 函数将消息发布到特定线程消息队列。 PostThreadMessage 类似于 PostMessage,只不过第一个参数是线程标识符而不是窗口句柄。 可以通过调用 GetCurrentThreadId 函数来检索线程标识符。

使用 PostQuitMessage 函数退出消息循环。 PostQuitMessageWM_QUIT 消息发布到当前正在执行的线程。 线程的消息循环在遇到 WM_QUIT 消息时终止并将控制权返回到系统。 应用程序通常调用 PostQuitMessage 以响应 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 函数向控件发送消息,指定控件的标识符和包含控件的对话框窗口的句柄。 以下示例取自对话框过程,将组合框的编辑控件中的字符串复制到其列表框中。 该示例使用 SendDlgItemMessageCB_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. 
    // 
 
}