Platinum Edition Using Visual Basic 5

Previous chapterNext chapterContents


- 20 -
Building a Multiple Document Interface

You will see how to create a Multiple Document Interface (MDI) application and manage all of the child forms of the application.
You will see how to create instances of forms on-the-fly. This will allow you to create applications similar to Word that handle multiple files at the same time.
You will learn how the menus interact and how to control the behavior of the child menus.
In an MDI application, you can automatically arrange all the open windows and keep a list of the windows in the menu. You will see how to apply these techniques to your MDI programs.

As you begin to write more advanced Visual Basic applications, at some point you will probably want to use Windows' Multiple Document Interface (MDI). The MDI allows your programs to work with multiple forms contained within a parent form. This makes your interface cleaner than one that has forms scattered about the screen.

The MDI standard can enhance your programs in two ways. First, you can have one container form that acts as the background for your overall application. If the user moves the container form, the child forms contained inside move as well. This helps keep your application's interface organized and self-contained. Second, and perhaps even more powerful, your users can work on multiple documents at one time. MDI applications allow the use of multiple instances of the same form, which can add much power and flexibility to your programs.

Introducing MDI Applications

Many of the applications that you create in Visual Basic will consist of a series of independent forms, like the ones shown in Figure 20.1. Each of these forms is displayed separately on the screen and is moved, maximized, or minimized separately from any other form. With this type of interface, there is no easy way to organize the forms or to deal with them as a group. Even with this limitation, this is a good interface to use for many programs and is probably the most prevalent interface design.

FIG. 20.1
This program's user interface consists of two forms that appear to have no visual relation-ship to each other.

An alternative to this standard interface is the Multiple Document Interface, or MDI. This type of application has one parent form that contains most of the other forms in the program. Other forms can be child forms, which are contained within the parent, or standard forms, which are not. With an MDI application, you can easily organize all the child forms, or minimize the entire group of forms just by minimizing the parent form. Programs such as Microsoft Word and Excel are examples of MDI applications. If you have worked with these programs, you know that you can open multiple windows in the program, access them easily from the menu, and minimize the whole thing with a single click of the mouse. In version 5, even Visual Basic has gone to a true MDI interface style. Figure 20.2 shows three blank workbooks opened simultaneously in Excel as an example of a typical MDI application.

FIG. 20.2
MDI applications let you manage multiple document windows with ease.

Characteristics of MDI Parent Forms

The MDI form, also known as the parent form, is the container for all the child forms of the application. The MDI form has a number of characteristics that define its behavior. These are:

Characteristics of MDI Child Forms

Just as the MDI form has characteristics of its behavior, the MDI child forms also behave in a certain way. The characteristics of an MDI child form are:

Creating a Simple MDI Program

As with many programming concepts, the best way to understand how MDI applications work is to create a simple MDI program. This section walks you through the process of setting up a "shell" of an MDI program. It will contain an MDI form and a single child form. You can then use this program as the basis for a fully functional MDI application.

The first step is to start a new project in Visual Basic by choosing the New Project item from the File menu.

Setting Up the Parent Form

After you have started the ew project, the next step is to create the MDI parent form. To create the MDI form for your project, select the Add MDI Form item from the Project menu, or choose MDI Form from the Add Object button's drop-down menu. Then, from the Add MDI Form dialog box, select the MDI Form icon and click Open. When the MDI Form is added to your project, it will look like the one in Figure 20.3.

FIG. 20.3
The MDI form has a darker background than a standard form.

You might also notice that the MDI Form is added to the Forms folder of your project. However, if you look closely, you might notice that the MDI form is displayed in the Project window with a different icon than a standard form. The icons help you to easily identify the type of form that you have. Figure 20.4 illustrates the difference between normal and MDI form icons.

FIG. 20.4
Icons show the form type in the Project window.

After you have added the form to your project, you should specify a descriptive name for the form and set any of the other properties that you need. Most of the properties of the MDI form are the same ones that you set to control the appearance of a standard form.

See "Parts of a Form," Chapter 4

There are, however, two properties that are unique to the MDI form and deserve special note--the AutoShowChildren property and the ScrollBars property. The AutoShowChildren property determines whether child forms are shown automatically as they are loaded. If the AutoShowChildren property is set to True (the default value), then child forms are shown as soon as they are loaded. This means that the Load statement and the Show method have the same effect on the form.

The ScrollBars property determines whether the MDI form shows scroll bars when necessary. When this property is set to True (the default value), scroll bars are shown on the MDI form if one or more of the child forms extend beyond the boundary of the MDI form, as shown in Figure 20.5. If the property is set to False, scroll bars are not shown under any conditions.

FIG. 20.5
Scroll bars let you view portions of child forms that extend beyond the boundary of the parent form.

One other property of note is the Picture property. While the MDI form does not support the Print method and graphics methods like a standard form, you can still include a picture as the background of the form.

Setting Up a Child Form

Setting up a child form in an MDI application is even easier than setting up the parent form. A child form is basically a standard form that has the MDIChild property set to True. Therefore, everything you know about creating standard forms applies to creating the child forms of an MDI application.

For the sample application, all you need to do is set the MDIChild property of the form that was first created for the project. To do this, select the form in the Project window, select the MDIChild property in the Properties window, and change its value to True. You might notice that the icon for the form in the Project window changes from a standard icon to an MDI child icon. This is the only change that you will notice in the form while you are in the design window (see Figure 20.6).

FIG. 20.6
Notice how the two child forms' icons differ from those for standard and parent forms.


TIP: As with other properties with predefined values, you can double-click the MDIChild property in the Properties window to toggle back and forth between the True and False values.

After setting the MDIChild property, all that is left to complete the form is to add the controls you need for your program. You can, of course, design the form first and change the MDIChild property later. The order of the operation has no effect on the behavior of the form. A typical MDI child form is shown in Figure 20.7.

FIG. 20.7
MDI child forms look just like standard forms.

Running the Program

After you have finished setting up both the parent and child forms, you are ready to run the program to see how a child form behaves inside the parent form. First, as always, you should save your work. Then click the Start button on the toolbar or press F5 to run the program. When the program runs, the form layout should resemble Figure 20.8.

FIG. 20.8
This simple MDI application shows a parent form and two child forms. Note that the child forms are two instances of the same form.

There are several things that you should try so that you fully understand the behavior of the parent and child forms. Try the following tasks:

One thing that you might have noticed when you started the program was that the child form was shown automatically when the program started. In the simple example, this is because the child form (the one first created when you started the project) was designated as the startup form. If you would like to have the empty MDI parent form shown when you start the program, you need to change the Startup Object setting in the Project Properties dialog box as shown in Figure 20.9. You access the project properties by choosing the Project Properties item from the Project menu.

FIG. 20.9
Set the startup form and other properties of the project from this dialog box.

Creating Multiple Instances of a Form

You can use the MDI form just to make your application neater and its forms easier to manage. However, if that's all you use MDI forms for, you're missing out on the real power of MDI applications. The most powerful feature of an MDI application is its capability to create and handle multiple instances of a form at the same time. For example, if you are working in Microsoft Word, each document you open or each new document that you create is a new instance of the same basic form. In fact, many MDI applications are made up of only two forms: the MDI parent form and the template form for all the child forms in the application.


NOTE: You can have more than one type of template child form in your application. For example, Visual Basic has two basic types of MDI child forms: the Form design child form and the Code child form. You can create as many of each of these types of forms as you need, within the constraints of your system.

Creating an MDI application of this type requires a little more work than was required in the sample application. You first have to define the basic MDI child form at design time and then use object variables to create instances of the form at run time.

To start the process of creating an MDI application with multiple instances of a form, you need to start a new project and then add an MDI form to the project as described in the section "Creating a Simple MDI Program." Next, be sure to set up the MDI form as the startup object using the Project Properties dialog box.

Setting Up the Basic Form

As was the case in "Creating a Simple MDI Program," creating the form template is the same as creating a standard form. You add all the controls to the form that you need for the user interface. Also, you need to write any necessary code for the controls to perform their intended functions. You also need to set the MDIChild property of the form to True.


NOTE: To optimize your application, you might need to write some code for the form in the MDI parent form or a separate module. See the section "Optimizing Your MDI Application" for more details on this subject.

One thing you might notice as you first create an MDI application is that the child form, when first shown at run time, is probably sized differently than it was when you created it. This is illustrated in Figure 20.10. The reason is because an MDI application, by default, assigns a certain size and position to each child form that is shown.

If the default size and position are not acceptable to you, you need to place code in the Load event procedure of the child form to position and size it the way you want. To determine the desired size of the child form, check the Height and Width properties of the form while you are in design mode, and then add code to the form's Load event procedure to set the Height and Width properties to their original values. The same concept applies to the position of the form. You can set the Top and Left properties of the child form to set its position. The code in Listing 20.1 shows how to set the size of a child form and center it within the parent form. Figure 20.11 shows the effect of this code.

FIG. 20.10
MDI applications automatically size and position their child forms.


NOTE: If a child window's size becomes too large for its parent, whether by user action or through code, the parent's size is not changed automatically. The parent does, however, automatically show scroll bars when needed, as mentioned in the section "Setting Up the Parent Form" earlier in this chapter.

Listing 20.1 MDIDEMO2.FRM--Use Code to Size and Position the Child Form

Private Sub Form_Load()
    Me.Height = 4545
    Me.Width = 6810
    Me.Top = (mdiMain.ScaleHeight - Me.Height) / 2
    Me.Left = (mdiMain.ScaleWidth - Me.Width) / 2
End Sub

FIG. 20.11
You can use code to override the default size and position of an MDI child form.


NOTE: You cannot use the StartUpPosition property to set the initial position of a child form in an MDI window. In fact, you cannot change the setting of the property from its default value of 0 - Manual.

Creating Forms Using Object Variables

After you have created the basic child form, you need a means to create an instance of the form (at run time) and display it in the MDI application. Doing this requires all of two code lines. First, you use a Dim statement to create an object variable (a variable of Object type) that will contain an instance of the form. In the Dim statement, you need to use the New keyword to tell Visual Basic to create a new instance of the form. Otherwise, the statement just creates a new handle to the existing form. After you create the object variable, you use the Show method to display the form. However, instead of using the form name, you specify the name of the variable. The two required lines of code are shown here:

Dim NewFrm As New frmText
NewFrm.Show

To see how this code works, place the lines of code in the Click event procedure of the MDI form, and then run the program. Each time you click the MDI form, a new instance of the child form is displayed.

Using the Keywords Me and ActiveForm

Because all the child forms are the same and you use the same variable to create each of them, how can you know which form to specify when running code? Especially code that is generic and can work with any of the forms?

There are two particular keywords that you will use extensively in working with MDI applications: Me and ActiveForm. These two keywords let you create generic code routines that will work with any child form that you create.

Me is a keyword that can be used in any form to refer to itself, just as you can use the word me to refer to yourself without having to use your name. You saw how this keyword was used in Listing 20.1 to size and position the child form on startup. If you write all code in the child form using Me to refer to the form name, your code will work for whichever instance of the form is active at the time.

ActiveForm is actually a property of the MDI form. Its purpose is similar to the Me keyword. ActiveForm refers to whichever MDI child form is currently active. By using the ActiveForm property in all code that resides in the MDI form, the code operates only on the active form and on no other. The following line of code provides a simple example of the ActiveForm property:


mdiMain.ActiveForm.Print "This form is currently active."


NOTE: Notice, here and in Listing 20.1, the use of the prefix mdi that was used when naming the MDI parent form.

Using the ActiveForm property, you can reference any property, method, or event of the currently active child form without having to know its name.

Working with Menus

In Chapter 5, "Adding Menus and Toolbars to Your Program," you saw how to create a menu for your application. You also found out that you can have a different menu for each form in your program, if you so desire. MDI forms can also have menus. You create a menu for your MDI form the same way that you create a menu for a standard form, using the Menu Editor. The menu for the MDI form is usually the primary means by which you access the capabilities of an MDI application.

See "Creating a Menu Bar," Chapter 5

In an MDI application, the child forms can also have menus. Like the menu for the MDI form itself, you create child form menus using the Menu Editor. However, when a child form is displayed, its menu is not displayed as part of the child form but on the menu bar of the MDI form. This behavior presents a problem in your MDI applications, because the MDI child form's menu actually replaces the MDI parent form's menu when the child is active. This problem is that you cannot access the functions of the parent form while the child is active.

There are two solutions to the problems associated with the replacement of menus. First, you can duplicate all the necessary parent form functions on each child form. Unfortunately, this can lead to a bloated and hard-to-maintain program if you have several child forms with menus.

An alternative solution is to include in the parent window's menu all the menus that are necessary for all child windows. Then you can place code in the Activate and Deactivate events of the child form to show the parent's menus that are applicable to that child form. For example, in a word processing program, you want the File and Help menus to be available all the time, but you only want the Edit and Format menus available when you are working on a document. You could use code similar to Listing 20.2 to show the menus when a document is active and to hide the menus when the document is inactive. As an alternative, you could show and hide the menus as the form opens and closes using the Load and Unload events.

Listing 20.2 MDIDEMO.FRM--Use Code to Show and Hide Child Menus

Private Sub Form_Activate()
    mnuEdit.Visible = True
    mnuFormat.Visible = True
End Sub
Private Sub Form_Deactivate()
    mnuEdit.Visible = False
    mnuFormat.Visible = False
End Sub

To make sure that any menu code in the parent form acts upon the proper child form, use the parent form's ActiveForm property as described in the preceding section.

Managing the Children

One of the other benefits of working with MDI applications is that it is easy to manage all the child forms of the application. Visual Basic has a number of tools that make it easy for your users to access the multiple forms that are open inside the MDI form. Your program can provide the user with the means to automatically arrange the child windows. You can even provide a menu item that keeps up with all the open child windows and lets the user access one of them by selecting it from the menu. These capabilities are particularly useful if the user will be switching back and forth between multiple tasks or multiple files in the application.

Using Automatic Organization

One way the user can access multiple forms is by displaying each form on the screen in a particular organizational style. This provides the user with access to each form with just a click of a mouse button. The key to this functionality is the Arrange method of the MDI form. The Arrange method organizes all the child forms of the application in a particular pattern. Each of these patterns results in at least a portion of each form's being visible to the user, and they are commonly used by MDI-compliant Windows applications. To use the Arrange method, you specify the name of the MDI form, the method itself, and a constant representing the pattern that you want to use for the arrangement of the forms. The following line of code illustrates the use of the method:

mdiMain.Arrange vbCascade

There are four possible window arrangement patterns that you can create with the Arrange method. Each of these patterns is represented by an intrinsic constant. Table 20.1 summarizes the patterns. The four patterns are illustrated in Figures 20.12 through 20.15.

FIG. 20.12
These child forms have been arranged in a cascade pattern.

FIG. 20.13
These child forms have been arranged in a vertically tiled pattern.

FIG. 20.14
These child forms have been arranged in a horizontally tiled pattern.

Table 20.1 Arrangements of MDI Child Windows

Constant Description
vbCascade Arranges the non-minimized forms in a pattern where each form is offset slightly from the others.
vbTileHorizontal Each non-minimized child form occupies the full width of the parent form and the child forms are displayed on top of one another. If there are many child forms, they can occupy multiple columns when tiled.
vbTileVertical Each non-minimized child form occupies the full height of the parent form and the child forms are displayed side-by-side. If there are many child forms, they can occupy multiple rows when tiled.
vbArrangeIcons Arranges the icons of all minimized child forms near the bottom of the parent.

FIG. 20.15
Minimized child forms are represented by icons arranged at the bottom of the MDI form.

Typically, you place the arrangement options in a Window menu on the MDI form. Each arrangement option that you want to support is a separate menu item.

Maintaining a Window List

The other way of providing easy access to the child forms of your application is to maintain a list of the open child forms. Fortunately, this is an easy task. You create a window list while you are creating the menu for the MDI parent form. You determine which menu will contain the list and then set that menu item's WindowList property to True in the Menu Editor, as shown in Figure 20.16.


NOTE: You can also change the setting of the WindowList property from code.

FIG. 20.16
Check the WindowList box to create a list of open child windows in your MDI menu.

As you add child forms to the application, the window list menu item is automatically updated to include the new form. The caption of the menu item is the caption that is given to the form that you create. The active form in the window list is indicated by a check mark. Figure 20.17 shows a window list for an MDI application.

FIG. 20.17
The window list lets the user select the form with which to work.

Creating a Sample Application--an MDI Contact Manager

Obviously, the best way to demonstrate the techniques of MDI applications is to build an application that you might actually use. This program uses multiple instances of a template form to become an MDI application.

If part of your job is keeping up with customer contacts, you probably use some kind of contact manager. These programs let you keep up with information about each of your customers, such as their name, address, phone numbers, the date you last contacted them, and so forth. One of the disadvantages of some contact managers is that you can only work with a single contact at a time. This can be very inconvenient if you are working on an order for one customer and another telephones to discuss a new service. In this case, you have to close the client information for the current customer and open the information for the second customer. Wouldn't it be great if you could just open the information for the second customer in a new window? Well, with an MDI contact manager, you can.

