为什么Canvas在所有VCL控件中都是"hidden"?
Why Canvas is "hidden" in all VCL controls?
我想做一个基本程序,在任何控件(按钮、面板等)上绘制一些东西(为简单起见,假设是一个三角形)canvas:
procedure DrawTriangle(Control: TCustomControl);
在这个函数中我需要使用Control.Width & Control.Height 来知道控件有多大。
原来比想象的还要难,因为Canvas被保护了
一个解决方案是获取程序内部控件的 canvas:
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;
但每次我想画点东西时,创建和销毁 canvas 似乎太浪费 CPU...
那么,我的问题是:
- 为什么 canvas 被设计隐藏(保护)?
- 如何优雅地解决这个问题(一个参数)并且不浪费CPU?
现在我重写了 Paint 方法,但这意味着在几个地方重复绘制代码。当然,DrawTriangle 可以接收更多参数(Canvas、Control Width/Height 等),......但是好吧......使用公开的 Paint 方法,一切都会更加优雅。
Why was canvas hidden by design?
没有真正隐藏,但在受保护的部分。要访问它,您必须从您感兴趣的那个派生出一个新的 class,并将 Canvas 声明为 public。
它是私有的,因为您不应该在应用程序级别访问它。
如果您在需要的源中使用 interposer
class,则无需安装您的组件。
您也可以考虑重写 Paint
方法并将绘图代码放在那里。
在对问题的评论中发现
- 此解决方案仅限于
TCustomControl
个后代就足够了,
- 如果绘图程序可以通过简单的函数调用从参数控件中获取canvas,就足够“优雅”了。
如果是这样,可以采用以下解决方案:
//
// 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;
注意 DrawFrog
只接受一个参数,即控件本身。然后它可以使用一个简单的函数调用以极少的 CPU 开销获得控件的 canvas。
完整示例:
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.
尽管如此,我个人仍然会使用传递 TCanvas
(甚至 HDC
)而不是控件的标准方法,以及一些维度:
procedure DrawFrog(ACanvas: TCanvas; const ARect: TRect);
这将允许我将它用于其他控件(不仅是 TCustomControl
后代),以及打印机 canvases 等
我想做一个基本程序,在任何控件(按钮、面板等)上绘制一些东西(为简单起见,假设是一个三角形)canvas:
procedure DrawTriangle(Control: TCustomControl);
在这个函数中我需要使用Control.Width & Control.Height 来知道控件有多大。 原来比想象的还要难,因为Canvas被保护了
一个解决方案是获取程序内部控件的 canvas:
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;
但每次我想画点东西时,创建和销毁 canvas 似乎太浪费 CPU...
那么,我的问题是:
- 为什么 canvas 被设计隐藏(保护)?
- 如何优雅地解决这个问题(一个参数)并且不浪费CPU?
现在我重写了 Paint 方法,但这意味着在几个地方重复绘制代码。当然,DrawTriangle 可以接收更多参数(Canvas、Control Width/Height 等),......但是好吧......使用公开的 Paint 方法,一切都会更加优雅。
Why was canvas hidden by design?
没有真正隐藏,但在受保护的部分。要访问它,您必须从您感兴趣的那个派生出一个新的 class,并将 Canvas 声明为 public。
它是私有的,因为您不应该在应用程序级别访问它。
如果您在需要的源中使用 interposer
class,则无需安装您的组件。
您也可以考虑重写 Paint
方法并将绘图代码放在那里。
在对问题的评论中发现
- 此解决方案仅限于
TCustomControl
个后代就足够了, - 如果绘图程序可以通过简单的函数调用从参数控件中获取canvas,就足够“优雅”了。
如果是这样,可以采用以下解决方案:
//
// 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;
注意 DrawFrog
只接受一个参数,即控件本身。然后它可以使用一个简单的函数调用以极少的 CPU 开销获得控件的 canvas。
完整示例:
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.
尽管如此,我个人仍然会使用传递 TCanvas
(甚至 HDC
)而不是控件的标准方法,以及一些维度:
procedure DrawFrog(ACanvas: TCanvas; const ARect: TRect);
这将允许我将它用于其他控件(不仅是 TCustomControl
后代),以及打印机 canvases 等