Click here to Skip to main content
Click here to Skip to main content

A Pretty Good Menu Cloner

, 3 Nov 2009
Rate this:
Please Sign up or sign in to vote.
Allows one to easily clone a menu item and demostrates how to clone a component's callback handlers

Introduction

I had a Tool Menu and a context menu with some of the same menu items. I wanted to make sure the duplicate items were in sync. If one takes an existing menu item and adds it to another menu, the item is moved not duplicated. Thus I needed a way of cloning menu items.

Writing a clone method should have been easy. Many articles describe how to use Reflection to copy properties and the web gives what properties to copy. But the major stumbling block was copying the handlers. In order to add or delete a handler, one needs to know the actual handler. One of .NET’s major frustrations is the inability to easily get at the list of Event Handlers for a component.

A web search showed little so I wrote my own. I call it a Pretty Good Menu Cloner because, although not perfect, the restrictions are modest and rarely come up in actual practice. It also demonstrates how to use Reflection to obtain the list of an objects' events.

Background

This code works by using Reflection to read the private members of a .NET object. I am assuming the reader understands this as well as LINQ. Thus, I will not be explaining the details of these technologies.

Using the Sample Program

The sample program, called MenuItemCopyHandlersExample, is basically a test bed that shows how to call the clone method to duplicate a menu item or create a context menu. All the reusable code is in the EventHandlerSupport project.

The sample program has a menu item called ‘Do Something…’ under the file menu. This item is three levels deep with assorted event callbacks, images and tool tips. The ‘Clone to Menu’ button will clone this item to the Cloned Menu item and the ‘Clone to Context Menu’ will clone it to a context menu. The program demonstrates that all the levels are copied and all the callbacks registered.

Using the Code

The EventHandlerSupport.dll contains a set of extensions to make the cloning easier. The major function is the Clone extension to ToolStipMenuItem. It returns a cloned menu item, including handlers and submenus. The cloned item has no owner and a slightly different, most likely unique, name from the original.

The decision to make a clone with a different name may seem strange but it is a result of two things. First to clone a menu item, one must duplicate any submenus. Second, although different objects with the same name can be inserted into different components, it is not good Windows Form design practice.

 //
 // Clone an item and adds it to another menu
 //
 ToolStripMenuItem menuItem = toolStripMenuItem.Clone();
 menuItem.Text = “Cloned: “ + menuItem.Text;
 anotherToolStripMenuItem.DropDownItems.Add(menuItem);

A second extension to a component is AddHandlers, this adds all the handlers from one component to another. Its usage, albeit not its implementation, is quite simple:

 //
 // Add the handlers from the sourceToolStripMenuItem to menuItem
 //
 menuItem.AddHandlers(sourceToolStripMenuItem);

This routine will work with two classes derived from IComponent, not just a menu item.

How to Clone a Menu Item

There are four parts to cloning a menu item:

  1. Create a new menu item
  2. Copy over certain properties
  3. Clone the Event Handlers
  4. Duplicate the DropDownItems

Copying Properties

As to what properties to copy, I decided the Read/Write properties that are exposed in the Visual Studio Properties Window would be sufficient. These are the properties whose attribute is not [Browsable(false)].

The one problem is that Microsoft decided to add some properties to make ToolStripMenuITem backward compatible with the older MenuItem. The only one of these that appears in the property window is DropDown.

Combining all these requirements, I came up with a pretty concise (albeit opaque) bit of LINQ.

var propInfoList = from p in typeof(ToolStripMenuItem).GetProperties()
                   let attributes = p.GetCustomAttributes(true)
                   let notBrowseable = (from a in attributes
                                        where a.GetType() == typeof(BrowsableAttribute)
                                        select !(a as BrowsableAttribute).Browsable
                                       ).FirstOrDefault()
                   where !notBrowseable && p.CanRead && p.CanWrite && p.Name != "DropDown"
                   select p;

The thing to notice is that FirstOrDefault returns false for a Boolean but a lack of attribute means to display it in the properties windows. So if the Browsable attribute is false, we want to negate it to make it true. Then we just choose all the ones that are false, read/writable and name is not the special DropDown property.

Next, we do the usual Reflection copy:

// Copy over using reflections
foreach (var propertyInfo in propInfoList)
{
    object propertyInfoValue = propertyInfo.GetValue(sourceToolStripMenuItem, null);
    propertyInfo.SetValue(menuItem, propertyInfoValue, null);
}

