Delphi / GDI +:何时创建/销毁设备上下文?

时间:2009-10-23 21:27:11

标签: delphi graphics gdi+ paint

通常在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 对象都是浪费而且性能很差。此外,当您拥有持久性图形对象时,只有<{3}}可以使用。

问题当然是可以创建图形对象吗?我需要知道句柄何时可用,然后何时不再有效。我需要这些信息,所以我可以创建并销毁我的图形对象。


解决方案尝试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 时使用无效的设备上下文。

在创建,销毁或重新创建 TPaintBox 设备上下文句柄时,Delphi的目标机制是什么?

4 个答案:

答案 0 :(得分: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。

这应该让你去。

- 的Jeroen

答案 1 :(得分:2)

检测创作很容易。只需覆盖后代TControlCanvas中的CreateHandle,然后将其替换为默认值as Jeroen's answer demonstrates。检测破坏更难。

避免此问题的一种方法是检查TGpGraphics句柄是否等于paint-box的句柄,因此,您只需检查设备上下文被释放的时刻,您只需在需要知道之前进行检查。

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在重新分配Handle属性时清除其Control属性,但这通常仅在创建控件时发生,因为很少共享TControlCanvas个实例。但是,TControlCanvas实例来自CanvasList中保存的设备上下文句柄池。每当其中一个需要DC(在TControlCanvas.CreateHandle中)时,它就会调用FreeDeviceContext在画布缓存中腾出空间来处理它即将创建的句柄。该函数调用(非虚拟)FreeHandle方法。缓存大小为4(请参阅CanvasListCacheSize),因此如果您的程序中有多个TCustomControlTGraphicControl的后代,那么每当超过四个时,您将获得缓存未命中的可能性很高他们需要立刻重新粉刷。

TControlCanvas.FreeHandle不是虚拟的,也不会调用任何虚拟方法。虽然您可以创建该类的后代并为其提供虚拟方法,但VCL的其余部分将继续调用非虚拟方法,而不会添加任何添加内容。


您可能最好使用不同的TGpGraphics构造函数,而不是尝试检测何时释放设备上下文。例如,使用the one that takes a window handle而不是DC句柄。窗口手柄破坏更容易检测。对于一次性解决方案,请将您自己的方法分配给TPaintBox.WindowProc属性并观察wm_Destroy条消息。如果您经常这样做,那么请创建一个后代类并覆盖DestroyWnd

答案 2 :(得分:1)

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

如果你需要缓存位图,你可能会考虑做的是创建你想用GDI +缓存的位图(使它成为正确的大小&w;你想要的任何抗锯齿设置),将它保存到tmemorystream,然后当你需要它时,从流中加载它并使用好的'bitblt绘制它。它比使用Graphics.DrawImage要快得多。我说的速度要快几个。

答案 3 :(得分:-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;