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


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

Функция 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 возвращает ненулевое значение, выражение в цикле оценивается как true. После вызова PostQuitMessage выражение становится false, и программа выходит из цикла. (Один из интересных результатов этого поведения заключается в том, что процедура окна никогда не получает WM_QUIT сообщение. Поэтому в процедуре окна не требуется оператор case.)

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

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

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

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

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

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

Следующий

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