For reasons quite unfathomable by me, you would like to get your hands on a list of all forms in your project. [1]
But wait. Do you want this at design time? Or at run time?
Oh, at run time. Pfffew. Ok. I can do that. [2]
But wait. Do you want a list of the forms that are instantiated? Or all the forms?
Please say instantiated. I can do that. It’s easy.
All forms is harder. Unless… you are using D2010 or higher.
Let’s ease into this. Let’s start with finding the instantiated forms first. I’ll cover finding all forms in a future post (if I forget, feel free to remind me).
All instantiated forms
**Edit**
As David mentioned in the comments, there is a much easier way of getting all instantiated forms in your application at run time. It has a caveat though. See take two on this subject for more information.
Finding the instantiated forms should be easy. Just iterate over the Application’s components and check each to see whether it is a descendant of TCustomForm
.
Why the check? Because the Application’s component list can include components that are not forms. Any data modules that you have instantiated are in there. As well as any other components that you have instantiated passing the Application as their owner.
Why not check for descendants of TForm
? Because there is nothing that says that a forms should descend from TForm
. TForm
just happens to be the class that forms created through the IDE descend from. And all it does when compared with TCustomForm
is give a whole slew of properties published visibility so the Object Inspector can see them. (For the nitpickers amongst us: it also adds a class constructor and destructor to register and unregister a style hook.)
Our first try would thus look like:
var Component: TComponent; idx: Integer; begin for idx := 0 to Application.ComponentCount - 1 do begin Component := Application.Components[idx]; if Component.InheritsFrom(TCustomForm) then // Whatever you want to do with them. Me, I am just adding them to a Memo. Memo1.Lines.Add(Format('%s (%s)', [Component.Name, Component.ClassName])); end; end;
That should get you a list of all instantiated forms in your application.
[Grumble]
By the way, I hate that we still have to use an ordinary for loop here. I would much prefer to use a for-in loop:for Component in Application.Components doI really don’t understand why the powers that be haven’t seen fit to enable this. All it takes is an enumerator and it would make a lot of code that much more readable and less (off-by-one) error prone.
[/Grumble]
But wait…
The code above will only get you the instantiated forms that have Application as their owner. That is forms instantiated by either:
Application.CreateForm(TMyForm, MyForm);
or
MyForm := TMyForm.Create(Application);
It won’t get you any of the forms that were instantiated without an owner:
SomeForm := TMySpecialForm.Create(nil);
Neither will it get you forms that were instantiated with a component other than Application as their owner:
SecondForm := TSecondForm.Create(FirstForm); ThirdForm := TThirdForm.Create(MyDataModule);
You could dig down from Application and examine the entire component tree recursively.
procedure DigDownComponent(const aComponent: TComponent; const aList: TStrings; const aPrefix: string); forward; procedure DigDownApplicationComponents; var FormList: TStringList; begin FormList := TStringList.Create; try DigDownComponent(Application, FormList, ''); finally FormList.Free; end; end; procedure DigDownComponent(const aComponent: TComponent; const aList: TStrings; const aPrefix: string); var idx: Integer; Current: TComponent; begin for idx := 0 to aComponent.ComponentCount - 1 do begin Current := aComponent.Components[idx]; if Current.InheritsFrom(TCustomForm) then aList.Add(Format('%s%s (%s)', [aPrefix, Current.Name, Current.ClassName])); if Current.ComponentCount > 0 then DigDownComponent(Current, aList, aPrefix + ' '); end; end;
That is a lot more complete. It will get you all forms instantiated with either Application or another component as their owner.
It still won’t get you the forms that were instantiated without an owner though.
There isn’t much you can do about that. Not unless you are prepared to do some heavy lifting in the realms of hooking, enumerating handles, posting and responding to messages, or some other means of getting your hands on otherwise elusive players.
I’d say it would be far easier to set up some form factory or registry of your own.
Assuming you are gunning for some consistency in your forms, you will be using your own base form already anyway, so getting all forms to register themselves upon instantiation should be simple (and may be the subject of a future post).
Notes
[1] What does “in your project” mean exactly? Just the forms in units that are included in the dpr’s uses clause? Or also forms in units that are included in uses clauses of other units? But what about forms included in the VCL and RTL then? Or in third party libraries for that matter? Or perhaps in your own libraries? Where do you draw the line? And how would you be able to recognize that line in code?
[2] Getting all the forms in your project at design time is possible too of course. It does need a different approach though. One I would have to do quite some research on. Research that isn’t of much interest to me at the moment as I don’t do a lot of UI development nowadays. That said, I might still dig into it out of curiosity. Couple of gentle pushes from the community might help 🙂
I think
is
is more idiomatic than InheritsFrom. Also, Screen.CustomForms is the registry of instantiated forms.Ouch, do I feel stupid. Forgot all about
Screen.CustomForms
.