Warum ist Canvas in allen VCL-Steuerelementen „versteckt“?
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:
- Warum wurde Leinwand durch Design versteckt (geschützt)?
- 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
In einem Kommentar zur Frage stellt sich heraus, dass
- es reicht aus, wenn diese Lösung auf
TCustomControl
Nachkommen beschränkt ist, und - 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 DrawFrog
nur 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 TCustomControl
Nachkommen) sowie für Druckerbilder usw. verwenden.
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 interposer
Klasse in der Quelle verwenden, die Sie benötigen.
Sie können auch die Paint
Methode überschreiben und Ihren Zeichnungscode dort ablegen.