Delphi: using IInterface to restore cursor at end of mehod (prelude to a memento that executes any code at end of method).
Posted by jpluimers on 2014/08/06
A long while ago, I wrote about a (then overdue post) on .NET/C#: Using IDisposable to restore temporary settrings example: TemporaryCursor class.
I had been using a similar technique in Delphi since before I found out about the [WayBack] TRecall class and thought: I think my TTemporaryCursor
is smarter, as it is based on interfaces.
TRecall
(and the [WayBack] Vcl.Graphics descendants [WayBack] TBrushRecall, [WayBack] TFontRecall, and [WayBack] TPenRecall) store [WayBack] TPersistent properties using the Assign method. They were introduced in Delphi 6.Too bad there are only [WayBack] very few people using TRecall as lots of TPersistent things warrant sasaving and restoring.
My [WayBack] TTemporaryCursor (now [WayBack] at bitbucket) class only stores an integer, so it cannot derive from
TRecall
. Besides it is based onIInterface
which got introduced in Delphi 6, but was present asIUnknown
since Delphi 3 (see [WayBack] Interface It! A quick guide to the ins and outs of interfaces in Delphi. By Jimmy Tharpe).This means that
TRecall
could have been based onIInterface
, so I wonder why it was not.Note I’m not the first to publish about such a class (Malcolm Grooves wrote [WayBack] TCursorSnapshot, SwissDelphiCenter has [WayBack] TMyCursor, Nick Hodges published about [WayBack] TAutoCursor), it’s just that it has been in my tool box for so long, and written memento classes that you will see 2 articles on it this week.
In the mean time (this works with Delphi 2009 and up), I also wrote a small class that does similar things for any [WayBack] anonymous method. More on that tomorrow.
Back to TRecall
: it is an example of [WayBack] the memento pattern in Delphi. The [WayBack] memento pattern allows you to restore state.
SourceMaking.com a.k.a. [WayBack] Design Patterns and Refactoring is a great site about [WayBack] Design Patterns, [WayBack] UML, [WayBack] AntiPatterns and [WayBack] Refactoring.
Most pattern example code is available in all of the C#, C++, Delphi, Java and PHP languages.
Great stuff!
One of the constructs for restoring state is a [WayBack] try … finally … end construct: it allows you to always execute something in the finally block, for instance restoring the state to right before the try block.
For instance something like this:
procedure TTemporaryCursorMainForm.TemporaryCursorButtonClick(Sender: TObject); var Button: TButton; SavedEnabled: Boolean; begin Button := Sender as TButton; SavedEnabled := Button.Enabled; // save state try Button.Enabled := False; // set state Sleep(3000); // sleep 3 seconds with the button disabled finally Button.Enabled := SavedEnabled; // restore state end; end;
Basically the save/set/restore state code can be anything: nothing/open/close file for instance.
Time for an example memento base class:
unit MementoUnit; interface uses TemporaryCursorUnit; type IMemento = interface(IInterface) ['{529987B4-0C7C-4103-BCE7-EB9651E88E58}'] end; TMemento = class(TInterfacedObject, IMemento) strict private FObject: TObject; public constructor Create(const AObject: TObject); destructor Destroy; override; procedure Restore(const AObject: TObject); virtual; abstract; class function CreateMemento(const AObject: TObject): IMemento; end; implementation { TMemento } constructor TMemento.Create(const AObject: TObject); begin inherited Create(); FObject := AObject; end; destructor TMemento.Destroy; begin Restore(FObject); inherited Destroy(); end; class function TMemento.CreateMemento(const AObject: TObject): IMemento; begin Result := TMemento.Create(AObject); end; end.
The cool thing about Delphi interfaces is that they are reference counted. The Delphi base interface is [WayBack] IInterface. When the reference goes out of scope, it calls [WayBack] _Release, and when the reference count gets to zero, the [WayBack] Destroy destructor is called. [WayBack] TMemento (now [WayBack] also at bitbucket) forwards that to Restore.
There is a little trick with Delphi interfaces: you do not have to assign the result of TMemento.CreateMemento
. [WayBack] Even if you do not assign it, Delphi will automatically keep the reference until the calling method ends: Delphi adds an implicit try
… finally
… end
block where the finally
… end
will [WayBack] decrease the reference count.
Based on the TMemento
idea, I created the TTemporaryCursor
class and ITemporaryCursor
interface. They could have been derived from TMemento
and IMemento
, but to keep it simpler, I have made the unit self-contained:
unit TemporaryCursorUnit; interface uses Vcl.Controls; // .NET/C# Equivalent: https://wiert.me/2012/01/26/netc-using-idisposable-to-restore-temporary-settrings-example-temporarycursor-class/ type ITemporaryCursor = interface(IInterface) ['{495ADE0F-EFBE-4A0E-BF37-F1ACCACCE03D}'] end; TTemporaryCursor = class(TInterfacedObject, ITemporaryCursor) strict private FCursor: TCursor; public constructor Create(const ACursor: TCursor); destructor Destroy; override; class function SetTemporaryCursor(const ACursor: TCursor = crHourGlass): ITemporaryCursor; end; implementation uses Vcl.Forms; { TTemporaryCursor } constructor TTemporaryCursor.Create(const ACursor: TCursor); begin inherited Create(); FCursor := Screen.Cursor; Screen.Cursor := ACursor; end; destructor TTemporaryCursor.Destroy; begin if Assigned(Screen) then Screen.Cursor := FCursor; inherited Destroy(); end; class function TTemporaryCursor.SetTemporaryCursor(const ACursor: TCursor = crHourGlass): ITemporaryCursor; begin Result := TTemporaryCursor.Create(ACursor); end; end.
You use it like this:
procedure TTemporaryCursorMainForm.TemporaryCursorButtonClick(Sender: TObject); begin TTemporaryCursor.SetTemporaryCursor(); Sleep(3000); // sleep 3 seconds with the crHourGlass cursor // Delphi will automatically restore the cursor end;
What you see is no try … finally … end at all. That makes the code simpler to read and maintain.
The next post will introduce a memento class that allows you to perform a restore operation at the end of any method.
Basically the whole idea of Smart Pointers (see the series articles by Barry Kelly; note [WayBack1/WayBack2] Smart Pointers will be in Spring4D 1.2) takes the above to a new level: instead of type specific, they are generic for any type.
Edit: Spring4D implemented Smart Pointers a while ago and now names smart pointers Shared
/IShared<T>
/TShared<T>
, see [WayBack] Spring4D – Pascal Today and the difference between the above WayBack1/WayBack2.
–jeroen
Referenced: [WayBack] Hello! There is a rather obscure and old couple of routines in the RTL that let you save things such as fonts and brushes without the need for a variab… – Andrea Raimondi – Google+
abouchez said
This won’t work in Delphi 10.4 any more due to a compiler change about interface release scope.
Check https://quality.embarcadero.com/browse/RSP-30050
If you were using Managed / IManaged in Spring4D, be aware they got renamed to Shared / IShared « The Wiert Corner – irregular stream of stuff said
[…] [WayBack] Delphi: using IInterface to restore cursor at end of mehod (prelude to a memento that executes any code at end of method). […]
jpluimers said
A. Bouchez shared a nice link with a similar construct. He named it TAutoFree/IAutoFree: https://plus.google.com/u/0/+ABouchez/posts/RK8BnQgCfQT
EMB said
Beside my inner nerd like and admire how clever this is, I’m not sure that don’t having a destroy to memento class is a good thing. Maybe I’m just not used to it, but I don’t find it really clearer than having an explicit destroy.
jpluimers said
(:
There are enough places I still use try/finally, but for some patterns, this “auto-free” makes things a lot more readable.
Some great reads from Barry Kelly « The Wiert Corner – irregular stream of stuff said
[…] Delphi: using IInterface to restore cursor at end of mehod (prelude to a memento that executes any c… […]
Sven said
Please note that this feature relies on an undocumented implementation detail of Delphi: In Delphi the reference count of the ITemporaryCursor result will be decreased at the end of the method, but it is nowhere documented that this must be at the end! The compiler would be free to decrease the reference count directly after the method call (and before the Sleep() in your example) and for example Free Pascal indeed does so, because there’s no need to keep the interface around any longer. It could also be that the new NextGen compiler handles this differently as well (untested).
Regards,
Sven
jpluimers said
It is documented. Just hard to find. And it includes pitfalls:
http://stackoverflow.com/questions/7759081/is-the-compiler-treatment-of-implicit-interface-variables-documented/19914385#19914385
The whole idea of Smart Pointers depend on it: http://blog.barrkel.com/2008/09/smart-pointers-in-delphi.html and http://members.adug.org.au/2011/12/05/smart-pointers/
FreePascal even has documentation on how to disable implicit try finally blocks: http://wiki.freepascal.org/Avoiding_implicit_try_finally_section
Sven said
We’ve not yet managed to find a documentation on this. So if you have a link to such then please enlighten me. :)
Disabling implicit try finally blocks will help you nothing here. If only it will result in the compiler not releasing interface variables (or arrays or strings) when an exception occurs. It will not change how it releases temporaries. I’m a developer of FPC so I know what I’m talking about… ;)
Yours,
Sven
jpluimers said
Good to know you’re on the FPC team.
Finding the hard-core Delphi compiler documentation is next to impossible. So you will have to do with conference sessions, blog posts, articles and Q&A threads written by (sometimes former) Delphi engineers. Look for Danny Thorpe, Barry Kelly and Allen Bauer.
Delphi: a memento that executes any code at end of method « The Wiert Corner – irregular stream of stuff said
[…] David Moorhouse on Delphi: using IInterface to re… […]
David Moorhouse said
Nice work. I wrote two functions, PushCursor and PopCursor, way back in Delphi 1 days to achieve the same end effect, but it had to be called within a try / finally code block.
Good to see how modern language features simplify our code.
Alister Christie said
I use similar techniques in the following videos
Escaping the Try…Finally…Free Nest
http://learndelphi.tv/index.php?option=com_content&view=article&id=180
and
Mouse Cursors in FireMonkey
http://learndelphi.tv/index.php?option=com_content&view=article&id=184
I think a good knowledge of Interfaces interfaces can save you a lot of coding, as well as improve readability.
jpluimers said
Thanks for these videos. I still need to find time to watch everything from your series (: