?
Solved

Avoiding the form 'hidden global variable' has me befuddled

Posted on 2003-02-26
11
Medium Priority
?
229 Views
Last Modified: 2010-04-07
Hello Experts. Thanks for your interest in my question.

I've got an executable that uses a 'switchboard' sort of main form to open other forms. Blissful in my ignorance, it seemed as though everything was working fine using the following 'switchboard' code to open other forms (The IsLoaded("x") proc loops through the forms collection trying to find one whose name is "x"):

If IsLoaded("form_name") Then
  form_name.Visible = True
  form_name.WindowState = vbNormal
  form_name.SetFocus
Else
  form_name.Show
End If

Then, one day, my code exploded when a private, form module-level variant-type variable wasn't empty when I expected it to be. The error appeared the second and subsequent times I opened the form. I figured out that the variable is never out-of-scope because the form_terminate event never fires because the hidden global variable I inadvertantly created with 'form_name.Show' is still alive. The solution is simple if I'm willing to open the forms modally:

Dim f as Form
Set f = New form_name
f.Show vbModal
Set f = Nothing

The form_terminate event fires and the module level variables go out-of-scope. If the user re-opens the form they're not haunted by 'ghosts of form variables past'. But this approach is unacceptable because users shouldn't have to close the widgets form just so they can open the doodads form. Cascading modal forms (modal widgets opens modal doodads opens modal etc) is also unacceptable.

My goal is to restore the instance of the form that the user had minimized and forgotten rather than create a new instance; but when the form is closed the module-level variables need to be destroyed. So, hands on buzzers, how can I get form x to restore form y if it's already open,  open it if it's not, and form y's module-level variables to go out-of-scope when the user closes it?
 
I'm working around the problem by resetting module-level variables to their original values in the QueryUnload event, but that's cheesy and I want to keep the cheese I'm aware of to an absolute minimum.

Thanks for your help.

Keith
0
Comment
Question by:ketchukf
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 5
  • 2
  • 2
  • +1
11 Comments
 
LVL 5

Expert Comment

by:JohnMcCann
ID: 8030692
If you post a bit more code I will see what I can do.

I understand most of what you are saying but do not understand what you are saying about the global variable.  Is there a variable in a module that hold a refernece to the form?
0
 
LVL 7

Expert Comment

by:webJose
ID: 8030715
You could explicitly initialize the variables in the Form_Load event.

Private myVariant as variant

private sub Form_Load()
    myvariant = empty
end sub

It is my understanding that even if the form was not unloaded from memory it will run the Load event, successfully getting rid of your problem.

Alternatively, you could try to find the reference that is holding the form in memory.

Alternatively, you could put the cleanup code in the Form_Unload event, or Form_QueryUnload event as it is also my understanding that these events will fire when the form is closed, regardless of the form being referenced elsewhere in some object variable.
0
 
LVL 5

Expert Comment

by:JohnMcCann
ID: 8030732
The approach you are already using seems correct and if the user is preesing the close button the form should unload and destroy all of its variables.

I have had a little playy and cannot recreate what you are saying.
0
Technology Partners: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 

Author Comment

by:ketchukf
ID: 8030904
It's there, but it's hidden.

Open:
msdn.microsoft.com/library/en-us/vbcon98/html/vbconcustomizingformclasses.asp

and search for 'Me and My Hidden Global Variable'.

Also: msdn.microsoft.com/library/en-us/vbcon98/html/vbconlifecycleofvisualbasicforms.asp

and search for 'Memory and Resources Completely Reclaimed'
0
 

Author Comment

by:ketchukf
ID: 8031050
Typo:
The line in the original post
'Dim f as Form'
should read
'Dim f as form_name'
Sorry.

webJose:
Explicitly initializing the variables in the Form_Load event is essentially the same as the workaround I mentioned I'm using now - re-initializing the variables in the Form_QueryUnload event.

The point is that when a form is loaded with 'form_name.Show' and later unloaded, the Hidden Global Form Variable - form_name - still exists and the form module-level variables retain their values. The Form_QueryUnload event has fired, but the Form_Terminate event doesn't until the object variable that references it is set to nothing.
0
 
LVL 7

Expert Comment

by:webJose
ID: 8031279
Yes, I know it is a workaround.  The only true solution is to find the variable that is holding the reference to the form, but without knowing the exact code of your application, it is impossible for me or anyone else to help you.
0
 
LVL 2

Expert Comment

by:ventond
ID: 8033944
I had this problem when I went to ADO from DAO. I would create dynamic Commands and Recordsets. If I unloaded the form and then reloaded the form the commands and recordsets would still exist. It would appear that the Form had a reference to the ADO objects and the ADO objects had a reference to the form. I just figured it was good programming practice to explicitly destroy my objects when I was done with them (it is).  (ie. Set rsADO = Nothing)

Along those lines, your if Isloaded.... statement:
The .Show method does the Isloaded for you.
You can change your code to
form.show
form.windowstate ....
form.zorder = 0
0
 

Author Comment

by:ketchukf
ID: 8034007
Here's the sample code. Create a new project and a form with 3 commmand buttons on it. Copy the Form1 text below and paste. Then create another form and paste the Form2 text below into its module. Then follow the instructions in Form1's module to reproduce the problem.

'**************** Begin Form1 ********************
Option Explicit

' First, press the Command1 button, then unload Form1.
' Notice that the Form1 QueryUnload event fires. Then
' Press the Command1 button gain. Notice that the Form1
' module-level variable is NOT empty.

