Сообщения окна (начало работы с 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. Если вам интересно, вы получите дополнительную информацию по ссылке на документацию MSDN.

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

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

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

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

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

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

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

Следующая

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