Visual Basic Concepts

Working with MDI Forms and Child Forms

When users of your MDI application open, save, and close several child forms in one session, they should be able to refer to the active form and maintain state information on child forms. This topic describes coding techniques you can use to specify the active child form or control, load and unload MDI and child forms, and maintain state information for a child form.

Specifying the Active Child Form or Control

Sometimes you want to provide a command that operates on the control with the focus on the currently active child form. For example, suppose you want to copy selected text from the child form's text box onto the Clipboard. In the Mdinote.vbp sample application, the Click event of the Copy item on the Edit menu calls EditCopyProc, a procedure that copies selected text onto the Clipboard.

Because the application can have many instances of the same child form, EditCopyProc needs to know which form to use. To specify this, use the MDI form's ActiveForm property, which returns the child form that has the focus or that was most recently active.

**Note   **At least one MDI child form must be loaded and visible when you access the ActiveForm property, or an error is returned.

When you have several controls on a form, you also need to specify which control is active. Like the ActiveForm property, the ActiveControl property returns the control with the focus on the active child form. Here's an example of a copy procedure that can be called from a child form menu, a menu on the MDI form, or a toolbar button:

Private Sub EditCopyProc ()
   ' Copy selected text onto Clipboard.
   ClipBoard.SetText _
      frmMDI.ActiveForm.ActiveControl.SelText
End Sub

If you're writing code that will be called by multiple instances of a form, it's a good idea to not use a form identifier when accessing the form's controls or properties. For example, refer to the height of the text box on Form1 as Text1.Height instead of Form1.Text1.Height. This way, the code always affects the current form.

Another way to specify the current form in code is to use the Me keyword. You use Me to reference the form whose code is currently running. This keyword is useful when you need to pass a reference to the current form instance as an argument to a procedure.

**For More Information   **For information on creating multiple instances of a form using the New keyword with the Dim statement, see "Introduction to Variables, Constants and Data Types" in "Programming Fundamentals" and "Dim Statement" in the Language Reference.

Loading MDI Forms and Child Forms

When you load a child form, its parent form (the MDI form) is automatically loaded and displayed. When you load the MDI form, however, its children are not automatically loaded.

In the MDI NotePad example, the child form is the default startup form, so both the child and MDI forms are loaded when the application is run. If you change the startup form in the MDI NotePad application to frmMDI (on the General tab of Project Properties) and then run the application, only the MDI form is loaded. The first child form is loaded when you choose New from the File menu.

You can use the AutoShowChildren property to load MDI child windows as hidden, and leave them hidden until you display them using the Show method. This allows you to update various details such as captions, position, and menus before a child form becomes visible.

You can't show an MDI child form or the MDI form modally (using the Show method with an argument of vbModal). If you want to use a modal dialog box in an MDI application, use a form with its MDIChild property set to False.

Setting Child Form Size and Position

When an MDI child form has a sizable border (BorderStyle = 2), Microsoft Windows determines its initial height, width, and position when it is loaded. The initial size and position of a child form with a sizable border depends on the size of the MDI form, not on the size of the child form at design time. When an MDI child form's border is not sizable (BorderStyle = 0, 1, or 3), it is loaded using its design-time Height and Width properties.

If you set AutoShowChildren to False, you can change the position of the MDI child after you load it, but before you make it visible.

**For More Information   **See "AutoShowChildren Property and "Show Method" in the Language Reference.

Maintaining State Information for a Child Form

A user deciding to quit the MDI application must have the opportunity to save work. To make this possible, the application needs to be able to determine, at all times, whether the data in the child form has changed since the last time it was saved.

You can do this by declaring a public variable on each child form. For example, you can declare a variable in the Declarations section of a child form:

Public boolDirty As Boolean

Each time the text changes in Text1, the child form's text box Change event sets boolDirty to True. You can add this code to indicate that the contents of Text1 have changed since the last time it was saved:

Private Sub Text1_Change ()
   boolDirty = True
End Sub

Conversely, for each time the user saves the contents of the child form, the text box's Change event sets boolDirty to False to indicate that the contents of Text1 no longer need to be saved. In the following code, it is assumed that there is a menu command called Save (mnuFileSave) and a procedure called FileSave that saves the contents of the text box:

Sub mnuFileSave_Click ()
   ' Save the contents of Text1.
   FileSave
   ' Set the state variable.
   boolDirty = False
End Sub

Unloading MDI Forms with QueryUnload

The boolDirty flag becomes useful when the user decides to exit the application. This can occur when the user chooses Close from the MDI form's Control menu, or through a menu item you provide, such as Exit on the File menu. If the user closes the application using the MDI form's Control menu, Visual Basic will attempt to unload the MDI form.

When an MDI form is unloaded, the QueryUnload event is invoked first for the MDI form and then for every child form that is open. If none of the code in these QueryUnload event procedures cancels the Unload event, then each child is unloaded and finally, the MDI form is unloaded.

Because the QueryUnload event is invoked before a form is unloaded, you can give the user the opportunity to save a form before unloading it. The following code uses the boolDirty flag to determine if the user should be prompted to save the child before it is unloaded. Notice that you can access the value of a public form-level variable anywhere in the project. This code assumes that there is a procedure, FileSave, that saves the contents of Text1 in a file.

Private Sub mnuFExit_Click()
   ' When the user chooses File Exit in an MDI
   ' application, unload the MDI form, invoke
   ' the QueryUnload event for each open child.
   Unload frmMDI
   End
End Sub

Private Sub Form_QueryUnload(Cancel As Integer, _
   UnloadMode As Integer)
   If boolDirty Then
      ' Call procedure to query the user and save
      ' file if necessary.
      FileSave
   End If
End Sub

**For More Information   **See "QueryUnload Event" in the Language Reference.