Menu handling can be a real pain sometimes, especially if you're doing menus in several languages and/or cultures. Having an event handler for every MenuItem.Click event makes code very ugly and hard to navigate. Having a single menu handler is advantageous, but you still need to determine which menu item you're dealing with and the only real property you can key off of is Text, which you can't do in multilingual apps. Even in typical single-language situations, you're still likely to rename menu items over the long haul and break your handler.
The class below is a stongly-typed menu handler using Visual Studio .NET's externder services. I love these. I've extended most of the WinForms controls at one time or another just to add a little bit of functionality and it's a huge hassle. The controls don't get added to the toolbar automatically, so you need to change the IDE-generated code manually, which never seems to stick long-term. Adding items to the toolbar requires a reference to an assembly and that becomes troublesome as your derived controls evolve. Creating extenders enables you to add functionality to existing controls without having to subclass them. Very, very cool.
Anyway, this class takes advantage of that, with one catch: You need to create an enum called MenuID with the IDs of each of your menu items. By adding this component to a windows form, every menu item will automagically get an extra property in its property window called "MenuCommandName on <componentName>" where componentName will default to menuHandler1. All you need to do is select the MenuID for each item and handle the component's Click event. In Click's event handler, simply do a switch on MenuClickEventArgs' MenuID.
This is overkill if you only have a few menu items in a single language, but it's a definite time saver if you're doing multiple languages or have a bunch of menu items. Having strongly-typed menu IDs to work with alone makes this worthwhile.
Since MenuID is a value type and application-specific, I can't encapsulate the code and you'll have to live with the source. There is a way around this, but it involves more interaction with the IDE and I haven't had time to look into it yet.
using System;
using System.Collections;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows.Forms;
namespace sliver.Windows.Forms
{
internal delegate void MenuClickEventHandler(object sender, MenuClickEventArgs e);
internal class MenuClickEventArgs : EventArgs
{
public MenuID MenuID;
public MenuItem MenuItem;
public MenuClickEventArgs() {}
public MenuClickEventArgs(MenuItem menuItem, MenuID menuID)
{
MenuID = menuID;
MenuItem = menuItem;
}
}
[ProvideProperty("MenuCommandName", typeof(MenuItem))]
[ToolboxItem(true)]
internal class MenuHandler : System.ComponentModel.Component, IExtenderProvider
{
public event MenuClickEventHandler Click;
private Hashtable _items = new Hashtable();
private System.ComponentModel.Container components = null;
public MenuHandler(System.ComponentModel.IContainer container) : this()
{
container.Add(this);
}
public MenuHandler()
{
components = new System.ComponentModel.Container();
}
public MenuID GetMenuCommandName(MenuItem control)
{
object o = _items[control];
return (o != null) ? (MenuID)o : new MenuID();
}
public void SetMenuCommandName(MenuItem control, MenuID commandName)
{
control.Click -= new EventHandler(ClickHandler);
control.Click += new EventHandler(ClickHandler);
_items[control] = commandName;
}
bool IExtenderProvider.CanExtend(object extendee)
{
return extendee is MenuItem;
}
private void ClickHandler(object sender, EventArgs e)
{
MenuID menuID = (MenuID)_items[sender];
if (menuID != MenuID.None)
Click(this, new MenuClickEventArgs((MenuItem)sender, menuID));
}
}
}
Back to dotnet