Author Archives: xamlbrewer

Getting started with the Telerik RadDataGrid on UWP

This article explains how to get started with the Telerik RadDataGrid for UWP. RadDataGrid is a tabular control with a long history (in different technology stacks including Windows Forms, WPF, and HTML) and a rich API. It displays a list of items and supports customizable columns, single and multi-column sorting, data editing, grouping, selection, filtering and so on. XAML-wise you may consider RadDataGrid as the successor of the DataGrid in WPF.

In this article we’ll focus on templating, sorting, filtering, and grouping. I built a small sample that displays a list of racing drivers with some of their properties (name, team, nationality, experience, …). As an end user of this app you can

  • change the order of the columns,
  • select which columns to display or hide,
  • sort the list on column values,
  • filter the list on column values and,
  • group list entries on column values.

Here’s how the app looks like:

Overview

RadDataGrid is part of the Telerik UI for UWP toolkit. Its source code is available for free (!) here on GitHub, the binaries are available through NuGet. For an overview of the RadDataGrid features –but without source code- check the corresponding Telerik web page. You can also download their sample Store App. It looks like this, but unfortunately again, there’s no source code available:

TelerikStoreApp

The official documentation for Telerik UI for UWP is here, and the starting point for the RadDataGrid documentation is right here. As already mentioned, the focus of this article is data visualisation (read-only). If you’re looking for an example of editing and validation, then you better check this example from Microsoft.

Off we go

First you need to install the NuGet package in your app:

NuGetPackage

On the page that’s going to host the RadDataGrid, define the necessary namespaces:

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      ...
      xmlns:grid="using:Telerik.UI.Xaml.Controls.Grid"
      xmlns:core="using:Telerik.Data.Core">

Then you just create a RadDataGrid XAML element on it. For a quick result, you can set AutoGenerateColumns to true. A default column will be created for each property in the ItemsSource’s item class.

A transparent background and no border or gridlines give the control a nice Windows 10-look. I also prefer to always display the column resize handlers – the double vertical lines in the column header. They look good and they’re really handy on a touch screen. For more details, check the properties and configurations and the visual structure documentation pages.

Here’s my initial control definition for the racing drivers grid:

<grid:RadDataGrid ItemsSource="{x:Bind ViewModel.Drivers}"
                    AutoGenerateColumns="True"
                    Background="Transparent"
                    BorderBrush="Transparent"
                    GridLinesVisibility="None"
                    CanUserChooseColumns="True"
                    ColumnResizeHandleDisplayMode="Always">

Here’s how it looks like at runtime:

AutoGenerateColumns

Default Column types

If you’re not auto-generating the columns, then you need to specify the column list yourself. RadDataGrid comes with different column types, including a set of types that allow visualization as well as editing and validation of String, Boolean, Date, Image, Numerical and Time properties, and for selection from a ComboBox. Here’s an example of a DataGridTextColumn and a DataGridDateColumn, each bound to a property in the itemssource.

As you see, you can apply your own header text, and formatting:

<grid:RadDataGrid.Columns>
    <grid:DataGridTextColumn PropertyName="Name"
                                Header="Name" />
    <!-- ... -->
    <grid:DataGridDateColumn PropertyName="MostRecentVictory"
                                Header="Last win"
                                CellContentFormat="{}{0:d}" />
    <!-- ... -->
</grid:RadDataGrid.Columns>

Template Columns

If you want complete control over the look and feel of a column, then you have to use a DataGridTemplateColumn. This allows you to provide your own data template. Here’s an example of two templated columns – the first one uses a Rating Control to visualize the the experience level (from an enumeration), the second one uses an icon to represent a Boolean value:

<grid:DataGridTemplateColumn Header="Experience">
    <grid:DataGridTemplateColumn.CellContentTemplate>
        <DataTemplate>
            <controls:Rating Maximum="5"
                                Value="{Binding ExperienceAsNumber}"
                                EmptyImage="ms-appx:///Assets/RatingIcons/wreath_empty.png"
                                FilledImage="ms-appx:///Assets/RatingIcons/wreath_full.png"
                                IsInteractive="False"
                                ItemHeight="24" />
        </DataTemplate>
    </grid:DataGridTemplateColumn.CellContentTemplate>
</grid:DataGridTemplateColumn>
<!-- No customization possible :-(-->
<!--<grid:DataGridBooleanColumn Header="Active"
            PropertyName="IsStillActive" />-->
<grid:DataGridTemplateColumn Header="Active">
    <grid:DataGridTemplateColumn.CellContentTemplate>
        <DataTemplate>
            <Path Data="{StaticResource SteeringWheelIcon}"
                    VerticalAlignment="Center"
                    HorizontalAlignment="Center"
                    Height="20"
                    Width="20"
                    Fill="{Binding IsStillActive, Converter={StaticResource BooleanToBrushConverter}}"
                    Stretch="Uniform" />
        </DataTemplate>
    </grid:DataGridTemplateColumn.CellContentTemplate>
</grid:DataGridTemplateColumn>

Sorting

According to the classic paradigm (and hence the user’s expectations) columns can be sorted by clicking on the column header. When clicking on the header of a column that is already sorted, the rows will iterate to the next stage: from sorted ascending, to sorted descending, and back to its original order (I love this last ‘unsort’ stage, it demonstrates the maturity of the API).

The built-in default column types enable sorting out of the box: they know to which property they’re bound –through PropertyName– and the underlying data types are all IComparable.

For templated columns, you need to explicitly declare that the user can sort the column via CanUserSort. Since such a column isn’t necessarily bound to a property, you have to provide a SortDescriptor yourself. Here’s how I defined that the column with the steering wheel icon can be sorted on the IsStillActive property of the racing driver class:

<grid:DataGridTemplateColumn Header="Active"
                                CanUserSort="True">
    <grid:DataGridTemplateColumn.SortDescriptor>
        <core:PropertySortDescriptor PropertyName="IsStillActive" />
    </grid:DataGridTemplateColumn.SortDescriptor>
    <grid:DataGridTemplateColumn.CellContentTemplate>
        <!-- ... -->
    </grid:DataGridTemplateColumn.CellContentTemplate>
</grid:DataGridTemplateColumn>

As you would expect, a sorted column is decorated with a triangle in its header. Here’s the sample data grid, reverse alphabetically sorted by the Name column:

Sorting

Filtering

Columns headers can be decorated with a control that allows filtering. Here’s how to enable this feature:

<grid:RadDataGrid ItemsSource="{x:Bind ViewModel.Drivers}"
                    AutoGenerateColumns="False"
                    UserFilterMode="Enabled">

With UserFilterMode enabled, all non-templated columns are decorated with a filter icon. When that icon is clicked, a data type specific mini UI pops open, and allows the user to specify a filter to apply on the column values. When the filter is activated, the rows that don’t comply disappear, and the column header is underlined to visually indicate that a filter is in place. Here’s how all of this looks like in the sample app. We’re filtering the drivers with their name ending on ‘son’:

Filtering

As mentioned, the filter UI is specific to the data type of the column. Here’s a screenshot of the date filter on the ‘most recent victory’ property:

FilterOnDate

If you want, you can even create your own custom filter UI control. And for the sake of completeness: filters can also be applied programmatically.

Grouping

The most powerful feature in the RadDataGrid is grouping: the ability to let the user group the data by column values. All the user needs to do is drag a column header to the grouping bar at the left.

Again it is a feature that you have to enable:

<grid:RadDataGrid ItemsSource="{x:Bind ViewModel.Drivers}"
                    ...
                    UserGroupMode="Enabled">

You can then selectively turn it of again, e.g. for columns with relatively unique values, like the Name in the sample app:

<grid:DataGridTextColumn PropertyName="Name"
                            Header="Name"
                            CanUserGroup="False" />

For template-type columns, you need to specify the property name and the group name through a GroupDescriptor. Here’s how I allow grouping on the values in the Active column:

<grid:DataGridTemplateColumn Header="Active">
    <grid:DataGridTemplateColumn.GroupDescriptor>
        <core:PropertyGroupDescriptor PropertyName="ActiveDescription"
                                        DisplayContent="Active" />
    </grid:DataGridTemplateColumn.GroupDescriptor>
    <!-- ... -->
</grid:DataGridTemplateColumn>

I did not not want to show true and false in the group header, so I created a new property in the racing driver class. This is not the only property that I defined just for the data grid, but after all, that’s what ViewModels are made for:

public string ActiveDescription => IsStillActive ? "Active" : "Retired";

When the icon on top of the grouping bar is clicked, a panel slides open that allows to change the nesting order, sort the group headers, and remove columns from the grouping hierarchy. Here’s how the UI looks like in the sample app. We group the drivers by nationality, and then ungroup again:

Grouping

When user grouping is enabled, it also makes sense to enable column selection. After all, the user will want to hide the columns on which the data is grouped on – their values are already displayed in the group headers. Column selection is enabled through the CanUserChooseColumns property. It displays a triangular button in the top right corner that triggers a panel with a checkbox for all the columns:

ColumnSelection

Combining the features

All RadDataGrid features (sorting, grouping, filtering) can be combined. Here’s the grid from the sample app, sorted and filtered by name and grouped by nationality of the driver:

GroupedFiltering

Theming

Every part of the RadDataGrid is themeable, but some parts are easier than other. Telerik provides the classic light and dark theme resources (different shades of grey, and an accent color). They also provide a UserThemeResource markup extension that enables you to easily override resources. Just create your own theme and register it, like this:

<ResourceDictionary.MergedDictionaries>
    <!-- Theme colors and icons -->
    <ResourceDictionary Source="Services/Icons/Icons.xaml" />
    <ResourceDictionary Source="Services/Theming/Theme.xaml" />
    <ResourceDictionary>
        <telerik:UserThemeResources x:Key="TelerikLightResources"
                                    LightResourcesPath="ms-appx:///Services/Theming/Telerik/TelerikTheme.xaml" />
    </ResourceDictionary>
</ResourceDictionary.MergedDictionaries>

In your custom theme, just override the Telerik named brushes:

<Color x:Key="TelerikSelectedColor">#B1560F</Color>
<Color x:Key="TelerikHighlightedColor">#75390A</Color>

<SolidColorBrush x:Key="TelerikGridHeaderBackgroundBrush"
                    Color="#7A6F41" />
<SolidColorBrush x:Key="TelerikGridGroupHeaderBackgroundBrush"
                    Color="#7A6F41" />
<SolidColorBrush x:Key="TelerikGridServiceColumnBackgroundBrush"
                    Color="#7A6F41" />
<SolidColorBrush x:Key="TelerikGridHeaderForegroundBrush"
                    Color="White" />
<SolidColorBrush x:Key="TelerikGridGroupHeaderForegroundBrush"
                    Color="White" />
<SolidColorBrush x:Key="TelerikGridServiceColumnForegroundBrush"
                    Color="White" />

Some of the theme assets are images, like the buttons to open and close panels (because it’s hard to draw a triangle in XAML?). If you want to create custom versions of these, then just download these from the source code in GitHub:

TelerikAssets

DefaultCloseButton

You can then modify the images (or replace them entirely) and refer to these in your resources dictionary:

<BitmapImage x:Key="TelerikGridColumnChooserOpenMouseOver"
                UriSource="ms-appx:///Services/Theming/Telerik/Assets/column_chooser_btn_themed.png" />
<BitmapImage x:Key="TelerikGridColumnChooserCloseMouseOver"
                UriSource="ms-appx:///Services/Theming/Telerik/Assets/close_btn_themed.png" />
<BitmapImage x:Key="TelerikGridFilterFlyoutExpanderIcon"
                UriSource="ms-appx:///Telerik.UI.Xaml.Grid.UWP/Assets/FilterFlyout/ic_arrow_down_white.png" />

Here’s a custom panel button in the sample app (it’s the triangle in the upper right corner):

ThemedCloseButton

Personal Experience

I’ve been using this control for a while now. Here’s a screenshot from one of the RadDataGrids that I’m building in another app. It shows a list of possible ingredients for brewing a beer:

AppSample

I have no idea which criteria the user is going to apply to find the ingredient(s) he’s looking for. But I’m sure that the control’s capabilities of presenting the data in a clear way and allowing him to sort, filter, and group the rows on any column or combination of columns will rapidly narrow down the search list.

Here are my personal observations on the Telerik UI controls for UWP, and the RadDataGrid in particular:

Thumbs up Thumbs down
Very rich and mature API and implementation. Shallow reference documentation.
Good quickstart documentation. Lack of detailed sample projects.
It’s for free! Occasional bugs*.

(*) I relatively often need to work around a “Layout cycle detected” error when using more advanced templated columns.

RadDataGrid for UWP is a tremendously powerful and useful control that you may consider for a large number of scenarios.

Source Code

My sample app lives here in GitHub.

Enjoy!

A Radial Range Indicator control for UWP

In this article we present a Radial Range Indicator control for use in XAML-based UWP apps. RadialRangeIndicator is a circular control for graphically displaying a Range of values (from a minimum to a maximum) inside a scale, e.g. in a Dashboard. Circular and square controls are excellent citizens of adaptive user interfaces, since they scale easily and are independent of the screen orientation. RadialRangeIndicator is derived from PercentageRing and RadialGauge.

Here’s how a RadialRangeIndicator looks like:

RadialRangeIndicator

Its main constituents are

  • the scale, i.e. the background arc,
  • the range, i.e. the foreground arc, and
  • the text.

All properties are implemented as dependency properties so you can bind to these in every way you like. Every change is immediately reflected in the UI. The control does not crash on unexpected values: it survives assignments like a maximum angle over 360 degrees, or any minimum value that is greater than its corresponding maximum. [note to self: add exception handling to the string.Format call that generates the Text Smile]

Here’s the list of configurable properties:


Scale related
ScaleMinimum double Gets or sets the minimum value of the scale.
ScaleMaximum double Gets or sets the maximum value of the scale.
ScaleWidth double Gets or sets the width of the scale, in percentage of the radius.
ScaleMinimumAngle int Gets or sets the start angle of the scale, which corresponds with the ScaleMinimum value, in degrees.
ScaleMaximumAngle int Gets or sets the end angle of the scale, which corresponds with the ScaleMaximum value, in degrees.
ScaleBrush Brush Gets or sets the brush for the scale.
ScaleStartCap PenLineCap Gets or sets the StrokeStartCap for the Scale.
ScaleEndCap PenLineCap Gets or sets the StrokeEndCap for the Scale.
Range related    
RangeMinimum double Gets or sets the minimum value for the range.
RangeMaximum double Gets or sets the maximum value for the range.
RangeStepSize double Gets or sets the rounding interval for the range values. If the StepSize is zero, no rounding is applied.
RangeBrush Brush Gets or sets the brush for the range.
RangeStartCap PenLineCap Gets or sets the StrokeStartCap for the Range.
RangeEndCap PenLineCap Gets or sets the StrokeEndCap for the Range.
Text related    
TextBrush Brush

Gets or sets the brush for the displayed value range.

TextStringFormat string Gets or sets the text string format. Use {0} and {1} to display range minimum and maximum.

The core of the control’s default style template is a ViewBox with two Path instances with configurable PenLineCaps:

<ControlTemplate TargetType="local:RadialRangeIndicator">
    <Border Background="{TemplateBinding Background}"
            BorderBrush="{TemplateBinding BorderBrush}"
            BorderThickness="{TemplateBinding BorderThickness}">
        <Viewbox>
            <Grid x:Name="PART_Container"
                    Height="200"
                    Width="200"
                    Background="Transparent">

                <!-- Scale -->
                <Path Name="PART_Scale"
                        Stroke="{TemplateBinding ScaleBrush}"
                        StrokeThickness="{TemplateBinding ScaleWidth}"
                        StrokeStartLineCap="{TemplateBinding ScaleStartCap}"
                        StrokeEndLineCap="{TemplateBinding ScaleEndCap}" />

                <!-- Range -->
                <Path Name="PART_Range"
                        Stroke="{TemplateBinding RangeBrush}"
                        StrokeThickness="{TemplateBinding ScaleWidth}"
                        StrokeStartLineCap="{TemplateBinding RangeStartCap}"
                        StrokeEndLineCap="{TemplateBinding RangeStartCap}" />

                <!-- Value -->
                <StackPanel VerticalAlignment="Center"
                            HorizontalAlignment="Center">
                    <TextBlock Name="PART_Text"
                                Foreground="{TemplateBinding TextBrush}"
                                FontSize="20"
                                FontWeight="SemiBold"
                                TextAlignment="Center" />
                </StackPanel>
            </Grid>
        </Viewbox>
    </Border>
</ControlTemplate>

The code-behind populates each of these Paths with an ArcSegment in a PathGeometry or a full circle EllipseGeometry. Here’s the code for the Range:

if (radialRangeIndicator.RangeMaximumValueAngle - radialRangeIndicator.NormalizedMinAngle == 360)
{
    // Draw full circle.
    var eg = new EllipseGeometry
    {
        Center = new Point(Radius, Radius),
        RadiusX = Radius - (radialRangeIndicator.ScaleWidth / 2)
    };

    eg.RadiusY = eg.RadiusX;
    range.Data = eg;
}
else
{
    range.StrokeStartLineCap = radialRangeIndicator.RangeStartCap;
    range.StrokeEndLineCap = radialRangeIndicator.RangeEndCap;

    // Draw arc.
    var pg = new PathGeometry();
    var pf = new PathFigure
    {
        IsClosed = false,
        StartPoint = radialRangeIndicator.ScalePoint(radialRangeIndicator.RangeMinimumValueAngle, middleOfScale)
    };

    var seg = new ArcSegment
    {
        SweepDirection = SweepDirection.Clockwise,
        IsLargeArc = radialRangeIndicator.RangeMaximumValueAngle > (180 + radialRangeIndicator.RangeMinimumValueAngle),
        Size = new Size(middleOfScale, middleOfScale),
        Point =
            radialRangeIndicator.ScalePoint(
                Math.Min(radialRangeIndicator.RangeMaximumValueAngle, radialRangeIndicator.NormalizedMaxAngle), middleOfScale)
    };

    pf.Segments.Add(seg);
    pg.Figures.Add(pf);
    range.Data = pg;
}

For more info on the algorithms and calculations, please read the article on the Percentage Ring. After all, this Radial Range Indicator is the very same control, but with a variable start point for the Range.

The Gallery page of the sample app shows some more advanced usages and styles of the RadialRangeIndicator:

Gallery

On the left, you see that a series of Radial Gauge Indicators can be used perfectly to indicate ranges inside (or outside) the scale of a Radial Gauge.

In the middle you see how to define a custom string format for the Text:

<controls:RadialRangeIndicator ScaleMinimumAngle="-150"
                                ScaleMaximumAngle="150"
                                ScaleBrush="Silver"
                                TextStringFormat="{}{0}-{1} Å"
                                TextBrush="{StaticResource PageForegroundBrush}" />

The instance in the middle also demonstrates how a DropShadowPanel control can be used inside a control’s template. There’s a white one around the Scale to smoothen the entire control, and a yellow one to add a glow effect to the Range:

<!-- Scale -->
<toolkit:DropShadowPanel Color="White">
    <Path Name="PART_Scale"
            Stroke="{TemplateBinding ScaleBrush}"
            StrokeThickness="{TemplateBinding ScaleWidth}"
            StrokeStartLineCap="{TemplateBinding ScaleStartCap}"
            StrokeEndLineCap="{TemplateBinding ScaleEndCap}" />
</toolkit:DropShadowPanel>

<!-- Range -->
<toolkit:DropShadowPanel Color="Yellow"
                            BlurRadius="20">
    <Path Name="PART_Range"
            Stroke="{TemplateBinding RangeBrush}"
            StrokeThickness="{TemplateBinding ScaleWidth}"
            StrokeStartLineCap="{TemplateBinding RangeStartCap}"
            StrokeEndLineCap="{TemplateBinding RangeStartCap}" />
</toolkit:DropShadowPanel>

Here’s an example of Radial Range Indicators in a more realistic app. They have a DropShadowPanel around the Scale, and a BackDrop underneath the Text to blur the background:

HopDetails

I also added a page with Radial Range Indicators inside a Simple Perfect Square. This gives an overview of the control in many different sizes and configurations, and allows to assess the performance when having multiple instances of it on the same page. On top of that, it’s also colorful and fun to look at:

SquareOfSquares

If you want to start using the control, it’s available on NuGet. If you want to dive in its source code, it’s on GitHub.

Enjoy!

Creating a fluid adaptive UI with VariableSizedWrapGrid and Implicit Animations

In this article we demonstrate an easy but powerful technique to implement a fluid adaptive UI for a XAML UWP app. The UI responds to changes in the page size by rearranging its elements. All elements are tilted and float smoothly to their new position. All you need to do, is select the correct main panel type, and call an extension method to hook up the animations.

ImplicitAnimation

What is a VariableSizedWrapGrid?

The VariableSizedWrapGrid is a layout panel that arranges it child elements in rows and columns, where each child element can span multiple rows and columns. These rows or columns automatically wrap to a new row or column. The Orientation property specifies the direction in which child elements are arranged and wrapped. The default size of an item or tile is determined by ItemHeight and ItemWidth, but individual items can demand more space through the ColumnSpan and RowSpan attached properties. When the control is resized (e.g. when the page is resized or rotated), the VariableSizedWrapGrid automatically rearranges its children. It seems that the VariableSizedWrapGrid has been mainly used for presenting collections of pictures and news items.

VariableSizedWrapGridSample

The VariableSizedWrapGrid is not an ItemsControl itself, but it can be used as ItemsPanel in a GridView to present item collections.

Hosting a UI Form in a VariableSizedWrapGrid

Its capabilities to host components of different sizes and to auto-(re)arrange their position make the VariableSizedWrapGrid a nice candidate panel for a responsive/adaptive UI. The control can be used as the main panel on your page. Specify at least values for ItemHeight, ItemWidth and Orientation. Also make sure that it can scroll in the opposite direction of its orientation. If you wrap horizontally then you should place it in a vertical ScrollViewer, like this:

<ScrollViewer VerticalScrollMode="Auto">
    <VariableSizedWrapGrid ItemHeight="100"
                           ItemWidth="250"
                           Orientation="Horizontal">
    <!-- UI Components here -->
    </VariableSizedWrapGrid>
</ScrollViewer>

You can now group all your input and other controls into appropriate subpanels (such as Grids and StackPanels), assign a value to the containers’ ColumnSpan and RowSpan and drop these into the VariableSizedWrapGrid:

<VariableSizedWrapGrid ItemHeight="100"
                       ItemWidth="250"
                       Orientation="Horizontal"
                       Margin="20 20 0 0">

    <Image VariableSizedWrapGrid.ColumnSpan="2"
            VariableSizedWrapGrid.RowSpan="3"
            ...
            Margin="0 0 20 20" />

    <TextBlock VariableSizedWrapGrid.ColumnSpan="2"
                VariableSizedWrapGrid.RowSpan="2"
                Padding="0 0 20 20"
                TextWrapping="WrapWholeWords">
    ... 
    </TextBlock>

    <StackPanel VariableSizedWrapGrid.ColumnSpan="1"
                VariableSizedWrapGrid.RowSpan="2"
                HorizontalAlignment="Stretch"
                VerticalAlignment="Top"
                Padding="0 0 20 20">
        <!-- ... -->
    </StackPanel>

    <Grid VariableSizedWrapGrid.ColumnSpan="2"
            VariableSizedWrapGrid.RowSpan="2"
            HorizontalAlignment="Left"
            VerticalAlignment="Top"
            Padding="0 0 20 20">
        <!-- ... -->
    </Grid>

    <!-- ... -->
   
</VariableSizedWrapGrid>

To distribute horizontal and vertical spacing, I gave the host control a Margin of “20 20 0 0” and each subpanel a Margin or Padding of “0 0 20 20”.

When you resize the page, all subpanels will be automatically rearranged:
VariableSizedWrapGrid

VariableSizedWrapGrid_2

Alternatives

If you want more granular control over the layout of a page in different sizes, then you can switch to a design based on a RelativePanel and Adaptive Triggers. Please note that this also involves a lot more work for you.

It’s adaptive, now let’s make it fluid

The grid repositions its children when the page width changes. The transition is abrupt: all children are just smashed into their new location. Let’s smoothen this process and go from ‘Smash’ to ‘Whoosh’. We’ll animate the journey to the new position and add a gentle tilt effect while moving.

smash whoosh

The code we’ll be using is derived from the LayoutAnimation sample in the Windows UI Dev Labs repository on GitHub. We’re going to use implicit animations. For a deep dive into this topic, please read Exploring Implicit Animations, by Robert Mikhayelyan.

Implicit Animations start automatically after a trigger has been fired, so they help decouple animation from app logic. It’s the Composition Engine that does all of the work: it discovers when a trigger fires, and executes the animations. App developers semi-declaratively define the animations which they want to execute, and the events that trigger these animations. ‘Semi-declaratively’ in the previous sentence stands for ‘in C# with string-based expressions‘ (note: ‘declaratively’ would stand for ‘in XAML’).

In the documentation, at the bottom of each page dealing with one of the classes related to implicit animations, you see that they are relatively new to the framework:

  • Device family: Windows 10 Anniversary Edition (introduced v10.0.14393.0)
  • API contract: Windows.Foundation.UniversalApiContract (introduced v3)

This implies that we need to check the users’ SDK with a call to ApiInformation.IsApiContractPresent before we can use these classes from our code:

// Check if SDK > 14393
if (!ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 3))
{
    return;
}