This section shows you how to build a very simple MDI contact manager. The program displays only name and address information for a client and is basically an illustration of the concept. To create a full-fledged contact manager, you have to add additional database code. The program uses a Microsoft Access database and the Jet engine to retrieve the data.

See "Using Tables," on Chapter 31

Creating the MDI Form

The setup of the MDI form is the same as you have seen in previous sections. You first need to add an MDI form to your project and then set the AutoShowChildren property to False. You also should set the Name and Caption properties of the MDI form to something other than the defaults.

After setting the properties of the form, you need to create a menu that displays the customer information in the appropriate child form. The menu items that you need to add are shown in Figure 20.18.

See "Creating a Menu Bar," Chapter 5

FIG. 20.18
Our sample MDI Contact Manager application has these menu items.

One menu item of note is the Create New Form item. The user can use this option to tell the program whether to display a selected customer in the existing child form or to create a new form for each new customer. The Checked property of the item is set to show the status of the user choice.

Of course, after you create the menu, you need to add code to make the menu options work. Listing 20.3 shows the code for the menu items shown in Figure 20.18.

Listing 20.3 MAINMDI.FRM--Use Menu Code to Handle the Tasks of the Contact Manager

Private Sub filExit_Click()
    Unload Me
End Sub
Private Sub MDIForm_Load()
    Me.WindowState = vbMaximized
End Sub
Private Sub MDIForm_Unload(Cancel As Integer)
    CustDb.Close
End Sub
Private Sub memCreate_Click()
Dim CheckSet As Boolean
    CheckSet = Not memCreate.Checked
    memCreate.Checked = CheckSet
    CreateForm = CheckSet
End Sub
Private Sub memNew_Click()
    If CreateForm Then
        Dim frmMem As New frmMember
        frmMem.Show
    End If
    ClearCust
End Sub
Private Sub memSearch_Click()
    frmSearch.Show vbModal
    If CreateForm Then
        Dim frmMem As New frmMember
        frmMem.Show
    End If
    ShowCust
End Sub

Setting Up the Customer Child Form

The next step in creating the contact manager is setting up the child form that will display a customer's information. To set up the customer form, add a form to your project (or use the initial form that was created) and then set its MDIChild property to True. You probably also need to change the Name and Caption properties of the form. (Set the name of the form to frmMember to match the code in the menu items.) After setting the properties of the form, you need to add controls to the form to display the data. The completed form is shown in Figure 20.19.

FIG. 20.19
Customer information is displayed in a child form.

Creating the Search Form

As you look up customer records, you need a search form to allow the user to enter a name to find. The search form can be very simple, consisting of a label, a text box to enter the name, and two command buttons to perform or cancel the search. The code for the form is also very simple; if you proceed with the search, the code uses the FindFirst method of the recordset that contains the contact information to locate the first name corresponding to the desired search information. The complete search form is shown in Figure 20.20, and the code for the form is shown in Listing 20.4.


NOTE: A recordset is a special type of object that acts as a link between a Visual Basic program and information stored in a database. You learn about recordsets in Chapter 31.

See "Deciding Which Recordset Type to Use," Chapter 31

FIG. 20.20
You can make the search form more complex by adding a First Name search as well.

Listing 20.4 SEARCH.FRM--Use the FindFirst Method to Locate the Desired Customer

Private Sub cmdCancel_Click()
    Unload Me
End Sub
Private Sub cmdSearch_Click()
Dim SrchStr As String
    SrchStr = txtSearch.Text
    CustRset.FindFirst "LastName = `" & SrchStr & "`"
    Unload Me
End Sub

Creating the Heart of the Program

The forms provide the interface of the program, but the real heart of the program is a group of procedures that actually display the data and set the program up. To create the procedures, you first need to add a module to your program. You can do this by selecting the Add Module item from the Project menu, or by selecting Module from the Add Object button's drop-down menu.

See "Determining the Scope of Procedures and Functions," Chapter 17

After the module is added to the project, you need to define a couple of Public variables and create the procedure that sets up the program. The public variables are used to provide your entire program with access to the database object. After you define the variables, you need to create a Sub Main procedure to set up the database information and display the MDI parent form. The Public variable declarations and the Sub Main procedure are shown in Listing 20.5.

Listing 20.5 MDIPROCS.BAS--Use Sub Main to Set Up the Database and Load the Main Form

Public CustDb As Database, CustRset As Recordset
Public CreateForm As Boolean
Sub Main()
    Set CustDb = DBEngine.Workspaces(0).OpenDatabase("D:\VB5Book\NewDb.mdb")
    Set CustRset = CustDb.OpenRecordset("Customers", dbOpenDynaset)
    mdiMain.Show
    CreateForm = True
End Sub

After you create the Sub Main procedure, you need to change the project options to make Sub Main the startup object of the program.

The next two procedures are the ones which either display information about a current customer or set up the information form for you to enter a new customer. These procedures are called by the appropriate menu items of the MDI form. The key feature to note in these procedures is that the ActiveForm property of the MDI form is used to designate which child form will receive the data being sent. The ClearCust and ShowCust procedures are shown in Listing 20.6.

Listing 20.6 MDIPROCS.BAS--Use the ActiveForm Property to Send the Output of the Procedure to the Proper Location

Public Sub ClearCust()
Dim I As Integer
    For I = 0 To 5
        mdiMain.ActiveForm.txtMember(I).Text = ""
   ext I
End Sub
Public Sub ShowCust()
    With mdiMain.ActiveForm
        .txtMember(0).Text = CustRset!LastName & ""
        .txtMember(1).Text = CustRset!FirstName & ""
        .txtMember(2).Text = CustRset!Address1 & ""
        .txtMember(3).Text = CustRset!City & ""
        .txtMember(4).Text = CustRset!State & ""
        .txtMember(5).Text = CustRset!Zip & ""
    End With
End Sub

Running the Program

As you run the program, you can create new windows for each customer that you add, or change the status of the Create New Form menu item to display each customer in the same window. Try it out. As stated before, this example is merely an illustration of the concept, so feel free to add your own enhancements to the program. The MDI contact manager is shown in Figure 20.21.

FIG. 20.21
You can display multiple clients at the same time.

Optimizing Your MDI Application

This chapter has demonstrated a umber of techniques that you can use to create MDI applications. As you can see, the MDI form can be a powerful tool for creating programs. However, there are several considerations to keep in mind to optimize your MDI applications. These considerations help keep the performance of your programs as crisp as possible and help keep your users from running into problems. The considerations are:

Adhering to these concepts both simplifies your code and improves the performance of your MDI application.

Creating an MDI Application Framework

The code shown in this section is designed to provide a basic skeleton for any MDI applications that you create. The skeleton code can be modified to suit your specific needs. Then you can use the skeleton project as a template for your other MDI applications. The completed application is shown in Figure 20.22.

FIG. 20.22
Creating a template project can simplify your future MDI work.

Creating the MDI Parent Template

The MDI parent form is the keeper (or container) of the child windows, so it is responsible for creating new children. With this duty, it is common for the parent to also keep track of the number of child windows it has created. In addition, the parent usually holds shared user interface elements like a toolbar, status bar, and so on.

The following code in Listing 20.7 shows the code used to maintain and expose the window count. WindowCreate and WindowDestroyed are called by the child windows in their Form_Load and Form_Unload event procedures, respectively. ChildWindowCount is a Public property that allows the child windows to find out how many children are loaded.

Listing 20.7 MDIPARENT.FRM--Use the Parent to Contain Common Code for All the Child Forms

`*********************************************************************
` MDIParent.frm - Demonstrates some basic concepts on how a MDI parent
`   form should behave in an MDI application.
`*********************************************************************
Option Explicit
Private mintChildWinCount As Integer
`*********************************************************************
` Returns how many child windows have been created
`*********************************************************************
Public Property Get ChildWindowCount() As Integer
    ChildWindowCount = mintChildWinCount
End Property
`*********************************************************************
` Called when a window is created to increment the window counter
`*********************************************************************
Public Sub WindowCreated()
    mintChildWinCount = mintChildWinCount + 1
    UpdateButtons True
