Recommended

Skip Navigation LinksHome / Articles / View Article

Anatomy of the ContextMenu: how it is made, how to use it and how to abuse it

+ Add to SilverlightShow Favorites
8 comments   /   posted by Walter Ferrari on May 05, 2010
(5 votes)

Introduction

One of the things I found frustrating in the previous versions of Silverlight was the lack of the Right Mouse Click support. Now we know that this functionality has turned real with Silverlight 4. Furthermore, we know that the April 2010 Silverlight toolkit release comes with a ContextMenu Control to be used in combination with the right click. So it’s time to put these new things in practice and try to use them in our projects. In this article we will see how to create and use a ContextMenu, how it is made and then we will extend it in a peculiar way.
You can download the source code used in the article from here.

 

Features included in this release of the ContextMenu Control

As you may already know, the ContextMenu is essentially a menu which appears on a UI control after clicking the right mouse button. This menu typically contains items related to the context of the control and comes in handy in various situations. As written in the Delay’s Blog this release of ContextMenu is a subset of its WPF’s cousin. What is not supported is principally the multi level menu; however, the rest is more or less there. You have obviously mouse and keyboard support, custom positioning of the menu, icon support, enable/disable items and so on.

 

How to use the ContextMenu Control

First of all you have to download and install the April 2010 release of the Silverlight Toolkit available on Codeplex.  After that, add the necessary references (System.Windows.Controls.Toolkit and System.Windows.Controls.Input.Toolkit) and move to your xaml page where you simply have to add to the list of namespace mappings what follows:

 1: xmlns:toolkit=”http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit”

By the way, this is a small but appreciated new entry of the April toolkit release. Now you are ready to attach your first ContextMenu to one of the controls of your UI. If you are already familiar with the WPF version of the ContextMenu you should not have any difficulty in following the rest of the paragraph. Suppose you want to add it to a button: what you have to do is first use an attached property, the so called ContextMenuService. ContextMenu, inside the xaml of the button as described (let’s leave aside what is in the middle for now, we will find it out soon):

 1: <Button Content="Right click on me" Name="MyButton" Height="23" Width="75" />
 2:     <toolkit:ContextMenuService.ContextMenu>
 3:        ....
 4:     </toolkit:ContextMenuService.ContextMenu>
 5: </Button>

 

This attached property is defined as follows:

 1: public static readonly DependencyProperty ContextMenuProperty = DependencyProperty.RegisterAttached(
 2:             "ContextMenu",
 3:             typeof(ContextMenu),
 4:             typeof(ContextMenuService),
 5:             new PropertyMetadata(null, OnContextMenuChanged));

and it essentially serves to set the owner of the ContextMenu. In fact, if you look at the source code of the Silverlight toolkit where the context menu is dealt with “Owner” is an internal property of ContextMenu and when it is set, also an event handler to the mouse right click on the owner is set. The real popup menu is created within this event handler using the Popup primitive and associating a Canvas to it. Let’s now move on to adding other elements to our xaml example as follows:

 1: <Button x:Name="MyButton" Content="Right click on me!">
 2:      <toolkit:ContextMenuService.ContextMenu>
 3:        <toolkit:ContextMenu>
 4:             <toolkit:MenuItem   Header="menu item 1" />
 5:             <toolkit:MenuItem   Header="menu item 2"/>
 6:      </toolkit:ContextMenu>
 7:  </toolkit:ContextMenuService.ContextMenu>
 8: </Button>

Here we have just added the ContextMenu in the attached property and inside the ContextMenu we added two menu items. In the image below you may see the result:

ContextMenu1

the ContextMenu is nothing but a class derived from ItemsControl which is the control generally used to present a collection of items. Also MenuItem derives from ItemsControl, but here the intermediate class in the hierarchy, HeaderedItemsControl, exposes something interesting. HeaderedItemsControl, in fact, contains a heading (or title) and multiple items. What we used in the example above is the Header property of HeaderedItemsControl to label our menu items with a string, but if you look at the definition of the Header:

 1: // Summary:
 2: // Gets or sets the item that labels the control.
 3: //
 4: // Returns:
 5: // The item that labels the control. The default value is null.
 6: public object Header { get; set; }

 

You will see that it is defined as an object; what does It mean? It means that here you can virtually insert whatever you want, not just a string. We will come back on this later on. Let's move on and let’s add other features to our first ContextMenu:

 

 1: Button x:Name="MyButton" Content="Right click on me!">
 2:      <toolkit:ContextMenuService.ContextMenu>
 3:        <toolkit:ContextMenu>
 4:             <toolkit:MenuItem   Header="menu item 1" />
 5:             <toolkit:MenuItem   Header="menu item 2"/>
 6:             <toolkit:Separator/>
 7:             <toolkit:MenuItem Header="menu item 3" >
 8:                  <toolkit:MenuItem.Icon>
 9:                      <Image Source="Paste.png"/>
 10:                  </toolkit:MenuItem.Icon>
 11:             </toolkit:MenuItem>
 12:        </toolkit:ContextMenu>
 13:      </toolkit:ContextMenuService.ContextMenu>
 14: </Button

 

Below is how it appears with the additions:

ContextMenu2

Separator is simply a basic control stylized as a line and it is useful to separate different items. The Icon property of the MenuItem class is more interesting; it is strongly recommended to set this property if you want to accompany a menu item with an icon. But take a look at how this property is defined:

 1: /// <summary>
 2: /// Identifies the Icon dependency property.
 3: /// </summary>
 4: public static readonly DependencyProperty IconProperty = DependencyProperty.Register(
 5:     "Icon",
 6:     typeof(object),
 7:     typeof(MenuItem),
 8:     new PropertyMetadata(null));

 

As you can see it is a simple object just like the “Header” property. So what happens if we swap the content of “Header” and “Icon” of our ContextMenu as in the xaml code below?

 1: toolkit:ContextMenuService.ContextMenu>
 2:     <toolkit:ContextMenu>
 3:         <toolkit:MenuItem   Header="menu item 1" />
 4:         <toolkit:MenuItem   Header="menu item 2"/>
 5:         <toolkit:Separator/>
 6:         <toolkit:MenuItem >
 7:             <toolkit:MenuItem.Header>
 8:                 <Image Source="save.png"/>
 9:             </toolkit:MenuItem.Header>
 10:             <toolkit:MenuItem.Icon>
 11:                 "menu item 3"
 12:             </toolkit:MenuItem.Icon>
 13:         </toolkit:MenuItem>
 14:     </toolkit:ContextMenu>
 15: </toolkit:ContextMenuService.ContextMenu>

 

It happens what we expect to happen:

ContextMenu3

In other words, “Icon” and “Header” are two virtually interchangeable properties not considering visual side effects.

 

 

How it is made

The ContextMenu consists of a “Grid” type container which includes a Rectangle of 28 pixel in width on the left and an ItemsPresenter.

ContextMenu7

The Rectangle on the left serves as background for the area in which icons are inserted. The ItemsPresenter is used to display the menu items.

Let’s focus on a single menu item now:

ContextMenu8

As you can see in the image above, the MenuItem is made of a grid with 4 columns. The first column is reserved for the Icon object, the second for the vertical separator and the third column contains the Header object. The fourth and last column serves as spacer.

How to abuse it

Let’s try to extend the ContextMenu by adding something that looks like a multilevel menu. The idea is: since we found that we can include whatever we want into the “Header” section of the MenuItem, why not adding another ContextMenu there?
Let’s suppose to add a TextBlock in the header and hook a ContextMenu to it through ContextMenuService as in the following piece of code:

 

 1: <toolkit:ContextMenuService.ContextMenu>
 2:     <toolkit:ContextMenu>
 3:         <toolkit:MenuItem>
 4:             <toolkit:MenuItem.Header>
 5:                     <TextBlock  Text="menu item 1" >
 6:                         <toolkit:ContextMenuService.ContextMenu>
 7:                             <toolkit:ContextMenu Margin="8">
 8:                                 <toolkit:MenuItem Header="menu AA" >
 9:                                 </toolkit:MenuItem>
 10:                                 <toolkit:MenuItem Header="menu AB"/>
 11:                             </toolkit:ContextMenu>
 12:                          </toolkit:ContextMenuService.ContextMenu>
 13:                      </TextBlock>
 14:             </toolkit:MenuItem.Header>
 15:         </toolkit:MenuItem>
 16:         <toolkit:MenuItem   Header="menu item 2"/>
 17:         <toolkit:Separator/>
 18:         <toolkit:MenuItem Header="menu item 3" >
 19:             <toolkit:MenuItem.Icon>
 20:                 <Image Source="save.png"/>
 21:             </toolkit:MenuItem.Icon>
 22:         </toolkit:MenuItem>
 23:  
 24:     </toolkit:ContextMenu>
 25: </toolkit:ContextMenuService.ContextMenu>

