¿Por qué Canvas está "oculto" en todos los controles VCL?
Quiero hacer un procedimiento básico que dibuje algo (digamos un triángulo, por simplicidad) en el lienzo de cualquier control (botón, panel, etc.):
procedure DrawTriangle(Control: TCustomControl);
En esta función necesito usar Control.Width & Control.Height para saber qué tan grande es el control. Resulta ser más difícil de lo imaginado porque Canvas está protegido.
Una solución sería obtener el lienzo del control dentro del procedimiento:
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;
Pero parece un desperdicio de CPU crear y destruir un lienzo cada vez que quiero pintar algo ...
Entonces, mis preguntas son:
- ¿Por qué el lienzo estaba oculto (protegido) por diseño?
- ¿Cómo resolver esto elegantemente (un solo parámetro) y sin desperdiciar CPU?
Ahora estoy anulando el método Paint, pero esto significa duplicar el código de pintura en varios lugares. Por supuesto, DrawTriangle podría recibir más parámetros (Canvas, Control Width / Height, etc.), ... pero bueno ... con un método Paint expuesto, todo habría sido mucho más elegante.
Respuestas
En un comentario a la pregunta resulta que
- basta con que esta solución se restrinja a los
TCustomControl
descendientes, y - es lo suficientemente "elegante" si el procedimiento de dibujo puede obtener el lienzo del control de argumento con una simple llamada de función.
Si es así, la siguiente solución es posible:
//
// 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;
Tenga en cuenta que DrawFrog
solo toma un único parámetro, el control en sí. Y luego puede obtener el lienzo del control mediante una simple llamada a función con muy poca sobrecarga de CPU.
Ejemplo 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.
Dicho todo esto, sin embargo, personalmente seguiría usando el enfoque estándar de pasar un TCanvas
(o incluso a HDC
) en lugar de un control, junto con algunas dimensiones:
procedure DrawFrog(ACanvas: TCanvas; const ARect: TRect);
Esto me permitirá usarlo también para otros controles (no solo TCustomControl
descendientes), así como para lienzos de impresora, etc.
¿Por qué el lienzo estaba oculto por el diseño?
No realmente escondido, pero en la sección protegida. Para acceder a él, debe derivar una nueva clase de la que le interesa y declarar Canvas como público.
Es privado porque se supone que no debe acceder a él en el nivel de la aplicación.
No necesita instalar su componente si usa una interposer
clase en la fuente que la necesita.
También puede considerar anular el Paint
método y poner allí su código de dibujo.