Solved

Tracking Child Windows

Posted on 2003-11-13
19
402 Views
Last Modified: 2010-04-05
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
0
Comment
Question by:tdk_man
  • 5
  • 4
  • 4
  • +3
19 Comments
 
LVL 27

Expert Comment

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

?? what for an array ??
0
 
LVL 5

Expert Comment

by:Darth_helge
ID: 9745906
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
0
 
LVL 8

Assisted Solution

by:gmayo
gmayo earned 20 total points
ID: 9745967
function GetUnusedArrayEntry : integer;
var
  used : array [0..100] of boolean;
  idx : integer;
begin
  for idx := Low(used) to High(used) do used[idx] := false;
  for idx := 0 to MDIChildCount - 1 do begin
    used[MDIChildren[idx].Tag] := true;
  end;
  Result := -1;
  idx := Low(used);
  while (Result < 0) and (idx <= High(used)) do begin
    if used[idx] = false then Result := idx;
    Inc(idx);
  end;
end;

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.
0
 
LVL 17

Expert Comment

by:geobul
ID: 9746279
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
0
 
LVL 2

Expert Comment

by:sgc_romania
ID: 9748658
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;
0
 
LVL 8

Expert Comment

by:gmayo
ID: 9748903
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.
0
 
LVL 27

Expert Comment

by:kretzschmar
ID: 9749387
just to reask,

what is the purpose of the array ?
0
 
LVL 1

Author Comment

by:tdk_man
ID: 9750138
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
0
 
LVL 8

Expert Comment

by:gmayo
ID: 9750280
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.
0
What Is Threat Intelligence?

Threat intelligence is often discussed, but rarely understood. Starting with a precise definition, along with clear business goals, is essential.

 
LVL 1

Author Comment

by:tdk_man
ID: 9751862
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



0
 
LVL 17

Accepted Solution

by:
geobul earned 80 total points
ID: 9753449
Hi,

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;
begin
  result := 0; // means there are no empty slots available
  for i := 1 to MaxChildren do begin
    if aChildren[i] = '' then begin
      result := i;
      break;
    end;
  end;
end;

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

Author Comment

by:tdk_man
ID: 9754175
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
0
 
LVL 1

Author Comment

by:tdk_man
ID: 9755249
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
0
 
LVL 17

Expert Comment

by:geobul
ID: 9762131
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
0
 
LVL 27

Expert Comment

by:kretzschmar
ID: 9762158
i would use a TList or TStringList rather than an array,

just as suggestion

meikl ;-)

0
 
LVL 1

Author Comment

by:tdk_man
ID: 9765654
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
0
 
LVL 8

Expert Comment

by:gmayo
ID: 9765975
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.
0
 
LVL 27

Expert Comment

by:kretzschmar
ID: 9766175
yep, nothing to append to goeff
0
 
LVL 17

Expert Comment

by:geobul
ID: 9769823
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
0

Featured Post

6 Surprising Benefits of Threat Intelligence

All sorts of threat intelligence is available on the web. Intelligence you can learn from, and use to anticipate and prepare for future attacks.

Join & Write a Comment

A lot of questions regard threads in Delphi.   One of the more specific questions is how to show progress of the thread.   Updating a progressbar from inside a thread is a mistake. A solution to this would be to send a synchronized message to the…
Introduction The parallel port is a very commonly known port, it was widely used to connect a printer to the PC, if you look at the back of your computer, for those who don't have newer computers, there will be a port with 25 pins and a small print…
Get a first impression of how PRTG looks and learn how it works.   This video is a short introduction to PRTG, as an initial overview or as a quick start for new PRTG users.
Polish reports in Access so they look terrific. Take yourself to another level. Equations, Back Color, Alternate Back Color. Write easy VBA Code. Tighten space to use less pages. Launch report from a menu, considering criteria only when it is filled…

759 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

22 Experts available now in Live!

Get 1:1 Help Now