Dlaczego Canvas jest „ukryty” we wszystkich kontrolkach VCL?
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:
- Dlaczego płótno zostało ukryte (chronione) zgodnie z projektem?
- 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
W komentarzu do pytania okazuje się, że
- wystarczy ograniczyć to rozwiązanie do
TCustomControl
potomków, a - 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 DrawFrog
przyjmuje 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 TCustomControl
potomków), a także do płótna drukarki itp.
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 interposer
klasy w źródle, którego potrzebujesz.
Możesz również rozważyć zastąpienie Paint
metody i umieścić tam swój kod rysowania.