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

Delphi/GDI+;:何时创建/销毁设备上下文?,delphi,graphics,gdi+,paint,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

通常在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;
这个范例的问题是每次创建一个图形对象都是浪费和性能低下的。此外,只有当您有一个持久的图形对象时,才可以使用一些

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


解决方案尝试编号: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设备上下文句柄时,我想要知道什么机制?

您不能使用标准TPaintBox,因为TPaintBox具有类型为TControlCanvas的画布,与此问题相关的成员包括:

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;
因此,您可以创建一个具有虚拟方法的下降TMYControl画布,以及一个分配画布的TMYPaint Box,如下所示:

 constructor TMyPaintBox.Create(AOwner: TComponent);
 begin
   inherited Create(AOwner);
   FCanvas.Free;
   FCanvas := TMyControlCanvas.Create;
   TMyControlCanvas(FCanvas).Control := Self;
 end;
然后,您可以使用TMyControlCanvas中的方法动态创建和销毁TGPGraphics

那会让你走的


--jeroen

检测创建很容易。只需在子体
TControlCanvas
中重写,并将您的控件替换为默认控件。探测破坏更难

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

if not Assigned(FGraphics)
    or (FGraphics.GetHDC <> PaintBox1.Canvas.Handle) then begin
  FGraphics.Free;
  FGraphics := TGpGraphics.Create(PaintBox1.Canvas.Handle);
end;
如果未指定(FGraphics)
或者(FGraphics.GetHDC PaintBox1.Canvas.Handle),然后开始
免费的;
FGraphics:=TGpGraphics.Create(PaintBox1.Canvas.Handle);
结束;
不过,这可能不可靠;句柄值很容易被重用,因此尽管两次检查之间的HDC值可能相同,但不能保证它仍然引用相同的OS设备上下文对象


TCanvas
基类从不清除自己的
Handle
属性,因此任何使画布无效的操作都必须在外部进行。当重新分配其
控件
属性时,清除其
句柄
属性,但这通常仅在创建控件时发生,因为
TControlCanvas
实例很少共享。但是,
TControlCanvas
实例从保存在
CanvasList
中的设备上下文句柄池中工作。每当其中一个需要DC时(在
TControlCanvas.CreateHandle
中),它就会调用
FreeDeviceContext
在画布缓存中为即将创建的句柄腾出空间。该函数调用(非虚拟)
freehold
方法。缓存大小为4(请参见
CanvasListCacheSize
),因此,如果您的程序中有多个
TCustomControl
TGraphicControl
的子体,则当需要同时重新绘制其中的四个以上时,很可能会出现缓存未命中

不是虚拟的,并且它不调用任何虚拟方法。尽管您可以创建该类的子类并为其提供虚拟方法,但VCL的其余部分将继续调用非虚拟方法,而不考虑您添加的任何方法



与其尝试检测何时释放设备上下文,不如使用不同的TGpGraphics构造函数。例如,使用而不是DC句柄。窗口句柄破坏更容易检测。对于一次性解决方案,请将您自己的方法指定给属性并查看消息。如果您经常这样做,那么创建一个子类并重写。

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

如果你需要缓存位图,你可以考虑做的是用GDI+创建你想要缓存的位图(让它成为正确的大小和W/任何你想要的反锯齿设置),把它保存到TMyMyMySt流中,然后当你需要它时,从一个流中加载它并用好的OL’BITBLT绘制它。它将比使用Graphics.DrawImage快得多。我说的是快了几个数量级。

过程TGraphicControl.WMPaint(var消息:TWMPaint);
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;
开始 如果是Message.DC.0,则 开始 帆布锁; 尝试 Canvas.Handle:=Message.DC; 尝试 油漆; 最后 Canvas.Handle:=0; 结束; 最后 画布。解锁; 结束; 结束; 结束; Canvas.Handle:=Message.DC;
调整控件大小时是否存在问题?您知道是否需要重新创建GDI+上下文,或者在任何其他情况下吗?仅仅因为您将虚拟方法添加到TMyControlCanvas并不意味着VCL中的任何内容都将调用t