Navigation through your Windows Style app

I was navigating through the code I found inside the default project templates that are available for Windows Style apps. They already contain a lot of plumbing, specially around navigating through pages.

While reading the LayoutAwarePage.cs I found some code that handles keystrokes. I even found out something I did not know.

- Navigate backward in an app using Alt+Left
- Navigate forward in an app using Alt+Right

But strangely, I’m using to navigate backward using the Backspace key, this was not in the default LayoutAwarePage.cs code. But when you for example use the Store app navigating backward using the Backspace key will work. So let’s improve the default LayoutAwarePage to support the Backspace key as well. Just add the highlighted code to the CodeDispatcher_AcceleratorKeyActived method inside the LayoutAwarePage.cs.

/// <summary>
/// Invoked on every keystroke, including system keys such as Alt key combinations, when
/// this page is active and occupies the entire window.  Used to detect keyboard navigation
/// between pages even when the page itself doesn't have focus.
/// </summary>
/// <param name="sender">Instance that triggered the event.</param>
/// <param name="args">Event data describing the conditions that led to the event.</param>
private void CoreDispatcher_AcceleratorKeyActivated(CoreDispatcher sender,
                                                    AcceleratorKeyEventArgs args)
{
    VirtualKey virtualKey = args.VirtualKey;

    // Only investigate further when Left, Right, or the dedicated Previous or Next keys
    // are pressed
    if ((args.EventType == CoreAcceleratorKeyEventType.SystemKeyDown ||
            args.EventType == CoreAcceleratorKeyEventType.KeyDown) &&
        (virtualKey == VirtualKey.Left || virtualKey == VirtualKey.Right ||
            (int) virtualKey == 166 || (int) virtualKey == 167))
    {
        CoreWindow coreWindow = Window.Current.CoreWindow;
        var downState = CoreVirtualKeyStates.Down;
        bool menuKey = (coreWindow.GetKeyState(VirtualKey.Menu) & downState) == downState;
        bool controlKey = (coreWindow.GetKeyState(VirtualKey.Control) & downState) == downState;
        bool shiftKey = (coreWindow.GetKeyState(VirtualKey.Shift) & downState) == downState;
        bool noModifiers = !menuKey && !controlKey && !shiftKey;
        bool onlyAlt = menuKey && !controlKey && !shiftKey;

        if (((int) virtualKey == 166 && noModifiers) ||
            (virtualKey == VirtualKey.Left && onlyAlt))
        {
            // When the previous key or Alt+Left are pressed navigate back
            args.Handled = true;
            this.GoBack(this, new RoutedEventArgs());
        }
        else if (((int) virtualKey == 167 && noModifiers) ||
                    (virtualKey == VirtualKey.Right && onlyAlt))
        {
            // When the next key or Alt+Right are pressed navigate forward
            args.Handled = true;
            this.GoForward(this, new RoutedEventArgs());
        }
    }
    if((args.EventType == CoreAcceleratorKeyEventType.SystemKeyDown ||
            args.EventType == CoreAcceleratorKeyEventType.KeyDown) && args.VirtualKey==VirtualKey.Back)
    {
        args.Handled = true;
        this.GoBack(this, new RoutedEventArgs());
    }
}

Get more quality reviews for your Windows Style apps

I’ve been creating Windows Phone apps for more than two years, so more and more concepts I use in the Phone apps should be ported to Windows Style apps (former Metro). One of these concepts is the question to review the app after 5 application starts. I currently have made two Windows Style apps, and more are coming, but it’s time to improve the quality and get some reviews.

Yesterday evening I started working on a solution and also discussed it with Matthijs Hoekstra, Fons Sonnemans, Martijn de Meulder and Glenn Versweyveld. So let’s start with the end result. How does it look?!

screenshot_10212012_195138

Yes it’s just a flyout with two buttons. The “Review” button should take you directly to the Store’s review page for the current app. But what about the “Improvements” button? The idea came from Martijn, give your users a way to tell you things you could improve in the app and you’ll definitely get less 1- and 2-star ratings. So it should open the e-mail app.

Store app startup count

Of course something like the startup count can be easily stored in the RoamingSettings, so that even over more than one device you won’t show the review question more than once.

private static int ApplicationStarts
{
    get
    {
        object val = ApplicationData.Current.RoamingSettings.Values["ReviewHelper.AppStarts"];
        return val == null ? 0 : (int)val;
    }
    set { ApplicationData.Current.RoamingSettings.Values["ReviewHelper.AppStarts"] = value; }
}

Flyout

The easiest way to get a flyout in your app is by making use of the Callisto framework created by Tim Heuer. Just make use of nuget to get it installed.

I’m just construction the UI you’ve seen in the example above in code. It’s not difficult code, but most of you probably create the UI only in Xaml. I thought this was easier to combine with the Flyout.

var appName = Application.Current.Resources["AppName"] as string;

var content = new StackPanel
                    {
                        Margin = new Thickness(16)
                    };
content.Children.Add(new TextBlock
                            {
                                Text =
                                    string.Format(
                                        "Thanks for using the {0} app. Would you like to review this app?",
                                        appName),
                                TextWrapping = TextWrapping.Wrap,
                                FontSize = 16,
                            });
var improveButton = new Button
                        {
                            Content = "Improvements?",
                            HorizontalAlignment = HorizontalAlignment.Right,
                            Background = new SolidColorBrush(Colors.Red),
                            Foreground = new SolidColorBrush(Colors.White),
                        };