An ImplicitAnimationCollection can be defined on the following properties on Visual:

  • AnchorPoint
  • CenterPoint
  • Offset
  • Opacity
  • Orientation
  • RotationAngle
  • RotationAngleInDegrees
  • RotationAxis
  • Scale
  • Size

The Offset property is the one we’re interested in. It corresponds to the relative position of a Visual (every subpanel) in its container (the VariableSizedWrapGrid).

The API and its documentation feel a bit swampy here, but you should

Here’s the code. It’s written as an extension method of Panel. So it applies not only to VariableSizedWrapView but also to Canvas, Grid and StackPanel:

public static void RegisterImplicitAnimations(this Panel panel)
{
    // Check if SDK > 14393
    if (!ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 3))
    {
        return;
    }

    var compositor = ElementCompositionPreview.GetElementVisual(panel).Compositor;

    // Create ImplicitAnimations Collection. 
    var elementImplicitAnimation = compositor.CreateImplicitAnimationCollection();

    // Define trigger and animation that should play when the trigger is triggered. 
    elementImplicitAnimation["Offset"] = CreateOffsetAnimation(compositor);

    foreach (var item in panel.Children)
    {
        var elementVisual = ElementCompositionPreview.GetElementVisual(item);
        elementVisual.ImplicitAnimations = elementImplicitAnimation;
    }
}

Here’s the code for the individual animation for each element. It’s a combination of two CompositionAnimation instances, each with a Duration of 0.4 seconds:

The code is a copy/paste from the LayoutAnimations sample. I didn’t feel the need to change any of the parameters. After all, this was done by designers:

private static CompositionAnimationGroup CreateOffsetAnimation(Compositor compositor)
{
    // Define Offset Animation for the Animation group
    var offsetAnimation = compositor.CreateVector3KeyFrameAnimation();
    offsetAnimation.InsertExpressionKeyFrame(1.0f, "this.FinalValue");
    offsetAnimation.Duration = TimeSpan.FromSeconds(.4);

    // Define Animation Target for this animation to animate using definition. 
    offsetAnimation.Target = "Offset";

    // Define Rotation Animation for Animation Group. 
    var rotationAnimation = compositor.CreateScalarKeyFrameAnimation();
    rotationAnimation.InsertKeyFrame(.5f, 0.160f);
    rotationAnimation.InsertKeyFrame(1f, 0f);
    rotationAnimation.Duration = TimeSpan.FromSeconds(.4);

    // Define Animation Target for this animation to animate using definition. 
    rotationAnimation.Target = "RotationAngle";

    // Add Animations to Animation group. 
    var animationGroup = compositor.CreateAnimationGroup();
    animationGroup.Add(offsetAnimation);
    animationGroup.Add(rotationAnimation);

    return animationGroup;
}

Thanks to the extension method, we can go from ‘Smash’ to ‘Whoosh’ with just one line of code:

// Yep: that's all.
VariableSizedWrapGrid.RegisterImplicitAnimations();

Here are some action shots from the animation:
ImplicitAnimation

ImplicitAnimation_2

Source Code

The code lives here on GitHub.

Enjoy!

Using a Dynamic System Accent Color in UWP

This article describes an algorithm that allows individual pages in a XAML UWP app to override the system accent color. By default, the system accent color is the Windows theme color chosen by end user. When a UWP app is running, this color is applied to lot of controls (checkbox, slider, focused text box) and hence this color could possibly collide with the app’s own theme colors. Therefor, a lot of apps statically override this accent color.

This article shows how this accent color can be overridden when navigating to or from individual pages, so that each page can have its own theme. Let’s say you’re building an app around the elements of nature. Wouldn’t it be nice to give all the pages on Water blue controls, and all pages on Fire red controls without creating different style for each control and each theme color?

Here’s a screen capture from the sample app, which is derived from my SplitView Navigation project. As you see, every page has its own accent color applied to all controls:
DynamicAccentColor

Static Change

Let’s start with explaining the correct way to statically override the system accent color. In your app.xaml, add a ThemeDictionary and override the SystemAccentColor for each theme (Default, Dark, and Light):

<Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.ThemeDictionaries>
            <ResourceDictionary x:Key="Default">
                <Color x:Key="SystemAccentColor">OrangeRed</Color>
            </ResourceDictionary>
            <ResourceDictionary x:Key="Dark">
                <Color x:Key="SystemAccentColor">DeepPink</Color>
            </ResourceDictionary>
            <ResourceDictionary x:Key="Light">
                <Color x:Key="SystemAccentColor">OrangeRed</Color>
            </ResourceDictionary>
        </ResourceDictionary.ThemeDictionaries>
        <ResourceDictionary.MergedDictionaries>
            <!-- Your resources -->
            <!-- ... -->
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Application.Resources>

This will impact the following default brushes (the list depends on your Windows 10 version), in order of appearance in generic.xaml:

  • SystemControlBackgroundAccentBrush
  • SystemControlDisabledAccentBrush
  • SystemControlForegroundAccentBrush
  • SystemControlHighlightAccentBrush
  • SystemControlHighlightAltAccentBrush
  • SystemControlHighlightAltListAccentHighBrush
  • SystemControlHighlightAltListAccentLowBrush
  • SystemControlHighlightAltListAccentMediumBrush
  • SystemControlHighlightListAccentHighBrush
  • SystemControlHighlightListAccentLowBrush
  • SystemControlHighlightListAccentMediumBrush
  • SystemControlHyperlinkTextBrush
  • ContentDialogBorderThemeBrush
  • JumpListDefaultEnabledBackground
  • InkToolbarAccentColorThemeBrush

Dynamic Change

The basics

To change the accent color from C#, it suffices to override the SystemAccentColor in the Resources of the current application. At least that was my theory:

Application.Current.Resources["SystemAccentColor"] = accentColor;

Respect the High Contrast theme

Before you override the accent color -or any other theme color- you have to make sure that the user did not select a high contrast theme. I quote from the documentation: “High Contrast Themes in Windows are useful for those computer users who have an eye-sight disability, since they heighten the color contrast of text, windows borders and images on your screen, in order to make them more visible and easier to read and identify”. Fortunately UWP has an AccessibilitySettings class that allows you to verify if a high contrast theme is active.

So here’s the –theoretical- full method to apply a new accent color:

public static void ApplyAccentColor(Color accentColor)
{
    if (!new AccessibilitySettings().HighContrast)
    {
        Application.Current.Resources["SystemAccentColor"] = accentColor;
    }
}

Caveat: the page should request a Theme

Changing the system accent color at runtime only has effect when the page that you’re navigating to has declaratively set its RequestedTheme:

<Page x:Class="XamlBrewer.Uwp.DynamicAccentColor.HorsePage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      ...
      RequestedTheme="Light">

If not, the new accent color is simply ignored.

Caveat: controls are cached

The method can be called from a page’s code. You have to do this before any control starts to render, so preferably in the constructor. The call even be done before InitializeComponent  (which is actually a weird place to write code). But even there it is already too late for some controls. Apparently UWP creates a cache of the most common control types. Sometimes you’re served with a control from this cache and you’ll end up with a recycled UI element that still uses a previous accent color, like this:
Oops_2

Whilst thoroughly testing the app I noticed that this phenomenon never occurred when navigating from one of the empty pages (Settings and About). So I added an new empty page to the solution, moved call to switch the accent color out of the Page and into the Navigation Service, and added an extra visit to the new empty page to the navigation logic. Hosting the logic in the Navigation Service instead of in the Page also allows to set back the default color when the user navigates away from the page. Finally, to remove all accent color responsibilities from the page, I decorated the Theming service with a dictionary to host the preferred accent color for each color page type:

private static Dictionary<Type, Color> AccentColors = new Dictionary<Type, Color>();
public static void RegisterAccentColor(Type pageType, Color accentColor)
{
    if (AccentColors.ContainsKey(pageType))
    {
        AccentColors.Add(pageType, accentColor);
    }
    else
    {
        AccentColors[pageType] = accentColor;
    }
}

The dictionary is populated by the Shell when the app starts up:

Theme.RegisterAccentColor(typeof(BirdPage), 
	(Color)Application.Current.Resources["BirdAccentColor"]);
Theme.RegisterAccentColor(typeof(DonkeyPage), 
	(Color)Application.Current.Resources["DonkeyAccentColor"]);
Theme.RegisterAccentColor(typeof(HorsePage), 
	(Color)Application.Current.Resources["HorseAccentColor"]);
Theme.RegisterAccentColor(typeof(RabbitPage), 
	(Color)Application.Current.Resources["RabbitAccentColor"]);

Note: these dictionary entries can be changed at runtime.

The Theming service was also extended with a method that sets the accent color based on the page type, with a fallback to the default color:

public static void ApplyAccentColor(Type pageType)
{
    if (AccentColors.ContainsKey(pageType))
    {
        ApplyAccentColor(AccentColors[pageType]);
    }
    else
    {
        ApplyAccentColor((Color)Application.Current.Resources["DefaultAccentColor"]);
    }
}

Apparently just hitting an empty page before navigating to a page with controls isn’t sufficient to clear the cache. You also need a short ‘asynchronous pause’ over there, with a Task.Delay() call with a timespan of over 100 milliseconds (250 seems long enough for most devices). I realize that this is the kind of call that you only do when you painted yourself into a corner, and it brings back memories to that guilty feeling when we needed to call old VB’s DoEvents function. But it does the trick, and I must admit that the short visit to the empty page actually enhances the transition animation.

Here’s the call that is done before navigating to a new destination:

private static async Task InitiateNavigation(Type sourcePageType)
{
    lock (typeof(Navigation))
    {
        // Apply the page's accent color (or the default)
        Theme.ApplyAccentColor(sourcePageType);

        // Clear native control and page caches so that they accept the new SystemAccentColor.
        // Navigate to an empty page.
        _frame.Navigate(typeof(BackgroundPage));
    }

    // Ye olde VB6 DoEvents.
    await Task.Delay(250);  // Put it to 100 to see the slider's delay.
}

The lock statement is there to serialize navigation steps and make the app behave properly when the user is wildly clicking around in the splitview’s menu (been there, done that).

There’s also a post-navigation step: to prevent the user from ending up in the empty page when pressing the back button we remove the last entry in the host frame’s navigation BackStack:

private static void CompleteNavigation()
{
    lock (typeof(Navigation))
    {
        if (_frame.BackStackDepth > 0)
        {
            _frame.BackStack.RemoveAt(_frame.BackStackDepth - 1);
        }
    }
}

Here’s the full Navigate method:

public static async Task<bool> Navigate(Type sourcePageType)
{
    if (_frame.CurrentSourcePageType == sourcePageType)
    {
        return true;
    }

    await InitiateNavigation(sourcePageType);
    var result = _frame.Navigate(sourcePageType);
    CompleteNavigation();

    return result;
}

Back button logic

The algorithm for navigating backwards is similar: we double check that the eventual target is not that empty page, pay a visit to the empty page and wait a while, and serialize the steps with a lock statement to prevent chaos when the user accidentally double clicks the back button:

public static async Task GoBack()
{
    try
    {
        if (!_frame.CanGoBack)
        {
            return;
        }

        // Just in case there's still a BackGroundPage hanging around.
        lock (typeof(Navigation))
        {
            if (_frame.BackStack[_frame.BackStackDepth - 1].SourcePageType == typeof(BackgroundPage))
            {
                _frame.GoBack();
            }
        }

        if (_frame.CanGoBack)
        {
            var type = _frame.BackStack[_frame.BackStackDepth - 1].SourcePageType;
            await InitiateNavigation(type);
            CompleteNavigation();

            _frame.GoBack();
        }
    }
    catch (Exception ex)
    {
        // Ignore, wild clicking going on.
        Debugger.Break();
    }
}

On top of that, the entire call is wrapped inside a Pokémon exception handler (to catch ‘m all). In a worst case scenario we end up with the wrong color on a page, but at least we never crash the app on a back button click:
Oops_1

Hardware back button processing

The same logic can be reused for processing hardware back button clicks. Just make sure to set the Handled property of the BackPressEventArgs immediately to true, or you just navigate straight out of the app:

public static async Task GoBack(BackPressedEventArgs e)
{
    if (!_frame.CanGoBack)
    {
        // Bail out.
        return;
    }

    // Stay in the app.
    e.Handled = true;
    await GoBack();
}

Source

The sample project lives here on GitHub.

I know that there are some code smells in the implementation and there’s no guarantee that this algorithm will work on all devices. However, “it works on my machine(s)”: I have tested it successfully on all of my devices and it runs it on the simulators and emulators that I have on my development boxes.

I just love how this effect improves the UI of some of my apps and that’s why I’m sharing it.

I may have painted myself in the corner, but at least I did it with an accent color of my choice. 🙂

Enjoy!

Building SplitView Navigation in UWP

This article explains how to build a production-ready navigation infrastructure into your XAML-based MVVM-oriented UWP app. I’ll focus on proper use of the SplitView control, configurable menus, icons and color schemes, and the back button. Fortunately, there’s no need for you to build everything from scratch: you can get a quickstart by using the UWP Community Toolkit Hamburger Menu Control or Template 10. This blog post will help you to understand these implementations and to refine the look and feel of their navigation service.

Foundation: Shell and SplitView

We’ll start with the canonical structure: a SplitView control is placed on the app’s main page. On the left, its Pane will contain a ListView with the main navigation menu(s). On the right, its Content will host a Frame that will display the different Page instances. The Hamburger button sits in a layer on top of the SplitView, since it has to be visible when the pane is closed. The app will always display this main page, it’s generally called the ‘Shell’. Here’s the basic XAML structure of the SplitView:

<SplitView x:Name="MySplitView"
            IsPaneOpen="False"
            CompactPaneLength="48"
            OpenPaneLength="200"
            PaneBackground="{StaticResource SplitViewBackgroundBrush}"
            DisplayMode="Overlay">
    <SplitView.Pane>
        <Grid x:Name="SplitViewPane"
                VerticalAlignment="Stretch"
                HorizontalAlignment="Stretch"
                Background="{StaticResource SplitViewBackgroundBrush}">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="*" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <ListView x:Name="Menu"
                        ItemsSource="{Binding Menu}"
                        SelectionChanged="Menu_SelectionChanged"
                        ItemContainerStyle="{StaticResource MenuListViewItemStyle}"
                        ItemTemplate="{StaticResource MenuItemTemplate}" />
            <ListView x:Name="SecondMenu"
                        ItemsSource="{Binding SecondMenu}"
                        SelectionChanged="Menu_SelectionChanged"
                        ItemContainerStyle="{StaticResource MenuListViewItemStyle}"
                        ItemTemplate="{StaticResource MenuItemTemplate}"
                        Grid.Row="2" />
        </Grid>
    </SplitView.Pane>
    <SplitView.Content>
        <Grid Background="{StaticResource PageBackgroundBrush}">
            <!-- Navigation Frame -->
            <Frame x:Name="SplitViewFrame" Navigated="SplitViewFrame_OnNavigated"
                    Padding="0 0 0 0">
            </Frame>
        </Grid>
    </SplitView.Content>
</SplitView>

Here’s how this looks like in a small sample app:

Home

The official Guidelines for Navigation Panes describe the sizes and other settings of a navigation pane depending on the page width. So the SplitView has its CompactPaneLength and its OpenPaneLenght properly set. The dynamic behavior –switching DisplayMode and IsPaneOpen– is implemented by Adaptive Triggers that observe the page width to bring the UI in the appropriate VisualState by calling Setters. Here’s the corresponding XAML:

<VisualStateManager.VisualStateGroups>
    <VisualStateGroup>

        <!-- VisualState to be triggered when window width is >=1007 effective pixels -->
        <VisualState x:Name="Large">
            <VisualState.StateTriggers>
                <AdaptiveTrigger MinWindowWidth="1007" />
            </VisualState.StateTriggers>
            <VisualState.Setters>
                <!-- SplitView pane shows as overlay -->
                <!-- Splitview pane will close however 😦 -->
                <Setter Target="MySplitView.DisplayMode"
                        Value="Inline" />
                <Setter Target="MySplitView.IsPaneOpen"
                        Value="True" />
            </VisualState.Setters>
        </VisualState>

        <!-- VisualState to be triggered when window width is >=640 and <=1007 effective pixels -->
        <VisualState x:Name="Medium">
            <VisualState.StateTriggers>
                <AdaptiveTrigger MinWindowWidth="641" />
            </VisualState.StateTriggers>
            <VisualState.Setters>
                <Setter Target="MySplitView.DisplayMode"
                        Value="CompactOverlay" />
                <Setter Target="MySplitView.IsPaneOpen"
                        Value="False" />
            </VisualState.Setters>
        </VisualState>

        <!-- VisualState to be triggered when window width is >=0 and <641 effective pixels -->
        <VisualState x:Name="Small">
            <VisualState.StateTriggers>
                <AdaptiveTrigger MinWindowWidth="0" />
            </VisualState.StateTriggers>
            <VisualState.Setters>
                <!-- Collapse the SplitView pane into overlay mode -->
                <Setter Target="MySplitView.DisplayMode"
                        Value="Overlay" />
                <Setter Target="MySplitView.IsPaneOpen"
                        Value="False" />
            </VisualState.Setters>
        </VisualState>
    </VisualStateGroup>
</VisualStateManager.VisualStateGroups>

Here’s how the app looks like at different page widths:
Wide

Medium

Narrow

Main navigation menus

If you have a static list of menu items, then you can simply hard-code it in the Shell. In a lot of apps, however, individual pages should be able to add and/or remove menu items and the menus should update their list accordingly. So I prefer to hold the current list of menu items in an ObservableCollection in the base class of the ViewModels:

internal class ViewModelBase : BindableBase
{
    private static readonly ObservableCollection<MenuItem> AppMenu = new ObservableCollection<MenuItem>();
    private static readonly ObservableCollection<MenuItem> AppSecondMenu = new ObservableCollection<MenuItem>();

    public ObservableCollection<MenuItem> Menu => AppMenu;

    public ObservableCollection<MenuItem> SecondMenu => AppSecondMenu;
}

I made a distinction between a functional menu (for links to functional pages) and a technical menu (for things like Settings, About, and Help).

The MenuItem class is the ViewModel for individual menu items. It can be used for navigation commands, but also for other command types (Print, Translate, whatever). It comes with a text, and icon, a command, and a navigation destination. Feel free to add your own required properties, such as ToolTipText and IsEnabled.

internal class MenuItem : BindableBase
{
    private string _glyph;
    private string _text;
    private DelegateCommand _command;
    private Type _navigationDestination;

    public string Glyph
    {
        get { return _glyph; }
        set { SetProperty(ref _glyph, value); }
    }

    public string Text
    {
        get { return _text; }
        set { SetProperty(ref _text, value); }
    }

    public ICommand Command
    {
        get { return _command; }
        set { SetProperty(ref _command, (DelegateCommand)value); }
    }

    public Type NavigationDestination
    {
        get { return _navigationDestination; }
        set { SetProperty(ref _navigationDestination, value); }
    }

    public bool IsNavigation => _navigationDestination != null;
}

The initial list of menu items is created in the Shell’s ViewModel:

public ShellViewModel()
{
    // Build the menus
    Menu.Add(new MenuItem() { Glyph = Icon.GetIcon("BirdIcon"), Text = "Bird", NavigationDestination = typeof(BirdPage) });
    Menu.Add(new MenuItem() { Glyph = Icon.GetIcon("DonkeyIcon"), Text = "Donkey", NavigationDestination = typeof(DonkeyPage) });
    Menu.Add(new MenuItem() { Glyph = Icon.GetIcon("HorseIcon"), Text = "Horse", NavigationDestination = typeof(HorsePage) });
    Menu.Add(new MenuItem() { Glyph = Icon.GetIcon("RabbitIcon"), Text = "Rabbit", NavigationDestination = typeof(RabbitPage) });

    SecondMenu.Add(new MenuItem() { Glyph = Icon.GetIcon("GearIcon"), Text = "Settings", NavigationDestination = typeof(SettingsPage) });
    SecondMenu.Add(new MenuItem() { Glyph = Icon.GetIcon("InfoIcon"), Text = "About", NavigationDestination = typeof(AboutPage) });
}

As you can see in the very first code snippets: the menus are ListView instances in the SplitView’s Pane in the Shell, bound to the observable collections of menu items.

Let’s Navigate

When the user selects a menu item in the ListView, we should execute the underlying command in the bound MenuItem. I did not use a command binding for this, but kept all the code in the View using a SelectionChanged event handler. That’s because there is more than one menu in the ListView, and after a click we need to unselect the other ones. Since this is a pure UI thing there’s no need to involve the ViewModels:

private void Menu_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    if (e.AddedItems.Count > 0)
    {
        // Unselect the other menu.
        if ((sender as ListView) == Menu)
        {
            SecondMenu.SelectedItem = null;
        }
        else
        {
            Menu.SelectedItem = null;
        }

        var menuItem = e.AddedItems.First() as MenuItem;
        if (menuItem != null && menuItem.IsNavigation)
        {
            Navigation.Navigate(menuItem.NavigationDestination);
        }
    }
}

The code that lets the frame navigate to a new page is encapsulated in a central location, because Views as well as ViewModels may be interested in what page you’re going to or coming from. In an MVVM architecture this kind of code typically ends up in a Service, so I created a Navigation Service. Here’s part of its code. The navigation service needs to get a reference to the main frame (the Content of the SplitView) which it get from the Shell at startup:

public static bool Navigate(Type sourcePageType)
{
    if (_frame.CurrentSourcePageType != sourcePageType)
    {
        return _frame.Navigate(sourcePageType);
    }

    return true;
}

Icons and Paths

For the menu icons, most samples and toolkits use one of the vector-based IconElement subclasses. That’s OK when you find all that you need in the limited list of existing font characters (for FontIcon) and symbol icons (for SymbolIcon), or when you create a custom font for your app. For custom icons, PathIcon seems to be the perfect candidate. Unfortunately PathIcon is such a lightweight class that is doesn’t even have a Stretch property. So its instances don’t scale very well. Your original SVG needs to be in the correct small size, otherwise you end up with cropped image in your menu, like these:
ScaledPathIcons

This is the XAML for the icons in the above screenshot:

<PathIcon Data="{x:Bind Glyph}"
            Foreground="{StaticResource SplitViewForegroundBrush}"
            Height="24"
            Width="24" />

I prefer to use full-fledged  Path instances for (single-color) icons:

<Path x:Name="Glyph"
        Data="{x:Bind Glyph}"
        VerticalAlignment="Center"
        HorizontalAlignment="Center"
        Height="24"
        Width="24"
        Fill="{StaticResource SplitViewForegroundBrush}"
        Stretch="Uniform" />

