Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations biv343 on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Form Controlling Others throwing Access Violation on repetitive use 1

Status
Not open for further replies.

Opieo

Programmer
Jul 26, 2006
454
GB
Okay, so the section I am working on is part of a MDI.
The user can run a query to search for a part and it displays some quick information into a string grid.
I have it set up so that the user can double click on a row in that grid, and it is supposed to basically take them through the process that they would go through to get to where they can edit that part.
It works like this:
Code:
  Form11.Show8();       // First select the program
  Form11.Child8.ComboComp.Text := Form11.DBProgs[searchTrack].Company;
  Form11.Child8.ComboCompSelect(Self);
  Form11.Child8.ComboProg.Text := Form11.DBProgs[searchTrack].Name;
  Form11.Child8.ComboProgSelect(Self);

It works just fine. I have tested it for all 3 needed scenarios and it works. Where it doesn't work, is if they don't close any of the forms, just go back to the Query form, and double click on a value of the same type.

(Example, there is scenario A, B, and C. They all work. If you have a type A double clicked through, and then do another search, and a type B shows up, then double click it, it will work right then. So A -> B or C, B -> A or C, etc. works, but not A->A , B->B, or C->C).

For each of these, the Query form basically just follows exactly how they would click and calls any needed button clicks, that way everything in the background that needs to happens still happens.
I have tried error trapping it down to a specific line it doesn't seem to want to be trapped.
The thing is, if I put in a [blue]ShowMessage[/blue] somewhere before it opens up the last form, after it has opened up the menu form, then it works fine. I can remove the [blue]ShowMessage[/blue] and it is broken again.
It doesn't matter which of the 3 scenarios it is, this happens. [blue]ShowMessage[/blue] and it works, no [blue]ShowMessage[/blue] and it doesn't.
What is Delphi doing that switching over to the tiny [blue]ShowMessage[/blue] dialog box allows it to just work smoothly?

I will also note that if they close the last form, be it the form corresponding to type A, B, or C, (even leaving open the menu screen) and then do the double click from query, it works just fine.
When the code selected the company and program, and then goes to the menu from there, that selector form already has in the called button click a section where it unmakes (release, free, set to nil) any children that are dependant on the menu form. That means that in the middle, even if they don't close the form specifically for A, B, or C, the forms poof anyways. Using the [blue]ShowMessage[/blue] has proven (at least by all I can tell) this by freezing the program when those forms do not exist.
The user can also click through these exact same processes manually an unlimited number of times without error.
The processes described above to create the error can also be repeated at least very many times with the error only occuring when mentioned.

I have no clue why.
Just in case, here is the code for an entire Scenario A:
Code:
  Form11.Show8();       // First select the program
  Form11.Child8.ComboComp.Text := Form11.DBProgs[searchTrack].Company;
  Form11.Child8.ComboCompSelect(Self);
  Form11.Child8.ComboProg.Text := Form11.DBProgs[searchTrack].Name;
  Form11.Child8.ComboProgSelect(Self);

  // Branch here if it is a Service part
  [blue]if[/blue] (partIs.Caption = [green]'It is a Service part number'[/green]) [blue]then[/blue]
   [blue]begin[/blue]
    Form11.Child8.ButServ.Click;
    [blue]for[/blue] i := 1 [blue]to[/blue] Form11.Child2.datagrid.RowCount - 1 [blue]do[/blue]
     [blue]if[/blue] (QryGrid.Cells[3,QryGrid.Row] = Form11.Child2.datagrid.Cells[2,i]) [blue]then[/blue]
      [blue]begin[/blue]
       Form11.Child2.datagrid.Row := i;
       Break;
      [blue]end;[/blue]
    Form11.Child2.ButDetail.Click;
    Exit;
   [blue]end;[/blue]

Scenario B and C occur later if not exited.
I just don't understand how adding the [blue]ShowMessage[/blue] can make it work and removing it can break it. It is not even functional. And the forms are destroyed and created with or without it.

~
Give a man some fire, he will be warm for a day, Set a man on fire, he will be warm for the rest of his life.
 
i dont have an answer to your problem because i cant quite follow what your doing.

but i have a tip, give your forms and components meaningful names.

i.e. frmSearchForCompany, btnOk, lkpcmbFirstName

tip, lowercase, remove vowels and double constanants from its default name then add what it does.

i.e. Form1 becomes 'frm', Button1 becomes 'btn', ListBox1 becomes 'lstbx'.


Aaron
 
Form11 is the MDI Parent.
I kind of follow that convention for components.
I suppose just not quite that exact way.
Combo is a combo box.
But is a button.
datagrid & Qrygrid are String grids.

When I have the time I will rename the forms.
Its been on the to do list for awhile.
Heck, I will probably do that after this works since the next section added will be for functions I haven't learned anything about yet. And as such, I will have to sit down with whoever does them now and learn about them before coding more.

