Pourquoi Canvas est-il «caché» dans tous les contrôles VCL?
Je veux faire une procédure de base qui dessine quelque chose (disons un triangle, pour plus de simplicité) sur le canevas de n'importe quel contrôle (bouton, panneau, etc.):
procedure DrawTriangle(Control: TCustomControl);
Dans cette fonction, j'ai besoin d'utiliser Control.Width & Control.Height pour savoir quelle est la taille du contrôle. Cela s'avère plus difficile qu'on ne l'imaginait car Canvas est protégé.
Une solution serait d'obtenir le canevas du champ à l'intérieur de la procédure:
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;
Mais cela me semble un tel gaspillage de CPU pour créer et détruire une toile à chaque fois que je veux peindre quelque chose ...
Donc, mes questions sont:
- Pourquoi la toile était-elle cachée (protégée) par la conception?
- Comment résoudre cela avec élégance (un seul paramètre) et sans gaspiller de CPU?
Maintenant, je remplace la méthode Paint, mais cela signifie dupliquer le code de peinture à plusieurs endroits. Bien sûr, le DrawTriangle pourrait recevoir plus de paramètres (Canvas, Control Width / Height etc), .... mais bon ... avec une méthode Paint exposée, tout aurait été tellement plus élégant.
Réponses
Dans un commentaire à la question, il s'avère que
- il suffit que cette solution soit réservée aux
TCustomControl
descendants, et - c'est assez "élégant" si la procédure de dessin peut obtenir le canevas du contrôle d'argument avec un simple appel de fonction.
Si tel est le cas, la solution suivante est 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;
Notez que DrawFrog
ne prend qu'un seul paramètre, le contrôle lui-même. Et il peut ensuite obtenir le canevas du contrôle à l'aide d'un simple appel de fonction avec une surcharge du processeur extrêmement faible.
Exemple complet:
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.
Tout cela étant dit, cependant, j'utiliserais personnellement toujours l'approche standard consistant à passer un TCanvas
(ou même un HDC
) au lieu d'un contrôle, ainsi que certaines dimensions:
procedure DrawFrog(ACanvas: TCanvas; const ARect: TRect);
Cela me permettra de l'utiliser également pour d'autres contrôles (pas seulement pour les TCustomControl
descendants), ainsi que pour les toiles d'imprimantes, etc.
Pourquoi la toile était-elle cachée par le design?
Pas vraiment caché, mais en section protégée. Pour y accéder, vous devez dériver une nouvelle classe de celle qui vous intéresse et déclarer Canvas comme public.
Il est privé car vous n'êtes pas censé y accéder au niveau de l'application.
Vous n'avez pas besoin d'installer votre composant si vous utilisez une interposer
classe dans la source dont vous en avez besoin.
Vous pouvez également envisager de remplacer la Paint
méthode et d'y placer votre code de dessin.