2. Класс TWinControl – предок оконных элементов управления

Напомню, что большинство элементов управления в операционной системе Windows является окнами. Каждое окно, зарегистрированное в системе, получает системный дескриптор. Этот дескриптор в TWinControl доступен для чтения через свойство Handle, которое и является ссылкой на идентификатор окна базового элемента управления.

2.1. Оконная функция WndProc

С технической точки зрения, окно является записью во внутренней системной таблице, которая соответствует элементу, отображаемому на экране и, как следствие, обладающему связанным с ним исполняемым кодом. Этот «код» иногда называют оконной процедурой. Каждому окну ставится в соответствие так называемая оконная процедура, которая и определяет «поведение» окна. Когда происходит какое-либо событие, одному из окон, ответственному в данный момент за обработку событий данного типа, посылается сообщение. Окно, получившее это сообщение, должно, разумеется, его обработать. Эту обработку и осуществляет оконная функция, в 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, т.е. работать с ними на высоком уровне.

2.2. Дочерние элементы

Как уже сказано во введении, важнейшим свойством TWinControl является то, что он может содержать другие «дочерние» элементы управления. Они упорядочены в виде списка. Методы и свойства для работы с этим списком приведены в таблице:

Таблица 7.1. Основные свойства и методы 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.

2.3. Методы создания окна

Теперь рассмотрим метод CreateWnd, который вызывается каждый раз, когда необходимо создать окно и получить его идентификатор. При этом CreateWnd вызывает метод CreateParams для установки настроек создаваемого окна, а затем метод CreateWindowHandle – для создания окна и получения его идентификатора. Очень часто наследники TWinControl переопределяют методы CreateWnd и CreateParams.

Метод CreateParams переопределяется, если требуется установить специфические настройки окна компонента, отличные от стандартных. Наиболее часто настраиваются:

Таблица 7.2. Основные настройки окна
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;

2.4. Родительское окно

Так как любое окно в среде 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; 

2.5. Пользовательская отрисовка TWinControl

Что касается пользовательской процедуры отрисовки компонентов, то считается, что в компонентах-наследниках 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;