Rewriting the section above to the method you said:
Code:
  frmMDIParent.ShowFrmProgramSelecter();       [green]// First select the program[/green]
  frmMDIParent.ChildProgSelect.CmbCompany.Text := frmMDIParent.DBProgs[searchTrack].Company;
  frmMDIParent.ChildProgSelect.CmbCompanySelect(Self);
  frmMDIParent.ChildProgSelect.CmbProgram.Text := frmMDIParent.DBProgs[searchTrack].Name;
  frmMDIParent.ChildProgSelect.CmbProgramSelect(Self);

  [green]// Branch here if it is a Service part[/green]
  [blue]if[/blue] (lblPartType.Caption = '[blue]It is a Service part number[/blue]') [blue]then[/blue]
   [blue]begin[/blue]
    frmMDIParent.ChildProgSelect.btnService.Click;
    [blue]for[/blue] i := 1 [blue]to[/blue] frmMDIParent.ChildMenu.datagrid.RowCount - 1 [blue]do[/blue]
     [blue]if[/blue] (QryGrid.Cells[3,QryGrid.Row] = frmMDIParent.ChildMenu.datagrid.Cells[2,i]) [blue]then[/blue]
      [blue]begin[/blue]
       frmMDIParent.ChildMenu.datagrid.Row := i;
       Break;
      [blue]end[/blue];
    frmMDIParent.ChildMenu.btnDetails.Click;
    Exit;
   [blue]end[/blue];

What would be the abbreviation for StringGrid, Sgd?
Also, SearchTrack is an Integer that keeps track of which program it was that the part is from. DBProgs is the local array of programs.
Upgrading my coding conventions to be even more next coder friendly is always good! I will make an effort to set aside time to correct the form names soon.

I feel more focused on the question of why adding a [blue]ShowMessage[/blue], even on the Menu form, before its dependants show, seems to prevent the Access Violation from this form.

~
Give a man some fire, he will be warm for a day, Set a man on fire, he will be warm for the rest of his life.
 
try stepping through your code to see where delphi throws the AV. shouldn't be hard...

-----------------------------------------------------
What You See Is What You Get
Never underestimate tha powah of tha google!
 
It throws the error when it goes to re-display the form that is dependant on the Menu.
The Menu can open up several other MDI Children (the A, B, C forms) (The Menu is itself an MDI Child, so it really tells the MDI Parent to create and show).
But the code works fine first time through.
It will go through, make the MDI parent display the program selector, do what it needs to there to have all of the location trackers update properly. It then opens up the menu, and goes through and selects what is needed, then clicks on the button for the user.
It is there when it goes bad.
To illustrate using the example before.
If I place the [blue]ShowMessage[/blue] anywhere above the line:
Code:
[blue]if[/blue] (lblPartType.Caption = [blue]'It is a Service part number'[/blue]) [blue]then[/blue]
   [blue]begin[/blue]
 [red]{Here}[/red]   frmMDIParent.ChildProgSelect.btnService.Click;
Where it shows the Menu from the button click, I still get the AV.
If I place it anywhere in:
Code:
    [blue]for[/blue] i := 1 [blue]to[/blue] frmMDIParent.ChildMenu.datagrid.RowCount - 1 [blue]do[/blue]
     [blue]if[/blue] (QryGrid.Cells[3,QryGrid.Row] = frmMDIParent.ChildMenu.datagrid.Cells[2,i]) [blue]then[/blue]
      [blue]begin[/blue]
       frmMDIParent.ChildMenu.datagrid.Row := i;
       Break;
      [blue]end[/blue];
    frmMDIParent.ChildMenu.btnDetails.Click;
But before that last line where it will open up the form dependant on the Menu, the AV goes away.
If I place it after that line, but before the Exit (judging it doesn't get called if placed later) then I get the AV back.
Somewhere between showing the Menu and showing the form that it tells the parent to create.
But, adding the ShowMessage in there makes it mystically disappear. That is where I get hung up. I can also place the [blue]ShowMessage[/blue] anywhere in the Menu form, before where that button gets clicked by this Query form, and the AV goes away then as well.

Having said all that.
I also added a Stop point in the code, and did the Step Over / Step Into tracing line by line.
I noticed that even though the Releasing was called for all forms dependant on the Menu MDI Child, it still existed when the MDI parent when to check for it before creating.
And since it had been released and was set to [blue]nil[/blue], when it went to set the background color of the dependant MDI child, it throws an AV.
This makes me think that I do not understand the Release / Free / nil relationship as well as I thought I did. I will re-examine these.

However, that still leaves my mind blank as to why adding a ShowMessage in there where I mentioned above, makes it realize that the form does in fact not exist. When the ShowMessage is added where I mentioned, when the MDI Parent searches through its created children, it sees that the child does not exist. No ShowMessage and it does. I must be in the wrong mindset because that simple fact has me more curious than actually solving this without rearranging code.
Just in case, here is the code the MDI Parent uses to check if a child exists:
Code:
  iFound := -1;
  [blue]for[/blue] i:= 0 [blue]to[/blue] MdiChildCount - 1 [blue]do[/blue]
   [blue]if[/blue] (MDIChildren[i] [blue]is[/blue] TForm16) [blue]then[/blue]
    iFound := i;
  [blue]if[/blue] (iFound = -1) [blue]then[/blue]
   [blue]begin[/blue]
    Child16 := TForm16.Create(Self);
    [blue]with[/blue] Child16 [blue]do[/blue]

Any other possible stuff that may help:
Every MDI Child has in an OnClose event handler, [blue]Action := caFree;[/blue]
And the Unmake function in the MDI Parents are simply:
Code:
[blue]procedure[/blue] TfrmMDIParent.UnmakeChildX();
 [blue]begin[/blue]
  [blue]if[/blue] ChildX <> [blue]nil then[/blue]
   ChildX.Release;
  ChildX := [blue]nil[/blue];
 [blue]end[/blue];
I say ChildX because it looks like this for all of them.

~
Give a man some fire, he will be warm for a day, Set a man on fire, he will be warm for the rest of his life.
 
I have pushed it a little farther.
I found a way to clean up the issue, but I still find myself left with questions.
If I change the unmaking procedure to:
Code:
[blue]procedure[/blue] TfrmMDIParent.UnmakeChildX();
 [blue]begin[/blue]
  [blue]if[/blue] ChildX <> [blue]nil then[/blue]
   ChildX.Release;
  ChildX.Free;
  ChildX := [blue]nil[/blue];
 [blue]end[/blue];
Then I have no errors.
From what I can tell after examining Release vs. Free, Release will free and destroy the form being released, but it returns immediately while the messages from the released form are still being processed.
However, if I directly Free it, it will clean up all pending messages, which allows it to finish being destroyed.

I have not dealt with the messages much, is there an easy way to just view any pending messages? Or to force it to handle any pending messages after calling Release.

Another question, since all of the MDI Children are of Type TFormX descended from TForm, is there any way to generalize that child "Unmake" procedure to where I can just have it be procedure UnmakeChild, and pass it which Child will be released?

~
Give a man some fire, he will be warm for a day, Set a man on fire, he will be warm for the rest of his life.
 
don't use Release! it used to destroy an object when called from an event handler from that object, this for custom components.

to make life easier just add an onlose event handler to your forms and put the line Action := caFree; in it. this will free the form when it is closed, so no need for unmake.

some easy rules:

a)
Formx.Create(Application) -> Form resources will be freed when application is terminated

b)
Formx.Create(Self) -> Form resources are freed when the owner object is destroyed (if Self is a descendand of TComponent)

c)
Formx.Create(nil) -> you are responsible for freeing the form

a is used by delphi when a form is autocreated
b is handy for a main form that has several child windows that need to close when the main form is closed
c is handy for showing a dialog window
-> example
Code:
procedure ShowMyCustomDialog;

var MyDialog : TMyDialog; (form called MyDialog)

begin
 MyDialog := TMyDialog.Create(nil);
 try
  MyDialog.ShowModal; // this will show the form and wait for user input
  if MyDialog.Modalresult =mrOk then
   begin
    // do something here...
   end;
 finally
  FreeAndNil(MyDialog); // free object
 end;
end;


I hope this makes sense to you...


-----------------------------------------------------
What You See Is What You Get
Never underestimate tha powah of tha google!
 
WRD. I dont think it was based on the info below, but I have been using 'release' like this for ages! (without any problems)

Delphi expert at about.com

Zarko seems to be saying its OK to use release so long as you dont create the form with (nil)




Steve: N.M.N.F.
Playing the blues isn't about feeling better. It's about making other people feel worse.
 
Yes daddy, I understand the Application, Self, Nil.
Read about it at delphi.about.com. Good site.
As stated above, all of the MDI children already have the Action := caFree; in their OnClose event handlers.
That was part of why I was confused at that point.

~
Chuck Norris is the reason Waldo is hiding.
 
Opieo: ok then, you don't need the Unmake thingy...

Steve: Agreed, but if you read the article he says it's better to use free though release was used in the code...

-----------------------------------------------------
What You See Is What You Get
Never underestimate tha powah of tha google!
 
Steve: release is nothing more than an async call (through PostMessage) to Free. Things get ugly when to object no longer exists when the message arrives from the main message pump. I tend to keep my code as synchronous as possible, this avoids a lot of trouble...

-----------------------------------------------------
What You See Is What You Get
Never underestimate tha powah of tha google!
 
Daddy, that is more the explanation I needed there in the last reply to Steve. It clears up some thoughts.
Thank you.

~
Chuck Norris is the reason Waldo is hiding.
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top