Mengapa Canvas "tersembunyi" di semua kontrol VCL?

Nov 28 2020

Saya ingin melakukan prosedur dasar yang menggambar sesuatu (katakanlah segitiga, untuk kesederhanaan) pada kanvas kontrol (tombol, panel, dll):

procedure DrawTriangle(Control: TCustomControl);

Dalam fungsi ini saya perlu menggunakan Control.Width & Control.Height untuk mengetahui seberapa besar kontrolnya. Ternyata lebih sulit dari yang dibayangkan karena Canvas dilindungi.

Solusinya adalah mendapatkan kanvas kontrol di dalam prosedur:

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;

Tapi sepertinya membuang-buang CPU untuk membuat dan menghancurkan kanvas setiap kali saya ingin melukis sesuatu ...

Jadi, pertanyaan saya adalah:

  1. Mengapa kanvas disembunyikan (dilindungi) oleh desain?
  2. Bagaimana mengatasi ini dengan elegan (satu parameter tunggal) dan tanpa membuang CPU?

Sekarang saya mengganti metode Paint, tetapi ini berarti duplikasi kode lukisan di beberapa tempat. Tentu saja, DrawTriangle dapat menerima lebih banyak parameter (Canvas, Control Width / Height dll), .... tapi yah ... dengan metode Paint yang terbuka, semuanya akan jauh lebih elegan.

Jawaban

2 AndreasRejbrand Nov 28 2020 at 16:49

Dalam komentar atas pertanyaan itu ternyata

  1. solusi ini cukup dibatasi untuk TCustomControlketurunan, dan
  2. itu cukup "elegan" jika prosedur menggambar bisa mendapatkan kanvas dari kontrol argumen dengan pemanggilan fungsi sederhana.

Jika demikian, solusi berikut mungkin dilakukan:

//
// 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;

Perhatikan bahwa DrawFroghanya membutuhkan satu parameter, kontrol itu sendiri. Dan kemudian dapat memperoleh kanvas kontrol menggunakan panggilan fungsi sederhana dengan overhead CPU yang sangat sedikit.

Contoh lengkap:

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.

Semua ini dikatakan, bagaimanapun, saya pribadi akan tetap menggunakan pendekatan standar melewati TCanvas(atau bahkan a HDC) alih-alih kontrol, bersama dengan beberapa dimensi:

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

Ini akan memungkinkan saya untuk menggunakannya untuk kontrol lain juga (tidak hanya TCustomControlturunan), serta kanvas printer, dll.

fpiette Nov 28 2020 at 14:27

Mengapa kanvas disembunyikan oleh desain?

Tidak benar-benar tersembunyi, tetapi di bagian terlindungi. Untuk mengaksesnya, Anda harus mendapatkan kelas baru dari yang Anda minati dan menyatakan Canvas sebagai publik.

Ini pribadi karena Anda tidak seharusnya mengaksesnya di tingkat aplikasi.

Anda tidak perlu menginstal komponen Anda jika Anda menggunakan interposerkelas di sumber yang Anda butuhkan.

Anda juga dapat mempertimbangkan untuk mengganti Paintmetode dan meletakkan kode gambar Anda di sana.