How to find all instantiated forms in your project at run time

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 do

I 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 🙂

Posted in Software Development
Tags: , , , , , , ,
2 comments on “How to find all instantiated forms in your project at run time
  1. David Heffernan says:

    I think is is more idiomatic than InheritsFrom. Also, Screen.CustomForms is the registry of instantiated forms.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

Show Buttons
Hide Buttons