Warum ist Canvas in allen VCL-Steuerelementen „versteckt“?

Nov 28 2020

Ich möchte eine grundlegende Prozedur ausführen, die etwas (der Einfachheit halber ein Dreieck) auf die Leinwand eines Steuerelements (Schaltfläche, Bedienfeld usw.) zeichnet:

procedure DrawTriangle(Control: TCustomControl);

In dieser Funktion muss ich Control.Width & Control.Height verwenden, um zu wissen, wie groß das Steuerelement ist. Es stellt sich als schwieriger heraus als gedacht, da Canvas geschützt ist.

Eine Lösung wäre, die Zeichenfläche des Steuerelements innerhalb der Prozedur zu erhalten:

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;

Aber es scheint eine solche Verschwendung von CPU zu sein, jedes Mal, wenn ich etwas malen möchte, eine Leinwand zu erstellen und zu zerstören ...

Meine Fragen sind also:

  1. Warum wurde Leinwand durch Design versteckt (geschützt)?
  2. Wie kann man dies elegant lösen (ein einziger Parameter) und ohne CPU zu verschwenden?

Jetzt überschreibe ich die Malmethode, aber dies bedeutet, dass der Malcode an mehreren Stellen dupliziert wird. Natürlich könnte das DrawTriangle mehr Parameter erhalten (Canvas, Control Width / Height usw.), aber gut ... mit einer exponierten Paint-Methode wäre alles so viel eleganter gewesen.

Antworten

2 AndreasRejbrand Nov 28 2020 at 16:49

In einem Kommentar zur Frage stellt sich heraus, dass

  1. es reicht aus, wenn diese Lösung auf TCustomControlNachkommen beschränkt ist, und
  2. Es ist "elegant" genug, wenn die Zeichenprozedur die Zeichenfläche mit einem einfachen Funktionsaufruf aus dem Argumentsteuerelement abrufen kann.

In diesem Fall ist folgende Lösung möglich:

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

Beachten Sie, dass DrawFrognur ein einziger Parameter verwendet wird, das Steuerelement selbst. Mit einem einfachen Funktionsaufruf und extrem geringem CPU-Overhead kann das Canvas des Steuerelements abgerufen werden.

Vollständiges Beispiel:

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.

Abgesehen davon würde ich persönlich immer noch den Standardansatz verwenden, ein TCanvas(oder sogar ein HDC) anstelle eines Steuerelements zusammen mit einigen Dimensionen zu übergeben:

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

Auf diese Weise kann ich es auch für andere Steuerelemente (nicht nur für TCustomControlNachkommen) sowie für Druckerbilder usw. verwenden.

fpiette Nov 28 2020 at 14:27

Warum wurde Leinwand durch Design versteckt?

Nicht wirklich versteckt, aber im geschützten Bereich. Um darauf zuzugreifen, müssen Sie eine neue Klasse von der Klasse ableiten, an der Sie interessiert sind, und Canvas als öffentlich deklarieren.

Es ist privat, da Sie auf Anwendungsebene nicht darauf zugreifen sollen.

Sie müssen Ihre Komponente nicht installieren, wenn Sie eine interposerKlasse in der Quelle verwenden, die Sie benötigen.

Sie können auch die PaintMethode überschreiben und Ihren Zeichnungscode dort ablegen.