Por que o Canvas está “escondido” em todos os controles VCL?
Quero fazer um procedimento básico que desenhe algo (digamos um triângulo, para simplificar) em qualquer tela de controle (botão, painel, etc):
procedure DrawTriangle(Control: TCustomControl);
Nesta função, preciso usar Control.Width & Control.Height para saber o tamanho do controle. É mais difícil do que se imaginava porque o Canvas está protegido.
Uma solução seria obter a tela do controle dentro do procedimento:
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;
Mas parece um desperdício de CPU criar e destruir uma tela cada vez que quero pintar algo ...
Então, minhas perguntas são:
- Por que a tela foi escondida (protegida) pelo design?
- Como resolver isso de forma elegante (um único parâmetro) e sem desperdiçar CPU?
Agora estou substituindo o método Paint, mas isso significa a duplicação do código de pintura em vários lugares. Claro, o DrawTriangle poderia receber mais parâmetros (Canvas, Control Width / Height etc), .... mas bem ... com um método Paint exposto, tudo teria sido muito mais elegante.
Respostas
Em um comentário sobre a pergunta, descobrimos que
- basta que essa solução seja restrita aos
TCustomControl
descendentes, e - é "elegante" o suficiente se o procedimento de desenho puder obter a tela do controle de argumento com uma chamada de função simples.
Nesse caso, a seguinte solução é possível:
//
// 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;
Observe que DrawFrog
leva apenas um único parâmetro, o próprio controle. E ele pode obter a tela do controle usando uma chamada de função simples com uma sobrecarga de CPU extremamente pequena.
Exemplo completo:
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.
Com tudo isso dito, no entanto, eu pessoalmente ainda usaria a abordagem padrão de passar um TCanvas
(ou mesmo um HDC
) em vez de um controle, juntamente com algumas dimensões:
procedure DrawFrog(ACanvas: TCanvas; const ARect: TRect);
Isso me permitirá usá-lo para outros controles também (não apenas TCustomControl
descendentes), bem como telas de impressão etc.
Por que a tela foi ocultada por design?
Não realmente escondido, mas na seção protegida. Para acessá-lo, você deve derivar uma nova classe daquela em que está interessado e declarar Canvas como pública.
É privado porque você não deve acessá-lo no nível do aplicativo.
Você não precisa instalar seu componente se usar uma interposer
classe na fonte que você precisa.
Você também pode considerar substituir o Paint
método e colocar seu código de desenho lá.