我的项目中有一个框架(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
,并且会像我一样头痛。但是,也许有更安全正确的方法在框架的表面上绘画吗?
最佳答案
如果我不在测试框架上放置任何控件,则可以复制您的问题(这也可能是我们谁都无法复制您的问题的原因-f.i.抛出控件以从视觉上确保框架在表单上)。
当没有控件时,不会调用PaintHandler
的原因;尽管没有控件,但在设置了DoubleBuffered
时,调用它的原因只是WM_PAINT
的TWinControl
消息处理程序是设计:
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
。至于为什么在
BeginPaint
消息处理程序中使用EndPaint
/ WM_PAINT
进行绘画似乎不起作用的原因,是因为绘画代码之前的inherited
调用会验证更新区域。呼叫rcPaint
后,检查您的PAINTSTRUCT
的BeginPaint
成员,您会发现它是(0,0,0,0)。由于此时没有剩余的无效区域,因此操作系统仅会在进行图形调用后忽略。您可以通过在画布上绘制之前使框架的客户矩形无效来验证这一点:
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
,或仅使要绘制的部分无效。