通常在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}}可以仅使用。
问题当然是当可以创建图形对象吗?我需要知道句柄何时可用,然后何时不再有效。我需要这些信息,所以我可以创建并销毁我的图形对象。
我可以通过在真正需要时创建它来解决创建问题 - 在第一次调用绘制周期时:
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的目标机制是什么?
答案 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
),因此如果您的程序中有多个TCustomControl
或TGraphicControl
的后代,那么每当超过四个时,您将获得缓存未命中的可能性很高他们需要立刻重新粉刷。
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;