При использовании локальной петли сообщений существует опасность бесконечной рекурсии. Рассмотрим это на простом примере: предположим, что сложный код, содержащий локальную петлю сообщений, выполняется при нажатии некоторой кнопки на форме приложения. Пока обработчик выполняется, нетерпеливый пользователь может снова нажать кнопку, запустив вторую активацию обработчика нажатия кнопки, и так несколько раз. Конечно, организовать таким образом очень глубокую рекурсию пользователь вряд ли сможет (терпения не хватит), но часто даже то, что несколько активаций обработчика вызваны рекурсивно, может привести к неприятным последствиям. А если программа организует локальную петлю сообщений в обработчике сообщений таймера, то здесь рекурсия действительно может углубляться до переполнения стека. Поэтому при организации петли сообщений следует принимать меры против рекурсии. Например, в случае с кнопкой в обработчике ее нажатие можно запретить (Enabled := False), и вновь разрешить только после окончания обработки, тогда пользователь не сможет нажать кнопку во время работы локальной петли сообщений. В очередь можно поставить сообщение, не привязанное ни к какому окну. Это делается с помощью функции PostThreadMessage. Такие сообщения необходимо самостоятельно обрабатывать в петле сообщений, потому что функция DispatchMessage их просто игнорирует.
Рис. 1.6. Блок-схема программы с локальной петлей сообщений
Существуют также широковещательные сообщения, которые посылаются сразу нескольким окнам. Проще всего послать такое сообщение с помощью функции PostMessage, указав в качестве адресата не дескриптор конкретного окна, а константу HWND_BROADCAST. Такое сообщение получат все окна, расположенные непосредственно на рабочем столе и не имеющие при этом владельцев (в терминах системы). Существует также специальная функция BroadcastSystemMessage (начиная с Windows ХР — ее расширенный вариант BroadcastSystemMessageEx), которая позволяет уточнить, каким конкретно окнам будет отправлено широковещательное сообщение.
Кроме параметров wParam и lParam, каждому сообщению приписывается время отправки и координаты курсора в момент возникновения. Соответствующие поля есть в структуре TMsg, которую используют функции GetMessage и DispatchMessage, но у оконной процедуры не предусмотрены параметры для их передачи. Получить время отправки сообщения и координаты курсора при обработке сообщения можно с помощью функций GetMessageTime и GetMessagePos соответственно.
Существует также ряд функций, которые могут обрабатывать сообщения без участия DispatchMessage и оконной процедуры. Если эти функции распознают сообщение, извлеченное из очереди, как "свое", они сами выполняют все необходимые действия по его обработке, и тогда TranslateMessage и DispatchMessage вызывать не нужно. К этим функциям, в частности, относятся следующие:
□ TranslateAccelerator — на основе загруженной из ресурсов таблицы распознает нажатие "горячих" клавиш меню и вызывает оконную процедуру, передавая ей сообщение WM_COMMAND или WM_SYSCOMMAND, аналогичное тому, которое посылается при выборе соответствующего пункта меню пользователем;
□ TranslateMDISysAccel — аналог предыдущей функции за исключением того, что распознает "горячие" клавиши системного меню MDI-окон;
□ IsDialogMessage — распознает сообщения, имеющие особый смысл для диалоговых окон (например, нажатие клавиши <Tab> для перехода между элементами управления). Используется для немодальных диалоговых окон и окон, не являющихся диалоговыми (т.е. созданными без помощи функций CreateDialogXXXX), но требующими аналогичной функциональности.
Перечисленные функции при необходимости вставляются в петлю сообщений. Листинг 1.6 показывает, как будет выглядеть петля сообщений, содержащая вызов TranslateAccelerator для родительской MDI-формы и TranslateMDISysAccel для дочерней.
Листинг 1.6. Петля сообщении с обработкой "горячих" клавиш главного меню и системного меню MDI-оконwhile GetMessage(Msg, 0, 0, 0) do
if not TranslateMDISysAccel(ActiveMDIChildHandle, Msg)
and not TranslateAccelerator(MDIFormHandle, AccHandle, Msg) then
begin
TranslateMessage(Msg);
DispatchMessage(Msg);