суббота, 5 января 2013 г.

C++: WinApi, сообщения окна и их обработка

Приложения с графическим интерфейсом пользователя (GUI) должны реагировать на действия (события) пользователя и ОС.
События пользователя это любое взаимодействие пользователя с программой, например перемещения курсора мыши, нажатие клавиш на клавиатуре и т.д.
События ОС включают в себя любое действие за пределами программы, которые могут повлиять на поведение программы. Например подключение нового устройства или переход в режим пониженного энергопотребления (а так же выключение или гибернация).
Все эти события могут происходить в любое время и в неизвестном порядке. Чтобы структурировать поток выполнения программы, Windows использует модель передачи сообщений. Она взаимодействует с окнами приложения передавая ему сообщения. По сути, сообщение это цифровой код, обозначающий конкретное событие. Например, при нажатии пользователем левой кнопки мыши окно получает сообщение со следующим кодом
#define WM_LBUTTONDOWN    0x0201
Некоторые сообщения имеют связанные данные. Например, WM_LBUTTONDOWN содержит в качестве данных координаты x и y курсора мыши.
Для передачи этих сообщений ОС использует вызов оконной процедуры (WindowProc) зарегистрированной для окна.

Цикл Message Loop

Так как приложение будет получать тысячи сообщений во время своей работы, а так же иметь несколько окон со своей собственной оконной процедурой, программе необходимо будет каким-то образом получать все эти сообщения и передать их в нужную процедуру окна. Для этого в приложении запускается цикл Message Loop, который будет получать сообщение и отправлять их в нужную оконную процедуру.
Для каждого потока, который создает окно ОС создает очередь оконных сообщений, которая содержит сообщения для всех окон этого потока. Очередь сообщений скрыта от выполняемой программы и нельзя управлять очередью напрямую. Но можно вытащить сообщение из очереди с помощью функции GetMessage.
MSG msg;
GetMessage(&msg, NULL, 0, 0);
Эта функция удаляет первое сообщение из головы очереди. Если она пуста, то функция блокируется до следующего сообщения в очереди. Блокирование GetMessage не будет означать остановку программы. Если сообщений нет, то программа не будет ничего выполнять. Т.е. иными словами GetMessage будет просто ожидать приход нового сообщения.
Функция GetMessage принимает 4 параметра. Первый параметр это адрес структуры MSG. Если функция завершается успешно, то она заполнит структуру информацией о сообщении. Остальные три параметра дают возможность фильтровать сообщения, получаемые из очереди. В большинстве случаев их устанавливают в ноль.
Несмотря на то, что MSG структура содержит информацию о сообщении, изучать ее не имеет никакого смысла. Вместо этого, эту структуру необходимо передать двум другим функциям
TranslateMessage(&msg); 
DispatchMessage(&msg);
Функция TranslateMessage связанна с клавиатурой. Она переводит нажатия клавиш в код символа. Разработчику в большинстве случаем не нужно знать как она работает, главное не забывать вызывать ее перед DispatchMessage.
Функция DispatchMessage вызывает оконную процедуру, того окна с которым связанно данное событие.
Рассмотрим пример, пользователь нажимает левую кнопку мыши. Вот что происходит:
  • ОС помещает сообщение WM_LBUTTONDOWN в очередь сообщений
  • Программа вызывает функцию GetMessage
  • GetMessage выталкивает сообщение WM_LBUTTONDOWN из очереди и заполняет структуру MSG
  • Программа вызывает TranslateMessage и DispatchMessage
  • Внутри DispatchMessage ОС вызывает оконную процедуру
  • Оконная процедура может отвечать на сообщение или игнорировать его
Когда оконная процедура завершается, выполнение программы передается в DispatchMessage, которая в свою очередь передает выполнение в цикл обработки сообщений Message Loop для следующего сообщения. Таким образом, пока программа работает, сообщения продолжают поступать в очередь. Вот пример цикла.
MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0))
{
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}
Вызов функции GetMessage должен быть расположен в условии цикла while. Так обеспечивается завершение цикла, когда GetMessage вернет нулевое значение. Всякий раз когда необходимо выйти из цикла Message Loop, необходимо вызвать функцию PostQuitMessage.
PostQuitMessage(0);

Функция PostQuitMessage ставит сообщение WM_QUIT в очередь сообщений. Сообщение WM_QUIT является специальным сообщением, оно заставляет GetMessage вернуть нулевое значение и таким образом завершить цикл Message Loop.

Комментариев нет:

Отправить комментарий