Tracking Child Windows

My MDI text editor application creates child windows with the following code:

  Form2.Tag := MDIChildCount;  

Each window has a memo component and important info is stored in arrays. When I access a child window, it's Tag property tells me that window's number so I know what array element to access.

I save the memo's contents to disk with:

(ActiveMDIChild as TForm2).Memo.Lines.SaveToFile(SaveFName);

I was told that this method wouldn't work with the arrays, but all my tests showed that it seemed to be fine. However, I should have listened because after more serious testing, I have discovered that I was wrong and it really doesn't work.

If you open say 5 child windows, then close window 3 there are four left so creating another window creates one with the same number as an existing one and all my array data gets all mixed up.

Is there a method of keeping track of multiple child windows where any number can be created and deleted in any order as and when required?

Who is Participating?

[Product update] Infrastructure Analysis Tool is now available with Business Accounts.Learn More

I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

>it's Tag property tells me that window's number so I know what array element to access.

?? what for an array ??
how about having one TForm variable like a buffer?
something like this.

var bufForm: TForm2;

So when you access the a window, you probably put this code in one of the forms event like onEnter
bufForm := self;

With this code, bufForm will ALWAYS be the active window.

Then you can:

just so you know, everytime you do the 'bufForm := self' you don't create a new instance of a class. you just copy the reference of the child window to a TForm2 variable.

hope this helps
function GetUnusedArrayEntry : integer;
  used : array [0..100] of boolean;
  idx : integer;
  for idx := Low(used) to High(used) do used[idx] := false;
  for idx := 0 to MDIChildCount - 1 do begin
    used[MDIChildren[idx].Tag] := true;
  Result := -1;
  idx := Low(used);
  while (Result < 0) and (idx <= High(used)) do begin
    if used[idx] = false then Result := idx;

Use the above to get an unused array entry, and use that as the tag value for the new form. Make sure the array is big enough for the maximum number of child windows that you may want.

Geoff M.
C++ 11 Fundamentals

This course will introduce you to C++ 11 and teach you about syntax fundamentals.


>Form2.Tag := MDIChildCount;
is wrong. You have to prepare a counter yourself:

// in your main MDI form
var ChildrenCount: integer;

In OnCreate event or in the initialization section:
ChildrenCount := 0;

When you are creating new child:
  Inc(ChildrenCount );
  Form2.Tag := ChildrenCount;

Regards, Geo
What about this?

for i:=1 to MDIChildCount-1 do
  if MDIChildren[i].Tag>max then max:=MDIChildren[i].Tag;
Form2.Tag := max+1;
Sorry to whinge, but both the above are inefficient and will require an array large enough for the maximum number of child windows that could possibly be used. My method at least *re-uses* array entries that are no longer being used.

Geoff M.
just to reask,

what is the purpose of the array ?
tdk_manAuthor Commented:
Lots of replies! Thank you all.

It will take me a bit to check out the various suggestions and report back.


"what is the purpose of the array ?"

Each child window contains a memo, but the actual content of the memo is not the only information which is stored on saving. One example is that in my application, each window is associated with a 'settings' file which isn't actually loaded, but information is saved to it when settings in the parent window are changed.

So, if I open and view a number child windows, when I select one and make changes, I need the name and location of the respective 'settings' file. This information is stored in an array and I just needed a way to make sure that the Tag value of any child window always matches the array element of it's respective information arrays.

There are other info arrays, but you get the idea...

Why don't you use the Tag value to store the *pointer* to the data you wish to store? You'd need to cast it but it's very easy.

For example,
  TMyInfo = record
     SettingsFile : string;
  PMyInfo = ^TMyInfo;

To set tag value:
MyForm.Tag := Integer(MyData);

To get tag value:
MyData := PMyInfo(MyForm.Tag);

where MyData is of type PMyInfo (ie a pointer to the structure).

Geoff M.
tdk_manAuthor Commented:

Doesn't your code just increase the number of ChildrenCount each time a new child window is created? If you close an existing one, the new child actually uses the 'gap' that's left, at which point your variable would not be in sync with the new window. As far as I can tell, the result would be wrong in exactly the same way as the method I used.

For example, open 5 windows, close #3, open another one and ChildrenCount would be 6 (5 if you decreased the var on closing a window). The new window would be in slot 3 on the window 'list', but your method would apply the value 6 (or 5) to that window's tag.


Sorry - I didn't follow what you were suggesting.


Sorry, but this didn't work for me either. I had to change the line

if used[idx] = false then Result := idx;


if used[idx] = false then Result := idx+1;

