It’s funny how this happens. I was already intending to write about the way I have exploited like-type names to my advantage, and then noticed that the same subject cropped up recently in the re-invented Delphi forums. In that instance, the issue though was a problem, and one that I’m sure many will be familiar with: When is a TBitmap not a TBitmap?
In my case, the question is: When is a TForm not TForm?
Keeping The VCL At Arms Length
A very large number of moons ago I was briefly involved in a PowerBuilder project (anybody else remember when 4GL’s were going to save the software development industry from itself?).
When OO reached PowerBuilder it brought with it an extensible component model – the PowerBuilder equivalent of the VCL – the PFC (PowerBuilder Foundation Class library). The team I was working with had learned from experience to keep the PFC at arms length.
Objects in the application were never instances of PFC classes, but of derived classes, even if the class derived added or changed nothing in the base class.
The reason being of course that if ever a problem was later discovered in the PFC class, or if some new behaviour or feature were desirable in the foundation classes, the additional class between the application and the PFC gave the developers an opportunity to make changes that would instantly propogate into all corners of their project, without having to modify the PFC itself, protecting themselves to an extent from changes in future versions of the PFC.
In Delphi terms this would mean for example never using a TButton, but instead declaring a TMyButton class and always using that as the “fundamental base class” for all buttons (and button classes) in your projects.
Adopting a comprehensive layer of intermediate classes to sit between an application and the VCL is obviously a huge, and largely pointless, task – the VCL is a very different beast than the PFC ever was.
But in the case of TForm an intermediate class could be useful, adding capabilities to forms that are widely, if not universally, useful.
The TBitmap problem identifies the mechanism to make an intermediate class instantly useful. The two TBitmap types caused problems because they were actually very different types and utterly incompatible. But having the same name wasn’t, itself, a problem. Just unfortunate.
With a little care, we can make like-names work for us.
A New TForm
To avoid the problems that occur with TBitmap we need to be careful to make any new TForm compatible with the VCL original. But isn’t that exactly what OO gives us? (One of the things anyway).
Consider this declaration:
unit Deltics.Forms; interface uses Forms; type TForm = class(Forms.TForm) end; implementation end.
This new TForm is compatible with TForm – it is a direct descendant. Anywhere that a Forms.TForm can be referenced, a Deltics.Forms.TForm is also sure to be perfectly valid because, unlike in the case of that TBitmap example, it is in fact a Forms.TForm.
It also happens to have exactly the same name, so any existing – or, more importantly, automatically generated – code that doesn’t know about my new form class can be made to use it simply by ensuring that the unit containing it is in scope.
Note that I am not talking about visual form inheritance here.
Now We Have It, What To Do With It?
Before going too far, let us first see how we incorporate this new TForm into our applications by looking at the boiler-plate code generated by the IDE when we create a new form in our projects:
unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs; type TForm1 = class(TForm) procedure FormCreate(Sender: TObject); private { Private declarations } public { Public declarations } end;
All we need to do is add the Deltics.Forms unit to the uses list in such a way that the TForm in that unit “hides” the TForm in the Forms unit. This means adding it after the Forms unit:
uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, Deltics.Forms;
That’s it. (We could of course just replace Forms with Deltics.Forms but there are other things in the Forms unit that we will likely want to refer to).
Either way, the TForm that the TForm1 class derives from is now Deltics.Forms.TForm.
Still, this doesn’t achieve very much other than to play a harmless little prank on the VCL. But what it does is provide us with an opportunity – we can now introduce things into TForm, or make changes using normal OO techniques.
For now let’s just add a fairly trivial new feature – a property that we can manipulate at runtime to provide our forms with a size grip if we wish (design-time properties are a whole different ball game when it comes to forms).
Again, this is so trivial a download is a bit much, so here’s the entire unit:
unit Deltics.Forms; interface uses Forms, Deltics.Controls.SizeGrip; type TForm = class(Forms.TForm) private fHasSizeGrip: Boolean; fSizeGrip: TSizeGrip; procedure set_HasSizeGrip(const aValue: Boolean); procedure CreateSizeGrip; protected procedure CreateWnd; override; procedure DestroyWnd; override; public property HasSizeGrip: Boolean read fHasSizeGrip write set_HasSizeGrip; end; implementation uses SysUtils; procedure TForm.CreateSizeGrip; begin if NOT HandleAllocated then EXIT; fSizeGrip := TSizeGrip.Create(self); fSizeGrip.Parent := self; end; procedure TForm.CreateWnd; begin inherited; if HasSizeGrip then CreateSizeGrip; end; procedure TForm.DestroyWnd; begin FreeAndNIL(fSizeGrip); inherited; end; procedure TForm.set_HasSizeGrip(const aValue: Boolean); begin if (fHasSizeGrip = aValue) then EXIT; fHasSizeGrip := aValue; if HasSizeGrip then CreateSizeGrip else FreeAndNIL(fSizeGrip); end; end.
Just The Tip of The TIceBerg
The Deltics.Forms unit actually started out with a different purpose and incorporates much more than a few trivial additions to TForm.
The Application object gets some attention, and TForm gets a significant further work-out, a lot of it in the name of Creating Windows Vista Ready applications. Since the Vista readiness changes hinge on the Application object, I shall look at how I achieved that next.
Hi again Jolyon.
I’m very much enjoying your blog as you’re seeing and doing Delphi in ways I haven’t considered or seen before which is great.
Anyhow, there may be one small ‘issue’ in your above …
# procedure TForm.CreateSizeGrip;
# begin
# if (Handle = 0) then
# EXIT;
I’m thinking that would be better to do …
if not HandleAllocated then EXIT;
since calling the ‘Handle’ property will cause the Handle to be allocated (if it isn’t already) which also indirectly calls CreateWnd thus calling CreateSizeGrip a second time. (I confess I haven’t tested this to be sure.)
Demonstrating once again the value of peer review!
🙂
Thanks angus – well spotted.
Jolyon,
your amended code has missed an important ‘not’. 🙂
#if HandleAllocated then EXIT;
should be …
if not HandleAllocated then EXIT;
Aarrggh! That’s what happens when you try to update a personal blog whilst munching on Skippy in a “spare” 10 minutes before heading out to your day-job!
🙂
Indebted to you.
From now on I think I’ll have to claim that every post contains a deliberate error with kudos points for the first to spot it. It won’t be true but it might save me some blushes.
🙂