Использование сообщений и очередей сообщений

В следующих примерах кода показано, как выполнять следующие задачи, связанные с Windows сообщениями и очередями сообщений.

Создание Loop сообщений

Система не создает автоматически очередь сообщений для каждого потока. Вместо этого система создает очередь сообщений только для потоков, выполняющих операции, для которых требуется очередь сообщений. Если поток создает одно или несколько окон, необходимо предоставить цикл сообщений; этот цикл сообщений извлекает сообщения из очереди сообщений потока и отправляет их в соответствующие процедуры окна.

Так как система направляет сообщения в отдельные окна в приложении, перед запуском цикла сообщений поток должен создать по крайней мере одно окно. Большинство приложений содержат один поток, который создает окна. Типичное приложение регистрирует класс окна для его главного окна, создает и отображает главное окно, а затем запускает цикл сообщений — все в функции WinMain .

Цикл сообщений создается с помощью функций GetMessage и DispatchMessage . Если приложение должно получить входные данные символов от пользователя, включите функцию TranslateMessage в цикл. TranslateMessage преобразует сообщения виртуального ключа в символьные сообщения. В следующем примере показан цикл сообщений в функции WinMain простого приложения на основе Windows.

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 и GetInputState, также позволяют изучить содержимое очереди сообщений потока. GetQueueStatus возвращает массив флагов, указывающих типы сообщений в очереди; это самый быстрый способ узнать, содержит ли очередь сообщения. GetInputState возвращает значение TRUE , если очередь содержит сообщения мыши или клавиатуры. Обе эти функции можно использовать для определения того, содержит ли очередь сообщения, которые необходимо обработать.

Публикация сообщения

Сообщение можно опубликовать в очереди сообщений с помощью функции PostMessage . PostMessage помещает сообщение в конец очереди сообщений потока и возвращается немедленно, не дожидаясь обработки сообщения потоком. Параметры функции включают дескриптор окна, идентификатор сообщения и два параметра сообщения. Система копирует эти параметры в структуру MSG , заполняет время и pt членов структуры и помещает структуру в очередь сообщений.

Система использует дескриптор окна, переданный функцией PostMessage , чтобы определить, какая очередь сообщений потока должна получать сообщение. Если дескриптор HWND_TOPMOST, система отправляет сообщение в очереди сообщений потока во всех окнах верхнего уровня.

Функцию PostThreadMessage можно использовать для публикации сообщения в определенной очереди сообщений потока. PostThreadMessage похож на PostMessage, за исключением того, что первый параметр является идентификатором потока, а не дескриптором окна. Идентификатор потока можно получить, вызвав функцию GetCurrentThreadId .

Используйте функцию PostQuitMessage для выхода из цикла сообщений. PostQuitMessage публикует сообщение WM_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 для отправки сообщения в элемент управления, указав идентификатор элемента управления и дескриптор диалогового окна, содержащего элемент управления. В следующем примере, взятом из процедуры диалогового окна, копируется строка из элемента управления редактирования поля со списком в его список. В этом примере для отправки сообщения CB_ADDSTRING в поле со списком используется SendDlgItemMessage.

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. 
    // 
 
}