Dynamic menus and the .NET Framework

This article was originally published in VSJ, which is now part of Developer Fusion.
Although it would be possible to use the same APIs that were used in last month's VB 6.0 article, this is a poor choice for several reasons. First and foremost is the fact that much less programming is required to perform the same function using the .NET framework. Not only is less programming involved, the code is a lot cleaner, more modular and lends itself well to using object-oriented (OO) techniques.

The .NET framework is more than just a new development platform. It is a set of namespaces and classes that represent a virtual machine similar to the Java Virtual Machine. Most of what you do using system APIs can now be done using one or more classes from the .NET framework. Although .NET is a Windows development platform at the moment, we may see implementations on other platforms in the future. Any code which uses system APIs will not port very easily to another platform. On the other hand, one would expect code developed with .NET to run correctly on another platform running the same version of .NET.

The Object-Oriented Approach

Although VB programmers have been writing object-enabled code for quite some time, VB has never been an object-oriented language until now. While most VB coders will welcome the idea of using a class instead of the often difficult to use System APIs, many may not be accustomed to designing and writing objects themselves. It is really not that bad, and as we will see it even works for simple things.

The .NET Menu Classes

Before we start creating our own menu classes, we need to see what comes along for free. The menu classes are quite straightforward and intuitive. All of the .NET menu classes are to be found in the System.Windows.Forms namespace. You won't need to care about this if you are writing code that is part of a Windows Forms project, but otherwise you will have to add a reference to this namespace. To do so, make sure the Solution Explorer is visible by selecting Solution Explorer from the View menu. Now, right click on the References folder and select Add Reference from the menu. Locate System.Windows.Forms from the list, press the Select and then OK buttons. Once the reference has been added to the project, you can access all the classes from this namespace. However, if you do no more than this, you may have to qualify each reference to the class by prefixing the class name with the namespace. For example, when referring to the class 'MainMenu' you would instead have to refer to it as System.Windows.Forms.MainMenu. This quickly becomes tedious, so normally you will want to 'import' or 'use' the namespace by placing the appropriate statement at the beginning of your code. You won't have to do it for a Windows.Forms application but you will for other types. This sort of works like a super 'With' statement and is placed at the beginning of the program like so:
VB.NET:
Imports System.Windows.Forms

C#:
using System.Windows.Forms;
Now we are ready to play with the System.Windows.Forms menu classes. The menu classes derive from the base Menu class. This class is an abstract class and as such is not instantiated – but rather forms a basis upon which the other menu classes are based. We don't really need to know or care about this class for the purposes of this article, but it seems worthy of mention, being the father of the ones we will be looking at.

For purposes of code, the first class we will be concerned with is the MainMenu class. This class represents the Menu Bar at the top of a form and will normally contain at least several objects of the MenuItem class. A MenuItem may itself contain additional MenuItems. Using the familiar Help|About construct found on most Windows applications, we would find that 'Help' is a MenuItem on the form's MainMenu and 'About' is a MenuItem on the 'Help' MenuItem. There is no real concept if a 'sub-menu' as with the APIs. Creating a sub-menu is done by adding a MenuItem to the MenuItems collection of a MenuItem.

Event Handling in .NET

Once we have added menus using the menu classes, we will want to respond to them in code some how. If you recall the Visual Basic 6.0 version, we did this by sub-classing the form, trapping each Windows Message sent to the form, and taking appropriate action when a Menu_Click message was intercepted – a messy business at best. Event handling in the .NET platform is done by specifying an Event Handler that will be given control when the event occurs. It is not necessary to trap messages from Windows. We can assign an event handler at run-time that will process the event.

The method for linking an event to a corresponding event handler is very similar to passing a function pointer to a callback API. In Visual Basic 6.0, you use the AddressOf function, followed by the name of a routine that will receive the callback from the API. VB.NET also supports an AddressOf function, which basically works the same way for most practical purposes. It is actually quite different under the covers, but we don't need to worry about that here.

OO concepts

A few basic OO concepts are new to VB.NET. First of all, it is important to understand the idea of a constructor, which is the first method to run when a class is instantiated. The constructor is responsible for initialising any internal variables or states that are needed in the class when it comes alive. A simple constructor accepts no parameters and simply initialises the class with default values. However, it is possible to write a more complex constructor that will accept parameters and initialise the class with values other than the default ones. Many classes in the .NET framework make rich use of constructors and the menu classes are no exception.

