Remember what MDI means? Multiple document interface.
This month column will talk about many topics all related to MDI forms and their children. Some of the topics include the children collection, options normally found under the Window menu, differences between VB.Net 2002 and 2003 (some bugs fix). I will also show you how to dock a child form. Finally, and probably the coolest feature, I will show you how to load a child form from a different assembly without having the parent knowing anything about it at compile time.
Creating the main MDI form
We will first start by creating the main form and add some controls to allow the navigation between child forms.
Create a new project using the Windows application template. Set the IsMdiContainer property of the default form to true. This will enable children forms to be hosted. Notice that more then one form can have this property set to true in the same project but trying to set a MDIContainer as a child of another form will generate an exception at runtime.
To this main form, add a treeview control and dock it to the left of form (using the Dock property). I use the treeview control instead of a menu to select child only to show you how you can dock child forms. You can easily replace the treeview with some menu items if you prefer this later method. Now add a splitter control that will allow us efficient resizing of the treeview and children forms without writing a single line of code.
Now add two forms (name them frmChild1 and frmChild2) that will become our children forms.
Add this method to fill the treeview. Notice that the Tag property is filled with the name of the child form.
Private Sub FillTreeView() Dim nodX As TreeNode Dim nodChild As TreeNode With tvwMenu .BeginUpdate() 'Add the First Parent nodX = New TreeNode("Hardcoded elements") nodChild = New TreeNode("Child1") nodChild.Tag = "frmChild1" nodX.Nodes.Add(nodChild) nodChild = New TreeNode("Child2") nodChild.Tag = "frmChild2" nodX.Nodes.Add(nodChild) .Nodes.Add(nodX) nodX = Nothing 'Add the parent for the dynamic elements nodX = New TreeNode("Dynamic elements") .Nodes.Add(nodX) nodX = Nothing .EndUpdate() End With End Sub
Private Sub tvwMenu_DoubleClick(ByVal sender As Object, _ ByVal e As System.EventArgs) _ Handles tvwMenu.DoubleClick Select Case DirectCast(tvwMenu.SelectedNode.Tag, String) Case String.Empty 'Do Nothing Case "frmChild1" Dim frm As New frmChild1() With frm .MdiParent = Me .FormBorderStyle = FormBorderStyle.None .Show() .Dock = DockStyle.Fill End With Case "frmChild2" Dim frm As New frmChild2() With frm .MdiParent = Me .Show() .Dock = DockStyle.Fill End With Case Else MessageBox.Show("Houston, we have a problem!") End Select End Sub
The bugs in VB.Net 2002
One thing that some people are calling a bug but is not, is that a child form (a from having its MdiParent set to another form) cannot be displayed using the ShowDialog method. It makes sense since a child cannot be modal!
One real bug is about the MaximumSize and MinimumSize properties. Even if you set these properties to some valid values, they won't be considered when the form is displayed as a child. This bug has a well-documented workaround. See http://support.microsoft.com/default.aspx?scid=kb;en-us;327824 . This bug has been fixed in VB.Net 2003.
Another bug is about the Activated event. The event seems to be raised only when no child forms are already displayed. Isn't supposed to be raised every time a form is activated? So don't rely on this event to refresh the state of a toolbar or a menu! This bug has also been fixed in VB.Net 2003. You can test the behaviour by adding some code to the Load and Activated events.
Private Sub ActivatedEvent(ByVal sender As Object, _ ByVal e As System.EventArgs) _ Handles MyBase.Activated Debug.WriteLine("frmChild1 -> Activated") End Sub Private Sub LoadEvent(ByVal sender As Object, _ ByVal e As System.EventArgs) _ Handles MyBase.Load Debug.WriteLine("frmChild1 -> Load") End Sub
Another one! The Opacity property has no effects on a child form. I read that Windows (up to XP) can only apply this setting to TopLevel forms (and children forms are not TopLevel). Maybe a next version of Windows will fix it! If someone as Windows 2003 installed, can you please try it for me and drop me a line?
Enough for the bugs (even if I am sure I can find more)!
Using the children collection
It is sometime required to loop through all open children of a MDI. VB.Net maintains a collection of active child forms automatically. This collection is accessible through MDIChildren.
In the next section, we will implement a Close All option that will close all the children. But for now we will implement something that will limit to only one instance of a specific form. I don't know if you tested it before, but if you double click twice on a child node, this child will get two distinct instances. And that's OK for most MDI application. But there are times when you want only one instance of a particular child in memory. This is what this code is doing.
First, here is the code to loop through the collection.
Private Function IsChildInMemory(ByVal pChildName As String) As Boolean Dim frmChild As Form For Each frmChild In Me.MdiChildren If TypeName(frmChild).ToUpper = pChildName.ToUpper Then frmChild.Activate() Return True End If Next Return False End Function
With this code, we can now modify the code that displays the child to limit to one instance a particular form. In the DoubleClick event of the treeview, replace the code to display the second child form to this:
Case "frmChild2" If Not IsChildInMemory("frmChild2") Then Dim frm As New frmChild2() With frm .MdiParent = Me .Show() End With End If
Showing the Window menu items
You can easily implement most of the features that you normally find under the Window menu with minimum code. This is what will build in this section.
You first need to start by creating a menu like the one you see in the image.
Figure 1: The Window Menu
The first four elements are really easy to code. A single line is required to implement each of these features. These four options all use "Me.LayoutMdi(MdiLayout.???)" method using a different parameter. The four available values for this parameter are Cascade, TileHorizontal, TileVertical, and ArrangeIcons. Can you guess which value is going with each option? I'll give you a hint: there are in the correct order!
The two remaining elements requires a bit more code, four lines each! In both case, you need to loop through the children collection and take appropriate action. Here is the code that will minimize all children forms:
Dim frmChild As Form For Each frmChild In Me.MdiChildren frmChild.WindowState = FormWindowState.Minimized Next
This menu also normally displays all the open children forms at the bottom with a check to the left of the current active form. This time, you only need to set a property to get this feature. Select your top-level menu item (Window in my example), go to its properties and set the MdiList property to true.
Isn't it easy!
Loading child form from a different assembly
Let's now talk about a really cool feature. What about adding child forms to a parent application that doesn't know anything about it at compile time, except that this application will eventually receive children? I would have needed this feature many times in the past but the solution was not that easy. For example, you can add new features to a parent application without recompiling it. Reflection helps us doing that.
I will not list all the code here. The downloadable demo contains it along with all the code so far. Here is the recipe:
That's it!
There is an article on MSDN on this subject titled Loading Classes On the Fly that you can read if you're interested in learning more.
Conclusion
I cannot see how creating MDI applications can be easier! The feature of adding dynamic forms to another application so easily is fascinating me.
If you decide to go that way using MDI, there is another topic you will want to explore is the menu merging (displaying specific child form menu elements inside the parent menu.
So I hope you learn new tricks and you appreciate this month column. See you next month.