If you want to reuse your icons, please note that you cannot store Paths in a XAML Resource Dictionary. Paths are UIElements and hence not shareable. Fortunately a Path’s Data can be instantiated from a String, which is of the XAML intrinsic data types. So I you want reusable icons, your can store their content in a Resource Dictionary:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <x:String x:Key="GearIcon">M23.253799,51.030369C21.992199,51.043957 20.758548,51.662549 20.012491,52.791646 18.818798,54.598202 19.316827,57.040463 21.124303,58.234765 22.932258,59.429383 25.374185,58.929718 26.567878,57.123162 27.761571,55.316609 27.263542,52.874348 25.455587,51.67973 24.777784,51.231865 24.010761,51.022217 23.253799,51.030369z M22.95236,44.11538L23.798931,47.304394 23.820499,47.409985C24.419302,47.450589,25.015712,47.566529,25.599384,47.75372L25.665401,47.653805 27.71519,45.074414 30.712991,47.05523 29.056375,49.909523 28.999517,49.995572C29.403156,50.459382,29.745935,50.96333,30.019874,51.498707L30.128641,51.47649 33.382753,51.007336 34.101965,54.52837 30.931781,55.469166 30.838098,55.488305C30.796382,56.089818,30.680124,56.688778,30.491348,57.274849L30.582653,57.335179 33.215211,59.303353 31.233726,62.302166 28.327211,60.725739 28.246421,60.672356C27.781021,61.076333,27.275636,61.418175,26.738351,61.692925L26.75992,61.79852 27.228536,65.052334 23.707992,65.771924 22.767259,62.601735 22.747476,62.504876C22.147392,62.462052,21.549863,62.344685,20.96491,62.155278L20.901104,62.251843 18.932104,64.884617 15.934304,62.903801 17.510131,59.996125 17.5692,59.906731C17.168106,59.443226,16.828031,58.940381,16.555049,58.405632L16.438343,58.429474 13.164777,58.805424 12.446047,55.284703 15.634409,54.436963 15.743176,54.414742C15.785224,53.81688,15.901978,53.222367,16.089817,52.639798L15.984646,52.570305 13.405155,50.521821 15.38664,47.523007 18.240089,49.179749 18.334741,49.242291C18.796659,48.841507,19.298553,48.50079,19.83155,48.22733L19.807064,48.107445 19.431335,44.834656z M49.723844,33.902482C47.754144,33.899346 45.876459,35.054593 45.058038,36.977158 43.966814,39.540581 45.164156,42.51574 47.72891,43.607533 50.294344,44.699612 53.268716,43.500313 54.359941,40.93689 55.451162,38.373471 54.25382,35.398312 51.688386,34.306229 51.047197,34.03328 50.380407,33.903527 49.723844,33.902482z M46.764089,25.314396L48.57591,29.153679 48.627725,29.282303C49.394968,29.194663,50.176679,29.202919,50.958807,29.304303L51.019159,29.162531 53.014371,25.422284 57.268163,27.233082 55.834356,31.229943 55.782377,31.352042C56.400896,31.845299,56.951707,32.403485,57.4227,33.017426L57.555193,32.964055 61.564512,31.614798 63.292242,35.903843 59.498651,37.830498 59.384534,37.876465C59.471403,38.647397,59.463342,39.4324,59.360513,40.217926L59.490072,40.273079 63.278696,42.15268 61.467306,46.407864 57.422933,45.087639 57.308294,45.038842C56.813104,45.658143,56.252882,46.208096,55.636716,46.680554L55.688528,46.809174 57.037077,50.818223 52.748724,52.546277 50.822103,48.752675 50.77458,48.63469C50.005197,48.719819,49.221742,48.710019,48.437479,48.606122L48.379148,48.743145 46.498571,52.532193 42.244779,50.721394 43.563951,46.675736 43.617948,46.548886C43.002721,46.055428,42.455587,45.498009,41.985956,44.88464L41.843793,44.94191 37.788213,46.177731 36.061163,41.88897 39.899366,40.07586 40.031858,40.022484C39.946256,39.256104,39.955976,38.476613,40.058431,37.695736L39.9092,37.632206 36.169088,35.638649 37.980478,31.383465 41.976339,32.817646 42.110651,32.874821C42.602174,32.260369,43.158235,31.712649,43.769277,31.242821L43.710455,31.096791 42.475057,27.042162z M18.660989,11.792016C14.879982,11.792016 11.804999,14.86901 11.804999,18.647999 11.804999,22.426989 14.879982,25.504014 18.660989,25.504014 22.443004,25.504014 25.517988,22.426989 25.517988,18.647999 25.517988,14.86901 22.443004,11.792016 18.660989,11.792016z M15.595985,0L21.867015,0 22.201,5.7499971 22.201,5.9300189C23.234996,6.2170067,24.219005,6.6209984,25.133009,7.1370196L25.270002,6.9999967 29.556991,3.1860025 33.992021,7.6209974 30.28099,12.041008 30.163009,12.15902C30.681015,13.075005,31.087997,14.059013,31.376998,15.094017L31.568008,15.094017 37.295,15.426993 37.295,21.699999 31.546005,22.201006 31.376998,22.201006C31.087997,23.236987,30.681015,24.220995,30.163009,25.137986L30.296005,25.271012 34.108995,29.557997 29.675003,33.992993 25.254987,30.281996 25.133009,30.159987C24.217998,30.675002,23.234996,31.078993,22.201,31.366011L22.201,31.568007 21.867015,37.296002 15.595985,37.296002 15.092994,31.546004 15.092994,31.358992C14.062995,31.069991,13.083991,30.664992,12.172003,30.149001L12.025,30.296004 7.619999,33.992993 3.1860064,29.557997 7.0130043,25.255997 7.1499978,25.119004C6.6359897,24.207995,6.2339817,23.229998,5.9469939,22.201006L5.7269927,22.201006 0,21.699999 0,15.426993 5.7489958,15.094017 5.9469939,15.094017C6.2339817,14.066001,6.6369968,13.086998,7.1499978,12.176018L6.9989968,12.025017 3.3029807,7.6209974 7.7379799,3.1860025 12.039008,7.0150104 12.172003,7.148006C13.082984,6.6310081,14.062995,6.2260094,15.092994,5.9370079L15.092994,5.728024z</x:String>
    <x:String x:Key="BirdIcon">M22.154999,0C24.042999,-1.7628554E-07 25.749008,0.65798962 26.947006,1.7119909 28.442001,1.4689947 29.848007,1.0179904 31.115997,0.39599609 30.626007,1.662995 29.585007,2.724992 28.229004,3.3959822 29.557007,3.2650008 30.822998,2.9739849 32,2.5419931 31.119995,3.6299912 30.007004,4.5850088 28.723999,5.3489863 28.737,5.581988 28.742996,5.8159968 28.742996,6.0510127 28.742996,13.215994 22.139008,21.478999 10.063995,21.478999 6.3560028,21.478999 2.9059982,20.581018 0,19.042016 0.51399994,19.092004 1.0370026,19.118005 1.5660019,19.118005 4.6419983,19.118005 7.4729996,18.251001 9.7189941,16.796991 6.8470001,16.753015 4.4220047,15.184989 3.586998,13.030996 3.987999,13.094992 4.3990021,13.128988 4.8219986,13.128988 5.4209976,13.128988 6.0009995,13.062002 6.5510025,12.938009 3.5480042,12.439993 1.2850037,10.249013 1.2850037,7.6219975 1.2850037,7.5989873 1.2850037,7.5760075 1.2860031,7.5540044 2.1709976,7.9600102 3.1839981,8.2029917 4.2600021,8.2320139 2.4980011,7.2589909 1.3390045,5.5999934 1.3390045,3.7180037 1.3390045,2.724992 1.663002,1.7929999 2.2280045,0.99299669 5.4660034,4.2729971 10.302994,6.4309875 15.759995,6.6580078 15.647995,6.2610046 15.589996,5.8470027 15.589996,5.4219844 15.589996,2.4279947 18.529007,-1.7628554E-07 22.154999,0z</x:String>
    <!-- Barn Icon Created by Loren Klein from the Noun Project -->
    <x:String x:Key="HomeIcon">M84.1 38.8L50 5L15.9 38.8L7 68.2l7-1V95H86V67.3l7 0.8L84.1 38.8z M23 36.1l3.9-3.9h14.2c-0.7 1.2-1.2 2.5-1.4 3.9H23z   M63 22.2H37.1l3.9-3.9H59L63 22.2z M66.1 25.3l3.9 3.9H56c-1.7-1.2-3.8-1.9-6-1.9c-2.2 0-4.3 0.7-6 1.9H30l3.9-3.9H66.1z   M60.3 36.1c-0.2-1.4-0.7-2.7-1.4-3.9h14.2l3.9 3.9H60.3z M50 30.3c4.1 0 7.4 3.3 7.4 7.4S54.1 45 50 45s-7.4-3.3-7.4-7.4  S45.9 30.3 50 30.3z M44 46.2c1.7 1.2 3.8 1.9 6 1.9c2.2 0 4.3-0.7 6-1.9h27.1l1.2 3.9H15.7l1.2-3.9H44z M85.2 53.2l1.2 3.9H13.6  l1.2-3.9H85.2z M82.2 43.1H58.9c0.7-1.2 1.2-2.5 1.4-3.9h19.8l1.3 1.3L82.2 43.1z M55.9 15.2H44.1L50 9.3L55.9 15.2z M19.9 39.2  h19.8c0.2 1.4 0.7 2.7 1.4 3.9H17.8l0.8-2.6L19.9 39.2z M11.4 64.5l1.3-4.3h74.7l1.3 4.4l-2.6-0.3L84.5 64h-1.6H17.1h-2.6l-0.5 0.1  L11.4 64.5z M32.4 78H17.1v-3.9h15.3V78z M32.4 71H17.1v-3.9h15.3V71z M17.1 81h15.3v3.9H17.1V81z M35.5 69.2l12.1 10.4L35.5 90  V69.2z M37.8 67.1h24.4L50 77.6L37.8 67.1z M50 81.6l12 10.3H38L50 81.6z M52.4 79.6l12.1-10.4V90L52.4 79.6z M67.6 81h15.3v3.9  H67.6V81z M67.6 78v-3.9h15.3V78H67.6z M67.6 71v-3.9h15.3V71H67.6z M17.1 88h15.3v3.9H17.1V88z M67.6 91.9V88h15.3v3.9H67.6z</x:String>
    <!-- Barn Icon Created by Bakunetsu Kaito from the Noun Project -->
    <x:String x:Key="FaultyHomeIcon">m 51.844 78.525 14.72 -9.041 0 18.082 z M 50 79.657 l 14.117 8.669 -28.233 0 z m -14.116 -10.932 28.233 0 L 50 77.395 Z m 12.274 9.8 -14.722 9.041 0 -18.082 z m 24.554 -49.861 -22.711 -9.407 -22.713 9.407 -9.406 22.713 0 38.877 13.626 0 0 -23.458 36.985 0 0 23.458 13.627 0 0 -38.877 -9.408 -22.713 z m -15.159 19.014 -15.106 0 0 -15.104 15.105 0 0 15.104 z m -13.177001 -6.587 4.66 0 0 4.659 -4.66 0 z m 0 -6.588002 4.66 0 0 4.659 -4.66 0 z m 6.588002 0 4.659 0 0 4.659 -4.659 0 z m 0 6.588002 4.659 0 0 4.659 -4.659 0 z M 10.369 55.401 C 10 55.401 9.625 55.33 9.264 55.18 7.787 54.568 7.086 52.877 7.698 51.401 L 19.764 22.27 50.001 9.747 80.237 22.271 92.302 51.402 c 0.611 1.477 -0.088 3.167 -1.564 3.779 -1.476 0.612 -3.167 -0.091 -3.778 -1.565 L 75.811 26.698 50.001 16.008 24.191 26.698 13.041 53.615 c -0.461 1.114 -1.539 1.786 -2.672 1.786 z</x:String>
    <x:String x:Key="InfoIcon">M17.137989,13.569001L17.161989,13.748992 17.161989,14.740941C17.161989,15.281914 17.15399,15.787888 17.137989,16.258863 17.123989,16.72884 17.10799,17.184816 17.09099,17.625793L17.09099,20.331654C17.09099,20.792629 17.11599,21.218609 17.161989,21.608587 17.20899,21.998568 17.283989,22.229555 17.386988,22.300552 17.487988,22.370548 17.643988,22.405546 17.855988,22.405546L18.219986,22.405546 18.336985,22.765528C18.290985,22.846523 18.197985,22.916521 18.055986,22.975517 17.914986,23.036514 17.804987,23.045513 17.726988,23.006516L14.270002,23.006516 13.918003,23.006516C13.776004,23.006516,13.651005,22.966518,13.541005,22.885523L13.541005,22.585537C13.635005,22.465544,13.784004,22.405546,13.988003,22.405546L14.411001,22.405546C14.616,22.284554 14.737,22.16256 14.775,22.041565 14.813999,21.920572 14.834,21.707582 14.834,21.404598 14.865999,21.121613 14.881,20.817629 14.881,20.494646 14.881,20.171661 14.888999,19.908676 14.904999,19.706686L14.904999,18.947725 14.904999,17.370806C14.904999,16.662842 14.884999,16.212866 14.846,16.021875 14.808,15.829885 14.755,15.592897 14.692,15.310912 14.553,15.371909 14.383001,15.441905 14.187002,15.521901 13.991003,15.601897 13.863004,15.641895 13.800004,15.641895L13.683004,15.461905C13.683004,15.281914,13.784004,15.120922,13.988003,14.979929L14.411001,14.679944C14.553,14.599949 14.692,14.524953 14.834,14.454956 14.975999,14.384959 15.147998,14.309963 15.351997,14.228968 15.602996,14.068975 15.871995,13.933983 16.162994,13.822989 16.451992,13.712995 16.77799,13.627998 17.137989,13.569001z M16.080093,7.1960044C16.331078,7.1960044 16.559063,7.2860045 16.761049,7.4660044 16.966036,7.6460047 17.142023,7.8670049 17.291014,8.1270046 17.441004,8.3880053 17.514,8.628005 17.514,8.8480053 17.514,9.3500061 17.370008,9.7400064 17.079027,10.020006 16.790047,10.302007 16.577061,10.461007 16.444071,10.502007 16.312078,10.542007 16.159088,10.562007 15.9871,10.562007 15.813111,10.562007 15.645123,10.517007 15.481133,10.427007 15.315145,10.336007 15.155154,10.171007 15.000165,9.930007 14.842175,9.6900063 14.741182,9.4400063 14.692185,9.1790056 14.662188,8.838006 14.741182,8.4780054 14.92917,8.0970049 15.117157,7.7170048 15.343143,7.4560046 15.610125,7.3150043 15.799112,7.2360044 15.954103,7.1960044 16.080093,7.1960044z M16,2.071991C8.3200073,2.071991 2.0710449,8.3200073 2.0710449,16 2.0710449,23.681 8.3200073,29.928986 16,29.928986 23.680054,29.928986 29.929016,23.681 29.929016,16 29.929016,8.3200073 23.680054,2.071991 16,2.071991z M16,0C24.822998,0 32,7.178009 32,16 32,24.822998 24.822998,32 16,32 7.177002,32 0,24.822998 0,16 0,7.178009 7.177002,0 16,0z</x:String>
    <x:String x:Key="HorseIcon">M11.462015,30.875999L11.366983,30.879997 11.233988,30.959 11.058023,31.127998 10.993997,31.252998 10.972025,31.361 10.99003,31.447998 11.046976,31.487999 11.097025,31.487999 11.21702,31.463997 11.34202,31.386002 11.418985,31.272995 11.483011,31.153 11.522013,31.000999 11.509012,30.913002z M12.319987,29.540001L12.20103,29.558998 12.040995,29.659996 11.960978,29.761002 11.95103,29.848 11.999003,29.933998 12.077983,29.959999 12.186992,29.920998 12.275981,29.866997 12.347026,29.765999 12.389994,29.659996 12.37498,29.572998z M14.246991,27.606995L14.111005,27.695 14.019025,27.792999 13.535015,28.278 13.136028,28.707001 12.852032,29.020996 12.837993,29.108002 12.868999,29.160995 12.934978,29.164001 13.038006,29.098 13.220013,28.933998 13.451031,28.723999 13.699993,28.466995 13.934978,28.238998 14.142011,27.999001 14.296002,27.770996 14.337994,27.669998 14.296002,27.616997z M15.100018,27.113998L15.012982,27.144997 14.91203,27.254997 14.879987,27.345001 14.896039,27.432999 14.967023,27.474998 15.027997,27.474998 15.119,27.371002 15.219038,27.277 15.264997,27.201996 15.259992,27.144997 15.210005,27.117996z M12.14799,24.946999L12.048991,24.973999 11.915995,25.078995 11.891032,25.179001 11.915995,25.268997 11.995036,25.306999 12.13798,25.268997 12.238017,25.184998 12.290995,25.078995 12.290995,24.994995 12.256999,24.946999z M9.1620253,24.781998L9.1169814,24.802002 9.1070326,24.889999 9.1410292,24.966995 9.2090224,24.993996 9.2109755,24.996002 9.2439955,25.006996 9.3710097,25.057999 9.6930321,25.177002 10.100991,25.299995 10.279031,25.316002 10.464028,25.328995 10.642008,25.365997 10.845011,25.407997 10.962015,25.399002 10.999002,25.340996 10.999002,25.263 10.967996,25.213997 10.904032,25.194 10.897989,25.186996 10.801004,25.160995 10.722025,25.143997 10.67399,25.131996 10.657999,25.129997 10.626993,25.124001 10.428994,25.109001 10.194009,25.066002 9.9100121,25.028999 9.7369775,25 9.5970238,24.962997 9.4640282,24.896996z M13.391033,24.722L13.360027,24.755997 13.350994,24.864998 13.350994,25.048996 13.396038,25.249001 13.470989,25.390999 13.633037,25.609001 13.738994,25.737 13.796978,25.758995 13.829021,25.754997 13.845989,25.722 13.806988,25.553001 13.766033,25.469002 13.761028,25.405998 13.757,25.228996 13.709026,25.008995 13.637005,24.842995 13.538006,24.748001 13.462017,24.722z M6.4020137,24.506996L6.2850093,24.511002 6.1280264,24.545998 5.9930166,24.594002 5.9540151,24.657997 5.9490102,24.743996 6.0190176,24.842995 6.0669912,24.886002 6.1709951,24.918999 6.2889765,24.915001 6.3450068,24.895996 6.4159907,24.804001 6.4549922,24.700996 6.4729975,24.618996 6.4590205,24.545998z M13.397014,22.000999L13.262981,22.015999 13.11198,22.179001 12.944012,22.389 12.842022,22.729996 12.796977,22.978996 12.801005,23.160995 12.86198,23.32 12.939983,23.32 12.969036,23.274002 12.978008,23.248001 12.980022,23.248001 13.001995,23.207001 13.021038,23.143997 13.074993,22.957001 13.181988,22.747002 13.238994,22.612999 13.353008,22.335999 13.434978,22.108002z M17.126999,20.356995L17.01799,20.388 16.913986,20.500999 16.931015,20.683998 17.113998,20.903999 17.139999,21.142998 17.184006,21.210999 17.266037,21.228996 17.358017,21.183998 17.383041,21.077995 17.426987,20.960999 17.444992,20.834999 17.444992,20.668999 17.418991,20.542999 17.319015,20.433998 17.23204,20.364998z M9.9640282,19.738998L10.142007,19.792999 10.319009,19.877998 10.655008,20.127998 10.952982,20.353996 11.186991,20.577995 11.374002,20.819 11.546976,21.100998 11.639994,21.389 11.676981,21.657997 11.87498,21.797997 12.152995,22.002998 12.292033,22.113998 12.358989,22.330002 12.301982,22.583 12.256999,23.085999 12.256999,23.514 12.311015,23.955002 12.434977,24.467995 12.603007,24.923996 12.814982,25.361 12.97398,25.661995 13.011028,25.903 12.882,26.200996 12.761027,26.444 12.535991,26.500999 12.115031,26.483002 11.956035,26.368996 11.546976,26.181999 11.085977,26.022995 10.616983,25.883995 9.973977,25.743996 9.5810326,25.687996 9.0049819,25.613998 8.8270034,25.661995 8.6789931,25.613998 8.3870009,25.595001 8.1559819,25.604996 8.0599736,25.575996 8.0069951,25.465996 7.8929814,25.407997 7.6239995,25.493996 7.3069824,25.567001 6.93302,25.604996 6.5149897,25.595001 6.2239736,25.500999 5.9549917,25.351997 5.7190298,25.183998 5.571019,25.119995 5.321019,24.776001 5.1869858,24.558998 5.1789902,24.43 5.2259873,24.362999 5.3650254,24.325996 5.5619858,24.242996 5.8500112,24.223999 6.1420034,24.223999 6.4579829,24.251999 6.8769897,24.262001 7.1390136,24.279999 7.3799804,24.362999 7.8010014,24.43 7.9789799,24.43 8.1080083,24.373001 8.3520278,24.362999 8.5640029,24.401001 8.7510146,24.484001 9.032997,24.606995 9.5909814,24.765999 10.048989,24.848 10.572977,24.915001 11.206034,24.952995 11.566995,24.933998 11.686991,24.886002 11.715983,24.755997 11.706035,24.598 11.491007,24.308998 11.233988,23.955002 10.907999,23.403999 10.634989,22.909996 10.457986,22.573997 10.207986,22.002998 10.021035,21.556999 9.796975,20.808998 9.6659936,20.241997 9.6280297,19.988998 9.6849755,19.849998 9.8149804,19.764z M3.3999969,18.913002L3.3349944,18.929001 3.3069793,19.022995 3.3399993,19.183998 3.4219695,19.257996 3.5370208,19.278999 3.631992,19.210999 3.6479832,19.105995 3.631992,19.016998 3.5599705,18.944z M12.969036,17.346001L12.882,17.360001 12.824992,17.419998 12.746989,17.614998 12.712993,17.769997 12.647014,18.050995 12.62498,18.276001 12.684001,18.338997 12.805034,18.353996 12.866009,18.309998 12.895,18.191002 12.98698,18.020996 13.077007,17.928001 13.176006,17.847 13.238994,17.719002 13.222027,17.5 13.134991,17.386002 13.085003,17.346001z M7.637,16.512001L7.5740117,16.575996 7.5669926,16.743996 7.6039799,16.925995 7.7010258,17.076996 7.8580083,17.240997 8.0989751,17.475998 8.2989882,17.633995 8.572975,17.886002 8.8240126,18.084 9.0610121,18.244995 9.6460351,18.641998 10.022988,18.893997 10.246987,19.034996 10.332986,19.042 10.303994,18.990997 10.259988,18.920998 10.191995,18.855995 10.134011,18.799995 10.037026,18.709 10.025002,18.697998 9.980996,18.655998 9.717019,18.417999 9.532998,18.253998 9.4490136,18.178001 9.4319848,18.163002 9.4230126,18.154999 9.3790053,18.116997 8.8879775,17.687996 8.717018,17.497002 8.5370253,17.297997 8.3920058,17.119995 8.2460087,16.972 8.1080083,16.805 7.9209965,16.623001 7.7480229,16.518997z M0.4779973,15.614998L0.31399583,15.706001 0.39599656,15.878998 0.55899095,15.920998 0.65798997,15.790001 0.61999559,15.632996z M21.064012,15.525002L20.972032,15.610001 20.976,15.643997 20.942003,15.679001 20.957994,15.826996 20.983019,16.092995 21.011034,16.375 21.030992,16.618996 21.017992,16.897995 21.006029,17.208 20.949022,17.487 20.882006,17.730995 20.767992,17.967995 20.619005,18.211998 20.467028,18.449997 20.332018,18.632996 20.266039,18.716995 20.101,18.920998 20.00902,19.074997 19.942003,19.216995 19.956041,19.321999 20.041002,19.358002 20.199022,19.321999 20.50902,19.045998 20.780016,18.75 20.957994,18.513 21.116991,18.236 21.223009,17.983002 21.314012,17.693001 21.381029,17.352997 21.387987,17.198997 21.387987,16.882996 21.387987,16.580002 21.335008,16.274002 21.26201,15.955002 21.17003,15.687996 21.12401,15.554001z M1.6309829,14.934998L1.6040053,14.965996 1.5560012,15.240997 1.5650039,15.517998 1.6170058,15.756996 1.7189956,16.010002 1.8200087,16.170998 1.8899856,16.293999 1.9339924,16.361 1.9919758,16.400002 2.0220052,16.400002 2.0489827,16.367996 2.0710164,16.272995 2.0090047,15.945999 1.9649982,15.746002 1.863008,15.517998 1.7890029,15.327995 1.7869887,15.187996 1.7770095,15.144997 1.7760024,15.136002 1.7659926,15.101997 1.7510085,15.039001 1.7460036,15.033997 1.7269912,14.970001 1.678987,14.934998z M3.3559905,14.141998L3.2410003,14.146996 3.3260223,14.216995 3.3119842,14.217995 3.3890105,14.274002 3.5840183,14.390999 3.8050266,14.575996 4.0329932,14.733002 4.1310157,14.781998 4.2720069,14.806 4.3799781,14.797997 4.3979834,14.733002 4.3840064,14.652 4.2980078,14.562996 4.1310157,14.478996 3.8990208,14.364998 3.6999851,14.255997 3.4830047,14.195999z M1.6510024,13.005997L1.5729995,13.036995 1.5249953,13.100998 1.3670058,13.330002 1.363008,13.487 1.4259963,13.529999 1.5199904,13.5 1.6139846,13.389999 1.6780105,13.267998 1.7250075,13.177002 1.7250075,13.045998z M11.517984,12.673996L11.381998,12.709999 11.334024,12.943001 11.431986,13.348999 11.670999,13.938995 11.970988,14.414001 12.311992,14.834 12.688029,15.200996 12.913006,15.409996 13.342998,15.629997 12.790019,14.931 12.517985,14.540001 12.314006,14.239998 11.985026,13.726997 11.858012,13.375999 11.788981,13.036995 11.706035,12.806 11.678018,12.792999 11.657022,12.737z M5.3869981,11.550995L5.3050278,11.572998 5.2070054,11.701996 5.0619854,11.955002 4.9869732,12.219002 4.9050029,12.487 4.7649883,12.768997 4.6570171,12.934998 4.5519751,13.040001 4.5599712,13.129997 4.6340069,13.178001 4.7649883,13.132996 4.8890117,13.045998 5.1319932,12.764 5.2720078,12.372002 5.4289902,11.962997 5.4850205,11.663002 5.4620103,11.580002z M10.210976,11.209999L10.064004,11.230995 9.9059838,11.353996 9.8039941,11.485001 9.8039941,11.616997 9.8689965,11.699997 9.947976,11.803001 10.087991,11.834999 10.298989,11.832001 10.384988,11.781998 10.426003,11.658997 10.405007,11.485001 10.389016,11.32 10.348,11.247002z M3.1800262,11.203995L3.1559783,11.25 3.1680022,11.344002 3.2170135,11.406998 3.3299895,11.521996 3.4510223,11.624001 3.5929905,11.705002 3.6930271,11.767998 3.7339817,11.764999 3.7549778,11.726997 3.7510105,11.653999 3.697971,11.572998 3.6089817,11.466995 3.4640227,11.377998 3.3719817,11.292999 3.2930022,11.221001 3.2220184,11.207001z M1.777986,10.650002L1.6419997,10.681999 1.5490127,10.776001 1.3689895,11.040001 1.2890024,11.402 1.2680063,11.592995 1.2680063,11.735001 1.3060007,11.806999 1.4169936,11.781998 1.6210036,11.514 1.7359939,11.195999 1.8249831,10.913002 1.829988,10.714996z M12.194011,9.9499969L12.058024,9.9609985 11.969035,10.034996 11.947978,10.127998 12.037028,10.231995 12.173015,10.244995 12.277995,10.208 12.313029,10.153 12.309001,10.059998z M8.631996,9.8479996L8.1849736,9.9150009 7.7400273,10.084999 7.5240239,10.194 7.4629887,10.277 7.4560307,10.390999 7.4809941,10.435997 7.551978,10.447998 7.6540288,10.439995 7.8110112,10.399002 7.9709843,10.344002 8.1719731,10.303001 8.3530043,10.281998 8.5569838,10.285995 8.7980117,10.285995 8.9919814,10.311996 9.1490248,10.371002 9.3149794,10.428001 9.4250268,10.445 9.4969872,10.428001 9.5170067,10.348 9.5060204,10.236 9.3400038,10.035995 9.1290053,9.9069977 8.9040297,9.8479996z M2.3999959,9.762001L2.2909871,9.772995 2.0820028,9.802002 1.9809894,9.8860016 1.949007,10.017998 1.9779987,10.114998 2.0940267,10.168999 2.2859822,10.145996 2.4869715,10.056999 2.5459925,9.9440002 2.5409876,9.8600006 2.473971,9.7859955z M12.220012,9.6100006L12.134013,9.6399994 12.097026,9.7319946 12.097026,9.8619995 12.181987,9.9150009 12.342021,9.9150009 12.449992,9.784996 12.439006,9.6889954 12.332011,9.6100006z M12.397014,9.1609955L12.285991,9.1839981 12.194987,9.2600021 12.194987,9.362999 12.230021,9.4489975 12.340983,9.4789963 12.447978,9.461998 12.550029,9.4039993 12.582988,9.322998 12.569011,9.223999 12.503032,9.1609955z M12.538981,8.7360001L12.413005,8.7589951 12.295024,8.8209991 12.272014,8.9229965 12.275004,8.9919968 12.353007,9.0550003 12.49998,9.060997 12.631022,9.0149994 12.684977,8.9179993 12.644999,8.798996z M12.712993,8.2139969L12.57603,8.2569962 12.493022,8.2929993 12.434977,8.3669968 12.360026,8.4570007 12.368022,8.5509949 12.475993,8.6110001 12.598002,8.6110001 12.722026,8.5810013 12.783976,8.4929962 12.850994,8.3919983 12.866985,8.2789993 12.819011,8.2139969z M12.798991,7.7279968L12.658,7.75 12.56602,7.822998 12.519999,7.901001 12.512004,8.0139999 12.569987,8.0919952 12.707988,8.0979996 12.816996,8.0670013 12.923015,8.0459976 12.982036,7.9369965 12.982036,7.8479996 12.926983,7.7610016z M12.967998,7.3310013L12.850994,7.3339996 12.769023,7.3740005 12.694987,7.4199982 12.608012,7.5100021 12.590983,7.6080017 12.626017,7.6819992 12.689982,7.7070007 12.741984,7.7179947 12.873028,7.7179947 12.975994,7.677002 13.110027,7.6119995 13.126995,7.4819946 13.079998,7.3740005z M12.959026,6.8899994L12.861004,6.9169998 12.652018,7.0449982 12.603984,7.086998 12.587993,7.1549988 12.618022,7.237999 12.735026,7.2579956 12.876995,7.2429962 12.998028,7.1920013 13.081035,7.1090012 13.126995,7.0139999 13.126995,6.9570007 13.081035,6.9059982z M12.993999,6.5029984L12.892986,6.5139999 12.795024,6.5390015 12.710978,6.5960007 12.668986,6.6529999 12.654033,6.7509995 12.673015,6.8389969 12.749003,6.8649979 12.82902,6.875 12.993999,6.7979965 13.059002,6.7149963 13.092998,6.6329956 13.092998,6.5540009 13.064983,6.5079956z M10.668009,6.2190018L10.545998,6.237999 10.387002,6.3860016 10.350991,6.4420013 10.344033,6.4489975 10.264016,6.5769958 10.079018,6.862999 9.7530297,7.4729996 9.5690087,7.8249969 9.3639907,8.2350006 9.1160048,8.6069946 9.0530165,8.7480011 9.0630263,8.7979965 9.1290053,8.8009949 9.3340224,8.723999 9.6280297,8.4259949 10.01603,7.8990021 10.259011,7.4629974 10.477029,7.1139984 10.639994,6.7009964 10.712992,6.4430008 10.720011,6.2979965z M13.040996,6.0439987L12.944012,6.048996 12.827983,6.0749969 12.749003,6.1339951 12.691996,6.1940002 12.681987,6.2939987 12.697001,6.3740005 12.772014,6.4179993 12.850994,6.4379959 12.991009,6.3839951 13.094036,6.3089981 13.116985,6.2439957 13.129986,6.1339951 13.108013,6.0649948z M12.960979,5.5279999L12.889995,5.5329971 12.853985,5.5779953 12.835978,5.6429977 12.816996,5.7249985 12.775981,5.7539978 12.696025,5.7539978 12.673991,5.798996 12.681987,5.8819962 12.743022,5.9570007 12.803019,5.9789963 12.863018,5.987999 13.007,5.9189987 13.118023,5.8219986 13.128033,5.6800003 13.118023,5.5929947 13.043987,5.552002z M12.985027,5.1609955L12.85899,5.1660004 12.754008,5.2149963 12.689006,5.2929993 12.689006,5.3839951 12.759013,5.461998 12.868999,5.461998 12.975017,5.4329987 13.04002,5.3499985 13.079998,5.2679977 13.059978,5.1910019z M12.969036,4.7750015L12.863994,4.8549957 12.801005,4.8929977 12.743998,4.9539948 12.720988,5.0410004 12.743998,5.1059952 12.840984,5.1179962 12.913006,5.1059952 12.993999,5.0839996 13.079998,5.0459976 13.133037,4.9649963 13.147014,4.8720016 13.079998,4.7789993z M11.259012,4.6220016L11.095987,4.7070007 10.986978,4.7929993 10.758036,4.9469986 10.587992,5.0369949 10.50602,5.0739975 10.501992,5.0810013 10.441018,5.1110001 10.392983,5.2039948 10.438027,5.2699966 10.568033,5.2699966 10.77598,5.2649994 10.97703,5.1759949 11.176981,5.0690002 11.322977,4.8819962 11.363016,4.7719955 11.363016,4.6500015z M13.012005,4.3649979L12.907024,4.3809967 12.840984,4.4189987 12.766032,4.4949951 12.762004,4.586998 12.762004,4.673996 12.811992,4.7019958 12.879986,4.711998 12.978985,4.673996 13.085979,4.5810013 13.092998,4.4949951 13.081035,4.4020004z M10.915018,4.1910019L10.814005,4.1999969 10.675028,4.2939987 10.522012,4.4420013 10.387978,4.6139984 10.322976,4.7139969 10.335,4.788002 10.423989,4.8160019 10.611002,4.7679977 10.793009,4.6520004 10.947,4.5039978 10.983011,4.3639984 10.983011,4.2360001z M7.5279912,4.1309967L7.4469975,4.1360016 7.3819946,4.2060013 7.3710083,4.3209991 7.4050049,4.4140015 7.4709843,4.4489975 7.5810307,4.4489975 7.6680058,4.4079971 7.6849736,4.3209991 7.6559819,4.2169952 7.6150273,4.1539993z M12.887005,4.0390015L12.800029,4.0459976 12.733989,4.0789948 12.667033,4.1699982 12.63499,4.2389984 12.652018,4.3180008 12.722026,4.3639984 12.809977,4.3529968 12.876018,4.3119965 12.947979,4.2559967 12.975994,4.1539993 12.957012,4.0849991z M10.068032,3.987999L9.9289941,4.0800018 9.6990136,4.2319946 9.3440322,4.4990005 9.3310317,4.5069962 9.3279799,4.5110016 9.322975,4.5139999 9.2670058,4.5499954 9.2179946,4.6210022 9.2519911,4.6899948 9.4430321,4.6899948 9.657998,4.6380005 9.8689965,4.5299988 9.9850243,4.3389969 10.030984,4.1759949 10.124978,4.0960007 10.116982,3.987999z M6.9380249,3.552002L6.8730224,3.5919952 6.7930049,3.7159958 6.7809809,3.8069992 6.8520263,3.8860016 6.9380249,3.9259949 7.0439819,3.9219971 7.097998,3.8969955 7.1349853,3.7779999 7.1249756,3.6429977 7.097998,3.5670013 7.0229858,3.552002z M6.3849848,3.522995L6.2569936,3.586998 6.0979971,3.802002 6.0350088,4.0209961 6.0630239,4.177002 6.1399892,4.223999 6.3619746,4.177002 6.5259761,4.0849991 6.644018,3.9679947 6.7079829,3.7539978 6.644018,3.651001 6.5019892,3.5339966z M12.220988,2.822998L12.162028,2.901001 12.105998,2.9789963 12.017985,3.0810013 11.95402,3.2490005 11.95402,3.3969955 11.983989,3.4789963 11.975993,3.5649948 12.032023,3.5849991 12.098979,3.5769958 12.296,3.4909973 12.467021,3.3969955 12.644023,3.2910004 12.780986,3.2399979 12.863018,3.2159958 12.944012,3.0879974 12.884014,3.0299988 12.769023,3.038002 12.633036,3.0690002 12.480998,3.1309967 12.381999,3.1380005 12.371989,3.0820007 12.38499,2.947998 12.370036,2.9339981 12.374003,2.8889999 12.309977,2.822998z M9.506997,2.5250015L9.2560195,2.5369949 9.0979994,2.6749954 9.0279921,2.8289948 9.0770034,2.9669952 9.2450331,3.0680008 9.4250268,3.1719971 9.6269921,3.2579956 9.7950219,3.2579956 9.9529809,3.1949997 9.9629906,3.0299988 9.9040307,2.8399963 9.8320092,2.663002 9.6899804,2.5599976z M8.6490248,2.4749985L8.5510024,2.4899979 8.4209975,2.560997 8.1349858,2.711998 7.8359746,2.862999 7.7309941,2.9199982 7.592017,2.9869995 7.5640019,3.0110016 7.5359868,3.026001 7.4590214,3.0999985 7.446021,3.1110001 7.4469975,3.1110001 7.373999,3.1800003 7.4770268,3.1920013 7.6780156,3.1759949 7.8849858,3.1169968 8.2209843,3.0139999 8.4620131,2.8460007 8.5649794,2.7519989 8.6119765,2.7399979 8.683998,2.7210007 8.6899794,2.7109985 8.7519911,2.6920013 8.7939833,2.6080017 8.7309951,2.4939957z M10.21,1.9929962L10.10801,2.0459976 9.9599999,2.1049957 9.9269799,2.2210007 9.9869775,2.348999 10.150002,2.3389969 10.285012,2.2789993 10.38102,2.1520004 10.340005,2.0459976z M12.283,1.8030014L12.146037,1.8089981 12.056987,1.8610001 11.996013,1.9309998 12.046977,2.0149994 12.118998,2.0599976 12.241008,2.086998 12.342997,2.0739975 12.41801,1.9499969 12.436992,1.8789978 12.386027,1.8349991z M12.059977,1.3119965L11.837993,1.3419952 11.780986,1.4909973 11.860026,1.5709991 11.952983,1.5960007 12.074015,1.6080017 12.197978,1.5960007 12.279033,1.5429993 12.274028,1.4349976 12.18101,1.3669968z M10.470986,0L10.905008,0.39599609 11.267008,0.78299713 11.371012,0.92199707 11.754985,0.92199707 12.197978,0.99099731 12.535991,1.1439972 12.801982,1.3170013 13.035992,1.5769958 14.353986,1.552002 14.533002,1.5859985 14.717999,1.6339951 14.762982,1.7159958 14.684003,1.8209991 14.345013,1.9249954 13.889019,2.0779953 13.632,2.298996 14.098004,2.4160004 14.436993,2.6159973 14.82298,3.0209961 15.279035,3.1289978 15.827009,3.5240021 16.176008,4.038002 16.084028,4.1329956 15.827009,3.9809952 15.509992,3.6289978 15.126997,3.6289978 14.684003,3.5240021 14.353986,3.4079971 14.061993,3.2559967 13.900982,3.185997 13.796978,3.2679977 13.923015,3.3850021 14.262982,3.6169968 14.566998,3.9219971 14.706036,4.0859985 15.301985,4.1199951 15.615034,4.1549988 16.048994,4.3779984 16.327986,4.6339951 16.423017,4.7289963 16.410017,4.8330002 16.293012,4.7979965 16.014021,4.598999 15.745039,4.4949951 15.392988,4.5169983 15.163008,4.5419998 14.892987,4.5419998 14.728009,4.5289993 14.600995,4.4819946 14.519025,4.423996 14.462018,4.4949951 14.624005,4.6899948 14.926984,4.8549957 15.488996,5.125 15.767012,5.2509995 16.084028,5.3209991 16.106001,5.4379959 15.827009,5.5209961 15.50102,5.4639969 15.197004,5.3209991 14.788007,5.298996 14.392987,5.1339951 14.145001,5.0079956 13.970989,4.9860001 13.994,5.125 14.483991,5.5550003 14.845013,6.0940018 14.949994,6.3600006 15.163008,6.538002 15.418989,6.8160019 15.488996,7.0629959 15.731001,7.284996 15.848982,7.3289948 15.767012,7.4589996 15.606001,7.4109955 15.324018,7.2589951 15.009992,6.9679947 14.762982,6.7819977 14.296979,6.512001 13.840984,6.151001 13.762005,6.151001 13.783977,6.2460022 14.133038,6.5719986 14.449994,6.7679977 14.728009,6.9459991 14.983991,7.177002 15.033002,7.3639984 15.231001,7.6549988 15.384016,7.784996 15.639997,7.8199997 15.86302,7.8769989 16.014021,8.0069962 15.908979,8.098999 15.639997,8.1459961 15.392988,8.0769958 15.207014,8.0769958 14.962994,7.9499969 14.811017,7.7369995 14.574994,7.6549988 14.087994,7.5159988 13.840984,7.4589996 13.73698,7.5289993 13.772014,7.6459961 14.004987,7.7719955 14.262982,8.0769958 14.379987,8.2159958 14.798016,8.4029999 15.207014,8.7669983 15.441023,9.1279984 15.558027,9.2890015 15.686994,9.2799988 15.731001,9.3499985 15.639997,9.4069977 15.564009,9.4389954 15.545027,9.4410019 15.197004,9.2330017 15.066998,8.9889984 14.728009,8.9509964 14.462018,8.810997 14.181012,8.7669983 14.24101,8.9160004 14.449994,9.0810013 14.426984,9.1849976 14.228009,9.163002 14.087994,9.0589981 13.853985,8.9889984 13.667034,8.9509964 13.645,9.0329971 13.772014,9.197998 14.061993,9.302002 14.40202,9.3590012 14.658002,9.4899979 14.892987,9.6889954 15.163008,9.7799988 15.475996,9.7929993 15.628034,9.862999 15.56999,9.9899979 15.371015,10.084999 14.996991,10.071999 14.624005,9.909996 14.392987,9.7929993 14.158002,9.723999 13.853985,9.723999 13.667034,9.8279953 13.762005,9.8850021 14.017987,9.9199982 14.296979,9.9550018 14.636029,10.131996 14.845013,10.327995 15.043988,10.458 15.207014,10.505997 15.113995,10.575996 14.915021,10.575996 14.671979,10.515999 14.519025,10.388 14.33299,10.423996 13.983991,10.423996 13.645,10.445 13.306988,10.445 13.154034,10.423996 13.14201,10.515999 13.306988,10.619995 13.689007,10.75 14.087994,10.832001 14.353986,11.028999 14.574994,11.288002 14.82298,11.484001 15.079999,11.519997 15.384016,11.472 15.731001,11.358002 15.837019,11.25 15.94499,11.240997 15.93199,11.322998 15.814009,11.437996 15.545027,11.601997 15.348982,11.694 14.996991,11.740997 14.504987,11.671997 14.228009,11.532997 13.702007,11.322998 13.210979,11.240997 12.944988,11.111 12.827983,11.193001 12.944988,11.288002 13.537029,11.519997 14.027997,11.776001 14.345013,12.057999 14.462018,12.219002 14.392987,12.313995 14.204999,12.231995 13.796978,11.962997 13.514995,11.871002 13.271038,11.871002 13.236004,11.962997 13.410992,11.998001 13.725017,12.114998 14.040997,12.405998 14.204999,12.675995 14.319989,12.849998 14.449994,12.931999 14.533002,12.896996 14.600995,12.909996 14.540997,13.027 14.275006,12.991997 14.017987,12.814995 13.819012,12.653999 13.431988,12.419998 13.072002,12.253998 12.756999,12.266998 12.371989,12.080002 12.128032,11.915001 12.033,11.998001 11.985026,12.419998 12.171977,13.097 12.863018,14.227997 13.493023,15.010002 14.647992,15.978996 15.452986,16.524002 15.710005,16.723 16.293012,17.201996 16.866989,17.75 17.132004,18.111 17.332016,18.358002 17.634994,18.776001 17.858017,19.209999 17.939987,19.511002 18.558029,19.476997 19.199999,19.114998 19.699999,18.658997 20.192003,18.041 20.322008,17.644997 20.392015,17.292999 20.436022,16.862 20.436022,16.453995 20.331041,15.696999 20.15703,14.706001 19.887987,13.878998 19.74901,13.352997 19.608995,12.744995 19.631029,12.163002 19.679003,11.731995 19.840013,11.297997 20.134996,10.763 20.436022,10.353996 20.857042,9.9799957 21.197008,9.6889954 21.395006,9.5589981 21.639027,9.4319992 21.69701,9.4769974 21.652027,9.5719986 21.173021,9.9799957 20.787035,10.458 20.496019,11.054001 20.331041,11.588997 20.296984,12.068001 20.309008,12.323997 20.343004,12.431 20.449022,12.441002 20.986986,11.810997 21.348009,11.461998 21.660999,11.206001 21.874012,10.853996 22.012989,10.505997 22.09502,10.423996 22.140003,10.550995 22.10503,10.948997 21.827015,11.519997 21.358019,12.219002 21.126024,12.594002 21.030992,12.991997 21.00902,13.422997 21.09099,13.796997 21.231005,13.878998 21.383043,13.853996 21.522999,13.540001 21.639027,13.352997 21.931019,13.001999 22.035023,12.931999 22.223011,12.653999 22.327015,12.535995 22.327015,12.335999 22.45702,12.396996 22.549,12.522995 22.525989,12.663002 22.421985,12.945 22.140003,13.388 21.944019,13.819 21.848987,14.252998 21.804005,14.601997 21.861011,15.150002 21.952991,15.674995 22.061024,16.188995 22.153004,16.896996 22.140003,17.470001 22.082996,18.111 21.908985,18.508995 21.69701,18.928001 21.322985,19.431 21.030992,19.687996 20.705004,19.862 20.400987,20.001999 20.179003,20.048996 20.121996,20.153999 20.283007,20.422997 20.470018,20.796997 20.496019,20.983002 20.496019,21.205002 20.426988,21.649002 20.413988,22.044998 20.426988,22.313995 20.504991,22.583 20.517992,22.639999 20.470018,22.735001 20.366991,22.696999 20.205004,22.335999 20.192003,22.326996 20.074999,21.961998 20.027025,21.436996 19.947985,21.040001 19.761034,20.701996 19.608995,20.514999 19.491991,20.457001 19.39702,20.504997 19.421984,20.644997 19.631029,20.935997 19.735033,21.309998 19.735033,21.845001 19.653002,22.382996 19.561022,22.792 19.353014,23.166 19.095993,23.573997 18.988998,23.783997 18.827011,24.004997 18.686996,24.192001 18.723007,24.401001 18.767013,24.517998 18.686996,24.625999 18.535019,24.625999 18.452988,24.378998 18.431015,24.122002 18.452988,23.901001 18.569992,23.539001 18.767013,23.117996 18.978989,22.674995 19.048996,22.219002 19.069992,21.882996 19.022995,21.670998 18.965988,21.448997 18.908981,21.391998 18.839035,21.391998 18.779037,21.518997 18.731003,21.788002 18.686996,22.173996 18.590988,22.439995 18.590988,22.756996 18.639999,22.944 18.605026,23.026001 18.535019,23.034996 18.452988,22.919998 18.384018,22.465996 18.384018,21.892998 18.405014,21.624001 18.431015,21.448997 18.39299,21.193001 18.371017,20.923996 18.28801,20.726997 18.197006,20.667 18.090988,20.692001 17.996994,20.783997 17.975021,21.064995 17.939987,21.436996 17.822983,21.834999 17.658005,22.196999 17.483993,22.513 17.275009,22.883995 16.983017,23.213997 16.701033,23.539001 16.423017,23.831001 16.014021,24.18 15.710005,24.438995 15.452986,24.600998 15.314009,24.683998 15.18498,24.891998 15.058026,25.325996 15.009992,25.487 15.009992,25.769997 15.033002,25.977997 15.137007,26.178001 15.348982,26.444 15.481001,26.671997 15.542036,26.811996 15.593,27.052002 15.606001,27.309998 15.663008,27.448997 15.58,27.552002 15.545027,27.646996 15.452986,27.739998 15.066998,27.951996 14.684003,28.208 14.181012,28.673996 13.910992,28.956001 13.783977,29.211998 13.632,29.304001 13.446026,29.408997 13.314983,29.503998 13.293011,29.712997 13.189007,29.969002 12.988994,30.250999 12.827983,30.459999 12.675029,30.555 12.581035,30.625 12.488994,30.598999 12.405986,30.576996 12.428996,30.694 12.475993,30.811996 12.392985,31.369995 12.241008,31.512001 12.079997,31.582001 11.975993,31.582001 11.926982,31.698997 11.624002,31.838997 11.392984,31.976997 11.170999,32 10.91398,32 10.657999,32 10.519021,31.850998 10.504983,31.615997 10.574991,31.324997 10.693033,30.973 10.892984,30.625 11.217996,30.284996 11.483988,29.994995 11.719035,29.702995 11.824015,29.490997 12.019999,29.304001 12.350016,29.177002 12.50999,29.082001 12.884991,28.673996 13.293011,28.229996 13.702007,27.703995 13.900982,27.425995 14.027997,27.216995 14.074994,27.087997 14.074994,26.981995 14.004987,26.831001 13.645,26.387001 13.340984,25.908997 13.118999,25.452995 12.837016,24.869995 12.616008,24.378998 12.52299,24.004997 12.452983,23.365997 12.52299,22.792 12.663005,22.243996 12.897014,21.822998 13.132,21.436996 13.306988,21.136002 13.363018,20.935997 13.387981,20.540001 13.387981,20.352997 13.457989,20.200996 13.548992,19.953995 13.65501,19.839996 13.645,19.731995 13.501995,19.731995 13.353985,19.805 13.280987,19.965996 13.259014,20.223 13.22398,20.548996 13.176006,20.844002 12.954998,21.226997 12.910015,21.496002 12.723002,21.695999 12.592997,21.822998 12.428996,21.845001 12.163005,21.740997 11.871013,21.531998 11.76298,21.252998 11.741008,20.983002 11.267008,20.339996 10.809976,19.953995 10.280984,19.576996 9.7079858,19.186996 9.2610243,18.949997 8.6659926,18.629997 7.9310063,18.066002 7.5389775,17.717995 7.0790161,17.213997 6.6990107,16.838997 6.4609736,16.486 6.3379878,16.250999 6.3790034,16.153999 6.4479731,15.973 6.0149892,15.973 5.4820298,15.973 5.0199932,15.818001 4.6589702,15.608002 4.6589702,15.400002 4.2660254,15.173996 3.4549895,14.782997 3.118991,14.658997 2.796969,14.461998 2.5719935,14.265999 2.4189783,14.265999 2.222994,14.375999 2.111971,14.671997 2.1819783,15.161995 2.3749715,15.568001 2.4889857,16.083 2.6290003,16.640999 2.796969,17.033997 3.0619842,17.340996 3.0489837,17.492996 3.0749847,17.729996 3.21799,17.775002 3.6070286,17.943001 3.8169895,18.152 3.9300266,18.319 3.9590183,18.417999 3.9720188,18.528999 4.0409888,18.683998 4.0409888,18.905998 4.0409888,19.228996 3.9839817,19.465996 3.889011,19.633995 3.803989,19.831001 3.6639744,19.871002 3.4969817,19.859001 3.2430144,19.703995 2.8949915,19.367996 2.6570154,19.019997 2.5019861,18.627998 2.2809774,18.292 2.0840169,18.095001 1.8710036,17.927002 1.7890029,17.729996 1.7030044,17.172997 1.5929885,16.809998 1.5520034,16.486 1.4660048,16.140999 1.3420119,15.650002 1.243989,15.145996 1.0480051,14.502998 0.96298312,14 0.97900485,13.721001 1.1300058,13.340996 1.3269968,13.004997 1.5639968,12.795998 1.7890029,12.655998 2.0840169,12.640999 2.5879847,12.655998 3.0049773,12.738998 3.4399749,12.823997 3.7749973,12.852997 4.0129737,12.906998 4.082981,12.837997 4.0129737,12.697998 3.7909886,12.598999 3.5669895,12.419998 3.3119842,12.194 3.0749847,11.915001 2.8669764,11.608002 2.6689783,11.382996 2.4889857,11.284996 2.3630086,11.243996 2.2649862,11.271996 2.1659871,11.395996 2.1249715,11.591995 2.111971,11.818001 2.0680257,11.985001 2.0149862,12.181 1.8880019,12.348999 1.7479873,12.473 1.6210036,12.587997 1.5350051,12.640999 1.3550124,12.655998 1.2569895,12.808998 1.1300058,12.921997 0.99099826,13.146996 0.85098361,13.577995 0.79498386,13.861 0.79498386,14.111 0.8389902,14.375999 0.92199802,14.727997 1.0069895,15.063995 1.0759897,15.341995 1.1300058,15.568001 1.0480051,15.846001 0.8389902,16.181999 0.59899949,16.473 0.43100023,16.625 0.25000047,16.681999 0.15200853,16.612 0.024993897,16.321999 0.040985108,15.957001 0,15.736 0.070007324,15.481995 0.070007324,14.866997 0.082000733,14.515999 0.095001221,14.306999 0.19299363,14.098 0.34899949,13.733002 0.40499925,13.298996 0.50399827,12.950996 0.68298387,12.598999 0.78198337,12.223 0.89300632,11.845001 0.99099826,11.608002 1.0069895,11.202995 1.1170053,10.863998 1.2290049,10.641998 1.3269968,10.278 1.4119887,10.040001 1.5350051,9.7330017 1.7760024,9.5649948 2.0560018,9.409996 2.44797,9.3279953 2.7549769,9.3819962 3.1039764,9.5079956 3.4269744,9.7750015 3.7220188,10.012001 3.9590183,10.193001 4.4189795,10.433998 4.827,10.612999 5.1059917,10.793999 5.3559922,10.907997 5.4820298,10.936996 5.6789902,10.838997 5.8180288,10.752998 5.9579824,10.572998 6.1540273,10.348 6.4609736,10.193001 6.8249873,9.9700012 7.0630249,9.7750015 7.2590088,9.6350021 7.4139775,9.4509964 7.525977,9.0879974 7.7339848,8.8659973 8.0630253,8.5859985 8.4219741,8.2279968 8.7029799,7.8389969 9.0170058,7.4430008 9.2770156,7.038002 9.3530043,6.8320007 9.467019,6.5339966 9.5749901,6.2070007 9.6819848,5.909996 9.7580346,5.6719971 9.796975,5.4659958 9.8119897,5.298996 9.7369775,5.1699982 9.5970238,5.1159973 9.2770156,5.0929947 9.0270156,5.137001 8.8650283,5.1159973 8.5680312,5.022995 8.3390273,5.0009995 8.1940068,4.9150009 8.0160283,4.8939972 7.8359746,4.9029999 7.7129887,4.8619995 7.5510014,4.8939972 7.3609985,4.9309998 7.0729736,5.0830002 6.8730224,5.1309967 6.689001,5.137001 6.5439814,5.137001 6.3630122,5.1159973 6.2779902,5.0390015 6.2870234,4.9469986 6.3379878,4.8619995 6.5979975,4.784996 6.9419922,4.6559982 7.0569824,4.5950012 7.0879883,4.5130005 7.0569824,4.427002 6.9209961,4.4219971 6.5820063,4.5569992 6.3310298,4.6339951 6.110021,4.6940002 5.9739736,4.7029953 5.860021,4.6439972 5.7289785,4.526001 5.7289785,4.3129959 5.7190298,4.1149979 5.7289785,3.9710007 5.8279775,3.8479996 5.9739736,3.5950012 6.1260122,3.3669968 6.1849722,3.2589951 6.2389883,3.0999985 6.3089961,2.9550018 6.4300288,2.9229965 6.7430171,2.9160004 6.9709839,2.909996 7.2309936,2.8470001 7.5069951,2.7269974 7.8099736,2.5270004 8.1940068,2.3529968 8.5300063,2.1159973 8.8050307,1.9700012 8.9859999,1.848999 9.0870131,1.802002 9.2549819,1.7419968 9.2930068,1.6809998 9.2610243,1.6049957 8.9029921,1.4440002 8.6589736,1.3839951 8.4529799,1.3199997 8.1849736,1.3079987 8.0580205,1.3139954 7.9349736,1.3679962 7.8260258,1.3990021 7.7129887,1.3740005 7.6579961,1.2829971 7.7060307,1.2159958 7.8570317,1.2070007 8.0469731,1.1459961 8.3019789,1.0919952 8.4690322,1.1149979 8.598976,1.0859985 8.6020278,0.96299744 8.5099868,0.94599915 8.2889785,0.95600128 8.2410039,0.81700134 8.6399916,0.73400116 8.8849868,0.72599792 9.4089736,0.83899689 9.9460229,0.99099731 10.238992,0.96799469 10.332009,0.88600159 10.319009,0.74700165 10.191995,0.58200073 10.204995,0.44300079z</x:String>
    <x:String x:Key="DonkeyIcon">M7.3040044,20.633007L7.9630015,20.798008 5.7800019,23.887008C5.7800019,23.887008,5.5330074,26.234007,7.5090063,31.175995L5.8210022,31.175995 4.1320059,25.163007C4.1320059,25.163007,6.1500123,23.228004,7.3040044,20.633007z M14.991005,20.331012C15.073005,27.937,16.858009,31.149002,16.858009,31.149002L15.650017,31.149002C13.344016,26.125005,13.481009,20.743007,13.481009,20.743007z M1.0019997,17.462013L18.135019,17.462013C18.135019,17.462013,15.348015,24.751005,19.851007,31.944998L17.710016,31.944998C17.710016,31.944998 15.348015,27.716999 15.513008,19.645008 15.513008,19.645008 9.5830123,21.896005 7.0020025,19.809009 7.0020025,19.809009 5.5189998,23.104004 3.8170035,24.806005 3.8170033,24.806005 3.4320087,26.673 5.5740078,31.999998L3.8710043,31.999998 1.9490064,24.806005C1.9490062,24.806005,2.8560042,20.496013,1.0019997,17.462013z M0,17.448013L0.79600567,17.448013C0.65901232,18.354009 0.98899907,22.55501 0.98899931,22.55501 1.3730019,24.504005 0.82400573,26.070005 0.82400555,26.070005 0.27500946,24.477005 0.41200295,22.445009 0.41200301,22.280008 0.41200295,22.116007 1.8268452E-07,17.448013 0,17.448013z M15.321007,11.383016L14.849006,12.836017 13.322013,12.836017 14.557014,13.734019 14.085013,15.186014 15.321007,14.28802 16.556007,15.186014 16.084007,13.734019 17.31901,12.836017 15.793007,12.836017z M9.8300064,11.383016L9.3570144,12.836017 7.8300059,12.836017 9.065007,13.734019 8.5940139,15.186014 9.8300064,14.28802 11.065008,15.186014 10.593008,13.734019 11.828008,12.836017 10.301016,12.836017z M4.2010062,11.383016L3.7290061,12.836017 2.2020123,12.836017 3.4380057,13.734019 2.9650133,15.186014 4.2010062,14.28802 5.4360073,15.186014 4.9640071,13.734019 6.2000001,12.836017 4.6730068,12.836017z M20.593012,3.2634284E-05C22.164013,-0.014970344,23.324016,5.1480224,23.324016,5.1480224L23.447017,4.8190281C23.48901,2.0180285 25.095012,0.57703191 25.095012,0.57703179 26.948011,-0.41096983 26.537016,0.82402944 26.537016,0.82402962L25.425016,3.6660256C24.68301,5.3540232 26.042021,7.0840247 26.042021,7.0840247 27.11302,9.2250211 26.248014,10.41902 26.248014,10.41902 26.166013,10.749022 26.454023,13.385021 26.454023,13.385021 26.454023,15.444016 24.766018,15.403016 24.766018,15.403016 23.612011,15.815011 23.57101,14.002017 23.57101,14.002017 23.57101,13.385021 22.871013,12.520016 22.871013,12.520016L21.759014,10.625022C19.412012,13.508013,18.382013,16.638016,18.382013,16.638016L0.014007567,16.638016C-0.069000092,14.497019 0.38400305,13.014019 0.38400314,13.014019 1.9900066,8.4430244 5.656009,9.2670209 5.656009,9.2670209 13.110007,12.314015 17.352014,8.9370201 17.352014,8.9370201L17.023018,8.1950233C17.16601,7.9910214,17.322016,7.7940233,17.487009,7.6050203L17.587015,7.4950201 18.25802,8.0580232 17.746011,7.3240225 17.829019,7.238024C17.946008,7.1190207,18.067011,7.0040233,18.18902,6.8920238L18.231012,6.8550212 18.876016,7.3300269 18.445017,6.6670253 18.561014,6.5690262C18.686014,6.4650223,18.812021,6.3660233,18.938013,6.2700226L18.989008,6.2320206 19.48002,6.7130229 19.171015,6.0960271 19.313013,5.9940221C19.375009,5.9500234,19.437021,5.9070241,19.499017,5.8650243L19.677011,5.7460287 20.167017,6.2870209 19.943018,5.5740244 20.034021,5.5170252C20.14802,5.4460261,20.259012,5.3790247,20.366007,5.3170283L20.409021,5.2920268 20.894006,5.848026 20.703012,5.1250274 20.809015,5.0670288C21.259014,4.8240254 21.55302,4.6950276 21.55302,4.6950276 19.947016,1.3590287 20.358011,0.041029351 20.358011,0.041029184 20.437021,0.014032769 20.516016,0.0010322942 20.593012,3.2634284E-05z</x:String>
    <x:String x:Key="RabbitIcon">M7.6860339,29.116028L9.9180281,30.31601 10.976011,30.394012 12.163023,30.134003 13.416014,29.859009 13.520018,32 12.163023,31.660004 10.77899,31.308014 9.6960432,31.387024 7.2690418,31.896027z M0.65203808,28.581024L2.8840327,29.389008 5.6379998,30.394012 5.4290154,31.569 2.8840327,30.695007 0.13104247,29.755005z M12.163023,18.362C11.406004,18.362 10.77899,18.975006 10.77899,19.745026 10.77899,20.503021 11.406004,21.128998 12.163023,21.128998 12.932005,21.128998 13.546995,20.503021 13.546995,19.745026 13.546995,18.975006 12.932005,18.362 12.163023,18.362z M12.367572,14.555755L12.340025,14.571014 12.211066,14.642218 12.212307,14.641455 12.21571,14.639533 12.255806,14.617428C12.272035,14.608477,12.290177,14.598469,12.310084,14.587483z M12.827025,14.302002C12.648268,14.400764,12.491398,14.487404,12.374952,14.551682L12.367572,14.555755 12.467437,14.500434C12.592638,14.4312,12.711531,14.365768,12.827025,14.302002z M13.057005,14.176025C13.012999,14.199005 12.971007,14.222015 12.929014,14.246002 12.971007,14.222015 13.016051,14.197998 13.057005,14.176025z M13.313048,14.034027C13.227049,14.082001 13.141051,14.128998 13.058043,14.176025 13.147032,14.125 13.231016,14.07901 13.313048,14.034027z M13.559019,13.89801C13.483031,13.940002 13.397032,13.988007 13.313048,14.034027 13.394041,13.990021 13.476011,13.944 13.559019,13.89801z M13.848997,13.738007C13.768004,13.78302 13.683043,13.830017 13.591002,13.880005 13.679014,13.832001 13.764036,13.785004 13.848997,13.738007z M14.316038,13.480011C14.18701,13.552002 14.033018,13.636017 13.848997,13.738007 14.017027,13.64502 14.175047,13.558014 14.316038,13.480011z M0.42999221,0C1.3430172,0.57400513 2.1660151,1.3050232 2.8829951,2.257019 4.6719963,4.6590271 5.7030022,8.5220032 5.3489983,15.047028 6.0150139,14.799011 6.6940295,14.838013 7.3720081,15.164001 8.7950423,13.08902 10.400023,11.144012 12.163023,9.3180237 15.530028,5.8330078 19.497007,2.7650146 24.091001,0.1289978 22.37103,6.375 18.472043,11.028015 12.725035,14.35202L12.21571,14.639533 12.213011,14.641022 12.212307,14.641455 12.205992,14.64502 12.211066,14.642218 12.200011,14.649017 12.163023,14.669006C11.576048,14.995026 10.975035,15.295013 10.348021,15.595001 11.028013,15.686005 11.62805,15.805023 12.163023,15.947998 15.269041,16.756012 16.42999,18.362 18.179013,20.659027 18.832029,21.547028 18.636045,22.107025 17.801023,23.151001 15.999997,25.39502 13.533995,25.944 12.163023,26.715027 11.393004,27.157013 10.960997,27.667023 11.21002,28.581024 9.1490462,28.659027 6.5640247,26.597015 5.0370476,28.802002L2.8829951,28.137024 0.54699657,27.419006C-0.052978727,23.751007 -2.173947E-07,20.071014 2.1530146,16.444 2.4400019,15.934998 2.6100459,15.47702 2.3350215,15.047028 0.92602507,12.776001 0.077026138,9.8270264 0,5.9500122L0,4.9330139C0.025024194,3.4190063,0.1690061,1.7880249,0.42999221,0z</x:String>
