Using Menus and MDI Forms
One of the worst interfaces ever created was the Menu Editor in Visual Basic 6 and earlier. It was a top complaint of developers; Microsoft heard their screams and provided an amazing, easy-to-use Menu Editor in Windows Forms. In addition to the editor being great, because items added to a menu are derived from a Menu class, you can easily create menu items dynamically at runtime, which was almost possible in Visual Basic 6. To test the new Menu Editor's features, you're going to create a Multiple Document Interface (MDI) application. The flexibility of MDI applications in .NET has also improved dramatically, as you'll see when you're done with the following exercise.
NOTE
The HelloNET application has been a SDI (single document interface) application until now. That means each form in the application is its own container. An MDI application has a parent form that contains children, and the child forms can't leave the screen boundary of the parent. MDI applications used to be the most popular type of UI, but in recent years SDI applications and Explorer-style applications have become more popular. Microsoft drives the UI trends with each new release of any product. Consider Microsoft Outlook: The Outlook Barstyle spread like wildfire when it was introduced, and it can be seen in hundreds of third-party applications today.
To get started, add three new forms to the HelloNET application by right-clicking the HelloNET project name in the Solution Explorer and selecting Add, New Windows Form. Name the new forms as follows:
parentForm
child1
child2
You'll notice that there is no MDI form template. This is now just a property on a form. After the three new forms are added, your Solution Explorer should look something like Figure 3.11.
Figure 3.11 The HelloNET application after adding three new forms.
From the Solution Explorer, double-click the parentForm form so that it's in the Forms Designer. In the Properties window, change the IsMdiContainer property to True. You'll notice that the form now has a dark gray background color, which is the indication that it's a MDI parent form.
Next, drag a MainMenu control from the Toolbox to the form. The MainMenu control is a nonvisual control, which means it doesn't appear as part of the form. When nonvisual controls are added to a form, the screen splits and the controls are added to the bottom portion of the Forms Designer. This makes it very easy to see what nonvisual controls have been added to a form. At this point, your application should look something like Figure 3.12.
Figure 3.12 The parentForm after adding a MainMenu control.
Adding menu items to the MainMenu is very intuitive: You simply type the name that you want the menu to display, and it shows up. Notice that you can go as many levels deep into a menu as you wantyou just keep typing where the arrow is in the editor, and that's where the menu item appears. Use the up-, down-, right-, and left-arrow keys on your keyboard to navigate the menu levels. Figure 3.13 demonstrates the addition of items to a MainMenu.
Figure 3.13 Adding items to a MainMenu.
Add the following five menu items to the MainMenu control:
Top Level Menu&File
Sub Level 1&Show child1, Name = showChild1
Sub Level 2&Show child2, Name = showChild2
Sub Level 3&Forms Count, Name = formsCount
Sub Level 4&Close All Forms, Name = exitApplication
While adding items to the menu, you can right-click on the menu itself to get the contextual menu with available options for the MainMenu control. One of the most useful items is the EditNames toggle. If you click EditNames, you can change the name of the menu item directly from the Menu Editor. You can also do this via the Properties window. From the Properties window, some of the individual properties that can be set are the Shortcut, RadioCheck, and Visible properties.
NOTE
In the code you just added to create the menu items, an ampersand was placed in front of each of the menu item captions. The ampersand will place an underscore underneath the character it prefixes in the menu, and will allow the end user to use the ALT key in combination with the underlined character as a shortcut to the menu item.
After you've added the new menu items, you'll add some code that brings up the child1 and child2 forms, gets a count of open forms, and exits the application.
To do this, add the code in Listing 3.5 to the click events for their corresponding controls.
Listing 3.5 The Code-Behind for the MDI Application
VB.NET
Private Sub MenuItem2_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles showChild1.Click Dim f As New child1() f.MdiParent = Me f.Show() End Sub Private Sub showChild2_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles showChild2.Click Dim f As New child2() f.MdiParent = Me f.Show() End Sub Private Sub formsCount_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles formsCount.Click ' Get the number of children MessageBox.Show(Me.MdiChildren.Length) ' Declare a Form object to get object properties Dim f As Form For Each f In Me.MdiChildren MessageBox.Show(f.Name.ToString) Next End Sub Private Sub closeAllForms_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles closeAllForms.Click ' Declare a Form object and call the Close event Dim f As Form For Each f In Me.MdiChildren f.Close() Next End Sub Private Sub exitApplication_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles exitApplication.Click ' Exit the Application Application.Exit() End Sub
C#
private void showChild2_Click(object sender, System.EventArgs e) { Form f = new child1(); f.MdiParent = this; f.Show(); } private void showChild1_Click(object sender, System.EventArgs e) { Form f = new child2(); f.MdiParent = this; f.Show(); } private void formsCount_Click(object sender, System.EventArgs e) { MessageBox.Show(this.MdiChildren.Length.ToString()); foreach (Form f in this.MdiChildren) { MessageBox.Show(f.Name.ToString()); } } private void closeAllForms_Click(object sender, System.EventArgs e) { foreach (Form f in this.MdiChildren) { f.Close(); } } private void exitApplication_Click(object sender, System.EventArgs e) { Application.Exit(); } }
Now press the F5 key to run the application. You should change the StartUp form in the parent form before starting. When the application is running, click the Show Child1 and Show Child2 menu items a few times each. You should see something like Figure 3.14.
Figure 3.14 Multiple MDIChildren open in an MDIParent form.
Each time you set the variable f to a new instance of a form, you're setting the MdiParent property to the current form. This makes it an MdiChild. The code for determining the number of MdiChildren open is simply the Length property of the MdiChildren class. Because the form is an object, you can use the For Each method to iterate through its collection to retrieve properties, methods, and events. So, by using the For Each method and calling the form's Close method, you're firing the Form_Closing method of each active form.
To exit the application, you called the Application.Exit() method. The Application object is similar to the App object in Visual Basic 6; it contains information about the current running application. It's important to note that the Closing event won't fire when you call the Application.Exit() method. So, make sure that you gracefully close your windows, and then call Application.Exit(). The code in the closeAllForms click event demonstrates the correct way to accomplish that.
Table 3.3 has some common properties of the Application object, and Table 3.4 has some common methods of the Application object that you'll find useful.
Table 3.3 Useful Application Object Properties
Property |
Description |
AllowQuit |
Gets a value indicating whether the caller can quit this application |
CommonAppDataPath |
Gets the path for the application data that's shared among all users |
CommonAppDataRegistry |
Gets the Registry key for the application data that's shared among all users |
CompanyName |
Gets the company name associated with the application |
CurrentCulture |
Gets or sets the culture information for the current thread |
ExecutablePath |
Gets the path for the executable file that started the application |
LocalUserAppDataPath |
Gets the path for the application data of a local, nonroaming user |
ProductName |
Gets the product name associated with this application |
ProductVersion |
Gets the product version associated with this application |
StartupPath |
Gets the path for the executable file that started the application |
UserAppDataPath |
Gets the path for the application data of a roaming user |
UserAppDataRegistry |
Gets the Registry key of the application data specific to the roaming user |
Table 3.4 Useful Methods of the Application Object
Method |
Description |
AddMessageFilter |
Adds a message filter to monitor Windows messages as they're routed to their destinations |
DoEvents |
Processes all Windows messages currently in the message queue |
Exit |
Informs all message pumps that they must terminate, and then closes all application windows after the messages have been processed |
ExitThread |
Exits the message loop on the current thread and closes all windows on the thread |
OnThreadException |
Raises the ThreadException event |
RemoveMessageFilter |
Removes a message filter from the message pump of the application |
Run |
Overloaded; begins running a standard application message loop on the current thread |