Напомню, что большинство элементов управления в операционной системе Windows является окнами. Каждое окно, зарегистрированное в системе, получает системный дескриптор. Этот дескриптор в TWinControl доступен для чтения через свойство Handle, которое и является ссылкой на идентификатор окна базового элемента управления.
С технической точки зрения, окно является записью во внутренней системной таблице, которая соответствует элементу, отображаемому на экране и, как следствие, обладающему связанным с ним исполняемым кодом. Этот «код» иногда называют оконной процедурой. Каждому окну ставится в соответствие так называемая оконная процедура, которая и определяет «поведение» окна. Когда происходит какое-либо событие, одному из окон, ответственному в данный момент за обработку событий данного типа, посылается сообщение. Окно, получившее это сообщение, должно, разумеется, его обработать. Эту обработку и осуществляет оконная функция, в VCL называемая WndProc.
Пример 7.1. Принцип работы оконной процедуры:
Procedure TmyObject.WndProc(var Message:TMessage) begin Case Message.msg of WM_Paint:DoSomething; WM_Char:DoSomething; else Inherited; end end;
Другими словами, в Delphi любой из классов, порожденных от TWinControl, перекрывает метод WndProc. Того же эффекта можно достигнуть при назначении нового значения свойства WindowProc. Несмотря на то, что приведенный в примере код внешне упрощает разработку компонента, желательно все-таки использовать специальные обработчики событий или то, что VCL преобразует события системы в события библиотеки VCL, т.е. работать с ними на высоком уровне.
Как уже сказано во введении, важнейшим свойством TWinControl является то, что он может содержать другие «дочерние» элементы управления. Они упорядочены в виде списка. Методы и свойства для работы с этим списком приведены в таблице:
property Controls[Index: Integer]: TControl; | Содержит список дочерних элементов. «Только для чтения» |
property ControlCount: Integer; | Содержит число элементов в списке. «Только для чтения» |
function ContainsControl(Control: TControl): Boolean; | Проверяет наличие элемента в списке. |
function ControlAtPos(const Pos: TPoint; AllowDisabled: Boolean): TControl; | Отыскивает в списке элемент, которому принадлежит заданная точка (в системе координат собственной клиентской области). Флаг AllowDisabled показывает, разрешен ли поиск среди недоступных (Enabled=False) элементов. |
procedure InsertControl(AControl: TControl); | Вставляет элемент в конец списка. |
procedure RemoveControl(AControl: TControl; | Удаляет элемент из списка. |
procedure Broadcast(var Message); | Рассылает всем дочерним элементам из списка сообщение Message. |
Теперь рассмотрим метод CreateWnd, который вызывается каждый раз, когда необходимо создать окно и получить его идентификатор. При этом CreateWnd вызывает метод CreateParams для установки настроек создаваемого окна, а затем метод CreateWindowHandle – для создания окна и получения его идентификатора. Очень часто наследники TWinControl переопределяют методы CreateWnd и CreateParams.
Метод CreateParams переопределяется, если требуется установить специфические настройки окна компонента, отличные от стандартных. Наиболее часто настраиваются:
Style | Стиль окна. Массив битовых флагов WS_*. Константы стилей объявлены в модуле WIndows Полный список стилей можно найти в Windows SDK в разделе CreateWindow. |
Caption | Заголовок окна. Как правило, совпадает со свойством Caption или Text |
X, Y | Координаты окна |
Width, Height | Ширина и высота окна |
Метод CreateWnd переопределяется, если при создании окна нужно произвести дополнительные действия. При переопределении методов будьте осторожны. Не забывайте вызвать inherited-метод чтобы обеспечить выполнение всех необходимых действий при создании окна.
Ниже приведен пример переопределения метода CreateParams для включения в стиль окна полосы прокрутки и метода CreateWnd для показа полосы.
Пример 7.2. Пример переопределения метода CreateParams
procedure TScrollListBox.CreateParams(var Params: TCreateParams); begin inherited; Params.Style:=Params.Style or WS_HSCROLL; end; procedure TScrollListBox.CreateWnd; begin inherited; Perform(LB_SETHORIZONTALEXTENT,2,0); ShowScrollBar(Handle,SB_HORZ,true) end;
Так как любое окно в среде Windows может иметь дочерние окна, TWinControl предоставляет возможность задавать объекту своего класса родительское окно, не принадлежащее VCL-компоненту. Для этого существует свойство ParentWindow, которое и определяет такое родительское окно для элемента управления.
Для создания элемента управления с родительским окном в класс TWinControl введены два конструктора, объявленных следующим образом:
constructor CreateParented(ParentWindow: HWnd); class function CreateParentedControl(ParentWindow: HWnd): TWinControl;
Пример 7.3. Пример кода, размещающего кнопку на рабочем столе Windows
procedure TForm1.Button4Click(Sender: TObject); begin with TButton.Create(Self) do begin ParentWindow:=GetDesktopWindow; SetBounds(100,100,200,30); Caption:='Кнопка на рабочем столе!'; end; end;
Что касается пользовательской процедуры отрисовки компонентов, то считается, что в компонентах-наследниках TWinControl не реализуется пользовательская отрисовка компонента.
Да, действительно, свойств Canvas и метода Paint в TWinControl нет. Каким же образом тогда происходит прорисовывание элементов управления? Ответ на этот вопрос очень прост – элементы управления, напрямую порожденные от этого класса, умеют прорисовывать себя сами. Обычно это либо стандартные элементы управления Windows, либо ActiveX компоненты.
Но, справедливости ради, следует отметить, что, все же рисовать на оконных компонентах можно средствами API, как показано в примере.
Пример 7.4. Пример пользовательского рисования в оконном элементе управления
procedure TPointEdit.WMPAINT(var M: TWMPaint); var DC: HDC; Pen, SavePen: HPEN; begin Inherited; if (EditPoint.X=0) and (EditPoint.Y=0) then begin DC:=GetDC(Handle); try Pen := CreatePen(PS_SOLID, 1, clRed); SelectObject(DC, Pen); SavePen := SelectObject(DC, Pen); Rectangle(DC, 0, 0, ClientWidth, ClientHeight); if SavePen <> 0 then SelectObject(DC, SavePen); DeleteObject(Pen); except ReleaseDC(Handle,DC) end; end; end;