завершает свою работу, и обработка сообщения на этом заканчивается. Таким образом, обработка сообщения по событию
OnMessage
не может отменить предварительную обработку сообщения и исчезновение всплывающей подсказки.
Рис. 1.6. Одна итерация петли сообщений VCL (блок-схема метода
Application.ProcessMessage
)
5. Если главная форма приложения имеет стиль MDIForm, и одно из его дочерних MDI-окон в данный момент активно, сообщение передается функции
TranslateMDISysAccel
. Если эта функция вернет
True
,
то обработка сообщения на этом завершается (все эти действия выполняются в методе
IsMDIMsg
).
6. Затем, если получено клавиатурное сообщение, оно отправляется на предварительную обработку тому же окну, что и в пункте 2 (метод
IsKeyMsg
). Предварительная обработка клавиатурного сообщения начинается с попытки найти полученную комбинацию клавиш среди "горячих" клавиш контекстно-зависимого меню и выполнить соответствующую команду. Если контекстно-зависимое меню не распознало сообщение как свою "горячую" клавишу, то вызывается обработчик события
OnShortCut
окна, осуществляющего предварительную обработку (если это окно не является формой и не имеет этого события, то вызывается
OnShortCut
его родительской формы). Если обработчик
OnShortCut
не установил свой
параметр
Handled в True, полученная комбинация клавиш ищется среди "горячих" клавиш сначала главного меню, а потом — среди компонентов
TActionList
. Если и здесь искомая комбинация не находится, возникает событие
Application.OnShortCut
, которое также имеет параметр
Handled
, позволяющий указать, что сообщение в дополнительной обработке не нуждается. Если обработчик не установил этот параметр, то сообщение передается главной форме приложения, которое пытается найти нажатую комбинацию среди "горячих" клавиш своего контекстного меню, передает его обработчику
OnShortCut
, ищет среди "горячих" клавиш главного меню и компонентов
TActionList
. Если нажатая клавиша не является "горячей", но относится к клавишам, использующимся для управления диалоговыми окнами (<Tab>, стрелки, <Esc> и т.п.), форме передается сообщение об этом, и при необходимости сообщение обрабатывается. Таким образом, на данном этапе средствами VCL эмулируются функции
TranslateAccelerator
и
IsDialogMessage
.
7. Если на экране присутствует один из стандартных диалогов (в VCL они реализуются классами
TOpenDialog
,
TSaveDialog
и т.п.), то вызывается функция
IsDialogMessage
, чтобы эти диалоги могли нормально функционировать (метод
IsDlgMsg
).
8. Если ни на одном из предыдущих этапов сообщение не было обработано, то вызываются функции
TranslateMessage
и
DispatchMessage
, которые завершают обработку сообщения путем направления его соответствующей оконной функции.
Примечание
Если внимательно проанализировать шестой этап обработки сообщения, видно, что нажатая комбинация клавиш проверяется на соответствие "горячим" клавишам меню сначала активной формы, затем — главной. При этом сначала возникает событие
OnShortCut
активной формы, потом —
Application.OnShortCut
, затем —
OnShortCut
главной формы. Если в момент получения сообщения главная форма активна, то она дважды будет проверять соответствие клавиши "горячим" клавишам своих меню и событие
OnShortCut
тоже возникнет дважды (первый раз поле
Msg.Msg
равно
CN_KEYDOWN
, второй —
CM_APPKEYDOWN
). Эта проверка осуществляется дважды только в том случае, если комбинация клавиш не распознается как "горячая" клавиша — в противном случае цепочка проверок обрывается при первой проверке.
Метод
ProcessMessage
возвращает
True
, если сообщение извлечено и обработано, и
False
, если очередь была пуста. Этим пользуется метод
HandleMessage
, который вызывает
ProcessMessage
и, если тот вернет
False
, вызывает метод
Application.Idle
для низкоприоритетных действий, которые должны выполняться только при отсутствии сообщений в очереди. Метод
Idle
, во-первых, проверяет, над каким компонентом находится курсор мыши, и сохраняет ссылку на него в поле
FMouseControl
, которое используется при последующей проверке, нужно ли прятать всплывающую подсказку. Затем, при необходимости, прячется старая всплывающая подсказка и показывается новая. После этого вызывается обработчик
Application.OnIdle
, если он назначен. Этот обработчик имеет параметр
Done
, по умолчанию равный
True
. Если в коде обработчика он не меняется на
False
, метод
Idle
инициирует события
OnUpdate
у всех объектов
TAction
, у которых они назначены (если
Done
после вызова принял значение
False
,
HandleMessage
не тратит время на инициацию событий
OnUpdate
).
Примечание
В BDS 2006 появилось свойство
Application.ActionUpdateDelay
, позволяющее снизить нагрузку на процессор, откладывая на некоторое время обновление объектов
TAction
. Если значение этого свойства не равно нулю, в методе
Idle
вместо вызова запускается таймер и
OnUpdate
вызывается по его сигналу.
Затем, независимо от значения
Done
, с помощью процедуры
CheckSynchronize
проверяется, есть ли записи в списке методов, ожидающих синхронизации (эти методы помещаются в указанный список при вызове
TThread.Synchronize
). Если список не пуст, выполняется первый из этих методов (при этом он, разумеется, удаляется из списка). Затем, если остался равным
True
, а список методов для синхронизации был пуст (т. е. никаких дополнительных действий выполнять не нужно),
HandleMessage
вызывает функцию Windows API
WaitMessage
. Эта функция приостанавливает выполнение нити до тех пор, пока в ее очереди не появятся сообщения.
Примечание
Вызов
Synchronize
приводит к тому, что соответствующий метод будет выполнен основной нитью приложения, а нить, вызвавшая
Synchronize
, будет приостановлена до тех пор, пока главная нить не сделает это. Отсюда видно, насколько бредовыми являются советы (заполонившие Интернет, а также встречающиеся в некоторых книгах, например, у Архангельского) помещать весь код нити в
Synchronize
. В этом случае дополнительная нить вообще не будет ничего делать, все будет выполняться основной нитью, и выигрыша от создания дополнительной нити просто не будет. Поэтому в
Synchronize
нужно помещать только те действия, которые не могут быть выполнены неосновной нитью (например, обращения к свойствам и методам VCL-компонентов).
Главная петля сообщений в VCL реализуется методом
Application.Run
, вызов которого автоматически вставляется в dpr-файл VCL-проекта.
Application.Run
вызывает в цикле метод
HandleMessage
, пока поле
FTerminate
не окажется равным
True
(напомним, что значение
True
присваивается этому полю, когда
ProcessMessage
извлекает из очереди сообщение
WM_QUIT
, а также при обработке сообщения
WM_ENDSESSION
и при закрытии главной формы).
Для организации локальной петли сообщений существует метод
Application.ProcessMessages
. Он вызывает
ProcessMessage
до тех пор, пока очередь не окажется пустой. Вызов этого метода рекомендуется вставлять в обработчики событий, которые работают долго, чтобы в это время программа не теряла способности реагировать на действия пользователя.
Из сказанного может сложиться впечатление, что главная нить проверяет список методов синхронизации только в главной петле сообщений, когда вызывается метод
Idle
. На самом деле это не так. Модуль
Classes
содержит переменную
WakeMainThread
, хранящую указатель на метод, который вызывается при помещении нового метода в список синхронизации. В конструкторе
TApplication
этой переменной присваивается указатель на метод
TApplication.WakeMainThread
, который посылает сообщение
WM_NULL
невидимому окну приложения. Сообщение
WM_NULL
— это "пустое" сообщение, на которое окно не должно реагировать (оно используется, например, при перехвате сообщений ловушкой: ловушка не может запретить передачу окну сообщения, но может изменить его на
WM_NULL
, чтобы окно проигнорировало сообщение). Невидимое окно приложения, тем не менее, не игнорирует это сообщение, а вызывает при его получении
CheckSynchronize
. Таким образом, синхронное выполнение метода не откладывается до вызова
Idle
, а выполняется достаточно быстро, в том числе и в локальной петле сообщений. Более того, если главная нить перешла в режим ожидания получения сообщения (через вызов
WaitMessage
), то вызов
Synchronize
в другой нити прервет это ожидание, т.к. в очередь будет поставлено сообщение
WM_NULL
.
Процедура
CheckSynchronize
и переменная
WakeMainThread
позволяют обеспечить синхронизацию и в тех приложениях, которые не используют VCL в полном объеме. Разработчику приложения необходимо обеспечить периодические вызовы функции
CheckSynchronize
из главной нити, чтобы можно было вызывать
TThread.Synchronize
в других нитях. При этом в главной нити можно обойтись без петли сообщений. Присвоение переменной
WakeMainThread
собственного метода позволяет реализовать специфичный для данного приложения способ ускорения вызова метода в главной нити.