Dlaczego Canvas jest „ukryty” we wszystkich kontrolkach VCL?

Nov 28 2020

Chcę wykonać podstawową procedurę, która rysuje coś (na przykład trójkąt, dla uproszczenia) na kanwie dowolnego elementu sterującego (przycisku, panelu itp.):

procedure DrawTriangle(Control: TCustomControl);

W tej funkcji muszę użyć Control.Width & Control.Height, aby wiedzieć, jak duża jest kontrola. Okazuje się, że jest to trudniejsze niż sobie wyobrażano, ponieważ Canvas jest chroniony.

Rozwiązaniem byłoby uzyskanie kanwy kontroli wewnątrz procedury:

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;

Ale wydaje się, że to strata procesora, aby tworzyć i niszczyć płótno za każdym razem, gdy chcę coś namalować ...

Więc moje pytania to:

  1. Dlaczego płótno zostało ukryte (chronione) zgodnie z projektem?
  2. Jak rozwiązać to elegancko (jeden parametr) i bez marnowania procesora?

Teraz zastępuję metodę Paint, ale oznacza to powielenie kodu malowania w kilku miejscach. Oczywiście DrawTriangle mógłby otrzymać więcej parametrów (Canvas, Control Width / Height itp.), Ale cóż ... przy odsłoniętej metodzie malowania wszystko byłoby o wiele bardziej eleganckie.

Odpowiedzi

2 AndreasRejbrand Nov 28 2020 at 16:49

W komentarzu do pytania okazuje się, że

  1. wystarczy ograniczyć to rozwiązanie do TCustomControlpotomków, a
  2. jest wystarczająco „elegancki”, jeśli procedura rysowania może uzyskać kanwę z kontrolki argumentu za pomocą prostego wywołania funkcji.

Jeśli tak, możliwe jest następujące rozwiązanie:

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

Zwróć uwagę, że DrawFrogprzyjmuje tylko jeden parametr, samą kontrolkę. Następnie może uzyskać kanwę kontrolki za pomocą prostego wywołania funkcji z bardzo małym obciążeniem procesora.

Pełny przykład:

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.

Mając to wszystko na uwadze, osobiście nadal stosowałbym standardowe podejście polegające na przekazaniu a TCanvas(lub nawet a HDC) zamiast kontroli, wraz z niektórymi wymiarami:

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

Pozwoli mi to używać go również do innych kontrolek (nie tylko TCustomControlpotomków), a także do płótna drukarki itp.

fpiette Nov 28 2020 at 14:27

Dlaczego płótno zostało ukryte przez projekt?

Nie jest naprawdę ukryty, ale w chronionej sekcji. Aby uzyskać do niego dostęp, musisz wyprowadzić nową klasę z tej, która Cię interesuje i zadeklarować Canvas jako publiczną.

Jest prywatny, ponieważ nie powinieneś uzyskiwać do niego dostępu na poziomie aplikacji.

Nie musisz instalować komponentu, jeśli używasz interposerklasy w źródle, którego potrzebujesz.

Możesz również rozważyć zastąpienie Paintmetody i umieścić tam swój kod rysowania.