Visual Basic is an object-oriented language and most of the applications developed with it work by manipulating the values of objects or invoking the methods of the various objects to perform certain tasks. Recall that an object is the instance of a control or Form. Not surprisingly, a fundamental concept in the design of MDI applications is that of objects and their instances. All the controls you use in building an application’s user interface are control objects. Forms are objects too, and they are called Form objects. It’s actually possible to create new objects and delete existing ones at runtime from within yow code.
“To help you handle objects from within your code, Visual Basic allows the declaration of variables that represent objects. These object variables allow You to manipulate objects (create new instances or delete existing instances) just as you’d manipulate any other type of variable. For example, you can design a single Form object and create multiple instances of this Form from within your code. You can even declare an array of object variables and manipulate them through the application’s code.
The Form you design is the prototype. An instance of a Form is a copy that inherits all the properties of the original but exists in your application independently of the original. On an MDI Form, all child Forms are usually instances of one basic Form. The Forms all have the same behavior, but the operation of each one doesn’t affect the others. When a child Form is loaded, for example, it will have the same background color as its prototype, but you can change this from within your code by setting the Form’s BackColor property.
An MDI Form can contain a number oi child Forms, therefore, you might start . the design process by designing each one of them. You could call the first one DocumentForml, the second one DocumentForm2, and so on. Unfortunately, this setup would require a lot of programming effort to handle individual Forms. The most common approach to designing MDI applications is to design a single child Form and use it as the prototype for as many child Forms as you want to place on the MDI Form. To do this, use an array of Forms, like Documentiormst). As with control arrays , you only need to .:.. design the first element of the array. With this index structure, each child Form can be accessed by an index, and the maintenance of the child Forms is simplified. The application shows an MDl Form with three child Forms, all members of the DocumentForms() array.
To create a Form that can be used as a prototype, follow these steps:
1. Start a new project: add an MDl Form, and set its caption to MDI Demo2.
2. Add a new Form to the.project, name it ChildForm, and set its MDI Child property to True.
3. ,Set other properties of the child Form (such as its background color) and add a few controls if you want.
When you’re done, you can use this Form as a prototype and declare an array of child Forms with a statement such as the following:
Dim DocumentForms(9) As New ChildForm
ChildForm is the name of the child Form. This declaration must appear in a Module or be a Form-wide declaration. Each element of the DocumentFprms() array is an instance of the ChildForm Form you just designed.
To change the Caption property of the third child Form/use the following statement:
DocumentForms(2).Caption = “Document #3”
The name of the DocumentForms() array followed by an Index value is equivalent to the name of the child Form.
The keyword New tells Visual Basic to create 10 new instances of the Form Child- Form. The one you designed doesn’t exist until it’s loaded. The ChildForm Form you see in the Project window is just the prototype. The next example illustrates the . , difference between the Childform Form and the array of child Forms.
Loading and ‘Unloading Child Forms
Let’s design a menu that will load and unload the child Forms. This menu, shown earlier, has two commands on the first level, Child Forms and Window. The Window menu is identical to that. The Child Forms command leads to a submenu with two commands:
Open Forms Opens all child Forms
Close Forms Closes all open child Forms
This section’s application is caned MDI Demo2 and you’ll find ‘it in this chapter’s folder on the CD.
VB6 at Work: The MDI Demo2 Project
The MDl Demo2 project demonstrates how to load and unload child Forms on an MDl window from within your code. The project’s complete code is shown next.
The Open Forms command loads all 10 child Forms and assigns a different background color and caption to each one as it’s loaded. The Close command closes the Forms. If you run the application now, you’ll see child Forms-the original ChildForm (the one with caption Forml), which is loaded automatically, and the 10 child Forms of the Documentions) array. To prevent the automatic loading of the first instance of the Forml child Form, set the AutoShowChildren property of the MDI Form to False.
You can rearrange the child Forms on the MDI window, minimize and maximize them as usual, and close them by clicking on their Close buttons. You can also choose Forms >Close Forms to close them all. If you do so, the original child Form will remain on the MDI window. This is because the window isn’t part of the array. It’s the first child window that Visual Basic loads automatically each time the MDl Form is loaded. You can modify this default behavior by changing the Start-up Form in the project’s Properties window. To do so, right-click the project’s name, select Project Properties, and change the value of the Start-up Object,box.
By default, the start-up object is the first child Form, but because a child Form can’t exist on its own, the MDl Form is loaded first and the child Form is displayed in it. Select the MDl Form in the Start-up Object Combo Box control and run the application again. This time, the MDI Form appears without any child Forms. To display a child Form, you must load it explicitly from within your code.
Tracking the Active Window
Since MDl applications manipulate multiple windows at once, you need to be able to keep track of the active ‘window from within your code. A useful keyword in working with MDI and child Forms is the Me keyword, which refers to the active child Form. If you consider that the MDl Form is the Desktop environment for its child Forms, the Me keyword is equivalent to the Screen object’s ActiveForm property (which is the active Form). Me is the active child Form in an MDI Form, and you can address any of its properties through the Me object. Me.BackColor is the background color of the current child . Form, Me Caption is its caption, and so on.
In most situations, however, you’ll want to know the index of the active child Form in your code. Say you can’t find out whether a specific child Form is open with the Me keyword. Visual Basic doesn’t report the index of the active window, so you must implement a method for keeping track of the active child Form. The solution is to use the Form’s Tag property. Every time a new child Form is loaded, you can store its index in the Form’s Tag property, as follows:
DocumentForms(i).Tag = i
When the Form is unloaded, Visual Basic resets its tag (you needn’t worry about resetting it). After the form is loaded, you can use the Form’s Tag property in your code to find the index of the active child Form and access its properties’ and methods.
Tweaking MDI Demo2
Let’s add a command to the MDI Demo2 application that changes the caption of the active window to the string “I’ve been activated!” and resets it to the original caption when the window loses focus. To use the Tag property to keep track of the active window, as described in the previous section, you must assign a value to it when the Form is loaded. You modify the Open Forms command as follows:
Changing the Caption property of the active window is simple. All you have to do is set its new value from within the Coniocus event of the child Form. Enter the following line in the child Form’s GotFocus event:
The Me keyword is equivalent to Documention), in which is the-index of the active window. Instead of tracking the active window’s index, use the Me keyword.
Similarly, the caption must be reset each time the window loses the focus. But this time you have to know the index of the active window in the DocumentForms1) array, which is given by its Me. Tag property. The Lost thus event handler is again only a single line:
This example concludes our introduction to the structure and mechanics of MDI applications. In the following section we’ll put this information to use by designing a couple of practical MDl applications. We’ll start by converting the TextPad application , to an MDl editor that will open multiple documents at once.
VB6 at Work: The MDI Pad Project
we reviewed the development of the TextPad application. Now you’re going to convert it to an MDI application. An MDl application lets you open and edit multiple documents simultaneously. You can also copy information from one window and paste it into another, and you can arrange multiple documents onscreen so that you can view any other document while editing the active one. All this is possible without invoking multiple instances of the application.
To convert TextPad to an MDl application, follow these steps:
1. Open the TextPad project and save it in a different folder.
2. Select the Editor Form and make sure its name is Editor.
3. Set its MDl Child property to True. It will become the child Form in which the documents will be opened and processed.
4. Choose Project >- Add MDl Form to add the MDl Form and set its Caprio property to MDI Pad .
.5. Run the application now to see how it works.
The child Form’s menu appears in the MDI Form’s menu bar and all its commands work as before. The code refers to the Editor TextBox control of the Forml Form and it still works. You can open and save the active window’s document with the Open, Save, and Save As commands on the File menu, resize the child Form,.and do everything you could do with the TextPad application.
Adding Child Forms to MDI Editor
The modified TextPad application isn’t an MDl application yet. For one thing, you can’t open a new child Form. The Open command loads another document in the child Form, and if you close the child Form by clicking on its Close button, you won’t be able to open it again. Some programming is needed to convert a regular application to an MDI application.
Let’s start by adding multiple child Forms. Add a Module to the application, and in its declaration section add these lines:
The DocumentForms() array tracks the active document. The OpenFile() array has the same rate as the Open File variable of the TextPad application: it holds the names of the documents open in each child Form. OpenFile() is used by the Save command of the File menu to save the active window’s contents to the text files from which they were read.
When a new file is opened, its name is stored in the OpenFile() array. When a new document is created, the corresponding entry in the OpenFile() array is blank. The currentDocument variable is the index of the active window in the DocumentForm() array. Each time the focus is moved to a new child window, the current Document variable is updated with the following code:
To continue updating the TextPad application, you must now create a new File menu for the MDl Form. This menu will contain the following commands:
New Opens a new child window
Open Opens an existing document in a new child window
Exit Terminates the application
Programming the New Command
Start the Menu Designer for the MDl Form and create the File menu with the commands New, Open, and Exit. Then, include the following code for the New menu command.
The New command doesn’t clear the contents of the active child window, as was the case with the TextPad application. Instead, it opens a new, blank child window. As you can see in this code, before the New command can create a new child window, it must first find out if a child Form is available. This is done with the FindFree() function, which returns the index of the first available child Form. The FindFree() function scans all the elements of the DocumentForms1() array looking for the first available MDI (an instance of the child Form with an empty tag). If MDI child Forms are open, the program will prompt the user to close one of the open windows and try again. If a free child window is found, the program does the following:
1. Loads the corresponding element of the DocumentForms() array
2. Sets its caption to New Document
3. Sets its tag to its index in the DocumentForms() array
4. Displays the window.
Later in the program, the window’s caption will be set to this file’s name if the user saves its document to a disk file. Here’s’ the code of the FindFree() function:
Programming the Open Command
The Open command’s code is longer, but not drastically different from the Open command of the TextPad application. Like the New command, it doesn’t display the file in the. active child window. Instead, it loads a new child Form and displays the file-in it.
The Open command first opens a new child window (as does the New command) and then prompts the user with the FileOpen common dialog box to select a file to open. The child window’s caption is set to the file’s name, which is also stored in the corresponding element of the OpenFile() array. Every instance of the OpenFile variable in the program must be replaced with Openthestcurrentdocument). This is the name of the file in which the document of the current child window must be saved. To Save and Save As commands are implemented similarly, so I won’t list the corresponding code here.
Programming the Exit Command
The Exit command of an MDI application is a bit more complicated than a simple End statement. See the code behind this command in the section “Ending an MDl Application,” later in this chapter.
Differences between Text Pad and MDI Pad
Some commands of the MDI Form’s menu have a different function than the commands with the same name on the child Form’s File menu. In the TextPad application, the New command clears the contents of-the editor. In the MDI version of the application, the New command opens a new, empty child window. Likewise, the Open command in the TextPad application reads.a file into the editor. In the MDI version of the application, the Open command reads a file into a new child window ..The differences in the operation of most MDI applications need to be kept in mind as you convert applications from the SOl to the MDI model. You can’t simply duplicate the code of the New and Open commands of the equivalent SOl application to its MDl version.
The problems arising out of the different behaviors of the New and Open commands in the two,types of applications can be overcome easily by duplicating the code of the MDl Form’s File menu. Simply copy the code behind the New and Open commands of the MDl Form’s menu to the equivalent subroutines ‘of the child Form. Even better, you can call the corresponding event handler of the MDl ~ Form from within the subroutines of the child Form’s File commands. The File :> New and File >Open commands of the child Form in the MDI Pad project are implemented as follows:
In spite of the differences between the two applications, you can get the File ·:’commands, which represent the most important differences between single and multiple window applications, to work in the MDl application. The next task is to ensure that the rest of the program references the Editor TextBox of the current child Form.
In the TextPad application, the program references the textbox control as Forml .Editor.The name of the child Form is no longer Forml; it’s DocumentForms- (current document), in which currentDocument is the index of the active window in the DocumentForms() array. Replace all instances of Forml in the code with DocumentForms(currentDocument)-this is all it takes. Even the Find & Replace .Form’s code will reference the correct window in the MDI Form. The Form1 variable is also referenced in the child Form’s Resize event.
Ending an MDI Application.
In most cases ending an application with the End statement isn’t necessarily the most user-friendly approach. Before you end an application, you must always offer your users a chance to save their work. Ideally, you should maintain a True/False variable whose value is set every time the user edits the open document (the Change event of many controls is a.good place to set this variable to True and reset every time the user saves the document (with the Save or Save As commands).
This simple setup doesn’t require too much code, but it’s one of the finishing touches I have skipped ill many of the examples in this book. The error-catching code required is usually unrelated to the point I’m trying to make, and it would make the examples too lengthy (the objective is to demonstrate the core of various applications, not provide truly professional applications). In a real-world application, however, these “details” ~an ma~e all the difference for your application’s future.
Handling unsaved data in normal applications is fairly simple, as there’s only one document to deal with. But in an MDI application, you have to cope with several possible scenarios:
• The user closes a child window by clicking its Close button. You should detect this condition and provide the same code you’d use with a single Form application.
• The user quits a single document only. This situation is easy to handle-it’s. just like a normal application.
• The user closes the MDl Form. If the MDl Form is closed, all the open documents will close with it! If losing the edits in a single document is bad, imagine losing the edits in multiple documents .
Therefore, terminating an MDl application with the End statement is unacceptable. One approach would be to go through all child Forms, examine whether they are open, and prompt the user for each open document. This is possible, but it would take a lot of programming, and as you may have guessed, there’s a simpler way, which we’ll explore next.
Using Query Unload to Protect Data
The MDI mechanism provides a better solution for terminating an MDI application, via the QueryUnload event. This event is triggered when a child window is unloaded. If the entire MDI Form is unloaded, the QueryUnload event is triggered for each open child Form and it gives your code a chance to cancel the action. The QueryUnload event’s syntax is as follows:
QueryUnload(cancel As Integer, unloadmode As Integer)
The application can set the cancel argument to halt the termination process. If you set the cancel argument to True in your code, the child Form won’t be unloaded, which in turn, prevents the unloading of the MDI Form.
The unload mode argument tells you which event caused the QueryUnload event according to the values in Table.
As you can see, the QueryUnload event is triggered even if Windows itself IS shutting down. If you program the QueryUnload event of the child Forms, you can rest assured that the users of your application won’t lose any data unless their computer crashes or they reset it. You can examine the value of this argument from within your code to find out why the Form is being unloaded and act accordingly. On certain occasions, you may want to handle the situation differently, depending on the external event that caused the Form to unload, but the press is practically the same. Offer your users a chance to save the data, discard the data, or cancel the programm’s termination altogether.
To use the QueryUnload event, you must unload the child Form from within its Exit menu command. The code behind the End command of the MDI Editor’s File menu is shown in Code.
Notice that this is the Exit command of the child Form’s File menu. The MDl Form’s File menu is visible only if no child windows are open, in which case there’s nothing to save and a single End statement will suffice. To terminate the MDl application, first unload the MDl Form and then End the application.
Unloading the MDl Form triggers a QueryUnload event for all its child Forms. In each child Form’s Unload event, you can prompt the users accordingly and give them a chance to save their data, discard the data, or cancer the operation. Here’s how you can handle this event in the MDI Edit application.
These few lines of code enable the user of the application to discard unsaved data, cancel the operation, or save any unsaved data with a call to the child ‘ Form’s FileSave_Click subroutine.
Handling the unloading of multiple Forms in a single point in your code is a great convenience because you can make the process of terminating the application as simple or as complicated as you wish for all child Forms. For instance, you can create temporary files with unsaved information in case the user decides that the discarded information is valuable after all.