The property clone has a slight problem. A menu item has a Visible property that is usually set to true; however, this property is always turned off when the item is not shown. Thus the clone cannot know what the original value was. It must set Visible back on. But if the Visible property was set to false originally, the clone improperly turns it on. Fixing this will require future investigation.

Cloning the Event Handlers

The menu clone would be easy if one could write menuItem.Click = sourceMenuItem.Click but events cannot occur on the left side of an assignment. In .NET, a component has a list of possible events stored in an EventHandlerList class. Furthermore, this class has a procedure AddHandlers that will add all the events in another EventHandlerList. Thus to clone, we need only write:

 //
 // Add one set of Event Handlers to another
 //
 EventHandlerList sourceEventHandlerList;
 EventHandlerList destinationEventHandlerList
 destinationEventHandlerList.AddHandlers(sourceEventHanderList)

Alas, the problem is that the event list is stored in a private property of the component called Events. Thus we must use Reflection to get the event list. This can be done by:

 /// <summary>
 /// Gets the event handler list from a component
 /// </summary>
 /// <param name="component">The source component.</param>
 /// <returns>The EventHanderList or null if none</returns>
 public static EventHandlerList GetEventHandlerList(this IComponent component)
 {
    var eventsInfo = component.GetType().GetProperty
	("Events", BindingFlags.Instance | BindingFlags.NonPublic);
    return (EventHandlerList)eventsInfo.GetValue(component, null);
 }

Here the non-static private property “Events” is found and its value returned. This is somewhat dangerous in the sense that we am inspecting private members and using its value. In another .NET release, this may not work because there is no guarantee that the private implementation will remain the same. However, in a mature framework as Windows Forms, such a change is unlikely.

Duplicating the DropDown Items

The very last thing is to clone the sources drop down items. We cannot just copy them because they are menu items and we cannot copy menu items directly. Moreover, the list can contain both ToolStripMenuItems and ToolStripSeparator. Duplicating this list is easily done via recursion.

//
// Recursively clone the drop down list
//
foreach (var item in sourceToolStripMenuItem.DropDownItems)
{
     ToolStripItem newItem;

     if (item is ToolStripMenuItem)
     {
          newItem = ((ToolStripMenuItem)item).Clone();
     }
     else if (item is ToolStripSeparator)
     {
          newItem = new ToolStripSeparator();
     }
     else
     {
          throw new NotImplementedException
		("Menu item is not a ToolStripMenuItem or a ToolStripSeparatorr");
     }

     menuItem.DropDownItems.Add(newItem);
}

Now we can assemble these parts to create a pretty good clone extension. See the actual code for how this is done.

Points of Interest

I've wondered why Microsoft does not have a clone function and the answer is pretty simple. One does not want an actual clone, nor is it possible to always deeply copy without other knowledge.

In my next article, I will show how to use Reflection to walk the EventHandlerList and return it in a useable form. This can then be used to delete the events or only copy specific ones.

History

  • First release, Halloween 2009

License

This article, along with any associated source code and files, is licensed under The Common Development and Distribution License (CDDL)

About the Author

jmertus
Other Brown University
United States United States
I received a BS in Mathematics from Carnegie Mellon University with the distinction of one of the few people ever to fail English at that University. I studied Mathematics Logic under Abraham Robinson in graduate school in some university located in downtown New Haven whose name I can never remember. But it had a lot of neat old building and a cool library underground. Couldn't have been very wealthy because when they built another library, they had to use thin sheets of rock instead of windows. Alas he died before I finished.
 
To pay for all of this, I work in the summers hauling frozen mushrooms around Lake Erie; I learned lots of truck repair because these trucks always broke down. I later became an NASE certified automobile mechanic, then mistakenly enrolled in a Celestial Mechanics course at Brown University thinking that it would help me repair Saturn automobiles. Before I discovered my error, I had accidentally written a thesis in Applied Mathematics and received a PhD
 
Now I write code for a living and get paid more than honest work.

Comments and Discussions

 
GeneralI use an easier method for dynamic menus PinmemberSom Shekhar3-Nov-09 5:05 
GeneralRe: I use an easier method for dynamic menus PinmemberWolpTec31-Mar-12 22:16 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140721.1 | Last Updated 3 Nov 2009
Article Copyright 2009 by jmertus
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid