Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / XML

An XML-Driven Menu Strip

4.71/5 (13 votes)
16 Apr 2007CPOL8 min read 1   2.1K  
In this article, I'm going to show how to generate a dynamic MenuStrip (.NET 2.0's replacement of the MainMenu control) using recursion in C#. The MenuStrip will be based on XML data which contains all the details about the generated Menu.

Screenshot - XMLDrivenMenuMain.jpg

Introduction

In this article, I'll be showing how you can generate a dynamic MenuStrip (.NET 2.0's replacement of the MainMenu control) via an XML Configuration file, using recursion in C#. The MenuStrip will be based on XML data which contains all the details about the generated menu. Things like: Caption, Name, Text, and the corresponding Event it has to trigger when a user clicks a MenuItem.

This is a generalized re-usable control written in such a way that it can be easily plugged in to any application based on the application's requirements.

Why an XML Driven MenuStrip?

Honestly, I've battled with the concept of an XML driven menu, and while I'm still not fully convinced that it would be 100% useful in all situations … I can see how it would be useful in some applications.

The only thing I can come up with to really answer this is … "why not?" I mean, .NET 3.0 will pretty much make this control out-dated with WPF and XAML, but in the meantime, why shouldn't I be able to supply a path to an XML configuration file to setup my MenuStrip control and have it generated at run time as opposed to hard coding all the ToolStripMenuItem values at design time.

The company that I work for has multiple clients that run our application. Each customer puts more focus on different areas of the application than the other customers, which is perfectly understandable. As many developers know, customers tend to want different things out of the application. This typically controls how dynamic an application should be as well as how customizable an application should be. Having to make a separate build for each of the customers just because we want to rename one of their menu items was not a feasible solution. This is where I originally got the idea for the XML-driven Menu Strip.

Pros and Cons

Pros of an XML-Driven Menu

  • Adding and removing ToolStripMenuItem does not require an application rebuild.
  • Customers, end-users, developers, etc. can rename MenuItem in the XML configuration file to better suit their needs or to give a MenuItem more "meaning" in their environment.
  • A decent way to get new developers started on the learning curve for the application.

Cons of an XML-Driven Menu

  • Customers, End Users, Developers, etc., have the ability to rename their MenuItem elements at any time. This could become confusing if someone renames the "File > Exit" MenuItem to "File > Open."
  • Supporting the application becomes a bit more difficult as Support no longer has the ability to say for certain where to go in the menu structure as their "File" could be easily renamed to "Option1" (just an example).

Of course, I'm sure there are many more pros and cons that you can come up with, but these were just a few that I came up with off the top of my head.

Points of Interest

One main thing here is that this control uses the new, relatively speaking, .NET 2.0 MenuStrip control and not the .NET 1.1 MainMenu control. There are some noticeable differences in the two. For a more detailed explanation, check out Jim Hollenhorst's CodeProject article: "Upgrading from MainMenu and ToolBar to MenuStrip and ToolStrip."

Things to Note

Aside from showing how to enhance the MenuStrip control to use an XML file as its data source, this control also shows some pretty basic techniques for using Reflection, loading files from embedded resources, parsing an XML file, and organizing an XML file. I'm sure there are other interesting things to note as well.

Using the Code

The main purpose of this control is to generate an applications menu via a MenuStrip control, at runtime, based on values provided in an XML configuration file that can be located in a file system folder or embedded as a resource directly in to your application.

The XML Structure

XML
<root>
        <TopLevelMenu ID="&amp;File">
                <MenuItem ID="New" OnClick="_New" />
                <MenuItem ID="Open" OnClick="_Open" />
                <MenuItem ID="-" />
                <MenuItem ID="Close" OnClick="_Close" />
                <MenuItem ID="-" />
                <MenuItem ID="E&amp;xit" OnClick="_Exit" />
        </TopLevelMenu>
        <TopLevelMenu ID="&amp;Edit">
                <MenuItem ID="Undo" OnClick="_Undo" />
                <MenuItem ID="-" />
                <MenuItem ID="Cut" OnClick="_Cut" />
                <MenuItem ID="Copy" OnClick="_Copy" />
                <MenuItem ID="Paste" OnClick="_Paste" />
                <MenuItem ID="-" />
                <MenuItem ID="Options">
                        <MenuItem ID="Sub Menu Item">
                                <MenuItem ID="Sub Sub Menu Item" />
                        </MenuItem>
                </MenuItem>
        </TopLevelMenu>
