Omer van Kloeten's .NET Zen

Programming is life, the rest is mere details

News

Omer van Kloeten's Facebook profile

Omer has been professionally developing applications over the past 8 years, both at the IDF’s IT corps and later at the Sela Technology Center, but has had the programming bug ever since he can remember himself.
As a senior developer at NuConomy, a leading web analytics and advertising startup, he leads a wide range of technologies for its flagship products.

Get Firefox


powered by Dapper 

.NET Resources

Articles :: CodeDom

Articles :: nGineer

Culture

Projects

Three Ways To Create Dynamic Menus

There are quite a few sources about how to create dynamic menus (menus that are not embedded in XAML or code, but rather loaded from an external file):

  1. Loose XAML - Using the XamlReader, one can load an external, non-pre-compiled fragment of XAML, much like the one presented here:
    <MenuItem
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    Header="_Simple">
    <MenuItem Header="1" />
    <MenuItem Header="2" />
    <MenuItem Header="3" />
    </
    MenuItem>
    The above fragment will then be loaded into the menu like so:
    using (FileStream looseXamlFile = File.OpenRead("Loose.xaml"))
    {
    this.mainMenu.Items.Insert(0,
    (MenuItem)XamlReader.Load(looseXamlFile));
    }
    And on the window, it will look like this:

    simple

    This is a very useful thing and you can read more about this method on Tamir's weblog.
    However, this way, you are bound to the elements of the WPF schema and all errors that would have occurred in the past during compile-time, will now occur during run-time. These presents a hardship for the developer. The bigger problem is what happens when receiving the items for the menu from a different source, such as a list you wish to bind to, in which case, you simply can't use Loose XAML.
  2. Data Templates - This way is the most intuitive way to bind a list of items or, better yet, an unary tree of items to a GUI representation of them. For instance, we have a list of Item objects (a simple class with one property - Value) and wish to bind them to menu items.
    <MenuItem x:Name="dataTemplateMenu" Header="_Data Template" >
    <MenuItem.Resources>
    <DataTemplate DataType="{x:Type local:Item}">
    <MenuItem Header="{Binding Path=Value}" />
    </DataTemplate>
    </MenuItem.Resources>
    </
    MenuItem>
    The XAML described here takes each Item and represents it using a MenuItem. To load a list into the menu, we simply call:
    dataTemplateMenu.ItemsSource = itemArray;
    After this call, all of the items will have MenuItems created for them and will look like this:

    data.templates 

    Very nice, but something is wrong here. Yes, the items are extremely large. Hovering over one reveals that we have something inside that menu:

    data.templates.hover

    Hum. That looks familiar. It's the same size as a normal MenuItem... Oh, wait, that's because it really is one. This is because when you create a Data Template, the items that enter the tree are actually your user-defined objects (in this case, the Item class). Before applying the template, the menu item asks the object about to be inserted into it whether it can be visually displayed. Because Item has no visual representation, the process creates a MenuItem to hold it, thus creating two menu items (the one automatically created and the one created using the Data Template).
  3. Item Container Style - So after two ways that didn't work well for us, the one that will is the MenuItem's ItemContainerStyle. This way, all you do is simply define a style for all of the menu items that will be placed under a certain menu item and use binding to bind those items to the values of your objects' properties:
    <MenuItem x:Name="itemContainerStyleMenu" Header="_Item Container Style">
    <MenuItem.ItemContainerStyle>
    <Style TargetType="{x:Type MenuItem}">
    <Setter Property="Header" Value="{Binding Path=Value}" />
    </Style>
    </MenuItem.ItemContainerStyle>
    </
    MenuItem>

    Then you set the source:
    itemContainerStyleMenu.ItemsSource = itemArray;
    and presto:

    item.container.style

    We now have what we needed.

    Extra: Recursive hierarchy could also be achieved using:
    1. Placing the style in the resources of a window, the application, etc. with a key.
    2. A setter of ItemsSource that binds to the collection of child items.
    3. A setter of ItemContainerStyle that will point to the style's key.