' Now try the Command2 button, then unload Form1 and you
' see the Form1 terminate event has fired. Press Command2
' again and notice that the Form1 module level variable
' is empty because the terminate event killed it.

' Opening the form modally is unacceptable because I can't
' limit the user to only having one form open at a time.
' So here's the question: how can I program a button to
' restore Form1 if it's already open but minimized or open
' it if it isn't already (like Command1 does), but fire
' the terminate event to release the memory used by form
' module-level variables (like Command2 does)?

Private Sub Command1_Click()
  If IsLoaded("Form2") Then
    Form2.Visible = True
    Form2.WindowState = vbNormal
    Form2.SetFocus
  Else
    Form2.Show
  End If
End Sub

Private Sub Command2_Click()
  Dim f As Form2
  Set f = New Form2
  f.Show vbModal
  Set f = Nothing
End Sub

Private Sub Command3_Click()
  Dim f As Form
  For Each f In Forms
    Unload f
    Set f = Nothing
  Next
End Sub

Private Function IsLoaded(sFormName$) As Boolean
  Dim f As Form
  IsLoaded = False
  For Each f In Forms
    If f.Name = sFormName$ Then
      IsLoaded = True
      Exit For
    End If
  Next
End Function
'**************** End Form1 ********************

'**************** Begin Form2 ********************
Option Explicit
Private vModuleLevelVariant As Variant

Private Sub Command1_Click()
  vModuleLevelVariant = "Nyah, nyah"
  Unload Me
End Sub

Private Sub Form_Load()
  If IsEmpty(vModuleLevelVariant) Then
    Debug.Print "Form1's vModuleLevelVariant is Empty"
  Else
    Debug.Print "Form1's vModuleLevelVariant is not Empty. " & vModuleLevelVariant
  End If
End Sub

Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
  Debug.Print "Form1 QueryUnload event has fired"
End Sub

Private Sub Form_Terminate()
  Debug.Print "Form1 terminate event has fired"
End Sub
'**************** End Form2 ********************
0
 

Author Comment

by:ketchukf
ID: 8037595
Thanks for the tip ventond, you've saved me a couple lines of code.

I do close recordset objects and set them equal to nothing when I'm finished with them. The problem here isn't so easily solved, however. How do I set the hidden variable (that you can read about by following the links I supplied in the comment I made on 02/26/2003 at 07:47PM PST above) that VB creates when I do a form_name.Show to nothing? If I don't set this hidden variable to nothing, then module level variables retain their value when I close the form. Isn't it reasonable to expect form module level variables to go out-of-scope when the form is closed? Do you reset each string variable to an empty string and each integer to zero at the end of each procedure?

One of the MSDN docs I refer to above says:

'Tip   Many professional programmers avoid the use of the hidden global variable, preferring to declare their own form variables (for example, Dim dlgAbout As New frmAboutBox) to manage form lifetime.'

If I follow the tip, how do I restore the form instead of opening a new one when the user clicks the same button again? If I don't follow the tip, how do I set the hidden global variable that VB created to nothing so that the form_terminate event fires?

0
 
LVL 2

Accepted Solution

by:
ventond earned 800 total points
ID: 8038293
You pose an interesting problem. VB seems to be doing exactly what it should not be doing. And what I teach that it doesn't do btw.
I found 3 api calls that were promising:
    x = ReleaseDC(Me.hwnd, Me.hdc)
    x = CloseWindow(Me.hwnd)
    x = DestroyWindow(Me.hwnd)

Not a one of them caused the form to terminate or lose it's module variables.

It appears that the only solution is to do the instantiation route.

Form1 (Place in event you want to show Form2):
    Dim f As Form2
    Dim i As Integer
    Dim b As Boolean
   
    For i = 0 To Forms.Count - 1
        If TypeOf Forms(i) Is Form2 Then
            Forms(i).Show
            Forms(i).WindowState = vbNormal
            Forms(i).ZOrder 0
            b = True
            Exit For
        End If
    Next
    If b = False Then
        Set f = New Form2
        f.Show
    End If


When form2 unloads the terminate event fires and the module scope variables release. Plus you will only ever have 1 copy of form2 in memory at once. I think this will meet all your requirements. Let me know if I missed something.
0
 

Author Comment

by:ketchukf
ID: 8038481
ventond, you are the Man! (or Woman!). I hadn't thought of using the TypeOf keyword. Your solution works like a charm. This was the first question I'd posted on Experts Exchange, and I am impressed. Thank you very much.
0

Featured Post

What does it mean to be "Always On"?

Is your cloud always on? With an Always On cloud you won't have to worry about downtime for maintenance or software application code updates, ensuring that your bottom line isn't affected.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

You can of course define an array to hold data that is of a particular type like an array of Strings to hold customer names or an array of Doubles to hold customer sales, but what do you do if you want to coordinate that data? This article describes…
Since upgrading to Office 2013 or higher installing the Smart Indenter addin will fail. This article will explain how to install it so it will work regardless of the Office version installed.
Get people started with the process of using Access VBA to control Excel using automation, Microsoft Access can control other applications. An example is the ability to programmatically talk to Excel. Using automation, an Access application can laun…
Get people started with the utilization of class modules. Class modules can be a powerful tool in Microsoft Access. They allow you to create self-contained objects that encapsulate functionality. They can easily hide the complexity of a process from…
Suggested Courses
Course of the Month13 days, 4 hours left to enroll

777 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