</root>

There's really nothing too extreme about the XML structure. It uses a root element to encompass the TopLevelMenu elements. The application can support N number of TopLevelMenu elements. Under each of the TopLevelMenu elements are MenuItem elements which also have N-level support.

The XML list above will generate a simple menu that appears as:

Screenshot - XMLDrivenMenuStrip.jpg

Currently, the control doesn't have an XSD file, but perhaps in future revisions, I'll get around to adding one for added functionality. The control only has a limited number of attributes at the moment, but in the near future, it will attempt to provide many hooks in to all the ToolStripMenuItem events and properties.

Valid XML Attributes

Name Required This is the .Name value that gets assigned to the ToolStripMenuItem.
Text Required This is the .Text value that gets assigned to the ToolStripMenuItem.
OnClick Optional This is the OnClick Event Handler that will be called when the ToolStripMenuItem is clicked.

The OnClick attribute is the most notable attribute as it tells the application which event should be fired when a user clicks that specific MenuItem element. Let's take a look at the "New" MenuItem in the above XML.

XML
<MenuItem ID="New" OnClick="_New" />

This means that in the Form that this MenuStrip is being displayed, it should have the following code to handle the Event:

C#
private void MenuItemOnClick_New( object sender, EventArgs e )
{
    MessageBox.Show( "New Clicked" );
}

This event handler is based on the following format:

MenuItemOnClick This is a hard coded value in the control. It doesn't come from your XML configuration file.
_New This is defined in the OnClick attribute of your element. If you change the _New to _NewItem, then your Form event should also be changed as well.

The Dynamic Menu control exposes Event Handlers for each and every MenuItem element you have created with an attribute value of "OnClick." If you don't want to create an Event Handler for a MenuItem, then you don't need to specify the OnClick attribute for it in the XML file.

Now, let's get into the nitty-gritty about the control…

MenuStripEnhanced Control

The MenuStripEnhanced control inherits from System.Windows.Forms.MenuStrip. So it provides all the original functionality of the standard MenuStrip as well as the added functionality for loading its data via an XML configuration file.

* Revision 1: Edited to reflect Matt's suggestions about the OnPaint() event.

The public method LoadDynamicMenu() is where all the magic happens. The method begins by doing some checks to make sure that the XML configuration file exists and is in the correct directory. After I check to make sure each of the conditions have been met, the control will then begin parsing the XML configuration file.

* Revision 2: Edited to reflect the overloaded LoadDynamicMenu(). Thanks, Matt.

LoadDynamicMenu() has three overloads to support different types of loading. The three overloads are:

  • public void LoadDynamicMenu();
  • public void LoadDynamicMenu( string xmlPath );
  • public void LoadDynamicMenu( XmlTextReader xmlReader );
C#
public void LoadDynamicMenu()
{
    if ( XmlPath != string.Empty )
        LoadDynamicMenu( XmlPath );
}

This overload just allows you to set the .XmlPath property and load the XML configuration file directly from a physical XML file on the hard drive:

C#
public void LoadDynamicMenu( string xmlPath )
{
    if ( System.IO.Path.IsPathRooted( XmlPath ) )
        xmlPath = XmlPath;
    else
        xmlPath = System.IO.Path.Combine( Application.StartupPath, XmlPath );

    if ( System.IO.File.Exists( XmlPath ) )
    {
        XmlTextReader reader = new XmlTextReader( xmlPath );
        LoadDynamicMenu( reader );
    }
}

This overload allows you to pass in a specific XML file so that you don't need to set the property and then call the method:

C#
public void LoadDynamicMenu( XmlTextReader xmlReader )
{            
    this.Items.Clear();

    XmlDocument document = new XmlDocument();
    document.Load( xmlReader );

    XmlElement element = document.DocumentElement;

    foreach ( XmlNode node in document.FirstChild.ChildNodes )
    {
        ToolStripMenuItem menuItem = new ToolStripMenuItem();

        menuItem.Name = node.Attributes["Name"].Value;
        menuItem.Text = node.Attributes["Text"].Value;

        this.Items.Add( menuItem );
        GenerateMenusFromXML( node, (ToolStripMenuItem)this.Items[this.Items.Count - 1] );
    }
}

This code allows you to pass in an XmlTextReader object. This is useful if you want to store your XML files as resources inside your application.

The downloadable sample application shows details on how you can use each of the overloads to load the menu configuration file.

All these overloads are fairly straightforward. They each flow to one main method that loads up the XML configuration file in to an XmlDocument() object and then walks through all the TopLevelMenu elements.

It will then pass in the current XML node as well as the ToolStripMenuItem in to the GenerateMenusFromXML( XmlNode rootNode, ToolStripMenuItem menuItem ) function. The GenerateMenusFromXML() is a recursive function that will parse N-level MenuItems.

C#
private void GenerateMenusFromXML( XmlNode rootNode, ToolStripMenuItem menuItem )
{
    ToolStripItem item = null;
    ToolStripSeparator separator = null;

    foreach ( XmlNode node in rootNode.ChildNodes )
    {
        if ( node.Attributes["Text"].Value == "-" )
        {
            separator = new ToolStripSeparator();

            menuItem.DropDownItems.Add( separator );
        }
        else
        {
            item = new ToolStripMenuItem();
            item.Name = node.Attributes["Name"].Value;
            item.Text = node.Attributes["Text"].Value;

            menuItem.DropDownItems.Add( item );

            if ( node.Attributes["FormLocation"] != null )
                item.Tag = node.Attributes["FormLocation"].Value;

            // add an event handler to the menu item added
            if ( node.Attributes["OnClick"] != null )
            {
                FindEventsByName( item, this.Form, true, "MenuItemOn", 
                                  node.Attributes["OnClick"].Value );
            }

            GenerateMenusFromXML( node, 
              (ToolStripMenuItem)menuItem.DropDownItems[menuItem.DropDownItems.Count - 1] );
        }
    }
}

So far, the code has been fairly straightforward, but things get a bit more interesting when the GenerateMenusFromXML() finds an OnClick attribute on a MenuItem element.

C#
// add an event handler to the menu item added
if ( node.Attributes["OnClick"] != null )
{
    FindEventsByName( item, this.Form, true, "MenuItemOn", 
                      node.Attributes["OnClick"].Value );
}

FindEventsByName() uses reflection to search the passed in receiver object, which is actually the Form that the MenuStripEnhanced control is attached to, to find the Event Handler that was provided in the value of the OnClick attribute.

Possible Future Enhancements

  • Add Icon attribute to support MenuItem element.
  • Add more Event attributes to the XML configuration.
  • Add support for encrypting the XML file so that it cannot be changed by just anyone. [This was sort of done by allowing the XML files to be loaded as Embedded Resources.]
  • Add XSD support for the XML configuration file.

Revision History

Wednesday, April 18, 2007

Version 1.0.2

  • Added two overloads for LoadDynamicMenu()
  • Can now load the menu configuration file from an Embedded Resource

Tuesday, April 17, 2007

Version 1.0.1

  • Removed the OnPaint() override and updated the article to reflect the changes [*Based on Matt's comments below]

Monday, April 16, 2007

Version 1.0

  • Initial release

Conclusion

The control itself could still use some work, but for the most part, it's a decent start to getting a fully customized MenuStrip control and I intend on making upgrades to it as I have the time.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)