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);
        };

MetroGridHelper for WinRT v1.1–Enhanced to work with Snapped and Portrait mode

Because the left-margin is different for Snapped and Portrait mode, the previous version of MetroGridHelper for WinRT was not optimal. It didn’t support a different left margins at all. Don’t be sad, v1.1 supports Filled (120px), Snapped(20px), Fullscreen Landscape(100px) and Fullscreen Portrait(100px).

You can find the updated version on NuGet.

If you’re new to the MetroGridHelper for winrt you can execute the below command in the NuGet Package Manager Console:

PM> Install-Package WinRT.MetroGridHelper

If you have already installed the previous version you can also do the Update-Package command via the Package Manager Console:

PM> Update-Package WinRT.MetroGridHelper

Ah you want to see how it looks like?

screenshot_07252012_204328

screenshot_08032012_224037screenshot_08032012_223554

The code? Yes of course.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using Windows.Graphics.Display;
using Windows.UI;
using Windows.UI.Core;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Shapes;

namespace MC.MetroGridHelper
{
    /// <summary>
    /// A utility class that overlays a designer-friendly grid on top of the
    /// application frame, for use similar to the performance counters in
    /// App.xaml.cs. The color and opacity are configurable. The grid contains
    /// a number of squares that are 24x24, offset with 12px gutters, and all
    /// 24px away from the edge of the device.
    /// </summary>
    public static class MetroGridHelper
    {
        private static bool _visible;
        private static double _opacity = 0.15;
        private static Color _color = Colors.Red;
        private static List<Shape> _shapes;
        private static Grid _grid;
        private static bool _eventsAttached;
        private static double _width;
        private static double _height;

        /// <summary>
        /// Gets or sets a value indicating whether the designer grid is
        /// visible on top of the application's frame.
        /// </summary>
        public static bool IsVisible
        {
            get { return _visible; }
            set
            {
                _visible = value;
                UpdateGrid();
            }
        }

        /// <summary>
        /// Gets or sets the color to use for the grid's squares.
        /// </summary>
        public static Color Color
        {
            get { return _color; }
            set
            {
                _color = value;
                UpdateGrid();
            }
        }

        /// <summary>
        /// Gets or sets a value indicating the opacity for the grid's squares.
        /// </summary>
        public static double Opacity
        {
            get { return _opacity; }
            set
            {
                _opacity = value;
                UpdateGrid();
            }
        }

        /// <summary>
        /// Updates the grid (if it already has been created) or initializes it
        /// otherwise.
        /// </summary>
        private static void UpdateGrid()
        {
            if (_shapes != null)
            {
                var brush = new SolidColorBrush(_color);
                foreach (Shape square in _shapes)
                {
                    square.Fill = brush;
                }
                if (_grid != null)
                {
                    _grid.Visibility = _visible ? Visibility.Visible : Visibility.Collapsed;
                    _grid.Opacity = _opacity;
                }
            }
            else
            {
                BuildGrid();
            }
        }

        /// <summary>
        /// Builds the grid.
        /// </summary>
        private static async void BuildGrid()
        {
            _shapes = new List<Shape>();

            var frame = Window.Current.Content as Frame;
            if (frame == null || VisualTreeHelper.GetChildrenCount(frame) == 0)
            {
                await Window.Current.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, BuildGrid);
                return;
            }

            DependencyObject child = VisualTreeHelper.GetChild(frame, 0);
            var childAsBorder = child as Border;
            var childAsGrid = child as Grid;
            if (childAsBorder != null)
            {
                // Not a pretty way to control the root visual, but I did not
                // want to implement using a popup.
                UIElement content = childAsBorder.Child;
                if (content == null)
                {
                    await Window.Current.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, BuildGrid);
                    return;
                }
                childAsBorder.Child = null;
                await Window.Current.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
                                                         () =>
                                                             {
                                                                 var newGrid = new Grid();
                                                                 childAsBorder.Child = newGrid;
                                                                 newGrid.Children.Add(content);
                                                                 PrepareGrid(newGrid);
                                                                 AttachEvents();
                                                             });
            }
            else if (childAsGrid != null)
            {
                PrepareGrid(childAsGrid);
                AttachEvents();
            }
            else
            {
                Debug.WriteLine("Dear developer:");
                Debug.WriteLine("Unfortunately the design overlay feature requires that the root frame visual");
                Debug.WriteLine("be a Border or a Grid. So the overlay grid just isn't going to happen.");
            }
        }

        private static void AttachEvents()
        {
            if (_eventsAttached)
                return;
            Window.Current.SizeChanged += (s, e) => BuildGridForCurrentView();
            DisplayProperties.OrientationChanged += s => BuildGridForCurrentView(); 
            _eventsAttached = true;
        }

        public static void CreateGrid()
        {
            IsVisible = true;
        }

        /// <summary>
        /// Does the actual work of preparing the grid once the parent frame is
        /// in the visual tree and we have a Grid instance to work with for
        /// placing the chilren.
        /// </summary>
        /// <param name="parent">The parent grid to insert the sub-grid into.</param>
        private static void PrepareGrid(Grid parent)
        {
            if (_grid == null)
            {
                _grid = new Grid
                            {
                                IsHitTestVisible = false,
                                Visibility = _visible ? Visibility.Visible : Visibility.Collapsed,
                                Opacity = _opacity,
                                CacheMode = new BitmapCache()
                            };
                parent.Children.Add(_grid);
            }
            BuildGridForCurrentView();
        }

        public static int GetMarginForCurrentView()
        {
            bool isSnapped = ApplicationView.Value == ApplicationViewState.Snapped;
            bool isPortrait = DisplayProperties.CurrentOrientation == DisplayOrientations.Portrait ||
                              DisplayProperties.CurrentOrientation == DisplayOrientations.PortraitFlipped;
            if (isSnapped)
                return 20;
            if (isPortrait)
                return 100;
            return 120;
        }

        public static void BuildGridForCurrentView()
        {
            // Places the grid into the visual tree. It is never removed once
            // being added.
            var frame = Window.Current.Content as Frame;
            if (frame == null)
                return;

            _width=Math.Max(_width, frame.ActualWidth);
            _height=Math.Max(_height, frame.ActualHeight);
            _grid.Children.Clear();
            _shapes.Clear();
            IEnumerable<Shape> shapes = GetGridShapesForMargin(_width, _height, GetMarginForCurrentView());
            foreach (Shape shape in shapes)
            {
                _grid.Children.Add(shape);
                _shapes.Add(shape);
            }
        }

        public static IEnumerable<Shape> GetGridShapesForMargin(double width, double height, int margin)
        {
            // To support both orientations, unfortunately more visuals need to
            // be used. An alternate implementation would be to react to the
            // orientation change event and re-draw/remove squares.
            var brush = new SolidColorBrush(_color);
            double max = Math.Max(width, height);

            const double strokeWidth = 2.0;

            var horizontalLine = new Line
                                     {
                                         IsHitTestVisible = false,
                                         Stroke = brush,
                                         X1 = 0,
                                         X2 = max,
                                         Y1 = 100 + (strokeWidth/2),
                                         Y2 = 100 + (strokeWidth/2),
                                         StrokeThickness = strokeWidth,
                                     };
            yield return horizontalLine;
            var horizontalLine2 = new Line
                                      {
                                          IsHitTestVisible = false,
                                          Stroke = brush,
                                          X1 = 0,
                                          X2 = max,
                                          Y1 = 140 + (strokeWidth/2),
                                          Y2 = 140 + (strokeWidth/2),
                                          StrokeThickness = strokeWidth,
                                      };
            yield return horizontalLine2;

            var verticalLine = new Line
                                   {
                                       IsHitTestVisible = false,
                                       Stroke = brush,
                                       X1 = margin - (strokeWidth / 2),
                                       X2 = margin - (strokeWidth / 2),
                                       Y1 = 0,
                                       Y2 = max,
                                       StrokeThickness = strokeWidth,
                                   };
            yield return verticalLine;

            var horizontalBottomLine = new Line
                                           {
                                               IsHitTestVisible = false,
                                               Stroke = brush,
                                               X1 = 0,
                                               X2 = max,
                                               Y1 = height - 130 + (strokeWidth/2),
                                               Y2 = height - 130 + (strokeWidth/2),
                                               StrokeThickness = strokeWidth,
                                           };
            _shapes.Add(horizontalBottomLine);
            var horizontalBottomLine2 = new Line
                                            {
                                                IsHitTestVisible = false,
                                                Stroke = brush,
                                                X1 = 0,
                                                X2 = max,
                                                Y1 = height - 50 + (strokeWidth/2),
                                                Y2 = height - 50 + (strokeWidth/2),
                                                StrokeThickness = strokeWidth,
                                            };
            _shapes.Add(horizontalBottomLine2);
            yield return horizontalBottomLine2;

            const int tileHeight = 20;

            for (int x = margin; x < /*width*/ max; x += (tileHeight*2))
            {
                for (int y = 140; y < /*height*/ max; y += (tileHeight*2))
                {
                    var rect = new Rectangle
                                   {
                                       Width = tileHeight,
                                       Height = tileHeight,
                                       VerticalAlignment = VerticalAlignment.Top,
                                       HorizontalAlignment = HorizontalAlignment.Left,
                                       Margin = new Thickness(x, y, 0, 0),
                                       IsHitTestVisible = false,
                                       Fill = brush,
                                   };
                    yield return rect;
                }
            }
        }
    }
}

MetroGridHelper for WinRT, a helper to get the alignment right

It’s already more than a year ago that Jeff Wilcox wrote the MetroGridHelper for Windows Phone. I’ve been using it a lot since then. But we now have the option to build WinRT applications, sadly without the MetroGridHelper.

Wait no more, I ported the code from Jeff and also modified it to fit the metro guidelines that exist for WinRT applications. I’ve had some help from Martin Tirion and Matthijs Hoekstra, both working as Evangelist for Microsoft in The Netherlands.

The result is something that helps me during the design of the Windows 8, WinRT, apps.

You can simply install the NuGet Package. Through the console like this:

PM> Install-Package WinRT.MetroGridHelper

And inside your App.xaml.cs you can add the following to the end of the OnLaunched method.

// Place the frame in the current Window and ensure that it is active
Window.Current.Content = rootFrame;
Window.Current.Activate();

//Only when the debugger is attached
if (System.Diagnostics.Debugger.IsAttached)
{
    //Display the metro grid helper
    MC.MetroGridHelper.MetroGridHelper.CreateGrid();
}

screenshot_07252012_204308

screenshot_07252012_204316

screenshot_07252012_204328

The current version only works for WinRT apps that are written in Xaml, though I’m investigating on how to create something similar within HTML5+JS. If you have suggestions for this version please let me know, and I will adjust the MetroGridHelper if they are helpful.

To be complete, you can use the below source code as an alternative to the NuGet Package.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using Windows.UI;
using Windows.UI.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Shapes;

namespace MC.MetroGridHelper
{
    /// <summary>
    /// A utility class that overlays a designer-friendly grid on top of the
    /// application frame, for use similar to the performance counters in
    /// App.xaml.cs. The color and opacity are configurable. The grid contains
    /// a number of squares that are 24x24, offset with 12px gutters, and all
    /// 24px away from the edge of the device.
    /// </summary>
    public static class MetroGridHelper
    {
        private static bool _visible;
        private static double _opacity = 0.15;
        private static Color _color = Colors.Red;
        private static List<Shape> _shapes;
        private static Grid _grid;

        /// <summary>
        /// Gets or sets a value indicating whether the designer grid is
        /// visible on top of the application's frame.
        /// </summary>
        public static bool IsVisible
        {
            get { return _visible; }
            set
            {
                _visible = value;
                UpdateGrid();
            }
        }

        /// <summary>
        /// Gets or sets the color to use for the grid's squares.
        /// </summary>
        public static Color Color
        {
            get { return _color; }
            set
            {
                _color = value;
                UpdateGrid();
            }
        }

        /// <summary>
        /// Gets or sets a value indicating the opacity for the grid's squares.
        /// </summary>
        public static double Opacity
        {
            get { return _opacity; }
            set
            {
                _opacity = value;
                UpdateGrid();
            }
        }

        /// <summary>
        /// Updates the grid (if it already has been created) or initializes it
        /// otherwise.
        /// </summary>
        private static void UpdateGrid()
        {
            if (_shapes != null)
            {
                var brush = new SolidColorBrush(_color);
                foreach (Shape square in _shapes)
                {
                    square.Fill = brush;
                }
                if (_grid != null)
                {
                    _grid.Visibility = _visible ? Visibility.Visible : Visibility.Collapsed;
                    _grid.Opacity = _opacity;
                }
            }
            else
            {
                BuildGrid();
            }
        }

        /// <summary>
        /// Builds the grid.
        /// </summary>
        private static async void BuildGrid()
        {
            _shapes = new List<Shape>();

            var frame = Window.Current.Content as Frame;
            if (frame == null || VisualTreeHelper.GetChildrenCount(frame) == 0)
            {
                await Window.Current.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, BuildGrid);
                return;
            }

            DependencyObject child = VisualTreeHelper.GetChild(frame, 0);
            var childAsBorder = child as Border;
            var childAsGrid = child as Grid;
            if (childAsBorder != null)
            {
                // Not a pretty way to control the root visual, but I did not
                // want to implement using a popup.
                UIElement content = childAsBorder.Child;
                if (content == null)
                {
                    await Window.Current.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, BuildGrid);
                    return;
                }
                childAsBorder.Child = null;
                await Window.Current.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
                                                                                            {
                                                                                                var newGrid = new Grid();
                                                                                                childAsBorder.Child =
                                                                                                    newGrid;
                                                                                                newGrid.Children.Add(
                                                                                                    content);
                                                                                                PrepareGrid(frame,
                                                                                                            newGrid);
                                                                                            });
            }
            else if (childAsGrid != null)
            {
                PrepareGrid(frame, childAsGrid);
            }
            else
            {
                Debug.WriteLine("Dear developer:");
                Debug.WriteLine("Unfortunately the design overlay feature requires that the root frame visual");
                Debug.WriteLine("be a Border or a Grid. So the overlay grid just isn't going to happen.");
            }
        }

        public static void CreateGrid()
        {
            IsVisible = true;
        }

        /// <summary>
        /// Does the actual work of preparing the grid once the parent frame is
        /// in the visual tree and we have a Grid instance to work with for
        /// placing the chilren.
        /// </summary>
        /// <param name="frame">The phone application frame.</param>
        /// <param name="parent">The parent grid to insert the sub-grid into.</param>
        private static void PrepareGrid(Frame frame, Grid parent)
        {
            var brush = new SolidColorBrush(_color);

            _grid = new Grid {IsHitTestVisible = false};

            // To support both orientations, unfortunately more visuals need to
            // be used. An alternate implementation would be to react to the
            // orientation change event and re-draw/remove squares.
            double width = frame.ActualWidth;
            double height = frame.ActualHeight;
            double max = Math.Max(width, height);

            const double strokeWidth = 2.0;

            var horizontalLine = new Line
                                     {
                                         IsHitTestVisible = false,
                                         Stroke = brush,
                                         X1 = 0,
                                         X2 = max,
                                         Y1 = 100 + (strokeWidth/2),
                                         Y2 = 100 + (strokeWidth/2),
                                         StrokeThickness = strokeWidth,
                                     };
            _grid.Children.Add(horizontalLine);
            _shapes.Add(horizontalLine);
            var horizontalLine2 = new Line
                                      {
                                          IsHitTestVisible = false,
                                          Stroke = brush,
                                          X1 = 0,
                                          X2 = max,
                                          Y1 = 140 + (strokeWidth/2),
                                          Y2 = 140 + (strokeWidth/2),
                                          StrokeThickness = strokeWidth,
                                      };
            _grid.Children.Add(horizontalLine2);
            _shapes.Add(horizontalLine2);

            var verticalLine = new Line
                                   {
                                       IsHitTestVisible = false,
                                       Stroke = brush,
                                       X1 = 120 - (strokeWidth/2),
                                       X2 = 120 - (strokeWidth/2),
                                       Y1 = 0,
                                       Y2 = max,
                                       StrokeThickness = strokeWidth,
                                   };
            _grid.Children.Add(verticalLine);
            _shapes.Add(verticalLine);

            var horizontalBottomLine = new Line
            {
                IsHitTestVisible = false,
                Stroke = brush,
                X1 = 0,
                X2 = max,
                Y1 = height - 130 + (strokeWidth / 2),
                Y2 = height - 130 + (strokeWidth / 2),
                StrokeThickness = strokeWidth,
            };
            _grid.Children.Add(horizontalBottomLine);
            _shapes.Add(horizontalBottomLine);
            var horizontalBottomLine2 = new Line
            {
                IsHitTestVisible = false,
                Stroke = brush,
                X1 = 0,
                X2 = max,
                Y1 = height - 50 + (strokeWidth / 2),
                Y2 = height - 50 + (strokeWidth / 2),
                StrokeThickness = strokeWidth,
            };
            _grid.Children.Add(horizontalBottomLine2);
            _shapes.Add(horizontalBottomLine2);

            const int tileHeight = 20;

            for (int x = 120; x < /*width*/ max; x += (tileHeight*2))
            {
                for (int y = 140; y < /*height*/ max; y += (tileHeight*2))
                {
                    var rect = new Rectangle
                                   {
                                       Width = tileHeight,
                                       Height = tileHeight,
                                       VerticalAlignment = VerticalAlignment.Top,
                                       HorizontalAlignment = HorizontalAlignment.Left,
                                       Margin = new Thickness(x, y, 0, 0),
                                       IsHitTestVisible = false,
                                       Fill = brush,
                                   };
                    _grid.Children.Add(rect);
                    _shapes.Add(rect);
                }
            }

            _grid.Visibility = _visible ? Visibility.Visible : Visibility.Collapsed;
            _grid.Opacity = _opacity;

            // For performance reasons a single surface should ideally be used
            // for the grid.
            _grid.CacheMode = new BitmapCache();

            // Places the grid into the visual tree. It is never removed once
            // being added.
            parent.Children.Add(_grid);
        }
    }
}

NuGet packages are powerful, but the Transformations are not. Time for a solution!

First of all I don’t want to complain about NuGet. I love it every day and I have created two packages myself that I want be easy to use for everyone. It should be a matter of just Install-Package PackageName and not much more.

I currently have the following packages.

It was last week when I wanted to update the Google Analytics package that I found a problem. When the package was used in a previous version, and I run the update it did a strange thing in the transformation I made before. The transformation I created was transforming the App.xaml file by adding an element to the xaml.

<GoogleAnalyticsService WebPropertyId="UA-12345-6" xmlns="clr-namespace:$rootnamespace$.Analytics" />

The transformation in NuGet aren’t really intelligent. It just adds the above element. And when you have an update for the package it will ad the element again. That’s something I would like to be able to prevent. So are the solutions at the table? Yes there are, you can use PowerShell.

So I took a little bit of time to learn more about PowerShell. I knew it was something like batch, but with support for .NET Libraries. The support for .NET Libraries implies easy use of XML editing, which will be handy Xaml is just Xml.

The first step I had to take is be able to read the content of the App.xaml file from the PowerShell script that can be used as part of the NuGet Package. I’m able to iterate through the project items to find the App.xaml project item which in turn helps me find the local path.

param($installPath, $toolsPath, $package, $project)

# find the App.xaml file 
$config = $project.ProjectItems | where {$_.Name -eq "App.xaml"}

# find its path on the file system 
$localPath = $config.Properties | where {$_.Name -eq "LocalPath"}

That wasn’t that hard, was it? Let’s read the App.xaml file into an Xml object. And look for an element with GoogleAnalyticsService as the name.

# load Web.config as XML 
$xml = New-Object xml 
$xml.Load($localPath.Value)

$gasnode = $xml.SelectNodes("//*") | where { $_.Name -eq "GoogleAnalyticsService"} 
if($gasnode -eq $null) 
{ 
} 

If we didn’t find that kind of element we want to create the element with the example WebPropertyId, just like I previously had in the App.xaml NuGet transformation. First I select the proposed parent element, being the “Application.ApplicationLifetimeObjects” element which I can use to add the to be created XmlNode to. Next I create the XmlNode in the appropriate namespace and add the element the parent element. What’s left is to save the App.xaml file.

$lto = $xml.SelectNodes("//*") | where { $_.Name -eq "Application.ApplicationLifetimeObjects"} 
$gas = $xml.CreateNode("element","analytics:GoogleAnalyticsService","clr-namespace:"+$project.Properties.Item("RootNamespace").Value+".Analytics") 
$gas.SetAttribute("WebPropertyId", "UA-12345-6") 
$lto.AppendChild($gas) 
# save the App.xaml file 
$xml.Save($localPath.Value)

And to be complete, this was the complete Install.ps1 file. Hope this helps all the people who have been struggling to solve the limited Transformations issue.

param($installPath, $toolsPath, $package, $project)

# find the App.xaml file 
$config = $project.ProjectItems | where {$_.Name -eq "App.xaml"}

# find its path on the file system 
$localPath = $config.Properties | where {$_.Name -eq "LocalPath"}

# load Web.config as XML 
$xml = New-Object xml 
$xml.Load($localPath.Value)

$gasnode = $xml.SelectNodes("//*") | where { $_.Name -eq "GoogleAnalyticsService"} 
if($gasnode -eq $null) 
{ 
  $lto = $xml.SelectNodes("//*") | where { $_.Name -eq "Application.ApplicationLifetimeObjects"} 
  $gas = $xml.CreateNode("element","analytics:GoogleAnalyticsService","clr-namespace:"+$project.Properties.Item("RootNamespace").Value+".Analytics") 
  $gas.SetAttribute("WebPropertyId", "UA-12345-6") 
  $lto.AppendChild($gas) 
  # save the App.xaml file 
  $xml.Save($localPath.Value) 
}