Introduction
While working on a WPF application, I had cause to require the implementation of context menus for tree items. Towards that end, I did the following:
<ContextMenu x:Key="ContextMenuScene">
<MenuItem Header="Add Condition..." x:Name="MenuAddSceneCondition" />
<MenuItem Header="Add Data Source" x:Name="MenuAddSceneDataSource" >
<MenuItem Header="Embedded Image..." x:Name="MenuAddDSEmbeddedImage" />
<MenuItem Header="Embedded Text..." x:Name="MenuAddDSEmbeddedText" />
<MenuItem Header="Current Date/Time..." x:Name="MenuAddDSCurrentDateTime" />
<MenuItem Header="Existing Item..." x:Name="MenuAddDSExistingItem" />
</MenuItem>
</ContextMenu>
<ContextMenu x:Key="ContextMenuSceneDataSources">
<MenuItem Header="Add" x:Name="MenuAddSceneDataSource2" >
<MenuItem Header="Embedded Image..." x:Name="MenuAddDSEmbeddedImage2" />
<MenuItem Header="Embedded Text..." x:Name="MenuAddDSEmbeddedText2" />
<MenuItem Header="Current Date/Time..." x:Name="MenuAddDSCurrentDateTime2" />
<MenuItem Header="Existing Item..." x:Name="MenuAddDSExistingItem2" />
</MenuItem>
</ContextMenu>
As you can see, I needed a couple of the context menus to have the same contents. What you see above worked fine. Then I decided that the above was too inefficient - I had three copies of the same menu, and since each copy existed in the XAML, each menu item had to have a unique Name
(as is evident by the code shown above). Being the natural-born C++ programmer that I am, I tried to optimize the code as follows to eliminate redundancy and clutter:
<coll:ArrayList x:Key="DataSourcesMenu" >
<MenuItem Header="Embedded Image..." x:Name="MenuAddDSEmbeddedImage" />
<MenuItem Header="Embedded Text..." x:Name="MenuAddDSEmbeddedText" />
<MenuItem Header="Current Date/Time..." x:Name="MenuAddDSCurrentDateTime" />
<MenuItem Header="Existing Item..." x:Name="MenuAddDSExistingItem" />
</coll:ArrayList>
<ContextMenu x:Key="ContextMenuScene" >
<MenuItem Header="Add Condition..." x:Name="MenuAddSceneCondition" />
<MenuItem Header="Add Data Source..." x:Name="MenuAddSceneDataSource"
ItemsSource="{StaticResource DataSourcesMenu}" />
</ContextMenu>
<ContextMenu x:Key="ContextMenuSceneDataSources">
<MenuItem Header="Add" x:Name="MenuAddSceneDataSource2"
ItemsSource="{StaticResource DataSourcesMenu}" />
</ContextMenu>
Much cleaner, right? Well, like that old joke goes, "...and that's when the fight started."
I noticed that the menu was fine until I actually utilized it (clicked on an item). At that point, two of the instances of the menu would result in an empty menu that was otherwise sized according to how many items *should* have been contained therein. Me and one of the other team members looked at this for about an hour, and couldn't figure out what was wrong. Since the menus were in XAML, it's impossible to find out what's really going on (and this is one of the things I absolutely hate about WPF). In a fit of desperation, I decided to build the context menus in code instead of in XAML, and the code looked like this:
List<menuitem> menu = new List<menuitem>();
menu.Add(CreateMenuItem("Embedded Image...", "MenuAddDSEmbeddedImage"));
menu.Add(CreateMenuItem("Embedded Text...", "MenuAddDSEmbeddedText"));
menu.Add(CreateMenuItem("Current Date/Time...", "MenuAddDSCurrentDateTime"));
menu.Add(CreateMenuItem("Existing Item...", "MenuAddDSExistingItem"));
I created a single instance of the sub menu (like I had in the XAML), and then added that instance to the three context menus that needed it. The very first run illustrated the problem.
Everything was fine until the code tried to add the same instance of the sub-menu to a different context menu. At that point, it threw an exception about having to detach the object from its current parent before being able to use it in the new context menu. The light went on, and it was smooth sailing from there. Yeah, there's probably something somewhere on Google about not doing what I did, but it's getting real hard to find usable info on the quagmire that Google has become. In any case, I ended up with something like this:
private List<menuitem> CreateAddDataSourceMenu()
{
List<menuitem> menu = new List<menuitem>();
menu.Add(CreateMenuItem("Embedded Image...", "MenuAddDSEmbeddedImage"));
menu.Add(CreateMenuItem("Embedded Text...", "MenuAddDSEmbeddedText"));
menu.Add(CreateMenuItem("Current Date/Time...", "MenuAddDSCurrentDateTime"));
menu.Add(CreateMenuItem("Existing Item...", "MenuAddDSExistingItem"));
return menu;
}
Since this technique ended up eliminating some code at the other end of the menus, and since I will readily eliminate the use of XAML where it makes sense, I decided to leave the C# code in and completely remove the XAML-based menus.
In the interest of full disclosure, I must mention that I considered using x:Shared
on the collection, but it seemed to me like Microsoft was saying "It's there if you need it, but we don't really recommend its use", and I get the distinct impression that it exists solely to address a desire to keep everything in the XAML. In other words, it's a hack (it doesn't even come up in intellisense - you simply have to know it's there). No, thank you.
The Tips
- XAML ain't all that. It obfuscates, disguises or worse - hides exceptions, and most of the time will let you blow your foot off without so much as a whisper.
- Don't use the same instance of a sub-menu in multiple menus in a given app. ALWAYS create a new instance.
- When all else fails, do it in "code behind" (I hate that phrase, by the way). At least you can catch and examine exceptions that way.