CanvasがすべてのVCLコントロールで「非表示」になっているのはなぜですか?

Nov 28 2020

コントロール(ボタン、パネルなど)のキャンバスに何か(簡単にするために三角形など)を描画する基本的な手順を実行したいと思います。

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を無駄にすることなく、これをエレガントに(1つのパラメーターで)解決するにはどうすればよいですか?

現在、Paintメソッドをオーバーライドしていますが、これは、ペイントコードをいくつかの場所で複製することを意味します。もちろん、DrawTriangleはより多くのパラメーター(Canvas、Control Width / Heightなど)を受け取ることができますが、....しかし、公開されたPaintメソッドを使用すると、すべてがはるかにエレガントになります。

回答

2 AndreasRejbrand Nov 28 2020 at 16:49

質問へのコメントでは、

  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.

ただし、これはすべて、個人的には、コントロールの代わりにTCanvas(またはHDC)を渡すという標準的なアプローチを、いくつかのディメンションとともに使用します。

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

これにより、他のコントロール(TCustomControl子孫だけでなく)や、プリンターのキャンバスなどにも使用できるようになります。

fpiette Nov 28 2020 at 14:27

なぜキャンバスはデザインによって隠されたのですか?

本当に隠されているわけではありませんが、保護されたセクションにあります。これにアクセスするには、関心のあるクラスから新しいクラスを派生させ、Canvasをパブリックとして宣言する必要があります。

アプリケーションレベルでアクセスすることは想定されていないため、プライベートです。

必要なinterposerソースでクラスを使用する場合は、コンポーネントをインストールする必要はありません。

Paintメソッドをオーバーライドして、そこに描画コードを配置することも検討してください。