Another new feature of VB.NET is the idea of function overloading. Simply put, this is the ability to create two or more methods – all with the same name – that differ only in their parameter list or return value. This is commonly referred to as the method's signature. Less simply put, this feature is called 'polymorphism'. Function overloading, or polymorphism, is actually a familiar operation to all VB programmers. Anyone who has ever used the plus sign to both add two integers together and to concatenate two strings has used function overloading. With VB.NET, not only is function overloading now part of the language itself, you will also encounter it quite often while using the .NET framework – especially with constructors. In this example, we will not only use function overloading provided by the menu APIs, we will also use it to good advantage in our own class as well.

Class Action

Having developed a good understanding of the overall picture, let's take a look at some code. To create a menu, you start off with the MainMenu class. There are two constructors for this class – one of which is overloaded to accept an array of MenuItems – but in this example we will use the simple constructor with no parameters. Once the MainMenu object is created, it is attached to the form by simply setting the Form's Menu property as shown below:
VB.NET:
Private m_mnuMenuBar As New MainMenu()
Me.Menu = m_mnuMenuBar

C#:
MainMenu m_mnuMenuBar = new _
	MainMenu();
this.Menu = m_mnuMenuBar;
The menu will not be usable or visible unless you add some items. The MenuItem constructor is also overloaded, offering quite a bit of latitude when instantiating objects. Here are a few examples:

VB.NET:
Instantiate a MenuItem with default properties.

Dim mnuMenuItem As New MenuItem()
Instantiate a MenuItem and set the caption to "Menu Caption".
Dim mnuMenuItem As New _
	MenuItem("Menu Caption")
Instantiate a MenuItem. Set the caption to "Menu Caption", and set the subroutine called Menu_Click as the event handler for the Click event.
Dim mnuMenuItem As New _
MenuItem("MenuCaption", AddressOf MenuItem_Click)
C#:
Instantiate a MenuItem with default properties.
MenuItem mnuMenuItem = new MenuItem();
Instantiate a MenuItem and set the Caption property to "Menu Caption".
MenuItem mnuMenuItem = new MenuItem("Menu Caption");
Instantiate a MenuItem. Set the Caption property to "Menu Caption", and set the subroutine called Menu_Click as the event handler for the Click event.
MenuItem mnuMenuItem =
	new MenuItem("MenuCaption", new
	System.EventHandler(Menu_Click));
A MenuItem is added to the MenuItems collection of the MainMenu class using the Add method, which is also very heavily overloaded. In fact, you don't always have to declare a MenuItems variable since some of the signatures allow MenuItems to be added implicitly. Here are some examples of adding a MenuItem to the MenuItems collection of a MainMenu.

VB.NET:
Add an implicit MenuItem and set its caption to "Menu Caption".

mnuMain.MenuItems.Add("Menu Caption")
Add an implicit MenuItem. Set its caption to "Menu Caption," and set a subroutine called Menu_Click as the Click event handler.
mnuMain.MenuItems.Add("Menu Caption",
	AddressOf MenuItem_Click)
Add a MenuItem object.
mnuMain.MenuItems.Add(mnuMenuItem)
Add a MenuItem object at position 5.
nuMain.MenuItems.Add(5, mnuMenuItem)
C#:
Add an implicit MenuItem and set its caption to "Menu Caption".
mnuMain.MenuItems.Add("Menu Caption");
Add an implicit MenuItem. Set its caption to "Menu Caption" and set a subroutine called Menu_Click as the Click event handler.
mnuMain.MenuItems.Add("Menu Caption",
new System.EventHandler(Menu_Click));
Add a MenuItem object.
mnuMain.MenuItems.Add(mnuMenuItem);
Add a MenuItem object at position 5.
mnuMain.MenuItems.Add(5, mnuMenuItem);
Notice that as the code moves past variable declaration and into actual programming, the differences between VB.NET and C# become less and less. Since the important part of the picture is the .NET framework itself, we will see that the VB.NET and C# versions are almost identical except for the syntax itself. All languages in the .NET framework are integrated so closely that they all use the same IDE, share the same class structure and all generate compatible MSIL code. You can even copy controls between a VB.NET and a C# project, as was done when creating the sample programs accompanying this article.