improveButton.SetValue(ToolTipService.ToolTipProperty, "Send an e-mail with improvements.");
improveButton.Click +=
    (s, e) =>
        {
        };
var reviewButton = new Button
                        {
                            Content = "Review",
                            HorizontalAlignment = HorizontalAlignment.Right,
                            Background = new SolidColorBrush(Colors.Red),
                            Foreground = new SolidColorBrush(Colors.White)
                        };
reviewButton.Click += (s, e) =>
                            {
                            };
var buttonPanel = new StackPanel
                        {
                            Orientation = Orientation.Horizontal,
                            HorizontalAlignment = HorizontalAlignment.Right
                        };
buttonPanel.Children.Add(improveButton);
buttonPanel.Children.Add(reviewButton);
content.Children.Add(buttonPanel);
var flyout = new Flyout
                    {
                        Content = content,
                        Placement = PlacementMode.Top,
                        PlacementTarget = page,
                    };

We should not forget to open the flyout.

flyout.IsOpen = true;

Handling the Button clicks

We are still missing the click implementations of the buttons. So let’s do the Improve button’s click implementation. I want to show the e-mail application with a subject prefilled. This is something that I’m used to do in websites by using the mailto uri scheme this also works in Windows Style apps. To be more precise using Uri’s is one of the ways to integrate your app with apps like Mail, Maps and the Store. Sadly the Uri schemes you can use are often not that well documented, yet. But we’re lucky, the mailto scheme, is well documented.

improveButton.Click +=
    (s, e) =>
        {
            Launcher.LaunchUriAsync(
                new Uri(string.Format("mailto:{0}?subject=Improvements for {1}",
                                        "support@someurl.com", appName.Replace("&", "%26"))));
        };

That was the easy part, how do we access the Store app? We can use an Uri scheme as well, documented, but not completely. We’re lucky again, Andy Beaulieu found out a way to show the review page for an app. That information together with the knowledge that we can get the “Package Family Name” through code as well, give us this code.

reviewButton.Click +=
    (s, e) =>
        {
            string familyName = Package.Current.Id.FamilyName;
            Launcher.LaunchUriAsync(
                new Uri(string.Format("ms-windows-store:REVIEW?PFN={0}", familyName)));
        };

Completing

So I package the above code in side a class (toggle to view the code).

public static class PageExtensions
{
    private static int ApplicationStarts
    {
        get
        {
            object val = ApplicationData.Current.RoamingSettings.Values["ReviewHelper.AppStarts"];
            return val == null ? 0 : (int) val;
        }
        set { ApplicationData.Current.RoamingSettings.Values["ReviewHelper.AppStarts"] = value; }
    }

    public static void AskForReviewAfter(this Frame page, int times)
    {
        ApplicationStarts++;
        if (ApplicationStarts != times)
        {
            var appName = Application.Current.Resources["AppName"] as string;

            var content = new StackPanel
                                {
                                    Margin = new Thickness(16)
                                };
            content.Children.Add(new TextBlock
                                        {
                                            Text =
                                                string.Format(
                                                    "Thanks for using the {0} app. Would you like to review this app?",
                                                    appName),
                                            TextWrapping = TextWrapping.Wrap,
                                            FontSize = 16,
                                        });
            var improveButton = new Button
                                    {
                                        Content = "Improvements?",
                                        HorizontalAlignment = HorizontalAlignment.Right,
                                        Background = new SolidColorBrush(Colors.Red),
                                        Foreground = new SolidColorBrush(Colors.White),
                                    };
            improveButton.SetValue(ToolTipService.ToolTipProperty, "Send an e-mail with improvements.");
            improveButton.Click +=
                (s, e) =>
                    {
                        Launcher.LaunchUriAsync(
                            new Uri(string.Format("mailto:{0}?subject=Improvements for {1}",
                                                    "support@someurl.com", appName.Replace("&", "%26"))));
                    };
            var reviewButton = new Button
                                    {
                                        Content = "Review",
                                        HorizontalAlignment = HorizontalAlignment.Right,
                                        Background = new SolidColorBrush(Colors.Red),
                                        Foreground = new SolidColorBrush(Colors.White)
                                    };
            reviewButton.Click +=
                (s, e) =>
                    {
                        string familyName = Package.Current.Id.FamilyName;
                        Launcher.LaunchUriAsync(
                            new Uri(string.Format("ms-windows-store:REVIEW?PFN={0}", familyName)));
                    };
            var buttonPanel = new StackPanel
                                    {
                                        Orientation = Orientation.Horizontal,
                                        HorizontalAlignment = HorizontalAlignment.Right
                                    };
            buttonPanel.Children.Add(improveButton);
            buttonPanel.Children.Add(reviewButton);
            content.Children.Add(buttonPanel);
            var flyout = new Flyout
                                {
                                    Content = content,
                                    Placement = PlacementMode.Top,
                                    PlacementTarget = page,
                                };

            flyout.IsOpen = true;
        }
    }
}

And I call this code just after activating the Window in the App.xaml.cs file.

// Ensure the current window is active
Window.Current.Activate();

rootFrame.Loaded +=
    (s, e) =>
        {
            //Rootframe loaded.
            Debug.WriteLine("RootFrame Loaded.");
            (s as Frame).AskForReviewAfter(5);
        };