Ознакомительная версия.
Теперь рассмотрим, как VCL обрабатывает извлеченные из очереди сообщения. Как уже было сказано ранее, для каждого класса формы VCL регистрирует одноименный оконный класс, а все окна, принадлежащие одному оконному классу, имеют общую оконную процедуру. С другой стороны, логика работы VCL требует, чтобы события обрабатывались тем экземпляром oбъекта, который инкапсулирует окно-адресат. Таким образом, возникает вопрос о том, как передать сообщение заданному экземпляру класса VCL. VCL решает эту задачу следующим образом. Модуль Classes содержит недокументированную функцию MakeObjectInstance, описанную так:
type TWndMethod = procedure(var Message: TMessage) of object;
function MakeObjectInstance(Method: TWndMethod): Pointer;
Тип TMessage хранит информацию о сообщении. Все методы VCL-компонентов, связанные с обработкой сообщения, используют этот тип (чуть позже мы рассмотрим его более подробно).
Функция MakeObjectInstance динамически формирует новую оконную процедуру и возвращает указатель на нее (следовательно, любое VCL-приложение содержит самомодифицирующийся код). Задача этой динамически созданной процедуры — передать управление тому методу, который был указан при вызове MakeObjectInstance (таким образом, различные оконные процедуры, сформированные этой функцией, отличаются только тем, метод MainWndProc какого экземпляра класса они вызывают).
Каждый экземпляр оконного компонента создает свою оконную процедуру, которая передает обработку сообщения его методу MainWndProc. Указатель на эту процедуру записывается в поле FObjectInstance. Как мы уже говорили в предыдущем разделе, при регистрации оконного класса в качестве оконной процедуры указывается InitWndProc, которая при получении первого сообщения создает подкласс, и оконной процедурой назначается та, указатель на которую хранится в поле FObjectInstance, т.е. функция, созданная с помощью MakeObjectInstance (см. листинг 1.12). Таким образом, каждый экземпляр получает свою оконную процедуру, а обработку сообщения начинает метод MainWndProc.
MainWndProc — это невиртуальный метод, обеспечивающий решение технических вопросов: удаление "мусора", оставшегося при обработке сообщения и обработку исключений. Собственно обработку сообщения он передает методу, на который указывает свойство WindowProc. Это свойство имеет тип TWndMethod и по умолчанию указывает на виртуальный метод WndProc. Таким образом, если разработчик не изменял значения свойства WindowProc, обработкой сообщения занимается WndProc.
Метод WndProc обрабатывает только те сообщения, которые должны быть обработаны специальным образом, чтобы поддержать функциональность VCL. Особым образом метод WndProc обрабатывает сообщения от мыши: он следит, в границы какого визуального компонента попадают координаты "мышиных" сообщений, и если этот компонент отличается от того, в чью область попало предыдущее сообщение, компоненту из предыдущего сообщения дается команда обработать сообщение CM_MOUSELEAVE, а новому — сообщение CM_MOUSENTER. Это обеспечивает реакцию визуальных компонентов на приход и уход мыши (в частности, генерирование событий OnMouseEnter и OnMouseExit). Необходимость реализации такого способа отслеживания прихода и ухода мыши вместо использования системных сообщений WM_MOUSEHOVER и WM_MOUSELEAVE связана с тем, что системные сообщения пригодны только для работы с окнами, а VCL отслеживает приход и уход мыши и на неоконные визуальные компоненты. Впрочем, WM_MOUSELEAVE в WndProc тоже служит дополнительным средством проверки ухода мыши.
Примечание
Описанный здесь способ отслеживание ухода и прихода мыши реализован, начиная с BDS 2006. В более ранних версиях Delphi за это отвечал метод Application.Idle, который, как мы помним, вызывается только тогда когда в очереди нет сообщений. Из-за этого иногда (например, при быстром движении мышью) события ухода и прихода мыши пропускались, нарушая логику работы программы. Поэтому в BDS 2006 способ контроля прихода и ухода мыши был изменен, и ответственность за это возложена на метод TWinControl.WndProc. Это позволило избавиться от одного недостатка — потери событий, но породило другой: теперь перехват и самостоятельная обработка "мышиных" сообщений до того, как это сделает метод WndProc, может привести к потере возможности отслеживания прихода и ухода мыши. Впрочем, эта проблема проявляется только при выполнении программистом определенных осмысленных действий по внедрению кода в оконную процедуру, поэтому она гораздо менее серьезна, чем та от которой удалось избавиться.
События мыши метод WndProc диспетчеризует самостоятельно, без помощи функции DispatchMessage. Это связано с тем, что DispatchMessage передаёт сообщение тому оконному компоненту, которому оно предназначено с точки зрения системы. Однако с точки зрения VCL этот компонент может являться родителем для неоконных визуальных компонентов, и если сообщение от мыши связано с их областью, то оно должно обрабатываться соответствующим неоконным компонентом, а не его оконным родителем. DispatchMessage ничего о неоконных компонентах не "знает" и не может передать им сообщения, поэтому разработчикам VCL пришлось реализовывать свой способ. Те сообщения, которые метод WndProc не обрабатывает самостоятельно (а их — подавляющее большинство), он передает в метод Dispatch, который объявлен и реализован в классе TObject. На первый взгляд может показаться странным, что в самом базовом классе реализована функциональность, использующаяся только в визуальных компонентах. Эта странность объясняется тем, что разработчики Delphi встроили поддержку обработки сообщений непосредственно в язык. Методы класса, описанные с директивой message, служат специально для обработки сообщений. Синтаксис описания такого метода следующий:
procedure <Name>(var Message: <TMsgType>); message <MsgNumber>;
<MsgNumber> — это номер сообщения, для обработки которого предназначен метод. Имя метода может быть любым, но традиционно оно совпадает с именем константы сообщения за исключением того, что в нем выбран более удобный регистр символов и отсутствует символ "_" (например, метод для обработки WM_SIZE будет называться WMSize).
В качестве типа параметра <TMsgType> компилятор разрешает любой тип, но на практике имеет смысл только использование типа TMessage или "совместимого" с ним. Тип TMessage описан в листинге 1.14.
Листинг 1.14. Описание типа TMessage
TMessage = packed record
Msg: Cardinal;
case Integer of
0: (
WParam: LongInt;
LParam: LongInt;
Result: LongInt);
1: (
WParamLo: Word;
WParamHi: Word;
LParamLo: Word;
LParamHi: Word;
ResultLo: Word;
ResultHi: Word);
end;
Поле Msg содержит номер сообщения, поля WParam и LParam — значение одноименных параметров сообщения. Поле Result — выходное: метод, осуществляющий окончательную обработку сообщения, заносит в него то значение, которое должна вернуть оконная процедура. Поля с суффиксами Lo и Hi позволяют обращаться отдельно к младшему и старшему словам соответствующих полей, что может быть очень полезно, когда эти параметры содержат пару 16-разрядных значений. Например, у сообщения WM_MOUSEREMOVE младшее слово параметра LParam содержит X-координату мыши, старшее — Y-координату. В случае обработки этого сообщения поле LParamLo будет содержать X-координату, LParamHi — Y-координату.
"Совместимыми" с TMessage можно назвать структуры, которые имеют такой же размер, а также параметр Msg, задающий сообщение. Эти структуры учитывают специфику конкретного сообщения. Их имена образуются из имени сообщения путем отбрасывания символа и добавления префикса T. Для уже упоминавшегося сообщения WM_MOUSEMOVE соответствующий тип выглядит, как показано в листинге 1.15.
Листинг 1.15. Тип TWMNCMouseMove
TWMNCMouseMove = packed record
Msg: Cardinal;
HitTest: LongInt;
XCursor: SmallInt;
YCursor: SmallInt;
Result: LongInt;
end;
Параметр WParam переименован в HitTest, что лучше отражает его смысл в данном случае, а параметр LParam разбит на две 16-разрядных части: XCursor и YCursor.
Параметр метода для обработки сообщения имеет тип, соответствующий обрабатываемому сообщению (при необходимости можно описать свой тип), или тип TMessage. Таким образом, обработчик сообщения WM_MOUSEMOVE будет выглядеть так, как показано в листинге 1.16.
Листинг 1.16. Объявление и реализация метода для обработки сообщения WM_MOUSEMOVE
type
TSomeForm = class(TForm)
...............
Ознакомительная версия.