Thankfully, adding a MenuItem to a MenuItem is done in exactly the same manner as adding a MenuItem to a MainMenu. The MenuItem class has its own MenuItems collection, and all of the examples above would work equally well using a MenuItem variable instead of a ManuMenu variable. There are several other signatures for the Add method as well, and the reader is directed to the on-line documentation for more information. Deleting a MenuItem is accomplished with the Remove method. Note that the item to be removed must be in the form of a MenuItem. There is no built in way to remove an item based on its parent and position, as is the manner when using the RemoveMenu API.

The Dynamic Menu Program

Recalling last month's VB 6.0 example, our task is to write a program that will present the user with a menu-less form. The user will have a button to add sub-menu items to the menu bar. When a menu item is clicked, the user will be presented with a dialog box indicating the caption of the item. From here, the user has the option of continuing with no further action, adding a menu item or adding a sub-menu item. A new item may be added before or after the one that was just clicked. This time, we will write the application using the menu classes just described, using an object-oriented approach.

We will create a class to encapsulate all the functionality needed for adding and removing menu entities. This will be a very simple class consisting of no more than three public methods, some of which will be overloaded. The simplest method, InsertMenuItem is no more than one or two lines of code. We will want the ability to both append menu items to the end and to insert menu items at a specific location. In the VB 6.0 version, an optional parameter was used. If the parameter was included, it indicated a position at which to insert the new item. If not, the new item was appended to the end of the list. Although VB.NET does support optional parameters, a cleaner way from an OO point of view is to use function overloading to a similar end. This is the approach that will be used here. We will declare two methods with different signatures as shown below:

VB.NET:
Append a menu item and append the new item, set the caption and the event handler.

Public Sub InsertMenuItem( _
	ByRef Parent As MenuItem, _
	ByVal MenuCaption As String, _
	ByVal Menu_Click As EventHandler)
	Parent.MenuItems.Add(_
	MenuCaption, Menu_Click)
End Sub
Insert a menu item at Position, instantiate a menu item, set the caption and the event handler.
Public Sub InsertMenuItem( _
	ByRef Parent As MenuItem, _
	ByVal Menu_Click As EventHandler, _
	ByVal MenuCaption As String, _
	ByVal Position As Integer)
	Dim mniNewItem As New MenuItem(_
		MenuCaption, 	Menu_Click)
	Parent.MenuItems.Add(Position, _
		mniNewItem)
End Sub
C#:
Append a menu item and append the new item, set the caption and the event handler.
public void InsertMenuItem(
	MenuItem Parent, EventHandler
	Menu_Click, string MenuCaption)
{
	Parent.MenuItems.Add(MenuCaption,
	Menu_Click);
}
Insert a menu item at Position, instantiate a menu item, set the caption and the event handler.
public void InsertMenuItem(
	MenuItem Parent, EventHandler 
	Menu_Click, string MenuCaption, int 
	Position)
{
	MenuItem mniNewItem = new
	MenuItem(MenuCaption, Menu_Click);
	Parent.MenuItems.Add(Position, mniNewItem);
}
At this point you may be wondering why bother wrapping a single line or two of code into a class method. The reason is simple. First of all, as we will see, the remaining class methods are a bit more complicated than this one. The idea of our home-grown class is that all the menu manipulation needed by the calling application will be encompassed within the class itself. This saves the calling application from having to worry about how to deal with building the menu. Instead the calling application indicates what to do, and the class does the rest. Another obvious point of contention is the fact that I have taken what would have been one routine, and doubled it. Any change that needs to be done to one of these routines in the future will also have to be done to the other one. This is a very valid point, and the truth is that any overloaded public routine that gets much more complicated than these should actually just pass along control to a private routine that does all the work. As we will see, this is the approach that is used next.

The second method we will look at is InsertSubMenu. This method will be implemented with five different signatures. We will provide interfaces for adding a sub-menu to either a MainMenu object or a MenuItem object. Additionally, for each of these two signatures we will another that also accepts a positional parameter. These public methods will initialise the appropriate private variables and call the fifth method, a private method that will actually perform the appending or inserting. Note the last three executable program statements at the end of the private members shown below. I have written the routines this way to show the actual steps that are being done in the process of adding a new item and assigning an event handler. The same result can actually be had with a single line of code, which is given at the end of each routine. Here is the code for the InsertSubMenu method. Note that several modular level variables (previxed with m_) are assumed to have been declared prior to this point:

