Click here to Skip to main content
15,884,099 members
Articles / Desktop Programming / WPF
Tip/Trick

The Process and Adventure of Discovery

Rate me:
Please Sign up or sign in to vote.
4.89/5 (2 votes)
9 Nov 2012CPOL3 min read 10.4K   1   1
Stuck on a really weird problem in WPF? Move your behind and write some code (behind).

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:

XML
<!-- Scene -->
<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:

XML
<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:

C#
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:

C#
//--------------------------------------------------------------------------------
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

  1. 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.
  2. Don't use the same instance of a sub-menu in multiple menus in a given app. ALWAYS create a new instance.
  3. 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.

License

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


Written By
Software Developer (Senior) Paddedwall Software
United States United States
I've been paid as a programmer since 1982 with experience in Pascal, and C++ (both self-taught), and began writing Windows programs in 1991 using Visual C++ and MFC. In the 2nd half of 2007, I started writing C# Windows Forms and ASP.Net applications, and have since done WPF, Silverlight, WCF, web services, and Windows services.

My weakest point is that my moments of clarity are too brief to hold a meaningful conversation that requires more than 30 seconds to complete. Thankfully, grunts of agreement are all that is required to conduct most discussions without committing to any particular belief system.

Comments and Discussions

 
GeneralMy vote of 4 Pin
sariqkhan21-Nov-12 1:32
sariqkhan21-Nov-12 1:32 

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

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