我的项目中有一个框架(TFrame
的后代),并且想在其上绘制一些内容。
正如我从论坛中看到的,常见的方法是重写 PaintWindow
方法。
我在一个干净的项目上尝试过这个:
type
TMyFrame = class(TFrame)
private
FCanvas: TCanvas;
protected
procedure PaintWindow(DC: HDC); override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy(); override;
end;
implementation
{$R *.dfm}
constructor TMyFrame.Create(AOwner: TComponent);
begin
inherited;
FCanvas := TCanvas.Create();
end;
destructor TMyFrame.Destroy();
begin
FCanvas.Free();
inherited;
end;
procedure TMyFrame.PaintWindow(DC: HDC);
begin
inherited;
FCanvas.Handle := DC;
FCanvas.Pen.Width := 3;
FCanvas.Pen.Color := clRed;
FCanvas.MoveTo(0, 0);
FCanvas.LineTo(ClientWidth, ClientHeight);
FCanvas.Pen.Color := clGreen;
FCanvas.MoveTo(ClientWidth, 0);
FCanvas.LineTo(0, ClientHeight);
end;
但是,将我的框架放在主窗体上后,调试器永远不会进入此方法,直到我在框架的属性中启用DoubleBuffered
。 ParentBackground
的任何值都不会影响结果。
重写 WM_PAINT
处理程序也可以解决问题:
type
TMyFrame = class(TFrame)
protected
procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
...
procedure TMyFrame.WMPaint(var Message: TWMPaint);
begin
inherited;
FCanvas.Handle := GetDC(Handle);
FCanvas.Pen.Width := 3;
FCanvas.Pen.Color := clRed;
FCanvas.MoveTo(0, 0);
FCanvas.LineTo(ClientWidth, ClientHeight);
FCanvas.Pen.Color := clGreen;
FCanvas.MoveTo(ClientWidth, 0);
FCanvas.LineTo(0, ClientHeight);
ReleaseDC(Handle, FCanvas.Handle);
end;
无论将哪个值分配给 DoubleBuffered
或 ParentBackground
,此代码始终绘制交叉线。
但是当我尝试使用 BeginPaint
/EndPaint
而不是 GetDC
/ReleaseDC
时,问题返回:
procedure TMyFrame.WMPaint(var Message: TWMPaint);
var
PS: PAINTSTRUCT;
begin
inherited;
FCanvas.Handle := BeginPaint(Handle, PS);
FCanvas.Pen.Width := 3;
FCanvas.Pen.Color := clRed;
FCanvas.MoveTo(0, 0);
FCanvas.LineTo(ClientWidth, ClientHeight);
FCanvas.Pen.Color := clGreen;
FCanvas.MoveTo(ClientWidth, 0);
FCanvas.LineTo(0, ClientHeight);
EndPaint(Handle, PS);
end;
FCanvas.Handle 不为零,但结果是空白帧。在这种情况下,设置 DoubleBuffered
或 ParentBackground
不会改变任何内容。
也许我叫错了?
现在我将 WM_PAINT
处理程序与 GetDC
/ReleaseDC
一起使用,因为我不想启用 DoubleBuffered
在这个框架上。另外,我担心其他程序员在将我的框架放入他们的项目后会意外禁用 DoubleBuffered
并且会遇到和我一样的头痛。
但也许有更安全、正确的方法在框架表面绘画?
最佳答案
如果我不在测试框架上放置任何控件,我可以重复您的问题(这也可能是我们没有人可以重复您的问题的原因 - 例如抛出一个控件以在视觉上确保框架位于表单上)。
没有控件时不调用PaintHandler
的原因,以及设置DoubleBuffered
时虽然没有控件但调用它的原因,这就是 TWinControl
的 WM_PAINT
消息处理程序的设计方式:
procedure TWinControl.WMPaint(var Message: TWMPaint);
var
..
begin
if not FDoubleBuffered or (Message.DC <> 0) then
begin
if not (csCustomPaint in ControlState) and (ControlCount = 0) then
inherited
else
PaintHandler(Message);
end
else
begin
..
如您所见,当 DoubleBuffered
未设置并且没有控件时,PaintHandler
不会被调用(毕竟没有什么可绘制的:我们不是自定义的)绘图(没有 csCustomPaint 标志)并且也没有可显示的控件)。设置 DoubleBuffered
后,将遵循不同的代码路径,调用 WMPrintClient
,后者又调用 PaintHandler
。
如果您最终要使用没有任何控件的框架(尽管不太可能),则从上面的代码片段可以看出解决方案是显而易见的(当您知道它时也是明智的):包括 csCustomPaint
:ControlState
中的
type
TMyFrame = class(TFrame)
..
protected
procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
..
procedure TMyFrame.WMPaint(var Message: TWMPaint);
begin
ControlState := ControlState + [csCustomPaint];
inherited;
ControlState := ControlState - [csCustomPaint];
end;
那么继承的WM_PAINT
处理程序将调用PaintHandler
。
WM_PAINT
消息处理程序中使用BeginPaint
/EndPaint
进行绘画似乎不起作用,原因是继承
在您的绘画代码之前调用会验证更新区域。调用 BeginPaint
后,检查 PAINTSTRUCT
的 rcPaint
成员,您会发现它是 (0, 0, 0, 0)。
由于此时没有留下无效区域,因此操作系统只会忽略后续的绘图调用。您可以通过在 Canvas 上绘制之前使框架的客户端矩形无效来验证这一点:
procedure TMyFrame.WMPaint(var Message: TWMPaint);
var
PS: PAINTSTRUCT;
begin
inherited;
InvalidateRect(Handle, nil, False); // <- here
FCanvas.Handle := BeginPaint(Handle, PS);
FCanvas.Pen.Width := 3;
FCanvas.Pen.Color := clRed;
FCanvas.MoveTo(0, 0);
FCanvas.LineTo(ClientWidth, ClientHeight);
FCanvas.Pen.Color := clGreen;
FCanvas.MoveTo(ClientWidth, 0);
FCanvas.LineTo(0, ClientHeight);
EndPaint(Handle, PS);
end;
现在你会看到你的绘图生效了。当然,您可以选择不调用inherited
,或者仅使您要绘制的部分无效。
关于delphi - 在 TFrame 表面上绘画最安全、最正确的方法是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10681027/