delphi - 在 TFrame 表面上绘画最安全、最正确的方法是什么?

标签 delphi frame delphi-xe paint

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

但是,将我的框架放在主窗体上后,调试器永远不会进入此方法,直到我在框架的属性中启用DoubleBufferedParentBackground 的任何值都不会影响结果。

重写 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 并且会遇到和我一样的头痛。

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

最佳答案

如果我不在测试框架上放置任何控件,我可以重复您的问题(这也可能是我们没有人可以重复您的问题的原因 - 例如抛出一个控件以在视觉上确保框架位于表单上)。

没有控件时不调用PaintHandler的原因,以及设置DoubleBuffered时虽然没有控件但调用它的原因,这就是 TWinControlWM_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 后,检查 PAINTSTRUCTrcPaint 成员,您会发现它是 (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/

相关文章:

delphi - 64 位应用程序上的流读/写错误

ssh - 输入隐藏,输出有多余的缩进

python - Tkinter 框架将自身添加到主窗口

delphi - Delphi 上限制 TCheckListBox 的选中项

delphi - 如何在Delphi中引入前端或启动浏览器

json - 如何使用 SuperObject 从 JSON 读取 DateTime?

iphone - 如何在自定义单元格中找到按钮框架的 x , y 位置

ios - 为什么横向 View 报告它是中心的,就好像它是纵向的一样?

delphi - 无法从 PascalScript 调用 Get_ProcAddress

delphi - 如果我更改使用它的项目的设置,是否会重新编译 "rebuild as needed"库?