The result would be:

ContextMenu4

Nice, isn't it? Actually, it is not quite as good as it appears because you have to do a right click twice: the first on the button and the second on the item of the ContextMenu and this is not what one would expect. So let’s try another way. Let’s take out the attached property ContextMenuService and look for another mechanism to hook the ContextMenu to a single item.
In the code below I added a StackPanel to the Header of the first MenuItem. I set the “Orientation” as “Horizontal” and I inserted a TextBlock to label the MenuItem and a ContextMenu detached from any Control.

 1: <toolkit:ContextMenuService.ContextMenu>
 2:     <toolkit:ContextMenu>
 3:         <toolkit:MenuItem>
 4:             <toolkit:MenuItem.Header>
 5:                 <StackPanel Orientation="Horizontal">
 6:                     <TextBlock Text="menu item 1" />
 7:                     <toolkit:ContextMenu Margin="2">
 8:                          <toolkit:MenuItem Header="menu AA" />
 9:                          <toolkit:MenuItem Header="menu AB"/>
 10:                     </toolkit:ContextMenu>
 11:                 </StackPanel>
 12:             </toolkit:MenuItem.Header>
 13:         </toolkit:MenuItem>
 14:         <toolkit:MenuItem   Header="menu item 2"/>
 15:         <toolkit:Separator/>
 16:         <toolkit:MenuItem Header="menu item 3" >
 17:             <toolkit:MenuItem.Icon>
 18:                 <Image Source="save.png"/>
 19:             </toolkit:MenuItem.Icon>
 20:         </toolkit:MenuItem>
 21:  
 22:     </toolkit:ContextMenu>
 23: </toolkit:ContextMenuService.ContextMenu>

 

The result is:

ContextMenu5

As you may see it does not look like a traditional multi-level menu because the main menu incorporates also the sub menu; however in some way it works. You can move the pointer on the single items of the sub menu to highlight them and the underlying item remains highlighted. The problem is that the sub menu does not disappear when you go on another MenuItem of the main menu. To add this functionality you can use the mechanism of the attached properties.

The attached property I used, which I called ContextMenuHelper.CheckSubMenu is defined in a ContextMenuHelper static class as follows:

 1: public class ContextMenuHelper
 2: {
 3:     public static readonly DependencyProperty CheckSubMenuProperty =
 4:     DependencyProperty.RegisterAttached("CheckSubMenu", typeof(bool), typeof(ContextMenuHelper),
 5:     new PropertyMetadata(CheckSubMenuPropertyChanged));
 6:  

 

 

 

 

Whenever this property is set by a MenuItem, the event handler CheckSubMenuPropertyChanged is fired. In this event handler whose code you can see below:

 1: private static void CheckSubMenuPropertyChanged (DependencyObject d, DependencyPropertyChangedEventArgs e)
 2: {
 3:     FrameworkElement menuItemWithSubMenu = d as FrameworkElement;
 4:  
 5:     if ((bool)e.NewValue)
 6:     {
 7:         menuItemWithSubMenu.GotFocus += new RoutedEventHandler(menuItemWithSubMenu_GotFocus);
 8:     }
 9:     else
 10:     {
 11:         menuItemWithSubMenu.GotFocus -= new RoutedEventHandler(menuItemWithSubMenu_GotFocus);
 12:     }
 13:  
 14: }

 

I added a RoutedEventHandler to the GotFocus event of each MenuItem in which the attached property has been set to “true”. The menuItemWithSubMenu’s RoutedEventHandler below has the logics which permit to visualize or hide the sub menu.

 1: static void menuItemWithSubMenu_GotFocus(object sender, RoutedEventArgs e)
 2: {
 3:     FrameworkElement menuItemWithSubMenu = (FrameworkElement)sender;
 4:  
 5:     if( null == menuItemWithSubMenu)
 6:         return;
 7:  
 8:     // search for subContextMenus inside the menuItem 
 9:     List<ContextMenu> contextMenusInMenuItemWithSubMenu = menuItemWithSubMenu.GetChildrenByType<ContextMenu>(c => c is ContextMenu);
 10:  
 11:     if (contextMenusInMenuItemWithSubMenu.Count > 0)
 12:     {
 13:         contextMenusInMenuItemWithSubMenu[0].Visibility = Visibility.Visible;
 14:     }
 15:     else
 16:     {
 17:         ContextMenu MainContextMenu1 = menuItemWithSubMenu.Parent as ContextMenu;
 18:         // search for other subContextMenus inside other itemMenus
 19:         List<ContextMenu> contextMenusInOtherMenuItems = MainContextMenu1.GetChildrenByType<ContextMenu>(c => c is ContextMenu);
 20:  
 21:         foreach (ContextMenu subCM in contextMenusInOtherMenuItems)
 22:         {
 23:             subCM.Visibility = Visibility.Collapsed;
 24:         }
 25:     }
 26: }

When a MenuItem receives the focus, a search is performed among the child controls to locate a sub-ContextMenu. If a sub-contextMenu is found then it is made visible. If there are no sub-contextMenu another search is performed, this time starting from the main ContextMenu and each sub-contextMenu found is made invisible.

What remains to do in order to make it work is adding this attached property to each MenuItem of the ContextMenu and set it to true as in the xaml code below:

 1: <Button Content="Mouse Right click here" Height="23" HorizontalAlignment="Left" Margin="167,109,0,0" Name="button1" VerticalAlignment="Top" Width="164">
 2:     <toolkit:ContextMenuService.ContextMenu>
 3:         <toolkit:ContextMenu >
 4:             <toolkit:MenuItem local:ContextMenuHelper.CheckSubMenu="True">
 5:                 <toolkit:MenuItem.Header>
 6:                     <StackPanel  Orientation="Horizontal" Background="Transparent">
 7:                         <TextBlock>menu item 1</TextBlock>
 8:                         <toolkit:ContextMenu Margin="2" Visibility="Collapsed" >
 9:                             <toolkit:MenuItem Header="submenu item 1" local:ContextMenuHelper.CheckSubMenu="True" Click="MenuItem_Click" >
 10:                             </toolkit:MenuItem>
 11:                             <toolkit:MenuItem Click="MenuItem_Click_1" Header="submenu item 2"/>
 12:                         </toolkit:ContextMenu>
 13:                     </StackPanel>
 14:                 </toolkit:MenuItem.Header>
 15:             </toolkit:MenuItem>
 16:             <toolkit:MenuItem   Header="menu item 2" local:ContextMenuHelper.CheckSubMenu="True" />
 17:             <toolkit:MenuItem local:ContextMenuHelper.CheckSubMenu="True">
 18:                 <toolkit:MenuItem.Header>
 19:                     <StackPanel  Orientation="Horizontal" Background="Transparent">
 20:                         <TextBlock>menu item 3</TextBlock>
 21:                         <toolkit:ContextMenu Margin="2" Visibility="Collapsed" >
 22:                             <toolkit:MenuItem Click="MenuItem_Click" Header="submenu item 1"  />
 23:                             <toolkit:MenuItem Click="MenuItem_Click_1" Header="submenu item 2"/>
 24:                         </toolkit:ContextMenu>
 25:                     </StackPanel>
 26:                 </toolkit:MenuItem.Header>
 27:             </toolkit:MenuItem>
 28:             <toolkit:MenuItem   Header="menu item 4" local:ContextMenuHelper.CheckSubMenu="True"/>
 29:         </toolkit:ContextMenu>
 30:     </toolkit:ContextMenuService.ContextMenu>
 31: </Button>

 

With this mechanism you can nest other sub-sub-ContextMenu as in the following image:

ContextMenu6

Finding the way to prevent the expansion of the parent ContextMenu is left to the reader.

An application to a real case

I have recently ported an application which I already described in two of my previous articles here and here to Silverlight 4 and I took the chance to introduce a ContextMenu. In the image below you can see an example of a ContextMenu stylized.

 

ContextMenu9

Summary

In this article we have explored in depth the new ContextMenu Control introduced with the April 2010 relase of the Silverlight toolkit. We have learned how to create it and use it and then we have enjoyed trying to extend it in an unusual way.

Share


Comments

Comments RSS RSS
  • RE: Anatomy of the ContextMenu: how it is made, how to use it and how to abuse it  

    posted by Marcelo on May 05, 2010 15:25
    Thats awesome, You should a sequence of post intitled "The Anatomy Of <Control here>". The hardest part of Silverlight and WPF are in understanding the structure of controls!

    Tks...
  • RE: Anatomy of the ContextMenu: how it is made, how to use it and how to abuse it  

    posted by walterf on May 05, 2010 18:55
    Thanks, this might be an idea.. :-) 
  • RE: Anatomy of the ContextMenu: how it is made, how to use it and how to abuse it  

    posted by simon on May 06, 2010 08:16
    i don't want to abuse the control - I just want to use it on a DataGrid and I'm having a really bad time getting this to work!! any tips?
  • RE: Anatomy of the ContextMenu: how it is made, how to use it and how to abuse it  

    posted by walterf on May 06, 2010 10:01

    Hi Simon,
    some more details?  I just tried in a trivial example and it seems to work. 

     

  • RE: Anatomy of the ContextMenu: how it is made, how to use it and how to abuse it  

    posted by Ziad JKhan on May 13, 2010 11:53

    If you want support for submenus uploaded a class that adds support for it on Codeplex:

    http://sl4popupmenu.codeplex.com/

    The project is still in beta version and unfortunately it is not based on the new ContextMenu Control introduced with the latest release of the Silverlight toolkit but it is a quite a decent altenative if you badly need support for submenus.

  • RE: Anatomy of the ContextMenu: how it is made, how to use it and how to abuse it  

    posted by Jon on May 15, 2010 02:15

    I'm wondering why this works differently than it does in WPF. Specifically, I'm wondering why you can't seem to just do the following:

        <Grid>
            <Grid.ContextMenu>
                <ContextMenu>
                    ...
                </ContextMenu>
            </Grid.ContextMenu>
        </Grid>

     i.e. the ContextMenu property is missing.

  • RE: Anatomy of the ContextMenu: how it is made, how to use it and how to abuse it  

    posted by walterf on May 15, 2010 09:25

    Hi Jon,

    no it is not possible to use exactly the same approach of WPF. As you can read in the Delay's Blog :  " Silverlight 4 doesn't have the FrameworkElement.ContextMenu property that's often used to attach a ContextMenu to an element. Instead, it is always necessary to use the (functionally equivalent and more flexible) ContextMenuService.ContextMenu attached property like I show above".

     

  • RE: Anatomy of the ContextMenu: how it is made, how to use it and how to abuse it  

    posted by J on May 28, 2010 18:04

    Hi Walter, thanks for the article.

    It helped me to understand how ContextMenu works, but it didn't help me to solve my problem.

    I need to bind ContextMenu items dynamically, this is my XAML:

     

     

     

    <toolkit:ContextMenu ItemsSource="{Binding ...}">

    <toolkit:ContextMenu.ItemTemplate>

     

     

     

     

    <DataTemplate>

     

     

     

     

    <toolkit:MenuItem Header="{Binding ...}">

     

     

     

     

    <toolkit:MenuItem.Icon>

     

     

     

     

    <Image Source="{Binding ...}"/>

     

     

     

     

    </toolkit:MenuItem.Icon>

     

     

     

     

    </toolkit:MenuItem>

     

     

     

     

    </DataTemplate>

     

     

     

     

    </toolkit:ContextMenu.ItemTemplate>

     

     

     

     

    </toolkit:ContextMenu>

     

    The databinding works as expected, but the icons are displayed on the right side of the separator with the rest of the menuitem... Any ideas?

     

     

     

Add Comment

 
 

   
  
  
   
Please add 5 and 2 and type the answer here:

Help us make SilverlightShow even better and win a free t-shirt. Whether you'd like to suggest a change in the structure, content organization, section layout or any other aspect of SilverlightShow appearance - we'd love to hear from you! Need a material (article, tutorial, or other) on a specific topic? Let us know and SilverlightShow content authors will work to have that prepared for you. (hide this)