End Sub
`*********************************************************************
` Called when a window is created to decrement the window counter
`*********************************************************************
Public Sub WindowDestroyed()
    mintChildWinCount = mintChildWinCount - 1
    UpdateButtons mintChildWinCount
End Sub

You also might notice a call to UpdateButtons in Listing 20.7. This private helper routine enables and disables toolbar buttons. If children exist, then the toolbar buttons are enabled. When the last child is unloaded, WindowDestroyed decrements the variable mintChildWinCount to 0, which causes UpdateButtons to disable the toolbar buttons.

The most important code in MDIPARENT.FRM is the File menu's Click event procedure. This code is responsible for creating windows, opening files, and terminating the application. Because all of these actions on the MDI parent file menu are also on the child form's file menu, you make this event public as shown in Listing 20.8.

Listing 20.8 MDIPARENT.FRM--Handling Menu Click Events

`*********************************************************************
` File menu handler for the MDI form when no windows are displayed.
` In this demo the child windows will have a menu just like this,
` so we will make this Public so the children can call this event.
`*********************************************************************
Public Sub mnuFileItems_Click(Index As Integer)
    Select Case Index
        `*************************************************************
        ` File New - Create a new child form, then display it.
        `*************************************************************
        Case 1
            Dim frmNew As New frmChild
            frmNew.Visible = True
        `*************************************************************
        ` File Open - Prompt the user for a filename, then load
        `   it into the child window (in OpenFile) if the user didn't
        `   press cancel in the dialog.
        `*************************************************************
        Case 2
            On Error Resume Next
            With cdlg
                .Flags = cdlOFNFileMustExist
                .Filter = "Text Files (*.txt)|*.txt|All Files (*.*)|*.*"
                .ShowOpen
            End With
            If Err <> cdlg.cdlCancel Then OpenFile cdlg.filename
        `*************************************************************
        ` Index 3 is the separator, so don't do anything.
        `*************************************************************
        `Case 3
        `*************************************************************
        ` File Exit - Terminate the application
        `*************************************************************
        Case 4
            Unload Me
    End Select
End Sub

When the File New menu item is clicked (Index = 1), the Dim frmNew As New frmChild line creates a new instance of your child form. However, this doesn't really create the new form. The form is actually created as soon as you access one if its properties or methods. This means that the frmNew.Visible = True line is the line of code that creates the form. After the form is created, the Visible property is set to True, which displays your form.


TIP: Forms created using New are hidden by default, so remember to display them by setting Visible = True.

The File Open (Index = 2) code in Listing 20.8 simply displays an Open dialog box so the user can supply a file name. If the user doesn't click Cancel, then the file is opened using the OpenFile routine, as presented in Listing 20.9. The last item in the select statement is Index 4, which represents the File Exit case. This is an easy one because the proper way to terminate an MDI application is to unload the MDI form.

As mentioned earlier, the OpenFile code is responsible for opening a text file and loading it into a text box on your child form. This code is very simplistic and includes no basic error handling for such cases as testing for files greater than 44K under Windows 95. However, it does provide a basic example of how to load a file into a text box, which is sufficient for this example.


CAUTION: Avoid using the End statement to terminate your applications. End terminates your application immediately, which prevents your Form_Unload events from being executed. The best way to end an MDI application is to unload the MDI form.

Listing 20.9 MDIPARENT.FRM--Shared Code

`*********************************************************************
` Code shared among the child windows should be put in either a
` module or the MDI parent form.  This OpenFile code will be used
` by all of the children, so we will keep it in the MDI parent form.
`*********************************************************************
Public Sub OpenFile(strFileName As String)
    Dim strFileContents As String
    Dim intFileNum As Integer
    `*****************************************************************
    ` Get a free file handle
    `*****************************************************************
    intFileNum = FreeFile
    `*****************************************************************
    ` Open the file
    `*****************************************************************
    Open strFileName For Input As intFileNum
        `*************************************************************
        ` Put the contents of the file into the txtData control of
        ` the child form. This code will fail if the file is too
        ` large to fit in the textbox, so you should include
        ` additional error handling in your own code.
        `*************************************************************
        With ActiveForm
            .txtData.Text = Input$(LOF(intFileNum), intFileNum)
            `********************************************************
            ` Set the caption of the child form to the filename
            `********************************************************
            .Caption = strFileName
        End With
    `*****************************************************************
    ` Always close files you open as soon as you are done with them
    `*****************************************************************
    Close intFileNum
End Sub

You might notice that the OpenFile routine simply loads the file into the text box on the active window by referencing the ActiveForm property. This is a valid assumption to make, because the active menu will always refer to the active form. Because the user can open a file via the menu (even if he is using the toolbar), you can always assume that any actions you perform in your menu event handlers should be applied to the active form.

The MDI Child

Now that you have had a chance to understand what your MDI parent form is responsible for, let's take a look at how the child should behave in this parent/child relationship.

As mentioned earlier, child forms are responsible for calling the WindowCreated and WindowDestroyed methods of the MDI parent form. Listing 20.10 demonstrates how this is done from the Form_Load and Form_Unload events. In addition, your child window sets its initial caption based on the MDI parent ChildWindowCount property. Although this technique is good for this sample, you might want to make your algorithm for setting your initial caption a little more complex. What do you think would happen if you had three windows, closed the second window, and then created a new window? How could you avoid this problem?

