Чтение онлайн

ЖАНРЫ

О чём не пишут в книгах по Delphi

Григорьев Антон Борисович

Шрифт:

Функция

TranslateMessage
, которая обычно вызывается в петле сообщений, служит для трансляции клавиатурных сообщении (если петля сообщений реализуется только для обработки сообщении невидимым окнам, которые использует, например, COM/DCOM, или по каким-то другим причинам ввод с клавиатуры не обрабатывается или обрабатывается нестандартным образом, вызов
TranslateMessage
можно опустить). Когда пользователь нажимает какую-либо клавишу на клавиатуре, система посылает окну, находящему в фокусе, сообщение
WM_KEYDOWN
. Через параметры этого сообщения передаётся виртуальный код нажатой клавиши — двухбайтное число, которое определяется только положением нажатой клавиши на клавиатуре и не зависит от текущей раскладки, состояния клавиш <CapsLock> и т.п. Функция
TranslateMessage
. обнаружив такое сообщение, добавляет в очередь (причем не в конец, а в начало) сообщение
WM_CHAR
,
в параметрах которого передается код символа, соответствующего нажатой клавише, с учетом раскладки, состояния клавиш <CapsLock>, <Shift> и т. п. Именно функция
TranslateMessage
по виртуальному коду клавиши определяет код символа. При этом нажатие любой клавиши приводит к генерации
WM_KEYDOWN
, а вот
WM_CHAR
генерируется не для всех клавиш, а только для тех, которые соответствуют какому-то символу (например, не генерирует
WM_CHAR
нажатие таких клавиш, как <Shift> <Ctrl>, <Insert>, функциональных клавиш).

Примечание

У многих компонентов VCL есть события

OnKeyDown
и
OnKeyPress
. Первое возникает при получении компонентом сообщения
WM_KEYDOWN
, второе — сообщения
WM_CHAR
.

Если очередь сообщений пуста, функция

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

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

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

PeekMessage
, которая позволяет проверить, есть ли в очереди сообщения. Если сообщения обнаружены, нужно вызвать
DispatchMessage
для передачи их требуемому окну. В этом случае сообщения будут извлекаться из очереди и обрабатываться до завершения работы обработчика. Блок-схема программы, содержащей локальную петлю сообщений, показана на рис. 1.5 (для краткости в главной петле сообщений показаны только две самых важных функции:
GetMessage
и
DispatchMessage
, хотя и в этом случае главная петля целиком выглядит так же, как на рис. 1.4).

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

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);

 end;

При отправке сообщения, в отличие от посылки, оно не ставится в очередь, а передается оконной процедуре напрямую. Отправить сообщение можно, например, с помощью функции

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

Примечание

Поскольку сообщения, отправленные окну, передаются оконной процедуре напрямую либо диспетчеризуются внутри

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

Поделиться с друзьями: