Delphi/GDI+:设备上下文何时创建/销毁?
通常在 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
您不能使用标准 TPaintBox,因为 TPaintBox 有一个 TControlCanvas 类型的 Canvas,与此问题相关的成员如下:
问题是 FreeHandle 和 SetControl 不是虚拟的。
但是: TControlCanvas 是在这里创建和分配的:
所以你可以做的是创建一个具有虚拟方法的降序 TMyControlCanvas 和一个像这样分配 Canvas 的 TMyPaintBox:
然后你可以使用 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:
The problem is that FreeHandle and SetControl are not virtual.
But: the TControlCanvas is created and assigned here:
So what you could do is create a descending TMyControlCanvas that does have virtual methods, and a TMyPaintBox that assigns the Canvas like this:
Then you can use the methods in TMyControlCanvas to dynamically create and destroy your TGPGraphics.
That should get you going.
--jeroen
检测创作很容易。只需在后代
CreateHandle
中重写 < code>TControlCanvas 并将您的替换为默认的 正如 Jeroen 的回答所示。检测破坏更加困难。避免此问题的一种方法是检查 TGpGraphics 句柄是否等于油漆盒的句柄,因此,您只需在需要知道之前进行检查,而不是检测设备上下文被释放的时刻。
但这可能并不可靠;句柄值很容易被重复使用,因此尽管两次检查之间的 HDC 值可能相同,但不能保证它仍然引用相同的操作系统设备上下文对象。
TCanvas
基类永远不会清除自己的Handle
属性,因此任何使画布无效的操作都必须在外部发生。TControlCanvas
清除其Handle 属性,当它的
Control
属性被重新分配时,但这通常只发生在创建控件时,因为TControlCanvas
实例很少被共享。但是,TControlCanvas
实例通过保存在CanvasList
中的设备上下文句柄池工作。每当其中一个需要 DC(在TControlCanvas.CreateHandle
中)时,它就会调用FreeDeviceContext
在画布缓存中为即将创建的句柄腾出空间。该函数调用(非虚拟)FreeHandle
方法。缓存大小为 4(请参阅CanvasListCacheSize
),因此,如果您的程序中有多个TCustomControl
或TGraphicControl
的后代,则很有可能每当需要同时重新绘制四个以上的缓存时,就会出现缓存未命中的情况。TControlCanvas.FreeHandle
不是虚拟的,并且它不调用任何虚拟方法。尽管您可以创建该类的后代并为其提供虚拟方法,但 VCL 的其余部分将继续调用非虚拟方法,而不会注意到您添加的任何内容。您最好使用不同的 TGpGraphics 构造函数,而不是尝试检测设备上下文何时被释放。使用采用窗口句柄的句柄而不是 DC 句柄,例如。窗把手损坏更容易被发现。对于一次性解决方案,请将您自己的方法分配给
TPaintBox。 WindowProc
属性并监视wm_Destroy< /code>
消息。如果您经常这样做,请创建一个后代类并覆盖
DestroyWnd
。Detecting creation is easy. Just override
CreateHandle
in a descendantTControlCanvas
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.
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 ownHandle
property, so anything that invalidates the canvas must occur externally.TControlCanvas
clears itsHandle
property when itsControl
property gets re-assigned, but that usually only happens when the control is created sinceTControlCanvas
instances are rarely shared. However,TControlCanvas
instances work from a pool of device-context handles kept inCanvasList
. Whenever one of them needs a DC (inTControlCanvas.CreateHandle
), it callsFreeDeviceContext
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 (seeCanvasListCacheSize
), so if you have several descendants ofTCustomControl
orTGraphicControl
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 forwm_Destroy
messages. If you're doing this often, then make a descendant class and overrideDestroyWnd
.创建/销毁图形对象对性能的影响很小。首先,它远远超过了使用 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.