Listing 20.10 MDICHILD.FRM--Use the Child Form for Code Specific to Each Child

`*********************************************************************
` MDIChild.frm - Demonstrates some basic techniques on how a MDI child
`   window should behave.
`*********************************************************************
Option Explicit
`*********************************************************************
` When a new form is created it should call the WindowCreated function
` in the MDI parent form (which increments the window count in this
` case). It should also set its caption to distinguish it from other
` child windows.
`*********************************************************************
Private Sub Form_Load()
    MDIParent.WindowCreated
    `*****************************************************************
    ` This works, but it has a fatal flaw.
    `*****************************************************************
    Caption = Caption & " - " & MDIParent.ChildWindowCount
End Sub
`*********************************************************************
` Make sure txtData always fills the client area of the form.
`*********************************************************************
Private Sub Form_Resize()
    txtData.Move 0, 0, ScaleWidth, ScaleHeight
End Sub
`*********************************************************************
` Let the MDI parent know that this window is being destroyed.
`*********************************************************************
Private Sub Form_Unload(Cancel As Integer)
    MDIParent.WindowDestroyed
End Sub

One other minor detail you might have noticed in this code is the Form_Resize event. This code makes sure your TextBox control always covers the entire client area of the form. This code works with any control, so keep this in mind for your own applications.

Another important concept mentioned previously is that your child forms should use event handlers of the parent menu whenever possible (and vice versa). Listing 20.11 contains the event handlers for all of the menus used by MDICHILD.FRM.

Listing 20.11 MDICHILD.FRM--Handling the Menu Code for the Application

`*********************************************************************
` Since the child File menu is identical to the MDI parent File menu,
` we should avoid duplicate code by calling the parent's mnuFileItems
` click event.
`*********************************************************************
Private Sub mnuFileItems_Click(Index As Integer)
    MDIParent.mnuFileItems_Click Index
End Sub
`*********************************************************************
` The options menu is unique to the child forms, so the code should
` be in the child form or separate BAS module.
`*********************************************************************
Public Sub mnuOptionsItems_Click(Index As Integer)
    `*****************************************************************
    ` Don't stop for errors
    `*****************************************************************
    On Error Resume Next
    `*****************************************************************
    ` Show the color dialog (since all menu items here need it)
    `*****************************************************************
    MDIParent.cdlg.ShowColor
    `*****************************************************************
    ` If the use selected cancel, then exit
    `*****************************************************************
    If Err = cdlCancel Then Exit Sub
    `*****************************************************************
    ` Otherwise set the color based on the value returned from the dlg
    `*****************************************************************
    Select Case Index
        Case 1 `Backcolor...
            txtData.BackColor = MDIParent.cdlg.Color
        Case 2 `Forecolor...
            txtData.ForeColor = MDIParent.cdlg.Color
    End Select
End Sub
`*********************************************************************
` If you set your indexes of your Window menu properly, you can save
` yourself some code. I was careful to make sure my Window menu items
` indices were equivalent to the possible values for the Arrange
` method.
`*********************************************************************
Private Sub mnuWindowItems_Click(Index As Integer)
    MDIParent.Arrange Index
End Sub

The first menu is the File menu, which is identical to the parent form, so you simply call the mnuFileItems_Click event in the parent for default processing. The second menu is the Options menu, which only appears in the child form, so you write your implementation code here. However, you make this event handler public so it could be accessed by your Toolbar control, which resides on the parent form. In addition, you use the CommonDialog control on the parent for your code, which displays the color dialog box.


TIP: If any menu item on your child form requires greater than 12 lines or so of code (excluding Dims, comments, and white space), you should move that code to a shared module or into the parent form. That prevents this code from consuming too much free memory every time a new form is added.

Finally, you have your Window menu that only applies to child forms (although it is the parent form that is responsible for this menu). By carefully creating your menu control array indexes, you are able to write the implementation code for this menu using only one line of code.

From Here...

This chapter has provided you with an introduction to creating MDI applications using Visual Basic. For more information about some of the related topics covered in this chapter, see the following chapters:


Previous chapterNext chapterContents


Macmillan Computer Publishing USA

© Copyright, Macmillan Computer Publishing. All rights reserved.