Link to home
Start Free TrialLog in
Avatar of tdk_man
tdk_man

asked on

Tracking Child Windows

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

  Application.CreateForm(TForm2,Form2);
  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?

TDK_Man
Avatar of kretzschmar
kretzschmar
Flag of Germany image

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

?? what for an array ??
Avatar of Darth_helge
Darth_helge

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:
bufForm).Memo.Lines.SaveToFile(SaveFName);

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
helgesen
SOLUTION
Avatar of gmayo
gmayo
Flag of United Kingdom of Great Britain and Northern Ireland image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Hi,

>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 );
  Application.CreateForm(TForm2,Form2);
  Form2.Tag := ChildrenCount;

Regards, Geo
What about this?

max:=MDIChildren[0].Tag;
for i:=1 to MDIChildCount-1 do
  if MDIChildren[i].Tag>max then max:=MDIChildren[i].Tag;
Application.CreateForm(TForm2,Form2);
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 ?
Avatar of tdk_man

ASKER

Lots of replies! Thank you all.

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

Quote:

"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...

TDK_Man
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,
type
  TMyInfo = record
     SettingsFile : string;
  end;
  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.
Avatar of tdk_man

ASKER

geobul:

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.

Darth_helge:

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

gmayo:

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

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

to

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.

sgc_romania:

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?

TDK_Man



ASKER CERTIFIED SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of tdk_man

ASKER

Geobul:

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_Man
Avatar of tdk_man

ASKER

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!

TDK_Man
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 ;-)

Avatar of tdk_man

ASKER

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?

TDK_Man
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