4

通常在 Delphi 中使用 GDI+,您可以使用TPaintBox,并在OnPaint事件期间进行绘制:

procedure TForm1.PaintBox1Paint(Sender: TObject);
var
   g: TGPGraphics;
begin
   g := TGPGraphics.Create(PaintBox1.Canvas.Handle);
   try
      g.DrawImage(FSomeImage, 0, 0);
   finally
      g.Free;
   end;
end;

这种范式的问题在于,每次都创建一个销毁Graphics对象既浪费又性能不佳。此外, GDI+中还有一些结构可用,您只能在拥有持久图形对象时使用。

当然,问题是我什么时候可以创建那个Graphics对象?我需要知道句柄何时可用,然后何时不再有效。我需要这些信息,以便创建和销毁我的Graphics对象。


解决方案尝试 Nº1

我可以通过在真正需要时创建它来解决创建问题 - 在第一次调用绘制周期时:

procedure TForm1.PaintBox1Paint(Sender: TObject);
begin
   if FGraphics = nil then
      FGraphics := TGPGraphics.Create(PaintBox1.Canvas.Handle);

   FGraphics.DrawImage(FSomeImage, 0, 0);
end;

但是我必须知道设备上下文何时不再有效,所以我可以销毁我的FGraphcis对象,以便在下次需要时重新创建它。如果由于某种原因重新创建了TPaintBox的设备上下文,我将在下次调用OnPaint时绘制无效的设备上下文。

Delphi 中的预期机制是什么让我知道何时创建、销毁或重新创建TPaintBox设备上下文句柄?

4

4 回答 4

3

您不能使用标准 TPaintBox,因为 TPaintBox 有一个类型为 TControlCanvas 的 Canvas,与此问题相关的成员如下:

TControlCanvas = class(TCanvas)
private
  ...
  procedure SetControl(AControl: TControl);
protected
  procedure CreateHandle; override;
public
  procedure FreeHandle;
  ...
  property Control: TControl read FControl write SetControl;
end;

问题是 FreeHandle 和 SetControl 不是虚拟的。

但是: TControlCanvas 是在这里创建和分配的:

 constructor TGraphicControl.Create(AOwner: TComponent);
 begin
   inherited Create(AOwner);
   FCanvas := TControlCanvas.Create;
   TControlCanvas(FCanvas).Control := Self;
 end;

因此,您可以做的是创建一个具有虚拟方法的降序 TMyControlCanvas,以及一个像这样分配 Canvas 的 TMyPaintBox:

 constructor TMyPaintBox.Create(AOwner: TComponent);
 begin
   inherited Create(AOwner);
   FCanvas.Free;
   FCanvas := TMyControlCanvas.Create;
   TMyControlCanvas(FCanvas).Control := Self;
 end;

然后您可以使用 TMyControlCanvas 中的方法来动态创建和销毁您的 TGPGraphics。

那应该让你继续前进。

——杰伦

于 2009-10-24T18:48:45.883 回答
2

检测创作很容易。正如Jeroen 的回答所展示的那样,只需覆盖CreateHandle后代TControlCanvas并将您的后代替换为默认值即可。检测破坏更难。

避免该问题的一种方法是检查 TGpGraphics 句柄是否等于画框的句柄,因此,与其检测设备上下文被释放的时刻,不如在需要知道之前进行检查。

if not Assigned(FGraphics)
    or (FGraphics.GetHDC <> PaintBox1.Canvas.Handle) then begin
  FGraphics.Free;
  FGraphics := TGpGraphics.Create(PaintBox1.Canvas.Handle);
end;

不过,这可能不可靠。句柄值可能会被重用,因此尽管 HDC 值在两次检查之间可能相同,但不能保证它仍然引用相同的 OS 设备上下文对象。


TCanvas类从不清除自己的Handle属性,因此任何使画布无效的事情都必须在外部发生。当其属性被重新分配时TControlCanvas清除其属性,但这通常仅在创建控件时发生,因为实例很少共享。但是,实例从保存在. 每当其中一个需要 DC (in ) 时,它就会调用以在画布缓存中为即将创建的句柄腾出空间。该函数调用(非虚拟)方法。缓存大小为 4(参见 参考资料),因此如果您有多个orHandleControlTControlCanvasTControlCanvasCanvasListTControlCanvas.CreateHandleFreeDeviceContextFreeHandleCanvasListCacheSizeTCustomControlTGraphicControl在您的程序中,当需要一次重绘超过四个缓存未命中时,您很有可能会遇到缓存未命中。

TControlCanvas.FreeHandle不是虚拟的,它不调用任何虚拟方法。尽管您可以创建该类的后代并为其提供虚拟方法,但 VCL 的其余部分将继续调用非虚拟方法,而忽略您的任何添加。


与其尝试检测何时释放设备上下文,不如使用不同的 TGpGraphics 构造函数。例如,使用带有窗口句柄而不是 DC 句柄的那个。窗口句柄破坏更容易检测到。对于一次性解决方案,将您自己的方法分配给TPaintBox.WindowProc属性并注意wm_Destroy消息。如果您经常这样做,请创建一个后代类并覆盖DestroyWnd.

于 2009-10-26T06:47:12.787 回答
1

创建/销毁图形对象对性能的影响很小。首先使用 gdi+ 的绘图命令对性能的影响远远超过了它。imo 在绘制用户界面时,这两者都不值得担心,因为用户无论如何都不会注意到。坦率地说,尝试携带图形对象并跟踪对 DC 句柄的更改可能非常不方便(特别是如果您将图形例程封装在自己的一组类中)。

如果您需要缓存位图,您可能会考虑使用 GDI+ 创建您想要缓存的位图(使其大小合适并使用您想要的任何抗锯齿设置),将其保存到 tmemorystream,然后在需要时保存,从流中加载它并使用好的 ol' bitblt 绘制它。它会比使用 Graphics.DrawImage 快得多。我说的是数量级更快。

于 2009-10-30T21:59:25.790 回答
-3
procedure TGraphicControl.WMPaint(var Message: TWMPaint);
begin
  if Message.DC <> 0 then
  begin
    Canvas.Lock;
    try
      Canvas.Handle := Message.DC;
      try
        Paint;
      finally
        Canvas.Handle := 0;
      end;
    finally
      Canvas.Unlock;
    end;
  end;
end;

Canvas.Handle := Message.DC;
于 2012-01-14T17:33:51.313 回答