InsertSubMenu
Append a sub-menu to the menu bar.

VB.NET:

Public Sub InsertSubMenu( _
ByRef Parent As MainMenu, _
	ByVal Menu_Click As EventHandler, _
	ByVal SubMenuCaption As String, _
	ByVal MenuItemCaption As String)
Set up the menu variables.
	m_mnuMainMenu = Parent
	m_mniMenuItem = Nothing
We're appending to the end.
	m_bAddToEnd = True
	InsertSubMenu(Menu_Click, _
	SubMenuCaption, MenuItemCaption)
End Sub
C#:
public void InsertSubMenu(MainMenu 
	Parent, EventHandler Menu_Click,
	string SubMenuCaption,
	string MenuItemCaption)
{
	m_mnuMainMenu = Parent;
	m_mniMenuItem = null;
	m_bAddToEnd = true;
	InsertSubMenu(Menu_Click,
	SubMenuCaption, MenuItemCaption);
}
Insert a sub-menu to the menu bar at Position.

VB.NET:

Public Sub InsertSubMenu(
	ByRef Parent As MainMenu, _
	ByVal Menu_Click As EventHandler, _
	ByVal SubMenuCaption As String, _
	ByVal MenuItemCaption As String, _
	ByVal Position As Integer)

	m_mnuMainMenu = Parent
	m_mniMenuItem = Nothing
	m_bAddToEnd = False
	m_intPosition = Position
	InsertSubMenu(Menu_Click, _
	SubMenuCaption, MenuItemCaption)
End Sub
C#:
public void InsertSubMenu(MainMenu 
	Parent, EventHandler Menu_Click,
 	string SubMenuCaption, string 
	MenuItemCaption, int Position)
{
	m_mnuMainMenu = Parent;
	m_mniMenuItem = null;
	m_bAddToEnd = false;
	m_intPosition = Position;
	InsertSubMenu(Menu_Click,
	SubMenuCaption, MenuItemCaption);
}
Append a sub-menu to a menu item:

VB.NET:

Public Sub InsertSubMenu( _
	ByRef Parent As MenuItem, _
	ByVal Menu_Click As EventHandler, _
	ByVal SubMenuCaption As String, _
	ByVal MenuItemCaption As String)

	m_mnuMainMenu = Nothing
	m_mniMenuItem = Parent
	m_bAddToEnd = True
	InsertSubMenu(Menu_Click, _
	SubMenuCaption, MenuItemCaption)
End Sub
C#:
public void InsertSubMenu(MenuItem 
	Parent, EventHandler Menu_Click,
 	string SubMenuCaption,
	string MenuItemCaption)
{
	m_mnuMainMenu = null;
	m_mniMenuItem = Parent;
	m_bAddToEnd = true;
	InsertSubMenu(Menu_Click,
	SubMenuCaption, MenuItemCaption);
}
Insert a sub-menu to a menu item at Position

VB.NET:

Public Sub InsertSubMenu( _
	ByRef Parent As MenuItem, _
	ByVal Menu_Click As EventHandler, _
	ByVal SubMenuCaption As String, _
	ByVal MenuItemCaption As String, _
	ByVal Position As Integer)

	m_mnuMainMenu = Nothing
	m_mniMenuItem = Parent
	m_bAddToEnd = False
	m_intPosition = Position
	InsertSubMenu(Menu_Click, _
	SubMenuCaption, MenuItemCaption)
End Sub
C#:
public void InsertSubMenu(MenuItem 
	Parent, EventHandler Menu_Click,
	string SubMenuCaption, string 
	MenuItemCaption, int Position)
{
	m_mnuMainMenu = null;
	m_mniMenuItem = Parent;
	m_bAddToEnd = false;
	m_intPosition = Position;
	InsertSubMenu(Menu_Click,
	SubMenuCaption, sMenuItemCaption);
}
The internal procedure for all public InsertSubMenu methods is simply:

VB.NET:

Private Sub InsertSubMenu(ByVal _
	ehMenu_Click As EventHandler, _
	ByVal strSubMenuCaption As String,_ 
	ByVal strMenuItemCaption As String)
Add the sub-menu to the menu bar:
	Dim mniSubMenu As New _
		MenuItem(strSubMenuCaption)
