Поделиться через


Сообщения окна (начало работы с Win32 и C++)

Приложение графического интерфейса должно реагировать на события от пользователя и операционной системы.

  • События от пользователя включают все способы взаимодействия с программой: щелчки мыши, штрихи клавиш, жесты сенсорного экрана и т. д.
  • События из операционной системы включают что-либо "за пределами" программы, которая может повлиять на поведение программы. Например, пользователь может подключить новое аппаратное устройство, или Windows может ввести состояние нижней мощности (спящий или гибернации).

Эти события могут возникать в любое время во время выполнения программы практически в любом порядке. Как структурировать программу, поток выполнения которой невозможно предсказать заранее?

Для решения этой проблемы Windows использует модель передачи сообщений. Операционная система взаимодействует с окном приложения, передав сообщения в него. Сообщение — это просто числовой код, указывающий определенное событие. Например, если пользователь нажимает левую кнопку мыши, окно получает сообщение со следующим кодом сообщения.

#define WM_LBUTTONDOWN    0x0201

Некоторые сообщения ассоциированы с данными. Например, сообщение WM_LBUTTONDOWN включает координату x и координату y курсора мыши.

Чтобы передать сообщение в окно, операционная система вызывает процедуру окна, зарегистрированную для этого окна. Теперь вы знаете, для чего нужна процедура окна.

Цикл сообщений

Приложение получит тысячи сообщений во время его запуска. (Учитывайте, что каждый нажатие клавиши и кнопка мыши создает сообщение.) Кроме того, приложение может иметь несколько окон, каждая из которых имеет собственную процедуру окна. Как именно программа получает все эти сообщения и передает их в нужную процедуру окна? Приложению требуется цикл для получения сообщений и их отправки в правильные окна.

Для каждого потока, создающего окно, операционная система создает очередь для сообщений окна. Эта очередь содержит сообщения для всех окон, созданных в этом потоке. Сама очередь скрыта от вашей программы. Невозможно напрямую управлять очередью. Однако вы можете извлечь сообщение из очереди, вызвав функцию GetMessage.

MSG msg;
GetMessage(&msg, NULL, 0, 0);

Эта функция удаляет первое сообщение из головы очереди. Если очередь пуста, функция блокируется до тех пор, пока другое сообщение не будет в очереди. Тот факт, что GetMessage блокирует, не сделает вашу программу неотзывчивой. Если нет сообщений, программа ничего не может сделать. Если необходимо выполнить фоновую обработку, можно создать дополнительные потоки, которые продолжают выполняться, пока GetMessage ожидает другого сообщения. (См. раздел Избегайте узких мест в процедуре окна.)

Первым параметром GetMessage является адрес структуры MSG. Если функция завершается успешно, она заполняет структуру MSG сведениями о сообщении. Это включает целевое окно и код сообщения. Другие три параметра позволяют фильтровать сообщения, полученные из очереди. В почти всех случаях эти параметры будут равны нулю.

Хотя структура MSG содержит сведения о сообщении, вы почти никогда не будете просматривать эту структуру напрямую. Вместо этого вы передадите его непосредственно в две другие функции.

TranslateMessage(&msg); 
DispatchMessage(&msg);

Функция TranslateMessage связана с вводом клавиатуры. Он преобразует нажатия клавиш (вниз, ключ вверх) в символы. Вам не обязательно знать, как работает эта функция; просто не забудьте вызвать её перед DispatchMessage.

Функция DispatchMessage сообщает операционной системе вызывать оконную процедуру для окна, являющегося целью сообщения. Другими словами, операционная система ищет дескриптор окна в таблице окон, находит указатель функции, связанный с окном, и вызывает функцию.

Например, предположим, что пользователь нажимает левую кнопку мыши. Это приводит к цепочке событий:

  1. Операционная система помещает WM_LBUTTONDOWN сообщение в очередь сообщений.
  2. Программа вызывает функцию GetMessage .
  3. GetMessage извлекает сообщение WM_LBUTTONDOWN из очереди и заполняет структуру MSG.
  4. Программа вызывает функции TranslateMessage и DispatchMessage.
  5. Внутри DispatchMessage операционная система вызывает вашу процедуру обработки оконного сообщения.
  6. Процедура окна может либо отвечать на сообщение, либо игнорировать его.

Когда процедура окна возвращается, она возвращается в DispatchMessage. Возвращается в цикл сообщений для следующего сообщения. Пока программа запущена, сообщения будут продолжать поступать в очередь. Таким образом, необходимо иметь цикл, который постоянно извлекает сообщения из очереди и отправляет их. Вы можете думать о цикле следующим образом:

// WARNING: Don't actually write your loop this way.

while (1)      
{
    GetMessage(&msg, NULL, 0,  0);
    TranslateMessage(&msg); 
    DispatchMessage(&msg);
}

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

        PostQuitMessage(0);

Функция PostQuitMessage помещает WM_QUIT сообщение в очередь сообщений. WM_QUIT — это специальное сообщение: это вызывает GetMessage вернуть ноль, сигнализируя о конце цикла сообщений. Ниже приведен измененный цикл сообщений.

// Correct.

MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0) > 0)
{
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

Если GetMessage возвращает ненулевое значение, выражение в цикле while оценивается как истина. После вызова PostQuitMessage выражение становится false, и программа выходит из цикла. (Один из интересных результатов этого поведения заключается в следующем: ваша процедура окна никогда не получает сообщение WM_QUIT. Поэтому оператор case для этого сообщения в процедуре окна не требуется.)

Следующий очевидный вопрос заключается в том, когда вызывать PostQuitMessage. Мы вернемся к этому вопросу в разделе "Закрытие окна", но сначала нужно написать процедуру окна.

Опубликованные сообщения и отправленные сообщения

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

Терминология этого различия может быть запутанной:

  • Публикация сообщения означает, что сообщение переходит в очередь сообщений и отправляется через цикл сообщений (GetMessage и DispatchMessage).
  • Отправка сообщения означает, что сообщение пропускает очередь, а операционная система вызывает процедуру окна напрямую.

На данный момент разница не очень важна. Процедура окна обрабатывает все сообщения. Однако некоторые сообщения обходят очередь и переходят непосредственно к процедуре окна. Однако это может измениться, если приложение взаимодействует между окнами. Более подробное обсуждение этой проблемы можно найти в разделе "О сообщениях и очередях сообщений".

Следующий

Написание процедуры окна