Multiple document interface (MDI) applications permit more than one document to be open at a time. This is in contrast to single document interface (SDI) applications, which can manipulate only one document at a time. Visual Studio .NET is an example of an MDI application—many source files and design views can be open at once. In contrast, Notepad is an example of an SDI application—opening a document closes any previously opened document.
There is more to MDI applications than their ability to have multiple files open at once. The Microsoft Windows platform SDK specifies several UI behaviors that MDI applications should implement. The Windows operating system provides support for these behaviors, and this support is exposed through Windows Forms as well.
MDI applications consist of a main form, which does not itself display any data, and one or more child forms, which appear only within the main form and are used for displaying documents. The main form is called the MDI parent, and the child forms are called the MDI children.
The Form class has two properties that control whether a given form is an MDI parent, MDI child, or neither. The Boolean IsMdiContainer property determines whether a form behaves as an MDI parent. The MdiParent property (which is of type Form) controls whether a form behaves as an MDI child. Setting the MdiParent property of a form to reference the application’s MDI parent form makes the form an MDI child form. Example 4-6 shows the minimum amount of code required to display an MDI parent form containing a single MDI child form.
Example 4-6. A minimal MDI application
Imports System Imports System.Windows.Forms Public Module AppModule Public Sub Main( ) Application.Run(New MainForm( )) End Sub End Module Public Class MainForm Inherits Form Public Sub New( ) ' Set the main window caption. Text = "My MDI Application" ' Set this to be an MDI parent form. IsMdiContainer = True ' Create a child form. Dim myChild As New DocumentForm("My Document", Me) myChild.Show End Sub End Class Public Class DocumentForm Inherits Form Public Sub New(ByVal name As String, ByVal parent As Form) ' Set the document window caption. Text = name ' Set this to be an MDI child form. MdiParent = parent End Sub End Class
Assuming that the code in Example 4-6 is saved in a
file named MyApp.vb
, it can be compiled from the
command line with this command:
vbc MyApp.vb /r:System.dll,System.Windows.Forms.dll
Running the resulting executable produces the display shown in Figure 4-7.
The Form class has two read-only properties related to MDI behavior. The IsMdiChild property returns a Boolean value that indicates whether the form is an MDI child. The MdiChildren property of a parent form contains a collection of references to the form’s child forms. The IsMdiChild and MdiChildren properties are both automatically maintained in response to setting the child forms’ MdiParent properties.
MDI applications usually have a main-menu item called Window. On this menu appear standard items for cascading, tiling, and activating child windows and arranging the icons of minimized child windows. Figure 4-8 shows a typical example.
Such a menu is easy to create using the support in Windows Forms. Assuming that you were to do it programmatically, Example 4-7 shows a revised version of Example 4-6 that has been modified to include a Window menu; the added code is shown in boldface. For details on how to work with menus from the Visual Studio IDE, as well as programmatically, see Section 5.5 in Chapter 5.
Example 4-7. An MDI application with a Window menu
Imports System Imports System.Windows.Forms Public Module AppModule Public Sub Main( ) Application.Run(New MainForm( )) End Sub End Module Public Class MainForm Inherits Form' Declare MainForm's main menu
Private myMainMenu As MainMenu
' Declare Windows menu
Protected WithEvents mnuWindow As MenuItem
Protected WithEvents mnuTileHoriz As MenuItem
Protected WithEvents mnuCascade As MenuItem
Protected WithEvents mnuTileVert As MenuItem
Protected WithEvents mnuArrangeAll As MenuItem
Public Sub New( ) ' Set the main window caption. Text = "My MDI Application" ' Set this to be an MDI parent form. IsMdiContainer = True' Create main menu
MyMainMenu = New MainMenu( )
' Define menu items
mnuWindow = New MenuItem( )
mnuTileHoriz = New MenuItem( )
mnuTileVert = New MenuItem( )
mnuCascade = New MenuItem( )
mnuArrangeAll = New MenuItem( )
' Set menu properties
mnuWindow.Text = "&Window"
mnuWindow.MdiList = True
mnuTileHoriz.Text = "Tile Horizontally"
mnuTileVert.Text = "Tile Vertically"
mnuCascade.Text = "Cascade"
mnuArrangeAll.Text = "Arrange Icons"
' Add items to menu
MyMainMenu.MenuItems.Add(mnuWindow)
mnuWindow.MenuItems.Add(mnuCascade)
mnuWindow.MenuItems.Add(mnuTileHoriz)
mnuWindow.MenuItems.Add(mnuTileVert)
mnuWindow.MenuItems.Add(mnuArrangeAll)
' Assign menu to form
Me.Menu = MyMainMenu
' Create a child form. Dim myChild As New DocumentForm("My Document", Me) myChild.Show End SubPublic Sub mnuCascade_Click(o As Object, e As EventArgs) _
Handles mnuCascade.Click
LayoutMdi(MdiLayout.Cascade)
End Sub
Public Sub mnuTileHoriz_Click(o As Object, e As EventArgs) _
Handles mnuTileHoriz.Click
LayoutMdi(MdiLayout.TileHorizontal)
End Sub
Public Sub mnuTileVert_Click(o As Object, e As EventArgs) _
Handles mnuTileVert.Click
LayoutMdi(MdiLayout.TileVertical)
End Sub
Public Sub mnuArrangeAll_Click(o As Object, e As EventArgs) _
Handles mnuArrangeAll.Click
LayoutMdi(MdiLayout.ArrangeIcons)
End Sub
End Class Public Class DocumentForm Inherits Form Public Sub New(ByVal name As String, ByVal parent As Form) ' Set the document window caption. Text = name ' Set this to be an MDI child form. MdiParent = parent End Sub End Class
To add a Window menu to the parent form of an MDI application,
perform the following steps. First, add a menu item to the MDI parent
form’s main menu, setting its Text property to anything desired
(usually Window
) and its MdiList property to
True
. It is the MdiList property that makes the
Window menu a Window menu. Setting the MdiList property to
True
causes the Windows Forms framework to add and
delete
menu items to and from this menu item as
necessary. This in turn will always display the current list of MDI
child windows in the menu.
Next, add menu items for Cascade, Tile Horizontally, Tile Vertically, and Arrange Icons. In the Click event handler for each of these menu items, call the Form class’s LayoutMdi method, passing the appropriate parameter value for the desired action.
The syntax of the LayoutMdi method is:
Public Sub LayoutMdi(ByVal value As MdiLayout)
The method’s single argument must be a value from the MdiLayout enumeration (defined in the System.Windows.Forms namespace). The values in this enumeration are:
-
ArrangeIcons
Indicates that the icons for the minimized MDI child windows should be neatly arranged.
-
Cascade
Indicates that the MDI child windows should be cascaded (displayed overlapping each other).
-
TileHorizontal
Indicates that the MDI child windows should be tiled (displayed without overlapping), with each child window filling the width of the MDI parent.
-
TileVertical
Indicates that the MDI child windows should be tiled, with each child window filling the height of the MDI parent.
Often, the items that should appear on an MDI application’s main menu are dependent on the type of document being displayed or on whether any document is displayed at all. Of course, this effect could be achieved in code by dynamically adding and removing menu items each time a child window is activated. However, the Windows Forms framework provides an easier way.
If an MDI child form has a main menu of its own, it and the MDI parent form’s main menu are merged to produce the menu that is shown to the user when the child form is displayed. Two properties of the MenuItem class affect how the menu items are merged. First, the MergeOrder property determines the order in which the menu items are displayed. This property can be set to any Integer value, and the values don’t have to be contiguous. The menu items from the two menus are sorted on this value to determine the order in which the menu items are displayed on screen.
For example, consider an MDI parent form that has a main menu with three menu items representing File, Window, and Help menus. Further, say that the MergeOrder properties of these menu items are 10, 20, and 30, respectively. Now, if an MDI child form is displayed and its main menu has, for example, an Edit item with a MergeOrder property value of 15, the menu displayed to the user will have four items: File, Edit, Window, and Help, in that order. Example 4-8 shows a revised version of Example 4-6 that contains the code necessary to create such a menu; lines shown in boldface have been added to define the main menu and its menu items.
Example 4-8. An MDI application with merged menus
Imports System Imports System.Windows.Forms Public Module AppModule Public Sub Main( ) Application.Run(New MainForm( )) End Sub End Module Public Class MainForm Inherits Form' Declare MainForm's main menu.
Private myMainMenu As MainMenu
' Declare the Window menu.
Protected WithEvents mnuFile As MenuItem
Protected WithEvents mnuWindow As MenuItem
Protected WithEvents mnuHelp As MenuItem
Public Sub New( ) ' Set the main window caption. Text = "My MDI Application" ' Set this to be an MDI parent form. IsMdiContainer = True' Create main menu
MyMainMenu = New MainMenu( )
' Define menu items
mnuFile = New MenuItem( )
mnuWindow = New MenuItem( )
mnuHelp = New MenuItem( )
' Set menu properties
mnuFile.Text = "&File"
mnuFile.MergeOrder = 10
mnuWindow.Text = "&Window"
mnuWindow.MergeOrder = 20
mnuWindow.MdiList = True
mnuHelp.Text = "&Help"
mnuHelp.MergeOrder = 30
' Add items to menu
MyMainMenu.MenuItems.Add(mnuFile)
MyMainMenu.MenuItems.Add(mnuWindow)
MyMainMenu.MenuItems.Add(mnuHelp)
' Assign menu to form
Me.Menu = MyMainMenu
' Create a child form. Dim myChild As New DocumentForm("My Document", Me) myChild.Show End Sub End Class Public Class DocumentForm Inherits Form' Declare menu
Private mdiMenu As New MainMenu
' Declare menu items
Protected WithEvents mnuEdit As MenuItem
Public Sub New(ByVal name As String, ByVal parent As Form) ' Set the document window caption. Text = name ' Set this to be an MDI child form. MdiParent = parent' Instantiate menu and menu items
mdiMenu = New MainMenu( )
mnuEdit = New MenuItem( )
' Set menu properties
mnuEdit.Text = "&Edit"
mnuEdit.MergeOrder = 15
' Add item to main menu
mdiMenu.MenuItems.Add(mnuEdit)
' Add menu to child window
Me.Menu = mdiMenu
End Sub End Class
If a menu item in the MDI child form menu has the same MergeOrder value as a menu item in the MDI parent form menu, a second property comes into play. The MergeType property of both MenuItem objects is examined, and the behavior is determined by the combination of their values. The MergeType property is of type MenuMerge (an enumeration defined in the System.Windows.Forms namespace) and can have one of the following values:
-
Add
The menu item appears as a separate item in the target menu, regardless of the setting of the other menu item’s MergeType property.
-
MergeItems
If the other menu item’s MergeType property is also set to
MergeItems
, the two menu items are merged into a single item in the target menu. Merging is then recursively applied to the subitems of the source menus, using their MergeOrder and MergeType properties.If the other menu item’s MergeType property is set to
Add
, both menu items appear in the target menu (just as though both had specifiedAdd
).If the other menu item’s MergeType property is set to
Remove
, only this menu item appears in the target menu (again, the same as specifyingAdd
for this menu item).If the other menu item’s MergeType property is set to
Replace
, only the child form’s menu item is displayed, regardless of which one is set toMergeItems
and which one is set toReplace
. (This seems like inconsistent behavior and may be a bug.)-
Remove
The menu item isn’t shown in the target menu, regardless of the setting of the other menu item’s MergeType property.
-
Replace
If the other menu item’s MergeType property is set to
Add
, both menu items appear in the target menu (just as though both had specifiedAdd
).If the other menu item’s MergeType property is set to
MergeItems
orReplace
, only the child form’s menu item is shown. (This seems like inconsistent behavior and may be a bug.)If the other menu item’s MergeType property is also set to
Replace
, only the child form’s menu item is shown.
Code in the MDI parent form class can be notified when an MDI child form becomes active inside an MDI parent form. (“Active” means that the child form receives the input focus after another MDI child form or the MDI parent form had the input focus.) To receive such notification, the MDI parent form must override the OnMdiChildActivate method (defined in the Form class). For example:
' Place this within the class definition of the MDI parent form. Protected Overrides Sub OnMdiChildActivate(ByVal e As EventArgs) MyBase.OnMdiChildActivate(e) ' Important ' ... End Sub
It is important to call the base-class implementation of OnMdiChildActivate within the overriding function, so that any necessary base-class processing (including raising of the MdiChildActivate event) can occur.
The e
parameter carries no information. To
find out which MDI child form became active, read the ActiveMdiChild
property of the MDI parent form. This property is of type Form.
Convert it to the MDI child form’s type to gain access to any
public members that are specific to that type. For example:
Protected Overrides Sub OnMdiChildActivate(ByVale
As EventArgs) MyBase.OnMdiChildActivate(e)' Assumes that SomeFormType is defined elsewhere and inherits
' from Form. Also assumes that the MDI child forms in the
' application are always of this type.
Dim childForm As SomeFormType = _
CType(ActiveMdiChild, SomeFormType)
' Do something with childForm here.
' ... End Sub
To have code outside of the MDI parent form class notified when an MDI child form becomes active, write a handler for the MDI parent form’s MdiChildActivate event. This event is defined in the Form class as:
Public Event MdiChildActivate( _ ByValsender
As Object, _ ByVale
As EventArgs _ )
The sender
parameter is the MDI parent
form, not the MDI child form that has been activated. The
e
parameter does not contain any
additional information about the event. As when overriding the
OnMdiChildActivate method, read the MDI parent form’s
ActiveMdiChild property to discover which MDI child form has been
activated.
Get Programming Visual Basic .NET now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.