or I got an access error when opening the first child window. From then on, it always returned 1 - regardless of the number of windows already open. It does seem to be along the lines of what I'm after though. I am assuming that closing window 3 of 5 leaves a gap and that any new window will actually be window 3 and that closing windows 3 and 4, the next new window will use the first free slot (3) - not 4.


Sorry, won't run - AccessViolation when a new window is created (at the line max:=MDIChildren[0].Tag;).

OK, here's an example of what I'm after:

You open a child window. Using a control on the parent window, you are able to set the colour of the text in the memo to red. This colour is stored in an array linked to the child's window number (I was incorrectly using MDIChildCount). The contents of the array is placed in an editbox on the main parent form.

Another child window is opened, the colour set to green and stored in the colour array.

The process is repeated with different colours until you have opened 5 child windows. Each time you click on one of the child windows, the colour of that window's memo text is shown in the edit box.

Using my method, this works fine using the value of MDIChildCount as the element in the array.

Now if you close window number 3 and open up another window and set it's memo text colour, the value of MDIChildCount will be a value already used by one of the windows in the array, so using it again will overwrite one of the other window's data.

I need to be able to open and close any windows and still be able to click on any window and access that window's text colour info in the array.

Obviously I don't want anyone to write a program to do this - I'm just demonstrating what it needs to do. Basically, I need a way to attach a value to the tag property of a newly created child window and use that value to place data into an array. But, the method of doing so must be able to withstand the closing of windows without the arrays going out of sync.

Is there a proper 'window number' value available anywhere once a child window has been created that could be used as the tag value?



Yes, my example increases the index but I thought you needed an unique index for each window. You didn't say what you used that index for and how.

Now it's more clear after your second comment. The right way (if you insist on using arrays) is what gmayo said in the beginning - on closing a window empty the corresponding record in the array and on creating a new one find the first empty slot. Something like:

const MaxChildren = 100;

var aChildren: array [1..MaxChildren] of string;

// on closing window
aChildren[self.Tag] := ''; // empty the slot

// on creating new child window

function GetIndex: integer;
var i: integer;
  result := 0; // means there are no empty slots available
  for i := 1 to MaxChildren do begin
    if aChildren[i] = '' then begin
      result := i;

// usage
var idx: integer;
  idx := GetIndex;
  if idx > 0 then begin
    Form2.Tag := idx;
    aChildren[idx] := 'something';
    // more code here
  end else ShowMessage('Maximum number of opened windows has been reached !');
Regards, Geo

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
tdk_manAuthor Commented:

Yes, sorry - after re-reading my initial post, although it does correctly describe the problem, it could have been a lot clearer! After reading my second post, I hope the first one makes more sense... :)

Anyway, the code you post looks promising so I'll try it out. The only (possibly stupid) question I have is this:

In the GetIndex function, 'result' is allocated the first empty slot in the aChildren array. If more than one window is closed, (say windows 2, 3 and 4), is it always guaranteed that the next open window will be number 2? Or doesn't it matter?

I assume that there is an internal window list maintained with MDIChildren[i] but the contents of this list is not static and windows are shunted around this list as they are created and closed. I never could get my head around this aspect... :)

tdk_manAuthor Commented:
Thanks Geobul - that worked perfectly.

I've split the points between you and gmayo as he was correct in the method as you said, and if I had worded my question better, would have probably offered the correct code. So, a nominal 20 points out of the 100 to him.

Thanks to both of you - and all the others who posted!

You are welcome :-)

About your question: GetIndex function will always return the first empty slot (with the smallest index) regardless of how many holes there are (or aren't). If it returns 0 then there are no empty index in the array. If you are using zero based arrays then make it returning '-1' in that case by replacing the first line with result := -1; and check its result for '> -1' instead of greater than zero (as in my example above).

Regards, Geo
i would use a TList or TStringList rather than an array,

just as suggestion

meikl ;-)

tdk_manAuthor Commented:
The arrays are working great, so I'm happy.

However, what benefit would using a TList or TStringList rather than an array be? Are there any advantages?

Dynamic, your flexible friend. Unless you are using dynamic arrays of course. Personally I avoid arrays, since TList and TStringList are so much easier to use. In fact, you can even access them like arrays:

var sl : TStringList;

sl[index] := 'Hello World';

Geoff M.
yep, nothing to append to goeff
I don't use array of string because TStringList is definately better and easier as Geoff already said. But I still prefer array of record instead of TList. Perhaps it's wrong but all that typecasting confuses me. I'd rather create a new class that does what I need instead (derived from TList or TStrings, of course) and encapsulate that typecasting and error checking inside the class. I have already written such classes for some basic types (like integer and currency). It's easy enough.

Regards, Geo
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today

From novice to tech pro — start learning today.