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.
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
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.
ToolStripMenuItem menuItem = toolStripMenuItem.Clone();
menuItem.Text = “Cloned: “ + menuItem.Text;
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:
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:
- Create a new menu item
- Copy over certain properties
- Clone the Event Handlers
- Duplicate the
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
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
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
where !notBrowseable && p.CanRead && p.CanWrite && p.Name != "DropDown"
The thing to notice is that
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
Next, we do the usual Reflection copy:
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:
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:
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
ToolStripSeparator. Duplicating this list is easily done via recursion.
foreach (var item in sourceToolStripMenuItem.DropDownItems)
if (item is ToolStripMenuItem)
newItem = ((ToolStripMenuItem)item).Clone();
else if (item is ToolStripSeparator)
newItem = new ToolStripSeparator();
throw new NotImplementedException
("Menu item is not a ToolStripMenuItem or a ToolStripSeparatorr");
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.
- First release, Halloween 2009