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?
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.
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
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
.