In a comment to the question it turns out that
- it is enough for this solution to be restricted to
TCustomControl
descendants, and
- it is "elegant" enough if the drawing procedure can obtain the canvas from the argument control with a simple function call.
If so, the following solution is possible:
//
// 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;
Notice that DrawFrog
only takes a single parameter, the control itself. And it can then obtain the control's canvas using a simple function call with extremely little CPU overhead.
Full example:
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.
All this being said, however, I would personally still use the standard approach of passing a TCanvas
(or even a HDC
) instead of a control, together with some dimensions:
procedure DrawFrog(ACanvas: TCanvas; const ARect: TRect);
This will allow me to use it for other controls as well (not only TCustomControl
descendants), as well as printer canvases etc.
WM_PAINT
message. It is not doable (without a lot of problems) to draw on a canvas at some other time (e.g. in anOnClick
event handler). For all we know, the control might chose to redraw itself the millisecond after you added your triangle. In addition, many Win32 controls are very tricky to custom draw in the first place. – EngineryDrawTriangle(ACanvas: TCanvas; const ARect: TRect)
which draws the triangle in theACanvas
canvas atARect
. In fact, this is how all of the GDI works behind the scenes in Win32. See, for instance, theRectangle
function: it takes a HDC, which is the functional equivalent of aTCanvas
. (In essence: you don't draw on controls, you draw on "canvases".) I often have s fcns to be able to draw both on scn&to a printer's cvs. – EngineryTCustomControl
descendants (custom windowed controls)? (2) Would it be enough if yourprocedure DrawTriangle(Control: TCustomControl);
has a local variableCanvas: TCanvas
andCanvas := GetCanvas(Control)
as its first line? – Enginery