</ResourceDictionary>

The icons are stored in a resource dictionary so they’re easily accessible by XAML. With some helper code -embedded in an MVVM Icon service- you can make them also easily available to C#. Here’s the basic code:

public static class Icon
{
    public static string GetIcon(string name)
    {
        return (string) Windows.UI.Xaml.Application.Current.Resources[name];
    }
}

You can extend this class with constants or enumerations to avoid typos and get intellisense when referring to icons from code behind.

Here’s how the data from the icon resource dictionary is used in the item template for the menu item:

<DataTemplate x:Key="MenuItemTemplate"
                x:DataType="vm:MenuItem">
    <StackPanel Orientation="Horizontal"
                Margin="0">
        <Border Background="Transparent"
                ToolTipService.ToolTip="{x:Bind Text}">
            <Path x:Name="Glyph"
                    Data="{x:Bind Glyph}"
                    VerticalAlignment="Center"
                    HorizontalAlignment="Center"
                    Height="24"
                    Width="24"
                    Fill="{StaticResource SplitViewForegroundBrush}"
                    Stretch="Uniform" />
        </Border>
        <TextBlock Text="{x:Bind Text}"
                    Margin="12 0 0 0"
                    Foreground="{StaticResource SplitViewForegroundBrush}"
                    VerticalAlignment="Center" />
    </StackPanel>
