"О чём не пишут в книгах по Delphi" - читать интересную книгу автора (Григорьев А. Б.)

1.2.3. Пример CoordLabel

CoordLabel — это пример визуального компонента, перехватывающего сообщения своего родителя. Компонент TCoordLabel отслеживает нажатие левой кнопки мыши на своем родителе и отображает координаты точки, в которой произошло нажатие. Для перехвата сообщений родителя используется тот же способ через свойство WindowProc, что и в предыдущем примере, но т.к. теперь перехватываются сообщения родителя, а не владельца, появляются некоторые нюансы.

Установка компонента TCoordLabel полностью аналогична установке компонента TLine из предыдущего раздела. На прилагаемом компакт-диске находится также проект LineCoordSample для того, чтобы работу компонента можно было увидеть без установки в палитру компонентов. На форме проекта LineCoordSample находится панель, кнопка Переместить и компонент TLineCoordSample, который по нажатию кнопки меняет родителя с формы на панель и обратно.

Код компонента TCoordLabel приведен в листинге 1.28.

Листинг 1.28. Компонент TCoordLabel

type

 TCoordLabel = class(TLabel)

private

 // Здесь хранится адрес обработчика

 // сообщений, бывший до перехвата.

 FOldProc: TWndMethod;

protected

 procedure SetParent(AParent: TWinControl); override;

 // Этот метод будет новым обработчиком

 // сообщений владельца

 procedure HookParentMessage(var Msg: TMessage);

end;

...


procedure TCoordLabel.SetParent(AParent: TWinControl);

begin

 if Assigned(Parent) and Assigned(FOldProc) then Parent.WindowProc := FOldProc;

 inherited;

 if Assigned(Parent) then

 begin

  FOldProc := Parent.WindowProc;

  Parent.WindowProc := HookParentMessage;

 end;

end;


procedure TCoordLabel.HookParentMessage(var Msg: TMessage);

begin

 if Msg.Msg = WM_LBUTTONDOWN then

  Caption := '(' + IntToStr(Msg.LParamLo) + ', ' + IntToStr(Msg.LParamHi) + ')';

 FOldProc(Msg);

end;

Класс TLabel, предок TCoordLabel, является визуальным компонентом и сам может получать и обрабатывать сообщения, поэтому метод Dispatch у него уже "занят". Соответственно, мы не можем диспетчеризовать с его помощью перехваченные сообщения и должны обрабатывать их внутри метода HookParentMessage.

Сам перехват осуществляется не в конструкторе, т.к. на момент вызова конструктора родитель компонента еще неизвестен. Он устанавливается позже, через свойство Parent, которое приводит к вызову виртуального метода SetParent. Мы перекрываем этот метод и выполняем в нем как восстановление обработчика старого родителя, так и перехват сообщений нового. Это позволяет компоненту менять родителя во время работы программы. Писать отдельно деструктор для восстановления оригинального обработчика родителя в данном случае нужды нет, поскольку деструктор, унаследованный от TControl, содержит вызов метода SetParent с параметром nil. Так как мы уже перекрыли SetParent, это приведет к восстановлению оригинального обработчика, т.е. к тому, что нам нужно.

Если на форму, содержащую TCoordLabel, поместить другие компоненты можно заметить, что TCoordLabel отлавливает нажатия мыши, сделанные на неоконных компонентах, но игнорирует те, которые сделаны на оконных. Это происходит потому, что неоконные компоненты получают сообщения через оконную процедуру родителя (которая перехвачена), а оконные имеют свою оконную процедуру, никак не связанную с оконной процедурой родителя. И, разумеется, компонент TCoordLabel имеет те же проблемы с восстановлением оригинального обработчика, что и TLine, если на одном родителе расположены несколько компонентов. Соответственно, применять TCoordLabel необходимо аккуратно, с учетом возможных последствий.