Reusing Vector Graphics in Windows 8 Store Apps

The Flat Design paradigm and the related anti-skeumorphism movement make bitmap images less and less popular in today's apps and applications. Everywhere in the user interface, PNG's and JPG's are being replaced by crisp scalable light-weight vector graphics. In the XAML world these vector graphics are represented by instances of the PathGeometry class. Recently I spent some time looking for a way to reuse Path objects in Windows 8 Store apps - and at the same time in different versions of WPF (our customers also want flat design in their Windows applications). Ideally, you should define a PathGeometry only once -e.g. as a Resource- and then reuse it in an app in multiple views, with different sizes and colors, preferably leveraging the data binding power of the XAML engine. I tried several approaches, and eventually ended up with creating a new custom control, the IconControl. Here's an example of the result. It displays a horizontal StackPanel with several instances of the same complex XAML icon -the prancing horse from a well known car brand- in different colors and sizes:

The corresponding XAML looks like this:

<local:IconControl Height="100"
                    Width="100"
                    Padding="20"
                    Background="White"
                    Foreground="PaleGreen"
                    Data="{StaticResource PrancingString}" />
<local:IconControl Height="100"
                    Width="100"
                    Padding="10"
                    Background="PaleGreen"
                    Foreground="White"
                    Data="{StaticResource PrancingString}" />
<local:IconControl Height="100"
                    Width="100"
                    Padding="20"
                    Background="White"
                    Foreground="Pink"
                    Data="{StaticResource PrancingString}" />
<!-- etcetera -->

I realize that is seems overkill to create a custom control to just display a Path, but I didn't find a light-weigth alternative.

Here are some of my attempts.

I started with defining a full Path object as a resource, like this:

<Path x:Key="PrancingHorse"
        Data="M22.923445,61.752014L22.733789,61.759857 22.468277,61.918045 (... there's more ...)  20.410011,0.88699341z"
        Fill="#FFFFFFFF" />

I hoped to be able to use the resource in a view, e.g. like this:

<ContentControl Content="{StaticResource PrancingHorse}" />

There's design time support in Visual Studio:

Unfortunately, this is what happens at run-time:

The next attempt was to store the PathGeometry as a Resource. I intended to use it in the views like this:

<Path Data="{StaticResource PrancingGeometry}" />

That sounds nice, except that it doesn't seem possible to define the PathGeometry as a resource. There are no type converters available to translate the path string into a PathGeometry's Segment or Figure. The following two resources are not accepted:

<PathGeometry x:Key="PrancingGeometry">No TypeConverter</PathGeometry>
<PathGeometry x:Key="PrancingGeometry" Figures="No TypeConverter">
    <PathFigure Segments="No TypeConverter" />
</PathGeometry>

After a couple of iterations, I concluded that the only way to reuse a path was to store it as a resource of type String:

<x:String x:Key="PrancingString">M22.923445,61.752014L22.733789,61.759857 22.468277,61.918045 (... there's more ...)  20.410011,0.88699341z</x:String>

This resource can be reused in multiple views, like this:

<Border Height="200"
        Width="200"
        BorderBrush="Black"
        Padding="20">
    <Viewbox Stretch="Uniform">
        <Path Data="{StaticResource PrancingString}"
                Fill="Red" />
    </Viewbox>
</Border>

Of course, giving each occurence a different size, margin, foreground and background color would require copy-pasting a large chunk of XAML. So I moved that chunk of XAML into a control template. I created a new custom control, with a dependency property called Data to store a PathGeometry, and gave it the following default style:

<Style TargetType="local:IconControl">
    <Setter Property="Padding"
            Value="0" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:IconControl">
                <Grid Background="{TemplateBinding Background}">
                    <Path Data="{TemplateBinding Data}"
                            Fill="{TemplateBinding Foreground}"
                            Stretch="Uniform"
                            Margin="{TemplateBinding Padding}" />
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Then, the lack of type converters in WinRT hit me again. When the dependency property was defined as String, nothing appeared in the control.

When the dependency property was defined as PathGeometry, I recieved an exception at design time:

 

And the control remained empty at runtime.

So I decorated the control with two properties: Data as a String and DataGeometry as a PathGeometry. The idea was to use the string in the binding (as Data= "{...}"):

<local:IconControl Height="200"
                    Width="200"
                    Padding="20"
                    Background="Yellow"
                    Foreground="Black"
                    Data="{StaticResource PrancingString}" />

... and to use the geometry in the control's template (in  "TemplateBinding DataGeometry"):

<ControlTemplate TargetType="local:IconControl">
    <Grid Background="{TemplateBinding Background}">
        <Path Data="{TemplateBinding DataGeometry}"
                Fill="{TemplateBinding Foreground}"
                Stretch="Uniform"
                Margin="{TemplateBinding Padding}" />
    </Grid>
</ControlTemplate>

Here's the control's code:

/// <summary>
/// Displays a Path.
/// </summary>
public sealed class IconControl : Control
{
    public static readonly DependencyProperty DataGeometryProperty =
        DependencyProperty.Register("DataGeometry", typeof(PathGeometry), typeof(IconControl), new PropertyMetadata(null));

    public static readonly DependencyProperty DataProperty =
        DependencyProperty.Register("Data", typeof(String), typeof(IconControl), new PropertyMetadata(null, new PropertyChangedCallback(OnDataChanged)));

    public IconControl()
    {
        this.DefaultStyleKey = typeof(IconControl);
    }

    // Write-only to be used in a binding.
    public String Data
    {
        private get { return (String)GetValue(DataProperty); }
        set { SetValue(DataProperty, value); }
    }

    // Read-only to be used in the control's template.
    public PathGeometry DataGeometry
    {
        get { return (PathGeometry)GetValue(DataGeometryProperty); }
        private set { SetValue(DataGeometryProperty, value); }
    }

    private static void OnDataChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        // Convert the String into a PathGeometry
        // ...
    }
}

The only thing left now, was to write the code to convert the String into a PathGeometry. In WPF this is literally a one-liner:

ic.DataGeometry = PathGeometry.CreateFromGeometry(PathGeometry.Parse(e.NewValue.ToString()));

In the Windows Store App API, the Parse method is missing, as well as the PathFigureCollectionConverter.

My favorite search engine brought me to this ancient String-to-PathGeometry Converter. It was written for SilverLight 2, but also does the trick in WinRT! I just had to change some namespaces when copying the code into a new class, a PathGeometryParser that converts a String into a PathGeometry, and back:

Using that class, the conversion is still a one-liner:

ic.DataGeometry = new PathGeometryParser().Parse(e.NewValue.ToString());

I would prefer an implementation of the Parse method as an Extension Method to String (e.g. AsPathGeometry), but I'm happy it works.

The new IconControl can be used to draw and redraw vector graphics with bindable path, size, and colors  - as you saw in the beginning of this article.

Here's a screenshot of the sample app that includes the IconControl and some other experiments to reuse a Path icon:

Here's the source code: U2UConsult.WinRT.ShapesReuse.zip (149.51 kb)

Enjoy!
Diederik