首页 > 解决方案 > 为什么 Canvas 在所有 VCL 控件中都“隐藏”?

问题描述

我想做一个基本程序,在任何控件(按钮、面板等)画布上绘制一些东西(为了简单起见,我们说一个三角形):

procedure DrawTriangle(Control: TCustomControl);

在这个函数中,我需要使用 Control.Width 和 Control.Height 来知道控件有多大。事实证明比想象的要困难,因为 Canvas 受到保护。

一种解决方案是在过程中获取控件的画布:

VAR
   ParentControl: TWinControl;
   canvas: TCanvas;
begin
 ParentControl:= Control.Parent;
 Canvas:= TCanvas.Create;
 TRY
  Canvas.Handle:= GetWindowDC(ParentControl.Handle);
  WITH Canvas DO
    xyz
 FINALLY
   FreeAndNil(canvas);
 END;
end;

但是每次我想画一些东西时,创建和销毁画布似乎都浪费了 CPU……

所以,我的问题是:

  1. 为什么画布被设计隐藏(保护)?
  2. 如何优雅地解决这个问题(一个参数)而不浪费 CPU?

现在我重写了 Paint 方法,但这意味着在几个地方重复绘制代码。当然,DrawTriangle 可以接收更多参数(Canvas、Control Width/Height 等),....但好吧...使用公开的 Paint 方法,一切都会更加优雅。

标签: delphicanvasvcllazarus

解决方案


在对该问题的评论中,事实证明

  1. TCustomControl将此解决方案仅限于后代就足够了,并且
  2. 如果绘图过程可以通过简单的函数调用从参数控件中获取画布,那就足够“优雅”了。

如果是这样,以下解决方案是可能的:

//
// Infrastructure needed
//

type
  TCustomControlCracker = class(TCustomControl)
  end;

function CustomControlCanvas(AControl: TCustomControl): TCanvas;
begin
  Result := TCustomControlCracker(AControl).Canvas;
end;

//
// My reusable drawing functions
// (Can only be used in TCustomControl descendants)
//

procedure DrawFrog(AControl: TCustomControl);
var
  Canvas: TCanvas;
begin
  Canvas := CustomControlCanvas(AControl);
  Canvas.TextOut(10, 10, 'Frog');
end;

请注意,DrawFrog它只接受一个参数,即控件本身。然后它可以使用一个简单的函数调用以极少的 CPU 开销获取控件的画布。

完整示例:

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls;

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

type
  TTestControl = class(TCustomControl)
  protected
    procedure Paint; override;
  end;

type
  TCustomControlCracker = class(TCustomControl)
  end;

function CustomControlCanvas(AControl: TCustomControl): TCanvas;
begin
  Result := TCustomControlCracker(AControl).Canvas;
end;

procedure DrawFrog(AControl: TCustomControl);
var
  Canvas: TCanvas;
begin
  Canvas := CustomControlCanvas(AControl);
  Canvas.TextOut(10, 10, 'Frog');
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  with TTestControl.Create(Self) do
  begin
    Parent := Self;
    Top := 100;
    Left := 100;
    Width := 400;
    Height := 200;
  end;
end;

{ TTestControl }

procedure TTestControl.Paint;
begin
  inherited;
  Canvas.Brush.Color := clSkyBlue;
  Canvas.FillRect(ClientRect);
  DrawFrog(Self); // use my reusable frog-drawing function
end;

end.

尽管如此,我个人仍然会使用传递 a TCanvas(甚至 a HDC)而不是控件的标准方法,以及一些维度:

procedure DrawFrog(ACanvas: TCanvas; const ARect: TRect);

这将允许我将它用于其他控件(不仅是TCustomControl后代),以及打印机画布等。


推荐阅读