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

ЖАНРЫ

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

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

Шрифт:

"Совместимыми" с

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)

 ...............

 procedure WMNCMouseMove(var Message: TWMNCMouseMove); message WM_NCMOUSEMOVE;

 ................

end;

procedure TSomeForm.WMNCMouseMove(var Message: TWMNCMouseMove);

begin

 ...............

 inherited; // Возможно, этот вызов не будет нужен

end;

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

inherited
позволяет вызвать унаследованный обработчик для данного сообщения. Таким образом, может образовываться целая цепочка вызовов унаследованных обработчиков одного и того же сообщения, каждый из которых выполняет свою часть обработки. Если у предков класса нет обработчика данного сообщения, директива
inherited
передает управление методу
TObject.DetaultHandler
. Вернемся к методу
Dispatch
. Он ищет среди обработчиков сообщения класса (собственных или унаследованных) метод для обработки сообщения, заданного полем
Msg
параметра
Message
и, если находит, передает управление ему. Если ни сам класс, ни его предки не содержат обработчика данного сообщения, то обработка передаётся методу
DefaultHandler
.

Метод

DefaultHandler
виртуальный, в классе
TObject
он не выполняет никаких действий, но наследники его переопределяют. Впервые он переопределяется в классе
TControl
для обработки сообщений, связанных с получением и установкой заголовка окна —
WM_GETTEXT
,
WM_GETTEXTLENGTH
и
WM_SETTEXT
. Напомним, что класс TControl является предком для всех визуальных компонентов, а не только оконных, и появление обработчика системных сообщений в этом классе — часть той имитации обработки сообщений неоконными компонентами, о которой мы уже говорили.

В классе

TWinControl
метод
DefaultHandler
также переопределен. Помимо передачи некоторых сообщений дочерним окнам (об этом мы будем подробнее говорить чуть позже) и обработки некоторых внутренних сообщений он вызывает оконную процедуру, адрес которой хранится в свойстве
DefWndProc
. Это свойство содержит адрес, который был присвоен полю
WindowClass.lpfnWndProc
структуры
TCreateParams
в методе
CreateParams
. По умолчанию это поле содержит адрес стандартной оконной процедуры
DefWindowProc
. Как было сказано ранее, обработка сообщений при использовании API обычно завершается вызовом этой процедуры. В классе
TCustomForm
метод
DefaultHandler
также переопределен, если форма является MDI-формой, сообщения, присланные ей, передаются в процедуру
DefFrameProc
(за исключением
WM_SIZE
, которое передается в
DefWindowProc
) независимо от того, какое значение имеет свойство
DefWindowProc
. Для всех остальных типов форм вызывается унаследованный от
TWinControl DefaultHandler
.

Повторим еще раз всю цепочку обработки сообщений оконными компонентами VCL (рис. 1.7). Для каждого компонента создается уникальная оконная процедура, которая передает управление методу

MainWndProc
.
MainWndProc
передает управление методу, указатель на который хранится в свойстве
WindowProc
. По умолчанию это метод компонента
WndProc
. Он осуществляет обработку некоторых сообщений, но в большинстве случаев передает управление методу
Dispatch
,
который ищет среди методов компонента или его предков обработчик данного сообщения. Если обработчик не найден, управление получает метод
DefaultHandler
(он может также получить управление и в том случае, если обработчик найден, но он вызывает
inherited
).
DefaultHandler
самостоятельно обрабатывает некоторые сообщения, но большинство из них передаётся оконной процедуре, адрес хранится в свойстве
DefWndProc
(по умолчанию это стандартная функция Windows API
DefWindowProc
).

Рис. 1.7. Блок-схема оконной процедуры оконных компонентов VCL

Класс

TControl
имеет метод
Perform
, с помощи которого можно заставить визуальный компонент выполнить обработку конкретного сообщения в обход оконной процедуры и системного механизма передачи сообщений.
Perform
приводит к непосредственному вызову метода, указатель на который хранится в свойстве
WindowProc
. Дальше цепочка обработки сообщений такая же, как и при получении сообщения через оконную процедуру. Для оконных компонентов вызов
Perform
по своим последствиям практически эквивалентен передаче сообщения с помощью
SendMessage
с двумя исключениями. Во-первых, при использовании
SendMessage
система обеспечивает переключение между нитями, и сообщение будет выполнено в той нити, которая создала окно, a
Perform
никакого переключения не производит, и обработка сообщения будет выполнена той нитью, которая вызвала
Perform
. Поэтому
Perform
, в отличие от
SendMessage
, можно использовать только в главной нити (напомним, что VCL — принципиально однонитевая библиотека, и создание форм вне главной нити с ее помощью недопустимо). Во-вторых,
Perform
выполняется чуть быстрее, т.к. оконная процедура и метод
MainWndProc
исключаются из цепочки обработки сообщения.

Но основное преимущество

Perform
перед
SendMessage
заключается в том, что
Perform
пригоден для работы со всеми визуальными компонентами, а не только с оконными. Неоконные визуальные компоненты не могут иметь оконной процедуры, но цепочка обработки сообщений у них есть. В ней отсутствует оконная процедура и метол
MainWndProc
, a
DefaultHandler
не вызывает никаких стандартных оконных процедур, но во всем остальном эта цепочка полностью эквивалентна цепочке оконных компонентов. Таким образом, цепочка обработки сообщений оконных компонентов имеет две точки входа: оконную процедуру и метод
Perform
, а цепочка неоконных компонентов — только метод
Perform
. Следовательно, метод
Perform
универсален: он одинаково хорошо подходит как для оконных, так и для неоконных компонентов. Он широко применяется в VCL, т.к. позволяет единообразно работать с любыми визуальными компонентами.

Неоконным визуальным компонентам сообщения посылает их родительское окно. Например, как мы уже говорили, обработка сообщений, связанных с мышью, в классе

TWinControl
включает в себя, не попадают ли координаты курсора в область какого-либо из дочерних неоконных компонентов. И если попадает, оконный компонент не обрабатывает это сообщение самостоятельно, а транслирует его соответствующему неоконному компоненту с помощью 
Perform
. Эта трансляция и обеспечивает получение сообщений неоконными компонентами.

Сообщения в VCL транслируются не только неоконным, но и оконным компонентам. В Windows все сообщения, информирующие об изменении состояния стандартных элементов управления, получает их родительское окно, а не сам элемент. Например, при нажатии на кнопку уведомительное сообщение об этом получает не сама кнопка, а окно, ее содержащее. Сама кнопка получает и обрабатывает только те сообщения, которые обычно разработчику неинтересны. Это упрощает работу программиста, т.к. не требуется для каждого элемента управления писать свою оконную процедуру, все значимые сообщения получает оконная процедура родительского окна. Рассмотрим, что происходит при нажатии кнопки на форме. Окно, содержащее эту кнопку, получает сообщение

WM_COMMAND
, уведомляющее о возникновении события среди оконных компонентов. Параметры сообщения позволяют определить, какое именно событие и с каким элементом управления произошло (в данном случае событие будет
BN_CLICKED
). Обработчик
WM_COMMAND
класса
TWinControl
находит компонент, вызвавший сообщение, и посылает ему сообщение
CN_COMMAND
(как видно из префикса, это внутреннее сообщение VCL) с теми же параметрами. В нашем примере это будет экземпляр класса
TButton
, реализующий кнопку, которую нажал пользователь. Получив
CN_COMMAND
, компонент начинает обработку произошедшего с ним события (в частности,
TButton
инициирует событие
OnСlick
).

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