Add to the menu or menu item depending on the parent type:
	If m_mnuMainMenu Is Nothing Then
		If m_bAddToEnd Then
		m_mniMenuItem.MenuItems.Add( _
			mniSubMenu)
		Else
		m_mniMenuItem.MenuItems.Add( _
			m_intPosition, mniSubMenu)
		End If
	Else
		If m_bAddToEnd Then
		m_mnuMainMenu.MenuItems.Add( _
			mniSubMenu)
		Else
		m_mnuMainMenu.MenuItems.Add( _
			m_intPosition, mniSubMenu)
		End If
	End If
Create the new menu item:
	Dim mniMenuItem As New _
		MenuItem(strMenuItemCaption)
Add the item to the sub-menu and assign the menu handler:
mniSubMenu.MenuItems.Add(mniMenuItem)
	AddHandler mniMenuItem.Click, _
		ehMenu_Click
End Sub
The preceding three lines of code could have been done with this one statement:
mniSubMenu.MenuItems.Add( _
	strMenuItemCaption, ehMenu_Click)
C#
private void InsertSubMenu(
	EventHandler ehMenu_Click,
	string strSubMenuCaption,
	string strMenuItemCaption)
{
	MenuItem mniSubMenu =
	new MenuItem(strSubMenuCaption);
	if (m_mnuMainMenu == null)
		if (m_bAddToEnd)
		m_mniMenuItem.MenuItems.Add(
			mniSubMenu);
		else
	 	m_mniMenuItem.MenuItems.Add(
			m_intPosition, mniSubMenu);
	else
		if (m_bAddToEnd)
			m_mnuMainMenu.MenuItems.Add(
				mniSubMenu);
		else
			m_mnuMainMenu.MenuItems.Add(
			m_intPosition, mniSubMenu);
	MenuItem mniMenuItem = new
		MenuItem(strMenuItemCaption);
	mniSubMenu.MenuItems.Add(
		mniMenuItem);
	mniMenuItem.Click += new
		System.EventHandler(
		ehMenu_Click);
}
The preceding three lines of code could have been done with this one statement:
mniSubMenu.MenuItems.Add(
	strMenuItemCaption, ehMenu_Click);
Having added items we need now only add a method to remove them when no longer needed. As was true with the VB 6.0 version, we are still faced with the problem of an orphaned parent caused by removing the last item in its MenuItems collection. We will again solve this problem with a recursive routine that will call itself back to delete the parent item(s) if applicable. This routine has only one signature, and so the public routine is the only method needed:

VB.NET:

Public Sub DeleteMenuItem(
	ByRef mniDeleteThis As MenuItem)
If the parent menu item's count is 1, this is the last item so delete this item and the parent.
If _
mniDeleteThis.Parent.MenuItems.Count _
= 1 And TypeOf mniDeleteThis.Parent _
Is MenuItem Then
		Dim mniDeleteThisToo As _
		MenuItem = CType( _
		mniDeleteThis.Parent, MenuItem)
mniDeleteThis.Parent.MenuItems.Remove(_
			mniDeleteThis)
		Me.DeleteMenuItem( _
			mniDeleteThisToo)
	Else
mniDeleteThis.Parent.MenuItems.Remove(_
		mniDeleteThis)
	End If
End Sub
C#:
public void DeleteMenuItem(MenuItem mniDeleteThis) {
	if
((mniDeleteThis.Parent.MenuItems.Count 
	== 1) & (mniDeleteThis.Parent is
	MenuItem))
	{
		MenuItem mniDeleteThisToo =
		(MenuItem) mniDeleteThis.Parent;
mniDeleteThis.Parent.MenuItems.Remove(
		mniDeleteThis);
	DeleteMenuItem(mniDeleteThisToo);
	}
	else
mniDeleteThis.Parent.MenuItems.Remove(
	mniDeleteThis);
}
As you can see, using the .NET framework provides quite a simpler and more elegant solution than was previously possible using the menu APIs. With the release of VB.NET, Visual Basic has finally become a truly object-oriented language which promises to set a new direction in the way VB developers design and write code.

The complete source listings for both the VB.NET and C# versions are from www.skycoder.com/downloads.


Jon Vote is an Independent Consultant based in Oregon, USA. He can be reached at [email protected].

You might also like...

Comments

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“The trouble with programmers is that you can never tell what a programmer is doing until it's too late.” - Seymour Cray