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

ЖАНРЫ

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

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

Шрифт:

Внутри

ShowModal
создается своя петля сообщений. В этой петле в цикле вызывается метод
Application.HandleMessage
до тех пор, пока не будет установлено свойство
ModalResult
или не придет сообщение
WM_QUIT
. После завершения этой петли вновь разрешаются все окна, которые были разрешены до вызова
ShowModal
, а "модальная" форма закрывается. В отличие от системных модальных диалогов модальная форма VCL во время своей активности не посылает родительскому окну сообщение
WM_ENTERIDLE
, но благодаря тому, что "модальная" петля сообщений использует
HandleMessage
, будет вызываться
Idle
, а значит, будет возникать событие
Application.OnIdle
, которое позволит выполнять фоновые действия.

Теперь рассмотрим,

как 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-координату.

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