Example 1: Return Values from a Modal Dialog Box

As a simple example, consider a File Open dialog box. You need to know whether the user clicked OK or Cancel, and if the user clicked OK, you also need the name of the file selected.

The calling pseudocode might look something like this:

frmFileOpen.Show vbModal  ' Puts up file open dialog
' Returns here when form is unloaded
If okpressed Then 
    filename = ???
End If

The two main questions are these:

  1. How do you return the OK or Cancel result?

  2. How do you return the filename?

One approach might be to store these in global variables, but I have already discounted this approach.

Another approach is a workaround that was used a lot on older versions of Visual Basic. Its merit is that it does avoid globals, but it’s clumsy. The trick is not to unload the form but simply to hide it. That way, you can still read the values of controls on the form, such as the filename.

This approach has two drawbacks, however. One is that you still cannot “read” whether the user clicked OK or Cancel because these are events, not controls. You might use the Tag property of the form here, but this is still very error-prone and limited. Sometimes you end up with several sets of information that all need to be stored in the Tag property. The second drawback is that the calling function is left with the job of cleaning up the mess. It must determine when it is safe to unload the form, and it must remember to do so. This is another thing waiting to go wrong.

A better approach comes from treating the form like a class. Forms can have Public variables, property procedures, and methods in the same way classes do. These are accessible even after the form is unloaded. So in this example, we simply store the state in two Public variables as follows. Form frmFileOpen looks like this:

Option Explicit
' Public interface for frmFileOpen is two Variants, one to hold the 
' okpressed state and another to hold the filename.
Public okpressed
Public filename

Private Sub cmdCancel_Click()
    okpressed = False
    Unload Me
End Sub

Private Sub cmdOK_Click()
    filename = txtFname ' Place content of text box in filename.
    okpressed = True
    Unload Me
End Sub

The calling program uses this approach:

frmfileopen.Show vbModal
' Form is now genuinely unloaded here.
If frmfileopen.okpressed Then
    MsgBox frmfileopen.filename
End If

It’s quite readable, and I think you’ll agree it’s an improvement.

This looks like the end of the story, but it isn’t. What we have here are disguised global variables! It is possible to access frmfileopen.filename from anywhere in the program and read from or write to its value.

The following line is legitimate anywhere. In all important respects, it is still just like a global!

frmfileopen.filename = "Fred"

Note This will not reload the form, and it is quite unlike referring to the text box directly, as in:

frmfileopen.txtFname = "Fred"

This will cause the form to be reloaded.

We can mitigate this to some extent by making the values read-only. This is done by using a Public Property Get but no corresponding Public Property Let. You also have to store the values in a private Form level variable, which is a little clumsy. If you use the Class Builder, it will create all this for you.

Option Explicit
Private m_okpressed
Private m_filename

' Public interface for frmFileOpen is two property gets,
' one to read the okpressed state and another to read the filename.
Public Property Get filename()
    filename = m_filename
End Property
Public Property Get okpressed()
    okpressed = m_okpressed
End Property

Private Sub cmdCancel_Click()
    m_okpressed = False
    Unload Me
End Sub

Private Sub cmdOK_Click()
' Place content of text box in private filename variable.
    m_filename = txtFname 
    m_okpressed = True
    Unload Me
End Sub

The frmfileopen.filename variable is now a global read-only variable, which is much safer. The only way it can be changed is by opening the form, changing the contents of the text box, and clicking OK.

We are treating forms as classes, and this is allowing us to use them more powerfully than before. However, we still have no control over the scope of the form. The real reason these variables are still global is that although the form name frmfileopen is a class name, it is also the name of a single global instance of that class. When the program loads this form for the first time, it creates an instance of the form class, which is global in scope and has the name of the class itself. This cleverly allows older code to run while making forms, in effect, classes. Once again, this is at the expense of the purity of the language.

To get around the scope issue, we can explicitly create an instance of the form and use that instead, as follows:

' Uses a second (nonglobal) instance of the form.
Dim f As frmfileopen
Set f = New frmfileopen

f.Show 1
' The form is now genuinely unloaded here.
If f.okpressed Then
    MsgBox f.filename
End If
' f terminate event occurs when f goes out of scope, after 
' which properties cannot be accessed.

The f.filename variable has the same scope as the form f, which in this instance is local to the procedure in which this bit of code is run. There is now no chance of side effects, and the code is much more robust.