1. TCustomControl – окно и графика

Класс 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 устроен изнутри, разберемся какие существуют варианты его использования для разработки элементов управления.