</DataTemplate>

Intermezzo: transforming and SVG file to a XAML path

With some luck, you’ll find all the images you want immediately in XAML format. Some excellent resources are

If your team has access to a professional designer, he will probably produce SVG files for you. For this file format, there are a lot more sources available (like The Noun Project). You can easily transform SVG to XAML with the free Inkscape tool. It allows you to open and edit SVG files, and comes with a ‘save as XAML’ option – but you can also boldly copy from the XML view. The save-as-XAML menu translates each object in the SVG structure (path, circle, …) to a XAML object. You can store the resulting XAML tree in a ContentControl. If you want to store the whole image as a single path data string (like I did in the icons resource dictionary) then you’ll need to manipulate the source and transform the original SVG to a single path element. You can use InkScape’s ‘transform object to path’ and ‘union paths’ menu items for this:
InkScape

Be aware that Microsoft’s Path Mini Language is closely related to the SVG syntax, but it is nowhere stated that they’re identical. Some SVG paths are not correctly displayed by the XAML engine. From time to time a relative move within the structure is replaced by an absolute move or vice versa, in which case you have a messed up icon like the one in the top right corner here:
SvgError

Keeping the menu in sync

The main navigation menu is not the only way to reach another page in your app: there may be hyperlink buttons on pages. There are some occurences of these in the sample app:

Rabbit

The logic behind these buttons may be executed by Views as well as ViewModels – that’s one of the reasons why be implemented Navigation as a Service. Apart from buttons outside the main menus, there’s also the back button to consider.

When navigation is triggered from outside the main menus, we’ll need to automatically select the corresponding item in the corresponding menu (and unselect the other menu). This is a pure UI thing, so we can implement this in the View, i.c. the Shell. Here’s the Navigated event handler of the main frame:

private void SplitViewFrame_OnNavigated(object sender, NavigationEventArgs e)
{
    // Lookup destination type in menu(s)
    var item = (from i in Menu.Items
                where (i as MenuItem).NavigationDestination == e.SourcePageType
                select i).FirstOrDefault();
    if (item != null)
    {
        Menu.SelectedItem = item;
        return;
    }

    Menu.SelectedIndex = -1;

    item = (from i in SecondMenu.Items
            where (i as MenuItem).NavigationDestination == e.SourcePageType
            select i).FirstOrDefault();
    if (item != null)
    {
        SecondMenu.SelectedItem = item;
        return;
    }

    SecondMenu.SelectedIndex = -1;
}

Handling the Back button

Some devices have a system provided button, gesture, or voice command for back-navigation. It totally makes sense to hook into that experience. It’s also useful to display a soft back button on those devices that don’t have one by default (like a PC). Use the SystemNavigationManager to take care of this. Here’s the code to show or hide the back button through AppViewBackButtonVisibility and to register an event handler on Backrequested. The code is embedded in the Navigation MVVM service:

private static readonly EventHandler<BackRequestedEventArgs> _goBackHandler = (s, e) => Navigation.GoBack();

public static void EnableBackButton()
{
    var navManager = SystemNavigationManager.GetForCurrentView();
    navManager.AppViewBackButtonVisibility = AppViewBackButtonVisibility.Visible;
    navManager.BackRequested -= _goBackHandler;
    navManager.BackRequested += _goBackHandler;
}

public static void DisableBackButton()
{
    var navManager = SystemNavigationManager.GetForCurrentView();
    navManager.AppViewBackButtonVisibility = AppViewBackButtonVisibility.Collapsed;
    navManager.BackRequested -= _goBackHandler;
}

public static void GoBack()
{
    if (_frame.CanGoBack)
    {
        _frame.GoBack();
    }
}

It’s easy to write code to hide the (soft) button when there is no previous page to navigate to: the Frame.OnNavigated in the Shell is the place to put it. Unfortunately there’s no way to disable that back button and grey it out: you can only it visible or collapse and each change updates your title bar structure. So I prefer to keep the button visible at all times – analogous to the devices with a ‘hard’ back button. Here’s how a ‘soft’ back button look like:

UnstyledTitleBar

Colors, colors, colors

If you ask me, that back button in the previous screenshot looks absolutely gross, and so does the entire title bar. The title bar belongs to Windows, not to your app. By default, the back button is drawn in the accent color that the user has selected in his profile. Unfortunately that same color is also used in other parts of your UI – e.g. in the keyboard focus visuals and in check boxes. There’s a high probability that this user preferred color will one way or another clash with your app’s color scheme. After all, there is no color scheme that matches each and every color from this list:

AccentColors

I think you have two options here:

  • Stick to a theme that has all shades of grey, plus the accent color (there’s nothing wrong with that: it’s what Outlook and Visual Studio do), or
  • Restyle your controls (not the structure, just the colors).

