在TFrame表面上绘制最安全,最正确的方法是什么?

时间:2012-05-21 07:26:54

标签: 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并且会遇到与我相同的头痛。

但也许有更安全和正确的方法在画面上画画?

2 个答案:

答案 0 :(得分:5)

如果我没有对测试框架进行任何控制,我可以复制你的问题(这也可能是我们没有人可以复制你的问题的原因 - fi抛出一个控件,以确保框架在窗体上)。

当没有控件时调用PaintHandler的原因,以及设置DoubleBuffered时调用它的原因,虽然它没有控件,只是{{1}设计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未被调用(毕竟没有任何颜色可绘:我们不是自定义绘图(没有csCustomPaint标志)并且没有控件可以显示)。设置PaintHandler后,会跟随不同的代码路径,调用DoubleBuffered,然后调用WMPrintClient


如果你最终会在没有任何控制的情况下使用框架(尽管不太可能),那么从上面的代码片段来看,修复是明显的(当你知道它时也很明智):include {{1} } 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成员,您会发现它是(0,0,0,0)。

由于当时没有无效区域,操作系统只是忽略了以下的绘图调用。您可以在绘制画布之前通过使框架的客户端矩形无效来验证这一点:

PAINTSTRUCT

现在您将看到您的绘图将生效。当然,您可以选择不打电话给BeginPaint,或者只取消您要吸引的部分。

答案 1 :(得分:0)

看起来它只在设计师

中被调用
procedure TCustomFrame.PaintWindow(DC: HDC);
begin
   // Paint a grid if designing a frame that paints its own background
   if (csDesigning in ComponentState) and (Parent is TForm) then
   with TForm(Parent) do
      if (Designer <> nil) and (Designer.GetRoot = Self) and
         (not StyleServices.Enabled or not Self.ParentBackground) then
          Designer.PaintGrid
   end; 

进行特殊绘制的唯一方法是将WM_PAINT添加到框架中:

TFrame3 = class(TFrame)
protected
  procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
end;