我的项目中有一个框架(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;


无论将哪个值分配给DoubleBufferedParentBackground,此代码始终会绘制交叉线。

但是,当我尝试使用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不为零,但结果为空白帧。在这种情况下,设置DoubleBufferedParentBackground不会更改任何内容。

也许我叫他们错了?

现在,我将WM_PAINT处理程序与GetDC / ReleaseDC一起使用,因为我不想在此框架上启用DoubleBuffered。另外,恐怕其他程序员在将框架放入他们的项目后会意外禁用DoubleBuffered,并且会像我一样头痛。

但是,也许有更安全正确的方法在框架的表面上绘画吗?

最佳答案

如果我不在测试框架上放置任何控件,则可以复制您的问题(这也可能是我们谁都无法复制您的问题的原因-f.i.抛出控件以从视觉上确保框架在表单上)。

当没有控件时,不会调用PaintHandler的原因;尽管没有控件,但在设置了DoubleBuffered时,调用它的原因只是WM_PAINTTWinControl消息处理程序是设计:

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后,检查您的PAINTSTRUCTBeginPaint成员,您会发现它是(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,或仅使要绘制的部分无效。

10-08 15:53