Класс TCustomControl объявлен в модуле Controls.
Рассмотрим это объявление подробней.
Пример 8.1. Объявление TCustomControl из модуля Controls
TCustomControl = class(TWinControl) private FCanvas: TCanvas; procedure WMPaint(var Message: TWMPaint); message WM_PAINT; protected procedure Paint; virtual; procedure PaintWindow(DC: HDC); override; property Canvas: TCanvas read FCanvas; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; end;
Первое, что понятно из объявления, так это то, что TCustomControl является наследником TWinControl, а, следовательно – настоящим оконным элементом управления.
Во-вторых, в классе реализован метод обработки сообщения WM_PAINT, который очень прост.
Пример 8.2. Метод обработки сообщения WM_PAINT класса TCustomControl
procedure TCustomControl.WMPaint(var Message: TWMPaint); begin Include(FControlState, csCustomPaint); inherited; Exclude(FControlState, csCustomPaint); end;
При получении сообщения на отрисовку он включает флаг пользовательской отрисовки в поле состояния элемента управления, с использованием ключевого слова Inherited вызывает метод отрисовки предка TWinControl, а затем исключает из поле состояния компонента.
Обратите внимание, что в методе реализовано обращение к приватному полу FControlState из TControl. Это возможно только потому, что эти классы объявлены в одном модуле.
В-третьих в классе вводится виртуальный метод Paint, одноименный с методом TGraphicControl.Paint
Метод TCustomControl.Paint является именно одноименным с методом TGraphicControl.Paint, а не переопределенным, так как эти классы находятся "в очень дальнем родстве" – они оба наследники TControl).
Реализация Paint предельна проста – в ней не делается ровным счетом ничего.
Пример 8.3. "Пустой" виртуальный метод TGraphicControl.Paint
procedure TCustomControl.Paint; begin end;
Может возникнуть резонный вопрос: "Для чего нужно объявлять метод, в котором ничего не выполняется?". Для того, чтобы он был, а точнее – чтобы использовать его в переопределенной виртуальной процедуре отрисовки окна PaintWindow.
Пример 8.4. Переопределенная виртуальная процедура отрисовки окна PaintWindow
procedure TCustomControl.PaintWindow(DC: HDC); begin FCanvas.Lock; try FCanvas.Handle := DC; try TControlCanvas(FCanvas).UpdateTextFlags; Paint; finally FCanvas.Handle := 0; end; finally FCanvas.Unlock; end; end;
Обратите внимание, что это весьма существенное продвижение относительно метода TWinConrol.PaintWindow, в котором лишь формируется сообщение на отрисовку контекста устройства и вызывается обработчик по умолчанию. Теперь, переопределив метод Paint, появилась возможность реализовать любую пользовательскую отрисовку элемента управления.
И, наконец, объявлено свойство Canvas, с использованием которого и становится возможным рисовать пользовательский элемент управления.
Объявление свойства может ввести в заблуждение относительно типа свойства Canvas. Можно подумать, что оно типа TCanvas. Несмотря на кажущуюся очевидность объявления это не так. И в этом легко убедиться, взглянув на реализацию переопределенного конструктора класса.
Пример 8.5. Конструктор и деструктор класса TCustomControl
constructor TCustomControl.Create(AOwner: TComponent); begin inherited Create(AOwner); FCanvas := TControlCanvas.Create; TControlCanvas(FCanvas).Control := Self; end; destructor TCustomControl.Destroy; begin FCanvas.Free; inherited Destroy; end;
Как видно из реализации конструктора класс TControlCanvas отличается от TCanvas наличием свойства Control, являющегося ссылкой на компонент, обладающий свойством Canvas. В остальном TControlCanvas аналогичен TCanvas.
Теперь, когда стало ясно как TCustomControl устроен изнутри, разберемся какие существуют варианты его использования для разработки элементов управления.