The source for this article is available here.

Pet peeve: Why would you change the "&Name" mnemonic notation from Win32 to something that is not better ("_Name" in WPF), only different? I'd have gone with a MenuItem.Mnemonic property that receives a character and doesn't pass validation if the Text property doesn't contain that character. If you're going to change, you might as well change for a reason.

Posted: Nov 14 2007, 08:47 AM by Omer van Kloeten | with 13 comment(s)
Filed under:

Comments

Christopher Steen said:

WPF Three Ways To Create Dynamic Menus [Via: Omer van Kloeten ] More updates to Woodstock [Via: Josh...

# November 15, 2007 8:17 AM

Christopher Steen said:

Link Listing - November 14, 2007

# November 15, 2007 8:17 AM

WPF Resources « G l i t t e r M o n k e y said:

Pingback from  WPF Resources &laquo; G l i t t e r M o n k e y

# November 20, 2007 9:25 PM

Andrew said:

I thought the change from &Name to _Name mnemonic notation was obvious. Have you ever tried to write lots of ampersands in attributes of Xml? It's a pain in the proverbial.

# November 23, 2007 12:04 AM

Omer van Kloeten said:

And here I thought you weren't _supposed_ to write XAML by hand >:)

# November 23, 2007 12:34 AM

Omer van Kloeten's .NET Zen said:

[ Caveat : This is constructive criticism about XAML and the current version of WPF. They both have their

# December 22, 2007 4:52 PM

Russell Wallace said:

I'm glad to see the & to _ switch, for the simple reason that & is sometimes used as a literal character in captions, whereas _ is not.

# January 28, 2008 8:47 AM

Michael said:

Replacing & by _ is quite understadable : if they had kept the & mnenonic, your xaml would have looked like :

Header="&amp;Item Container Style"

which is not nice

:)

# January 31, 2008 5:02 AM

Noam said:

Thank you ver much for writing this article, it solved a problem I was having with context menus.

# May 10, 2008 12:46 AM

Prudhvi said:

Is there a way that I can directly add the menu items to the collection of menu items of the menu. Like MainMenu.Items.Add(menuItem);

# May 26, 2008 2:44 AM

Carole - MSFT said:

I know this is kind of an old post and you might've addressed this elsewhere, but someone just pointed me to this post.  You can use a DataTemplate such as the following:

<MenuItem x:Name="dataTemplateMenu" Header="_Data Template" >

   <MenuItem.Resources>

       <DataTemplate DataType="{x:Type local:Item}">

           <TextBlock Text="{Binding Path=Value}" />

       </DataTemplate>

   </MenuItem.Resources>

</MenuItem>

A MenuItem will be created and wrapped around the TextBlock.

# May 28, 2008 8:32 PM

Omer van Kloeten said:

Carole - Yes, but this way it's not an actual menu item, so you can't use its features like the check box.

# May 29, 2008 1:10 AM

James Miles said:

What happens if you want to dynamically bind an image as well? This doesn't work;

<Style TargetType="{x:Type MenuItem}">

   <Setter Property="Header" Value="{Binding Path=DisplayName}"/>

   <Setter Property="Icon">

       <Setter.Value>

           <Image Height="16" Width="16"  Source="{Binding Path=IconURI}"/>

       </Setter.Value>

   </Setter>

</Style>

I've found one post that suggested re-templating the control.

social.msdn.microsoft.com/.../800ccf62-06ef-498a-9076-f79ee357abef

Seems like overkill, I just did something like this instead;

foreach (IEntitySearchView view in Data.SearchViews)

   myControl.ContextMenu.Items.Add(new MenuItem()

   {

       Header = view.DisplayName,

       Command = SelectViewCommand,

       CommandParameter = view,

       Icon = new Image() { Source = new BitmapImage(new Uri(view.IconURI)), Height = 16, Width = 16 }

   });

# October 22, 2008 5:17 AM
Leave a Comment

(required) 

(required) 

(optional)

(required)