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 TRecall class and thought: I think my TTemporaryCursor is smarter, as it is based on interfaces.
TRecall (and the Vcl.Graphics descendants TBrushRecall, TFontRecall, and TPenRecall) store TPersistent properies using the Assign method. They were introduced in Delphi 6.
Too bad there are only very few people using TRecall as lots of TPersistent things warrant sasaving and restoring.
My TTemporaryCursor class only stores an integer, so it cannot derive from TRecall. Besides it is based on IInterface which got introduced.
Note I’m not the first to publish about such a class (Malcolm Grooves wrote TCursorSnapshot, SwissDelphiCenter has TMyCursor, Nick Hodges published about 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 anonymous method. More on that tomorrow.
Back to TRecall: it is an example of the memento pattern in Delphi. The memento pattern allows you to restore state.
SourceMaking.com a.k.a. Design Patterns and Refactoring is a great site about Design Patterns, UML, AntiPatterns and 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 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 IInterface. When the reference goes out of scope, it calls _Release, and when the reference count gets to zero, the Destroy destructor is called. TMemento forwards that to Restore.
There is a little trick with Delphi interfaces: you do not have to assign the result of TMemento.CreateMemento. 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 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: http://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.
–jeroen
Filed under: Delphi, Delphi 2005, Delphi 2006, Delphi 2007, Delphi 2009, Delphi 2010, Delphi 3, Delphi 4, Delphi 5, Delphi 6, Delphi 7, Delphi x64, Delphi XE, Delphi XE2, Delphi XE3, Delphi XE4, Development, Software Development