A third option would be to simply override the accent color. It’s a ThemeResource called ‘AccentColor’. It should be possible to override it, but I’m still working on a version that works on my machine(s). In this article I’ll go for option two. I created a Theme service, hosting a resource dictionary with several useful brush resources. Some of these brushes are general purpose (a lightening brush, a darkening brush, a highlight brush):

<SolidColorBrush x:Key="TenPercentDarkerBrush"
                    Color="Black"
                    Opacity=".1" />
<SolidColorBrush x:Key="TenPercentLighterBrush"
                    Color="White"
                    Opacity=".1" />
<SolidColorBrush x:Key="HighlightBrush"
                    Color="OrangeRed" />

The other brushes are used for the apps’ main UI components. I used Paletton.com to build a set of dirty theme colors that would work well together but clash with most -if not all- of the possible accent colors:

Paletton

The brown and green colors in this palette are used for the navigation menus, the hamburger button, and the content pages. Again, I used a resource dictionary for this:

<!-- Header -->
<SolidColorBrush x:Key="TitlebarBackgroundBrush"
                    Color="#816B1B" />
<SolidColorBrush x:Key="TitlebarBackgroundDarkBrush"
                    Color="#534307" />
<SolidColorBrush x:Key="TitlebarBackgroundLightBrush"
                    Color="#AE973B" />
<SolidColorBrush x:Key="StatusbarBackgroundBrush"
                    Color="#816B1B" />

<!-- Splitview Pane -->
<SolidColorBrush x:Key="SplitViewBackgroundBrush"
                    Color="#814F1B" />
<SolidColorBrush x:Key="SplitViewForegroundBrush"
                    Color="#FFCD98" />
<SolidColorBrush x:Key="HamburgerForegroundBrush"
                    Color="#AE973B" />

<!-- Splitview Content -->
<SolidColorBrush x:Key="PageBackgroundBrush"
                    Color="#DBC366" />
<SolidColorBrush x:Key="PageForegroundBrush"
                    Color="#532E07" />

The colors are applied to the menu ListViews by providing a custom Style where the references to the AccentColor are replaced:

<Style x:Key="MenuListViewItemStyle"
        TargetType="ListViewItem">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="ListViewItem">
                <ListViewItemPresenter SelectedBackground="{StaticResource TenPercentLighterBrush}"
                                        SelectedPointerOverBackground="{StaticResource TenPercentLighterBrush}"
                                        PointerOverBackground="{StaticResource TenPercentDarkerBrush}"
                                        ContentTransitions="{TemplateBinding ContentTransitions}"
                                        HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
                                        VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
                                        ContentMargin="{TemplateBinding Padding}" />
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Storing icons and brushes in resource dictionaries makes them maintainable and easily reusable in other projects. Maybe you’re building a suite of apps, or you have –like me- a separate app that generates splash screens and tiles for your main app. All you need to do to reuse the icons and colors, is to link to the files that host the resource dictionaries into your different projects.

Let’s go back to the title bar that hosts the back button. Apart from the brushes, the Theme service also owns the code that applies their color to the title bar. Before we get access to the title bar, we need to check the device (PC’s have a TitleBar, mobile devices have a StatusBar). The device check is done with a call to ApiInformation.IsTypePresent. The service routine is to be called in App.OnLaunched:

public static void ApplyToContainer()
{
    // PC customization
    if (ApiInformation.IsTypePresent("Windows.UI.ViewManagement.ApplicationView"))
    {
        var titleBar = ApplicationView.GetForCurrentView().TitleBar;
        if (titleBar != null)
        {
            titleBar.BackgroundColor = ((SolidColorBrush)Application.Current.Resources["TitlebarBackgroundBrush"]).Color;
            titleBar.ForegroundColor = Colors.White;
            titleBar.ButtonBackgroundColor = titleBar.BackgroundColor;
            titleBar.ButtonForegroundColor = Colors.White;
            titleBar.ButtonHoverBackgroundColor = ((SolidColorBrush)Application.Current.Resources["TitlebarBackgroundDarkBrush"]).Color;
            titleBar.ButtonHoverForegroundColor = Colors.White;
            titleBar.ButtonPressedBackgroundColor = ((SolidColorBrush)Application.Current.Resources["TitlebarBackgroundLightBrush"]).Color;
            titleBar.ButtonPressedForegroundColor = Colors.White;
            titleBar.InactiveBackgroundColor = titleBar.BackgroundColor;
            titleBar.InactiveForegroundColor = titleBar.ForegroundColor;
            titleBar.ButtonInactiveBackgroundColor = titleBar.BackgroundColor;
            titleBar.ButtonInactiveForegroundColor = titleBar.ButtonForegroundColor;
        }
    }

    // Mobile customization
    if (!ApiInformation.IsTypePresent("Windows.UI.ViewManagement.StatusBar")) return;

    var statusBar = StatusBar.GetForCurrentView();
    if (statusBar == null) return;

    statusBar.BackgroundOpacity = 1;
    statusBar.BackgroundColor = ((SolidColorBrush)Application.Current.Resources["StatusbarBackgroundBrush"]).Color;
    statusBar.ForegroundColor = Colors.White;
}

Our basic navigation infrastructure is complete now: its look and feel are what the user may expect, and everything is nicely componentized.

Source code

If you want to play around with the sample app, it lives here on GitHub.

Enjoy!

A splash screen animation for UWP

What’s a splash screen if it doesn’t splash?

This article shows how to zoom the splash screen of a UWP app to the size of its window with an animation that doesn’t use the UI thread. This is how it looks like in a sample app:

AnimatedSplashScreen

The splash screen grows and gets transparent while the app is navigating to its main page. I did not wrote the feature myself, the beef of the code can be found in the Windows UI Dev Labs. I just refactored the code, made it more generic, and packaged it for easy reuse. I was able to implement it as an extension method of the Page class.

All you need to do to add the animation to your app is adding one line of code in the Application.OnLaunched method (in app.xaml.cs):

if (rootFrame.Content == null)
{
    rootFrame.Navigate(typeof(Shell), e.Arguments);
    (rootFrame.Content as Page).OpenFromSplashScreen(
	e.SplashScreen.ImageLocation);
}

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

While the app navigates to its first page, a copy of the splash screen (or another image if you want) is placed on the position of the original image. The LaunchActivatedEventArgs instance from OnLaunched has a reference to the SplashScreen instance, which exposes its ImageLocation. That rectangle is passed to the OpenFromSplashScreen call, optionally together with the path to the image file, and a background color (in case you have a transparent image).

Here’s an alternative call, we provide the path to the (transparent) splash screen of the UWP Community Toolkit, and specify a black background:

(rootFrame.Content as Page).OpenFromSplashScreen(
	e.SplashScreen.ImageLocation, 
	Colors.Black, 
	new Uri("ms-appx:///Assets/ToolkitLogoTransparent.png"));

The OpenFromSplashScreen method first registers a handler to the Loaded event, to start the animation. Then the SurfaceLoader helper instance is initialized, and finally we display a copy of the splash screen image that will be animated later:

public static void OpenFromSplashScreen(
	this Page page, 
	Rect imageBounds, 
	Color backgroundColor, 
	Uri imageUri)
{
    page.Loaded += Page_Loaded;

    // Initialize the surface loader
    SurfaceLoader.Initialize(
	ElementCompositionPreview.GetElementVisual(page).Compositor);

    // Show the custom splash screen
    ShowImage(page, imageBounds, imageUri, backgroundColor);
}

The copy of the splash screen image is entirely drawn through the Composition API. Via ElementCompositionPreview.GetElementVisual we get access to the page’s Visual and its Compositor. This compositor factory is used to create a ContainerVisual that will have the background color (a SpriteVisual painted with a solid CompositionColorBrush) and the image (in a CompositionDrawingSurface) as its Children:

private static async void ShowImage(Page page, Rect imageBounds, Uri imageUri, Color backgroundColor)
{
    var compositor = ElementCompositionPreview.GetElementVisual(page).Compositor;
    var windowSize = new Vector2((float)Window.Current.Bounds.Width, (float)Window.Current.Bounds.Height);

    //
    // Create a container visual to hold the color fill background and image visuals.
    // Configure this visual to scale from the center.
    //
    var container = compositor.CreateContainerVisual();
    container.Size = windowSize;
    container.CenterPoint = new Vector3(windowSize.X, windowSize.Y, 0) * .5f;
    ElementCompositionPreview.SetElementChildVisual(page, container);

    //
    // Create the colorfill sprite for the background, set the color to the same as app theme
    //
    var backgroundSprite = compositor.CreateSpriteVisual();
    backgroundSprite.Size = windowSize;
    backgroundSprite.Brush = compositor.CreateColorBrush(backgroundColor);
    container.Children.InsertAtBottom(backgroundSprite);

    //
    // Create the image sprite containing the splash screen image.  Size and position this to
    // exactly cover the Splash screen image so it will be a seamless transition between the two
    //
    var surface = await SurfaceLoader.LoadFromUri(imageUri);
    var imageSprite = compositor.CreateSpriteVisual();
    imageSprite.Brush = compositor.CreateSurfaceBrush(surface);
    imageSprite.Offset = new Vector3((float)imageBounds.X, (float)imageBounds.Y, 0f);
    imageSprite.Size = new Vector2((float)imageBounds.Width, (float)imageBounds.Height);
    container.Children.InsertAtTop(imageSprite);
}

When the main page is loaded (in the sample app, it’s the shell that hosts the main SplitView), the animation is started:

private static void Page_Loaded(object sender, RoutedEventArgs e)
{
    (sender as Page).Loaded -= Page_Loaded;

    // Now that loading is complete, dismiss the custom splash screen
    ShowContent(sender as Page);
}

The animation contains a ScalarKeyFrameAnimation for the opacity, and a Vector2KeyFrameAnimation for the image’s size. Both animations are started through a StartAnimation call, right after the declaration of a ScopedBatch instance on the same compositor:

private static void ShowContent(Page page)
{
    var container = (ContainerVisual)ElementCompositionPreview.GetElementChildVisual(page);
    var compositor = container.Compositor;

    // Setup some constants for scaling and animating
    const float scaleFactor = 7.5f;
    var duration = TimeSpan.FromMilliseconds(2000);

    // Create the fade animation which will target the opacity of the outgoing splash screen
    var fadeOutAnimation = compositor.CreateScalarKeyFrameAnimation();
    fadeOutAnimation.InsertKeyFrame(1, 0);
    fadeOutAnimation.Duration = duration;

    // Create the scale up animation for the Splash screen visuals
    var scaleUpSplashAnimation = compositor.CreateVector2KeyFrameAnimation();
    scaleUpSplashAnimation.InsertKeyFrame(0, new Vector2(1, 1));
    scaleUpSplashAnimation.InsertKeyFrame(1, new Vector2(scaleFactor, scaleFactor));
    scaleUpSplashAnimation.Duration = duration;

    // Configure the visual to scale from the center
    var frameworkElement = page.Content as FrameworkElement;
    var visual = ElementCompositionPreview.GetElementVisual(frameworkElement);
    visual.Size = new Vector2((float)frameworkElement.ActualWidth, (float)frameworkElement.ActualHeight);
    visual.CenterPoint = new Vector3(visual.Size.X, visual.Size.Y, 0) * .5f;

    //
    // Create a scoped batch for the animations.  When the batch completes, we can dispose of the
    // splash screen visuals which will no longer be visible.
    //
    var batch = compositor.CreateScopedBatch(CompositionBatchTypes.Animation);

    container.StartAnimation("Opacity", fadeOutAnimation);
    container.StartAnimation("Scale.XY", scaleUpSplashAnimation);

    currentPage = page; // TODO: find a better way to pass the page to the event.
    batch.Completed += Batch_Completed;
    batch.End();
}

This enables us to clean up all Composition API instances in the batch’s Completed event:

private static void Batch_Completed(
	object sender, 
	CompositionBatchCompletedEventArgs args)
{
    // Now that the animations are complete, dispose of the custom Splash Screen visuals
    ElementCompositionPreview.SetElementChildVisual(currentPage, null);
}

What about the phone?

The animated splash screen is a UWP library, so it also works on your Windows phone. But on such a small-screen device the splash screen is relatively big (its width is the same as the whole screen width). So the animation is less noticeable than on a PC. For smaller screens it makes sense to let the whole content zoom in, instead of just the splash screen. The code for that is in the code base, but commented out.

Where’s the code?

The library and the sample app are hosted on GitHub. if you want to add the effect right away to your app then just install its NuGet package.

Enjoy!

A Percentage Ring control for UWP

In this article we present a XAML PercentageRing control for UWP. We started with a  stripped down version of the RadialGauge control from the UWP Community Toolkit and then added some extra properties. PercentageRing is an interactive circular control to display and select a value between 0 and 100. Here’s how it looks like in the sample app:

PercentageRing

The names of the properties were also borrowed from the RadialGauge control, so

  • the Scale is the background arc, and
  • the Trail is the foreground arc.

The default start and end position of the arc is the top, but you can change it by providing different values for MinAngle and MaxAngle. These same properties also allow you to draw half a circle or any other segment. So PercentageArc would have been a more appropriate name for the control. Anyway, here’s the full list of dependency properties:

IsInteractive bool, default false Whether or not the control accepts setting its value through interaction.
MinAngle int, default 0 Start angle of the scale (value 0), in degrees.
MaxAngle int, default 360 End angle of the scale (value 100), in degrees.
ScaleBrush Brush, default dark grey Brush for the scale.
ScaleEndCap PenLineCap, default Triangle End cap style for the scale.
ScaleStartCap PenLineCap, default Round Start cap style for the scale.
ScaleWidth double, default 25 Width of the scale, relative to the radius of the control.
StepSize double, default 0 Rounding interval for the Value. 0 means ‘no rounding’.
TrailBrush Brush, default orange Brush for the trail.
TrailEndCap PenLineCap, default Triangle End cap style for the trail.
TrailStartCap PenLineCap, default Round Start cap style for the trail.
Value double, default 0 The value.
ValueBrush Brush, default black Brush for the value.
ValueStringFormat string, default ’0 %’ StringFormat applied to the value.

Here’s the default style template for the control: a ViewBox that hosts two Path controls, and a TextBlock at the bottom to display the value:

<Style TargetType="local:PercentageRing">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:PercentageRing">
                <Border Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}">
                    <Viewbox>
                        <Grid x:Name="PART_Container"
                                Height="200"
                                Width="200"
                                Background="Transparent">

                            <!-- Scale -->
                            <Path Name="PART_Scale"
                                    Stroke="{TemplateBinding ScaleBrush}"
                                    StrokeThickness="{TemplateBinding ScaleWidth}"
                                    StrokeStartLineCap="{TemplateBinding ScaleStartCap}"
                                    StrokeEndLineCap="{TemplateBinding ScaleEndCap}" />

                            <!-- Trail -->
                            <Path Name="PART_Trail"
                                    Stroke="{TemplateBinding TrailBrush}"
                                    StrokeThickness="{TemplateBinding ScaleWidth}"
                                    StrokeStartLineCap="{TemplateBinding TrailStartCap}"
                                    StrokeEndLineCap="{TemplateBinding TrailStartCap}" />

                            <!-- Value -->
                            <StackPanel VerticalAlignment="Bottom"
                                        HorizontalAlignment="Center">
                                <TextBlock Name="PART_ValueText"
                                            Foreground="{TemplateBinding ValueBrush}"
                                            FontSize="20"
                                            FontWeight="SemiBold"
                                            Text="{TemplateBinding Value}"
                                            TextAlignment="Center" />
                            </StackPanel>
                        </Grid>
                    </Viewbox>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

The code behind will then populate the Path controls with an ArcSegment in a PathGeometry or a full circle EllipseGeometry.

The start and end style of scale and trail are configurable PenLineCap values, so they can be flat, round, triangular or square. The only reason to retemplate the control, is when you want to display the value in another place, or when you want to do fancy things with the arc segment, like overriding StrokeDashArray and StrokeDashCap. The control on the left of the gallery page of the sample app has a custom style template (the others don’t need one): 

Gallery

Here’s the overridden trail section in the custom template:

<!-- Trail -->
<Path Name="PART_Trail"
        Stroke="{TemplateBinding TrailBrush}"
        StrokeThickness="{TemplateBinding ScaleWidth}"
        StrokeStartLineCap="Round"
        StrokeEndLineCap="Round"
        StrokeDashArray="0 2"
        StrokeDashCap="Round" />

For more details on the implementation, check this article and skip the Composition API stuff (or not).

I did make one major improvement in the code: the control accepts any value for MinAngle and MaxAngle. The values that are assigned to these properties –programmatically or through binding- are normalized so

  • the MinValue in all calculations is between -180° and +180°, and
  • the MaxValue in all calculations is greater than the MinValue

The normalization algorithm required the calculation of the Modulus. This is NOT the C# % operator, which actually calculates the Remainder. [Read this to find out more.] Here’s my implementation of the Modulus:

private static double Mod(double number, double divider)
{
    var result = number % divider;
    result = result < 0 ? result + divider : result;
    return result;
}

For testing the UI and the performance, I packed a SquareOfSquares container with 21 interactive percentage rings. This is how it looks like:

Squares

All rings respond nicely, even on ‘lesser hardware’. Here’s how the whole sample app looks like on my phone:

PercentageRing_Phone Gallery_Phone Squares_Phone

The PercentageRing control is available on GitHub (if you’re interested in the source) and on NuGet (if you want to use it straightaway).

Enjoy!