Random geekery since 2005.

Husband, father, programmer, speaker, writer, blogger, podcaster, collector, traveler, golfer.

31 Days of Windows 8 | Day #16: Context Menus

Written in

by

This article is Day #16 in a series called 31 Days of Windows 8.  Each of the articles in this series will be published for both HTML5/JS and XAML/C#. You can find additional resources, downloads, and source code on our website.

advertisementsample

Today, our focus is on context menus.  These are those small little popup commands that appear from time to time in your application when you right click on something. Microsoft offers some very specific guidance on when to use these context menus vs. when to use the AppBar control instead, so we will be following those rules in this article. 

What Is a Context Menu?

If you’ve used Windows 8 at all, you’ve likely encountered these before.  Often they result from a right-click on something you couldn’t select, or on text you wanted to interact with.  Here’s a quick sample of a context menu:

An image of the context menu for text shown over editable text while there is no text in the clipboard to paste.

(image from http://msdn.microsoft.com/library/windows/apps/Hh465308)

You could also launch a context menu from an element on your page that is unselectable, like this image in my sample app:

16-XAML-ContextMenuExample

Right-clicking the image launches the context menu to the right.  (I will show you how to make this happen next.)  Each of the command items will have an action assigned to it, which is executed when the item is clicked.

Determining The Location of an Element

You may have noticed that context menus appear directly adjacent to the element that has been selected.  This doesn’t happen by magic.  We’ll actually have to determine the position of the clicked element by ourselves (as well as its size), and pass that to our Popup menu control when we create it in the form of a Rect object.  Here’s the method I’ve created to determine this location:

private Rect GetRect(object sender)
{
    FrameworkElement element = sender as FrameworkElement;
    GeneralTransform elementTransform = element.TransformToVisual(null);
    Point point = elementTransform.TransformPoint(new Point());
    return new Rect(point, new Size(element.ActualWidth, element.ActualHeight));
}
 

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

As you can see, I will be able to pass my “sender” object into this method, and return a Rect object with a point (the location of the top left corner of our element) and size (the dimensions of our calling object.)

Creating a Context Menu

Once I have this GetRect() method in place, creating a context menu around any of my controls is simple.  In this example, I have added a RightTapped event to my image, which calls this method:

private async void Element_RightTapped(object sender, RightTappedRoutedEventArgs e)
{
    PopupMenu p = new PopupMenu();
    p.Commands.Add(new UICommand("31 Days of Windows 8", (command) =>
        { ((Grid)Logo.Parent).Background = new SolidColorBrush(Colors.Orange); }));
    await p.ShowForSelectionAsync(GetRect(sender), Placement.Right);
}
 

As you can see above, this simply creates a new PopupMenu control, adds a command to it (with the executing code specified in the lambda expression), and calls the ShowForSelectionAsync() method to show it.  This method takes two parameters:

  • The first is the Rect value that we are retrieving from our GetRect() method.  Pass the sender in, get the appropriate Rect in return.
  • The second is the placement of the context menu.  In my example, I set Placement.Right as my value.  You have four options, but don’t go crazy with this.  Microsoft’s guidance on context menus is that “they should appear above the object they are acting upon, unless it obscures other UI elements, in which case, moving it to the side or below the object is acceptable.”  Keep the experience in Windows 8 consistent for your users unless you have a good reason to do otherwise.

Running this code, as shown above, will result in a menu that looks like this:

16-XAML-ContextMenuRight

And when I execute that option, the background of the page turns orange.  Like this:

16-XAML-ContextMenuRightLight

So, that’s how we pop a context menu.  But how many commands can we have at once?  The answer is SIX.  Adding more than 6 elements to your PopupMenu control will result in an error when you try to create it.  I showed it earlier, but here’s an example of a 6 element menu:

16-XAML-ContextMenuExample

Finally, you also have the option to create a UICommandSeparator(), which is simply a horizontal line instead of a command.  Separators still take up one of your 6 available slots, however, so keep this in mind when using it.  It looks like this:

16-XAML-ContentSeparator

To do this, your command creation statement would look like this:

p.Commands.Add(new UICommand("31 Days of Windows 8", (command) => { ((Grid)Logo.Parent).Background = new SolidColorBrush(Colors.Orange); }));
p.Commands.Add(new UICommandSeparator());
 

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

 

That’s about it!  You should now be able to add contextual menus to your applications when AppBars and other UI don’t make sense.  But what about interacting with just some of the text in a TextBox?

Launching a Context Menu From a TextBox

Actually, nearly everything about this process is the same, except for the math on where to pop the box from.  Initially when setting out to solve this problem, I was hoping to add on to the existing context menu that already appears when you right-click on text:

16-XAML-TextMenuDefault

As it turns out, you can’t.  So, for our example, let’s say that we want to keep all of those options, but also add a new one titled “Delete” at the bottom.  To do this, we first need to override the default context menu by creating an event handler to cancel the default one, and then recreate all of the functionality of the old one.

To do this, we create a new event handler like this:

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    Lipsum.ContextMenuOpening += Lipsum_ContextMenuOpening;
}
 
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
    Lipsum.ContextMenuOpening -= Lipsum_ContextMenuOpening;
}
 

I also created a statement to remove this event handler when we leave the page.

In the method that this event handler calls, we will cancel the initial call with Handled = true, and then create our own context menu that still makes all of the appropriate calls to the clipboard.  We are only going to briefly cover using the Clipboard in this article, but we will spend the entire article tomorrow on the different ways to use this great tool.

In addition, finding the right place to pop a context menu inside of a TextBox is different than just determining the position of a control on the page.  Here’s my GetTextBoxRect() method:

private Rect GetTextBoxRect(TextBox t)
{
    Rect temp = t.GetRectFromCharacterIndex(t.SelectionStart, false);
 
    GeneralTransform transform = t.TransformToVisual(null);
    Point point = transform.TransformPoint(new Point());
    point.X = point.X + temp.X;
    point.Y = point.Y + temp.Y;
 
    return new Rect(point, new Size(temp.Width, temp.Height));
}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

And here’s the method for creating our PopupMenu control (you’ll notice that I used a secondary method for determining which command is tapped that doesn’t use lambdas this time.  It makes for easier readability, but you should also notice the method is much, much longer.)

async void Lipsum_ContextMenuOpening(object sender, ContextMenuEventArgs e)
        {
            e.Handled = true;
            TextBox t = (TextBox)sender;
 
            PopupMenu p = new PopupMenu();
            p.Commands.Add(new UICommand("Cut", null, 0));
            p.Commands.Add(new UICommand("Copy", null, 1));
            p.Commands.Add(new UICommand("Paste", null, 2));
            p.Commands.Add(new UICommand("Select All", null, 3));
            p.Commands.Add(new UICommandSeparator());
            p.Commands.Add(new UICommand("Delete", null, 4));
 
            var selectedCommand = await p.ShowForSelectionAsync(GetTextBoxRect(t));
 
            if (selectedCommand != null)
            {
                String text;
                DataPackage d;
                
                switch ((int)selectedCommand.Id)
                {
                    case 0: //CUT
                        text = t.SelectedText;
                        t.SelectedText = "";
                        d = new DataPackage();
                        d.SetText(text);
                        Clipboard.SetContent(d);
                        break;
                    case 1: //COPY
                        text = t.SelectedText;
                        d = new DataPackage();
                        d.SetText(text);
                        Clipboard.SetContent(d);
                        break;
                    case 2: //PASTE
                        text = await Clipboard.GetContent().GetTextAsync();
                        t.SelectedText = text;
                        break;
                    case 3: //SELECT ALL
                        t.SelectAll();
                        break;
                    case 4: //DELETE
                        t.SelectedText = "";
                        break;
                }
            }
        }
 

If you’re following along in your own project, you should now be able to highlight some text in your TextBox, and get a menu that looks like this one:

16-XAML-FinalTextBox

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Summary

Today we looked at the art of creating inline context menus for our users.  They are an excellent way to provide interactions with elements that aren’t selectable, or for commands that make more sense directly adjacent to the element being interacted with.

If you would like to see the entire code sample from this article, click the icon below:

downloadXAML

Tomorrow, as promised, we will venture further into the Clipboard functionality available to us in Windows 8 development.  See you then!

downloadTheTools

Tags

3 responses to “31 Days of Windows 8 | Day #16: Context Menus”

  1. The Daily Six Pack: November 18, 2012 | Dirk Strauss Avatar

    […] 31 Days of Windows 8 | Day #16: Contect Menus, Jeff Blankenburg […]

  2. […] 31 Days of Windows 8 | Day #16: Context Menus – Jeff Blankenburg […]

  3. Shashank Avatar
    Shashank

    Its cool, but how can i change the color of context menu from White to say Black?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Create a website or blog at WordPress.com

%d bloggers like this: