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

发布于 2024-08-08 14:01:39 字数 1254 浏览 5 评论 0原文

通常在 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对象?我需要知道句柄何时可用,以及何时不再有效。我需要这些信息,以便我可以创建和销毁我的图形对象。


解决方案尝试 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 中的预期机制是什么?

Normally using GDI+ in Delphi you can use a TPaintBox, and paint during the OnPaint event:

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;

The problem with this paradigm is that creating a destroying a Graphics object each time is wasteful and poorly performing. Additionally, there are a few constructs availabe in GDI+ you can only use when you have a persistent Graphics object.

The problem, of course, is when can i create that Graphics object? i need to know when the handle becomes available, and then when it is no longer valid. i need this information so i can create and destroy my Graphics object.


Solution Attempt Nº1

i can solve the creation problem by creating it when it is really needed - on the first time the paint cycle is called:

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

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

But i have to know when the device context is no longer valid, so i can destroy my FGraphcis object, so that it is re-created the next time it's needed. If for some reason the TPaintBox's device context gets recreated, i would be drawing on an invalid device context the next time OnPaint is called.

What is the intended mechanism in Delphi for me to know when the device context handle of a TPaintBox is created, destroyed, or re-created?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

仅一夜美梦 2024-08-15 14:01:39

您不能使用标准 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 中的方法来动态创建和销毁你的TGP图形。

这应该能让你继续前进。

——杰罗恩

You can't with the standard TPaintBox because the TPaintBox has a Canvas of type TControlCanvas, for which members relevant to this issue are these:

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

The problem is that FreeHandle and SetControl are not virtual.

But: the TControlCanvas is created and assigned here:

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

So what you could do is create a descending TMyControlCanvas that does have virtual methods, and a TMyPaintBox that assigns the Canvas like this:

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

Then you can use the methods in TMyControlCanvas to dynamically create and destroy your TGPGraphics.

That should get you going.

--jeroen

写给空气的情书 2024-08-15 14:01:39

检测创作很容易。只需在后代 CreateHandle 中重写 < code>TControlCanvas 并将您的替换为默认的 正如 Jeroen 的回答所示。检测破坏更加困难。

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

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

但这可能并不可靠;句柄值很容易被重复使用,因此尽管两次检查之间的 HDC 值可能相同,但不能保证它仍然引用相同的操作系统设备上下文对象。


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

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


您最好使用不同的 TGpGraphics 构造函数,而不是尝试检测设备上下文何时被释放。使用采用窗口句柄的句柄而不是 DC 句柄,例如。窗把手损坏更容易被发现。对于一次性解决方案,请将您自己的方法分配给 TPaintBox。 WindowProc 属性并监视 wm_Destroy< /code>消息。如果您经常这样做,请创建一个后代类并覆盖 DestroyWnd

Detecting creation is easy. Just override CreateHandle in a descendant TControlCanvas and put yours in place of the default one as Jeroen's answer demonstrates. Detecting destruction is harder.

One way to avoid the issue is to check whether the TGpGraphics handle is equal to the paint-box's handle, so, rather than detect the moment when the device context is freed, you simply check before you need to know.

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

This probably isn't reliable, though; handle values are liable to be re-used, so although the HDC value might be the same between two checks, there's no guarantee that it still refers to the same OS device-context object.


The TCanvas base class never clears its own Handle property, so anything that invalidates the canvas must occur externally. TControlCanvas clears its Handle property when its Control property gets re-assigned, but that usually only happens when the control is created since TControlCanvas instances are rarely shared. However, TControlCanvas instances work from a pool of device-context handles kept in CanvasList. Whenever one of them needs a DC (in TControlCanvas.CreateHandle), it calls FreeDeviceContext to make room in the canvas cache for the handle it's about to create. That function calls the (non-virtual) FreeHandle method. The cache size is 4 (see CanvasListCacheSize), so if you have several descendants of TCustomControl or TGraphicControl in your program, chances are high that you'll get cache misses whenever more than four of them need to be repainted at once.

TControlCanvas.FreeHandle is not virtual, and it doesn't call any virtual methods. Although you could make a descendant of that class and give it virtual methods, the rest of the VCL is going to continue calling the non-virtual methods, oblivious to any of your additions.


Instead of trying to detect when a device context is released, you might be better off using a different TGpGraphics constructor. Use the one that takes a window handle instead of a DC handle, for instance. Window-handle destruction is much easier to detect. For a one-off solution, assign your own method to the TPaintBox.WindowProc property and watch for wm_Destroy messages. If you're doing this often, then make a descendant class and override DestroyWnd.

山田美奈子 2024-08-15 14:01:39

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

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

The performance hit you take for creating/destroying the graphics object is minimal. It's far outweighed by the performance hit of using gdi+'s drawing commands in the first place. Neither of which, imo, are worth worrying about when it comes to drawing user interfaces because the user wont notice anyways. And frankly, it can be very inconvenient to try to carry around a graphics object and track changes to the DC handle (especially if you're encapsulating graphics routines inside your own set of classes).

If you need to cache bitmaps, what you may consider doing is creating the bitmap you want to cache with GDI+ (make it the right size & w/ whatever antialias settings you want), saving it to a tmemorystream, and then when you need it, load it from a stream and draw it using good ol' bitblt. It'll be much, much faster than using Graphics.DrawImage. I'm talking orders of magnitude faster.

当梦初醒 2024-08-15 14:01:39
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;
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;
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文