Visually Located

XAML and GIS

Migrating from the Windows Phone Toolkit ContextMenu to the new Runtime MenuFlyout

This is part of a series on migrating from the WP Toolkit.

With the release of the new Windows Phone 8.1 Runtime (aka XAML) apps comes a heap of new controls. Many of these controls were only available through third party control libraries. One of these controls was the ContextMenu. In Windows Phone Silverlight apps (even 8.1) displaying a context menu requires another library. One of the most popular (and free) libraries is the Windows Phone Toolkit. If you are used to using the ContextMenu from the toolkit you may be wondering how to show a context menu in Windows Phone Runtime apps.

Previously you would generally add a ContextMenu to your items in XAML like such (this example assumes context is set in an item template of a list.

<toolkit:ContextMenuService.ContextMenu >
    <toolkit:ContextMenu>
        <!-- using the Click event -->
        <toolkit:MenuItem Header="reply" Click="OnReplyClicked"/>
        <!-- using commanding to DataContext of MenuItem -->
        <toolkit:MenuItem Header="retweet" Command="{Binding RetweetCommand}"/>
        <!-- using commanding to DataContext of parent list -->
        <toolkit:MenuItem Header="favorite"
                          Command="{Binding DataContext.FavoriteCommand, 
                                    ElementName=TweetList}"
                          CommandParameter="{Binding}"/>
    </toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>

The above example shows how you can use the toolkit to display a ContextMenu when an item is tapped and held. It has three different items all behaving differently. One responds to the Click event and the other two using commanding. The two commanding items bind to different DataContext objects. One item (the retweet item) uses the command, RetweetCommand, which is on the item (model) it is bound to. The other item (favorite) binds to the ListBox that it is contained within. I generally use the first and last examples when building context menus. I rarely find a need to put a command on a model.

You could also create and open a context menu in code-behind if you wanted.

contextMenu = new ContextMenu();
// this example does not respond to an item selected, only a sample
contextMenu.Items.Add(new MenuItem { Header = "retweet"});
 
// Have to set the owner!
contextMenu.Owner = element;
contextMenu.IsOpen = true;

Windows Phone 8.1 Runtime apps brings the MenuFlyout for showing a context menu in your apps. The MenuFlyout is a FlyoutBase object which means you can place it on anything that has a Flyout dependency property like a button. If the control you want to place it on does not have a Flyout DP, no problem because there is also the AttachedFlyout attached DP on FlyoutBase. Adding the MenuFlyout to an item template is very similar to how we did it in the WP toolkit, but there are some things still missing.

<StackPanel>
    <FlyoutBase.AttachedFlyout>
        <MenuFlyout>
            <!-- using the Click event -->
            <MenuFlyoutItem Text="reply" Click="OnReplyClicked"/>
 
            <!-- using commanding to DataContext of MenuItem -->
            <MenuFlyoutItem Text="retweet" Command="{Binding RetweetCommand}"/>
 
            <!-- using commanding to DataContext of parent list -->
            <MenuFlyoutItem Text="favorite"
                            Command="{Binding DataContext.FavoriteCommand, 
                                      ElementName=TweetList}"
                            CommandParameter="{Binding}"/>
        </MenuFlyout>
    </FlyoutBase.AttachedFlyout>
 
    <!-- Content for you template -->
</StackPanel>

Looks like the toolkit code for the most part. If you build an app with this and tap and hold an item you will notice that the menu does not open. I said above there are some things missing, this is one of them, and it’s a big one! We now have a context menu built into the controls, but it won’t show. To show the menu, we need to subscribe to the Holding event of the element the menu will be attached to.

<StackPanel Holding="OnElementHolding">
    <FlyoutBase.AttachedFlyout>
        <!-- MenuFlyout -->
    </FlyoutBase.AttachedFlyout>
    <!-- content -->
</StackPanel>

In the event handler we can show the menu.

private void OnElementHolding(object sender, HoldingRoutedEventArgs args)
{
    // this event is fired multiple times. We do not want to show the menu twice
    if (args.HoldingState != HoldingState.Started) return;
 
    FrameworkElement element = sender as FrameworkElement;
    if (element == null) return;
 
    // If the menu was attached properly, we just need to call this handy method
    FlyoutBase.ShowAttachedFlyout(element);
}

Here is an example using the Pivot App project template

MenuFlyout

So there you have it. But wait. This is not acceptable. I am not going to add a holding event to all of the items and pages that I want to show a context menu. 

I really liked the ContextMenuSerice from the WP Toolkit. It was easy to use, little xaml required, just got to the point. Why don’t we create a MenuFlyoutService that works the same way the ContextMenuService did? 

The first step is to add a new file to Visual Studio named MenuFlyoutService. Then copy the source for the [class only] ContextMenuService. Paste the class into your file and find/replace ContextMenu for MenuFlyout. Happy news, most of the work is already done! Last thing we need to do is attach a menu to the element and subscribe to the Holding event. We’ll make all of our changes within the new OnMenuFlyoutChanged method. Any new line below are shown with comments.

private static void OnMenuFlyoutChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
    var element = o as FrameworkElement;
    if (null != element)
    {
        // just in case we were here before and there is no new menu
        element.Holding –= OnElementHolding;
 
        MenuFlyout oldMenuFlyout = e.OldValue as MenuFlyout;
        if (null != oldMenuFlyout)
        {
            // Remove previous attachment
            element.SetValue(FlyoutBase.AttachedFlyoutProperty, null);
        }
        MenuFlyout newMenuFlyout = e.NewValue as MenuFlyout;
        if (null != newMenuFlyout)
        {
            // attach using FlyoutBase to easier show the menu
            element.SetValue(FlyoutBase.AttachedFlyoutProperty, newMenuFlyout);
 
            // need to show it
            element.Holding += OnElementHolding;
        }
    }
}

The holding event handler is the same handler that we created above. Now our xaml will need to change to match our new MenuFlyoutService

<StackPanel>
    <local:MenuFlyoutService.MenuFlyout>
        <MenuFlyout>
            <!-- using the Click event -->
            <MenuFlyoutItem Text="reply" Click="OnReplyClicked"/>
 
            <!-- using commanding to DataContext of MenuItem -->
            <MenuFlyoutItem Text="retweet" Command="{Binding RetweetCommand}"/>
 
            <!-- using commanding to DataContext of parent list -->
            <MenuFlyoutItem Text="favorite" 
                            Command="{Binding DataContext.FavoriteCommand, 
                                      ElementName=TweetList}"
                            CommandParameter="{Binding}"/>
        </MenuFlyout>
    </local:MenuFlyoutService.MenuFlyout>
 
    <!-- content for template -->
</StackPanel>

And now we are able to show a MenuFlyout (aka context menu) whereever we want. No extra code needed in our pages that wasn’t there before. We can even copy/paste find/replace from old WP Silverlight apps to new Runtime apps pretty easily.

You can download a sample app using the MenuFlyoutService.

blog comments powered by Disqus