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.
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:
(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:
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:
And when I execute that option, the background of the page turns orange. Like this:
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:
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:
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:
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:
.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:
Tomorrow, as promised, we will venture further into the Clipboard functionality available to us in Windows 8 development. See you then!
Leave a Reply