Category Archives: Composition API

Upgrading Radial Gauge from UWP to WinUI 3

In this article we describe the process of migrating a user control from UWP to WinUI 3. The subject is the Radial Gauge that we wrote many years ago and is currently part of Windows Community Toolkit. For its UI the radial gauge uses XAML as well as the Composition API. To successfully migrate the control from UWP to WinUI3

  • we had to remove some UWP calls that don’t exist in WinUI 3,
  • we replaced some UWP calls by their WinUI 3 counterpart, and
  • we decided to give the control a more rounded Windows 11 look-and-feel.

Setting up the environment

When we started this journey, the most recent version of Windows App SDK was the experimental preview of v1.0, so the list of required NuGet packages looked like this:

We installed the new Visual Studio templates:

After the upgrade to the official Windows App SDK 1.0 release, the dependencies list conveniently shrunk to this:

We created a sample app wherein all relevant UWP Radial Gauge source files from Windows Community Toolkit were copy-pasted.

Here’s the taxonomy of the current Windows 8 -style Radial Gauge control:

There was no need to change these properties, just as there was no need to change the control’s XAML template:

<ControlTemplate TargetType="local:RadialGauge">
    <Viewbox>
        <Grid x:Name="PART_Container"
              Width="200"
              Height="200">
 
            <!--  Scale  -->
            <Path Name="PART_Scale" />

            <!--  Trail  -->
            <Path Name="PART_Trail" />

            <!--  Value and Unit  -->
            <StackPanel HorizontalAlignment="Center"
                        VerticalAlignment="Bottom">
                <TextBlock Name="PART_ValueText" />
            </StackPanel>
        </Grid>
    </Viewbox>
</ControlTemplate>

The scale and trail of the gauge are Path elements, and then there are text boxes for value and unit. The rest of the UI parts -needle, ticks, and scale ticks- are drawn by the Composition API.

Goodbye Windows.UI namespace

The first step in migrating a code base from UWP to WinUI would be changing the namespace from Windows.UI.* to Microsoft.UI.* in a zillion places. All compiled well after this change…

Goodbye UI Events

… but at runtime the Toolkit’s ThemeListener crashed the app. It’s a known issue and the reason is that UWP’s UISettings.ColorValuesChanged Event and AccessibilitySettings.HighContrastChanged Event are no longer supported in desktop apps. Unlike UWP apps, WinUI 3 desktop apps are not notified when the user changes the theme, the contrast color, or high contrast mode. This is not a showstopper – as long as you stick to ThemeResources in your XAML. Since theming support is a popular feature these days, we assume that the majority of recent UWP apps is already using theme resource dictionaries. Make sure you don’t forget one for HighContrast. You don’t really need a notification when the theme changes at runtime: all XAML and Composition elements that get their color from a ThemeResource will immediately and automatically change color.

Here’s how we configured the Radial Gauge instances on the Home page of the sample app:

<ResourceDictionary.ThemeDictionaries>
    <ResourceDictionary x:Key="HighContrast">
        <!-- This makes the background disappear in High Contrast mode -->
        <x:Double x:Key="BackgroundOpacity">0</x:Double>
        <SolidColorBrush x:Key="TenPercentContrastBrush"
             Color="{ThemeResource SystemColorWindowTextColor}" />
        <SolidColorBrush x:Key="SystemAccentColorBrush"
             Color="{StaticResource SystemAccentColor}" />
        <SolidColorBrush x:Key="AppNeedleBrush"
             Color="{ThemeResource SystemAccentColor}" />
    </ResourceDictionary>
    <ResourceDictionary x:Key="Dark">
        <x:Double x:Key="BackgroundOpacity">.075</x:Double>
        <SolidColorBrush x:Key="TenPercentContrastBrush"
             Color="White"
             Opacity=".1" />
        <Color x:Key="SystemAccentColor">CadetBlue</Color>
        <SolidColorBrush x:Key="SystemAccentColorBrush"
             Color="{StaticResource SystemAccentColor}"
             Opacity=".5" />
        <SolidColorBrush x:Key="AppNeedleBrush"
             Color="OrangeRed" />
    </ResourceDictionary>
    <ResourceDictionary x:Key="Light">
        <x:Double x:Key="BackgroundOpacity">.15</x:Double>
        <SolidColorBrush x:Key="TenPercentContrastBrush"
             Color="Black"
             Opacity=".1" />
        <Color x:Key="SystemAccentColor">CadetBlue</Color>
        <SolidColorBrush x:Key="SystemAccentColorBrush"
             Color="{StaticResource SystemAccentColor}"
             Opacity=".5" />
        <SolidColorBrush x:Key="AppNeedleBrush"
             Color="OrangeRed" />
    </ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>

Here’s that page in Light, Dark, and HighContrast theme:

As it turned out that all the Toolkit’s ThemeListener related code in the Radial Gauge is actually obsolete in a WinUI 3 desktop context, we removed that code.

Goodbye CoreWindow

Radial Gauge supports keyboard input. The Value property can be changed via the arrow keys, with the CTRL key to manage the change interval. To detect the virtual key press Radial Gauge was using CoreWindow.GetKeyState():

var ctrl = Window.Current.CoreWindow.GetKeyState(VirtualKey.Control);
if (ctrl.HasFlag(CoreVirtualKeyStates.Down))
{
    step = LargeChange;
}

Since UWP’s CoreWindow is not available in WinUI 3 desktop apps, we had to look for another way to detect key presses. We found it in the appropriately named KeyboardInput class. Here’s the new code:

if (KeyboardInput.GetKeyStateForCurrentThread(VirtualKey.Control) == CoreVirtualKeyStates.Down)
{
    step = LargeChange;
};

Goodbye KeyboardInput

Just a few days after that last modification, we upgraded the project to Windows App SDK v1.0 only to observe that the KeyboardInput class was not there anymore. We abandoned the keyboard events and went for Keyboard Accelerators. In hindsight that would always have been the proper approach. We created an extension method to facilitate the declaration:

public static void AddKeyboardAccelerator(this UIElement element,
  VirtualKeyModifiers keyModifiers,
  VirtualKey key,
  TypedEventHandler<KeyboardAccelerator, KeyboardAcceleratorInvokedEventArgs> handler)
{
    var accelerator =
      new KeyboardAccelerator()
      {
          Modifiers = keyModifiers,
          Key = key
      };
    accelerator.Invoked += handler;
    element.KeyboardAccelerators.Add(accelerator);
}

Then we replaced the keypress event handler by eight keyboard accelerators (one for each arrow key, with and without CTRL). Here are two of those:

this.AddKeyboardAccelerator(
    VirtualKeyModifiers.Control,
    VirtualKey.Left,
    (ka, kaea) =>
    {
        Value = Math.Max(Minimum, Value - LargeChange);
        kaea.Handled = true;
    });
this.AddKeyboardAccelerator(
    VirtualKeyModifiers.None,
    VirtualKey.Left,
    (ka, kaea) =>
    {
        Value = Math.Max(Minimum, Value - SmallChange);
        kaea.Handled = true;
    });

Goodbye DesignMode

One of the major differences between UWP and Win32 Desktop is the application model. Unsurprisingly the Windows.ApplicationModel namespace did not survive into Windows App SDK v1.0. As a result, the DesignMode to detect whether the control is hosted by a Visual Designer is not available anymore. It is used in the Toolkit’s DesignTimeHelper class. There seem to be no plans for a WinUI 3 Desktop XAML Designer, and with features like XAML Live Preview and XAML Hot Reload there’s no urgent need for one. Again, we happily removed another part of the Radial Gauge code.

Goodbye Windows 8 style

The original looks of the Radial Gauge were a product of the Windows 8 design style: sharp (“rounded corners, shadows, and gradients are bad for battery life”) and plump (“touch first: designed for fingers”). We decided it was time for a make-over to bring the UI closer to Windows 11 design. We made another copy of the control (“RadialGauge2”) where we

  • decreased the default scale width,
  • rounded the Scale and Trail shapes by changing their PenLineCaps in the XAML template,
  • rounded the Tick, ScaleTick and Needle elements, and
  • provided support for Opacity in these elements.

Rounding the shapes was not a trivial job, since we were relying on SpriteVisual instances – unrounded rectangles filled with a non-transparent CompositionColorBrush. Here’s the old code for the ticks:

SpriteVisual tick;
for (double i = radialGauge.Minimum; i <= radialGauge.Maximum; i += radialGauge.TickSpacing)
{
    tick = radialGauge._compositor.CreateSpriteVisual();
    tick.Size = new Vector2((float)radialGauge.TickWidth, (float)radialGauge.TickLength);
    tick.Brush = radialGauge._compositor.CreateColorBrush(radialGauge.TickBrush.Color);
    tick.Offset = new Vector3(100 - ((float)radialGauge.TickWidth / 2), 0.0f, 0);
    tick.CenterPoint = new Vector3((float)radialGauge.TickWidth / 2, 100.0f, 0);
    tick.RotationAngleInDegrees = (float)radialGauge.ValueToAngle(i);
    radialGauge._root.Children.InsertAtTop(tick);
}

The new Radial Gauge uses a CompositionRoundedRectangleGeometry to fill a CompositionSpriteShape for each tick, and then groups all the ticks into a single ShapeVisual. Transparent brushes are not possible in this part of the API (well, at least not in a simple way). As a work-around we applied the opacity of the source brush to the Opacity of the Visual. Here’s the -much longer- new code:

var ticks = radialGauge._compositor.CreateShapeVisual();
ticks.Size = new Vector2((float)radialGauge.Height, (float)radialGauge.Width);
ticks.BorderMode = CompositionBorderMode.Soft;
ticks.Opacity = (float)radialGauge.TickBrush.Opacity;
 
var roundedTickRectangle = radialGauge._compositor.CreateRoundedRectangleGeometry();
roundedTickRectangle.Size = new Vector2((float)radialGauge.TickWidth, (float)radialGauge.TickLength);
roundedTickRectangle.CornerRadius = new Vector2((float)radialGauge.TickWidth / 2, (float)radialGauge.TickWidth / 2);
 
var tssFillBrush = radialGauge._compositor.CreateColorBrush(radialGauge.TickBrush.Color);
var tssOffset = new Vector2(100 - ((float)radialGauge.TickWidth / 2), 0.0f);
var tssCenterPoint = new Vector2((float)radialGauge.TickWidth / 2, 100.0f);
 
for (double i = radialGauge.Minimum; i <= radialGauge.Maximum; i += radialGauge.TickSpacing)
{
    var tickSpriteShape = radialGauge._compositor.CreateSpriteShape(roundedTickRectangle);
    tickSpriteShape.FillBrush = tssFillBrush;
    tickSpriteShape.Offset = tssOffset;
    tickSpriteShape.CenterPoint = tssCenterPoint;
    tickSpriteShape.RotationAngleInDegrees = (float)radialGauge.ValueToAngle(i);
 
    ticks.Shapes.Add(tickSpriteShape);
}
 
radialGauge._root.Children.InsertAtTop(ticks);

The Home page of our sample app compares the new look to the original one:

The Gallery page displays two sets of controls: the top row has some gauge configurations using the old style, the second row has the same gauges but is using the new style:

We believe that the second row looks better in any configuration.

Here’s how it looks like with the official v1.0.0. of Windows App SDK, bringing the Windows 11 style into the equation:

Here’s how the upgraded Radial Gauge looks like in a candidate production app that is in the middle of its migration to WinUI 3:

For the sake of completion: that other control in the screenshot is a Radial Range Indicator.

The Verdict

Migrating controls and apps from UWP to WinUI 3 involves more than a namespace change, and we’re OK with that. Some API’s have disappeared, but none of these was crucial – not to this user control and not to the apps we’re currently migrating.

We are happy with the result: Radial Gauge now has better looks and less source code to maintain.

The sample app lives here on GitHub.

Enjoy!

Fun with Bindable Squares in UWP

In the previous article we introduced some bindable Composition-drawn Path controls for UWP. In this article we will extend the list of controls with Rectangle and Square shapes, and introduce some optimizations in the framework. But above all: we’re going to have fun recreating some famous optical illusions with squares.

Bindable Rectangle

For starters, we created a Rectangle control, inspired by its SVG definition. Our base class already exposed a start point, and the necessary stroke and fill properties, so our new Rectangle subclass only required a height and a width. Semantically this new Rectangle control refers to ‘a panel that contains a rectangle figure somewhere in it’, so it did not make sense to reuse the inherited Height and Width properties. Instead we created SideX and SideY dependency properties.

Rendering a rectangle with a CanvasPathBuilder is quite obvious:

  • move to the start point with BeginFigure,
  • draw three lines with AddLine, and
  • close the figure with EndFigure (it will add the missing fourth line).

Here’s the full Render code:

protected override void Render()
{
    var canvasPathBuilder = GetCanvasPathBuilder();

    // Figure
    canvasPathBuilder.BeginFigure(new Vector2((float)StartPointX, (float)StartPointY));
    canvasPathBuilder.AddLine(
        new Vector2((float)(SideX + StartPointX), (float)(StartPointY)));
    canvasPathBuilder.AddLine(
        new Vector2((float)(SideX + StartPointX), (float)(SideY + StartPointY)));
    canvasPathBuilder.AddLine(
        new Vector2((float)(StartPointX), (float)(SideY + StartPointY)));
    canvasPathBuilder.EndFigure(CanvasFigureLoop.Closed);

    Render(canvasPathBuilder);
}

Observe that most of the rendering (creating the CanvasPathBuilder, creating and manipulating the CanvasGeometry, the SpriteShape, and the ShapeVisual) is common to all shapes, so we moved that to the base class.

Of course we created a small page to test the new control and its binding capabilities. Here how it looks like:

Rectangle

Bindable Square

Next in this series of bindable path controls comes a Square with a center point and a side – all implemented as dependency properties. Here’s the obvious rendering code:

protected override void Render()
{
    var canvasPathBuilder = GetCanvasPathBuilder();

    // Move starting point.
    StartPointX = CenterPointX - (Side / 2);
    StartPointY = CenterPointY - (Side / 2);

    // Figure
    canvasPathBuilder.BeginFigure(new Vector2((float)StartPointX, (float)StartPointY));
    canvasPathBuilder.AddLine(
        new Vector2((float)(Side + StartPointX), (float)(StartPointY)));
    canvasPathBuilder.AddLine(
        new Vector2((float)(Side + StartPointX), (float)(Side + StartPointY)));
    canvasPathBuilder.AddLine(
        new Vector2((float)(StartPointX), (float)(Side + StartPointY)));
    canvasPathBuilder.EndFigure(CanvasFigureLoop.Closed);

    Render(canvasPathBuilder);
}

Here’s the sample page:

Square

It already contains the elements to test some properties that we’ll define later in this article.

Here’s the XAML for the test square:

<controls:Square x:Name="Square"
                    Stroke="LightSlateGray"
                    CenterPointX="150"
                    CenterPointY="250"
                    Side="100"
                    Height="400"
                    Width="400" />

Let’s start the fun

To discover new requirements and optimizations the framework, we decided to reenact some famous optical illusions involving only squares.

Most of the pages start with just an empty canvas like this:

<Viewbox>
    <Canvas x:Name="Canvas"
            Height="1000"
            Width="1500"
            Background="Transparent" />
</Viewbox>

We’ll draw most of the squares programmatically.

The first one is the Café Wall Illusion (a.k.a. the shifted chessboard), a 2D geometrical-optical illusion where parallel lines appear to be sloped. Here’s how our bindable shape implementation looks like:

CafeWall

Believe it or not, all these horizontal lines are parallel.

Here’s how a square is added to the canvas:

var blackSquare = new Square
{
    Side = side,
    Fill = Colors.LightSlateGray,
    Height = Canvas.Height,
    Width = Canvas.Width,
    CenterPointX = (240 * i),
    CenterPointY = 120 + (240 * j)
};
Canvas.Children.Add(blackSquare);

This first test case did not reveal new requirements, but allowed us to demonstrate the programmatic instantiation of our new controls – and we had a lot of fun.

Optimizing for quantity

Our second test case is the Bulging Checkerboard Illusion, a 3D geometrical-optical illusion that makes a checkerboard to look like bulging out of the screen, just by adding small squares (or circles) in it:

Checkerboard

When we rendered the image, we noticed that it took an unacceptably long time. Our first attempt to solve this, was to drastically reducing the number of calls to the core rendering code (a technique that we borrowed from some of the WinRT XAML Toolkit Controls). Every change of every dependency property triggers rendering, so that’s a lot of calls when the control is loading. So we added a DelayRendering property to the base class an made sure not to call the core logic as long as the property is set to false:

public CompositionPathHost(bool delayRendering) : this()
{
    _delayRendering = delayRendering;
}
protected static void Render(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var path = (CompositionPathHost)d;
    if (path._delayRendering || path.Container.ActualWidth == 0)
    {
        return;
    }

    path.Render();
}
public bool DelayRendering
{
    get
    {
        return _delayRendering;
    }

    set
    {
        _delayRendering = value;
        if (!_delayRendering)
        {
            Render();
        }
    }
}

Of course this has an impact on the drawing code on the client side. We have to make sure that the property is assigned before any other (in a constructor), and have to reset is after all the other properties have their value:

var blackSquare = new Square(true)
{
    Side = side,
    Fill = Colors.LightSlateGray,
    Height = Canvas.Height,
    Width = Canvas.Width,
    CenterPointX = i * 100 + 50,
    CenterPointY = j * 100 + 50
};
Canvas.Children.Add(blackSquare);
blackSquare.DelayRendering = false;

Unfortunately we were underwhelmed by the impact of this code on the rendering time of all squares. So we started looking into other ways to speed up the process, by looking at opportunities to share ‘expensive’ objects – probably in the Win2D space. From the home page of CompositionProToolkit we learned that a CanvasDevice can be shared between CanvasPathBuilder instances, so we created a static device and reused it:

protected static CanvasDevice CanvasDevice = new CanvasDevice();

protected CanvasPathBuilder GetCanvasPathBuilder()
{
    var canvasPathBuilder = new CanvasPathBuilder(CanvasDevice);
    // ...

    return canvasPathBuilder;
}

The difference in rendering time is quite significant, and there is no impact on the client code. So it’s another successful test case!

Adding bindable Rotation properties

Our third test case (and personal favorite) is the hypnotizing Square Circe Spiral Illusion, an intertwining illusion where a set of concentric circles made of squares appear to spiral and intersect:

Intertwining

The effect is caused by selecting appropriate colors and by applying a rotation scheme to the squares. The latter is missing in our current API. Since it’s quite common to apply a rotation to shapes, we added the necessary dependency properties to the base class: RotationCenterX, RotationCenterY, and RotationAngle. We then only had to update the shared part of the rendering code to use these new properties:

shapeVisual.CenterPoint = new Vector3((float)RotationCenterX, (float)RotationCenterY, 0f);
shapeVisual.RotationAxis = new Vector3(0f, 0f, 1f);
shapeVisual.RotationAngle = (float)RotationAngle;

Et voilà: free rotation capabilities for all bindable shapes. Here’s a code snippet from the sample page:

var square = new Square
{
    Side = side,
    Stroke = i % 2 == 0 ? Colors.WhiteSmoke : Colors.LightSlateGray,
    StrokeThickness = 6,
    Height = Canvas.Height,
    Width = Canvas.Width,
    CenterPointX = (size / 2) + Math.Sin(Math.PI * 2 * i / squares) * radius,
    CenterPointY = (size / 2) + Math.Cos(Math.PI * 2 * i / squares) * radius,
    RotationCenterX = (size / 2) + Math.Sin(Math.PI * 2 * i / squares) * radius,
    RotationCenterY = (size / 2) + Math.Cos(Math.PI * 2 * i / squares) * radius,
    RotationAngle = (90d - i * (360d / squares) + rot * 15d) * Math.PI / 180
};
Canvas.Children.Add(square);

Adding animation

The fourth test case required animation: in the Breathing Square Illusion a spinning rectangle seems to grow and shrink, but it’s actually just rotating without any size change.

BreathingSquare

There is no way to expose animation behavior by just adding dependency properties. Instead we decided to expose the rendered ShapeVisual to the client of the control – not as a dependency property however, since the visual is recreated at each Render call. The shape visual is exposed via an Event on the control, so we first declared an argument:

public class RenderedEventArgs : EventArgs
{
    public ShapeVisual ShapeVisual { get; set; }
}

And then used it in a Rendered event:

public event EventHandler<RenderedEventArgs> Rendered;

protected virtual void OnRendered(RenderedEventArgs e)
{
    Rendered?.Invoke(this, e);
}

protected void Render(CanvasPathBuilder canvasPathBuilder)
{
    // ...
    root.Children.InsertAtTop(shapeVisual);
    OnRendered(new RenderedEventArgs { ShapeVisual = shapeVisual });
}

Here’s the definition of the square in the back:

var backSquare = new Square
{
    Side = 600,
    Fill = Colors.LightSteelBlue,
    Height = Canvas.Height,
    Width = Canvas.Width,
    CenterPointX = 500,
    CenterPointY = 500,
    RotationCenterX = 500,
    RotationCenterY = 500
};
backSquare.Rendered += BackSquare_Rendered;
Canvas.Children.Add(backSquare);

And here’s the handler for the Rendered event. It starts a ScalarKeyFrameAnimation with a LinearEasingFunction on the RotationAngleInDegrees:

private void BackSquare_Rendered(object sender, RenderedEventArgs e)
{
    // Start animation
    var shape = e.ShapeVisual;
    var compositor = Window.Current.Compositor;
    var animation = compositor.CreateScalarKeyFrameAnimation();
    var linear = compositor.CreateLinearEasingFunction();
    animation.InsertKeyFrame(1f, 360f, linear);
    animation.Duration = TimeSpan.FromSeconds(8);
    animation.Direction = AnimationDirection.Normal;
    animation.IterationBehavior = AnimationIterationBehavior.Forever;
    shape.StartAnimation(nameof(shape.RotationAngleInDegrees), animation);
}

Each time that the Square control is Rendered (when a dependency property changed), the shape visual is exposed via the event, and the rotation is started.

With these few –but fun- test cases, we were able to extend the base class of the Bindable Path API with nice features and improvements. This is how it now looks like:

ClassDiagram1

Note: there are more subclasses than just Square …

Look Mom, all XAML

Most of the test cases created the ‘bindable’ path controls programmatically, so we decided to add one entirely in XAML, to finish it off. Here’s how the Motion Binding Illusion looks like:

MotionBinding

At first sight there is nothing common in the movement of the four bars, except that they seem to move in pairs. Again this illusion is entirely made up of squares. The four bars are the sides of a 45° rotated square of which the corners are hidden by four smaller (and also rotated) square. The center square is making a circular motion.

Here’s how these five squares are defined in XAML – we now surely wish we had added a RotationInDegrees property:

<Canvas x:Name="Canvas"
        Height="1000"
        Width="1000"
        Background="LightSteelBlue">
    <controls:Square x:Name="Square"
                        Stroke="LightSlateGray"
                        StrokeThickness="16"
                        CenterPointX="500"
                        CenterPointY="500"
                        Side="540"
                        Height="1000"
                        Width="1000"
                        RotationCenterX="500"
                        RotationCenterY="500"
                        RotationAngle=".785"
                        Canvas.Left="-50"
                        Canvas.Top="-50">

    </controls:Square>
    <controls:Square Fill="LightSteelBlue"
                        StrokeThickness="0"
                        CenterPointX="500"
                        CenterPointY="142"
                        Side="200"
                        Height="1000"
                        Width="1000"
                        RotationCenterX="500"
                        RotationCenterY="142"
                        RotationAngle=".785" />
    <controls:Square Fill="LightSteelBlue"
                        StrokeThickness="0"
                        CenterPointX="500"
                        CenterPointY="858"
                        Side="200"
                        Height="1000"
                        Width="1000"
                        RotationCenterX="500"
                        RotationCenterY="858"
                        RotationAngle=".785" />
    <controls:Square Fill="LightSteelBlue"
                        StrokeThickness="0"
                        CenterPointX="142"
                        CenterPointY="500"
                        Side="200"
                        Height="1000"
                        Width="1000"
                        RotationCenterX="142"
                        RotationCenterY="500"
                        RotationAngle=".785" />
    <controls:Square Fill="LightSteelBlue"
                        StrokeThickness="0"
                        CenterPointX="858"
                        CenterPointY="500"
                        Side="200"
                        Height="1000"
                        Width="1000"
                        RotationCenterX="858"
                        RotationCenterY="500"
                        RotationAngle=".785" />
</Canvas>

The circular motion of the is done through a storyboarded animation on the Canvas.Top and Canvas.Left attached properties – both DoubleAnimations with a SineEase easing function in ‘EaseInOut’ mode (slow-fast-slow). To end up with a circular motion, one animation should follow a cosine function while the follows a sine function. This is solved by delaying the start of one animation with one second (half the full duration of the 180° animation).

Here’s the whole XAML: 

<Storyboard x:Name="myStoryboard"
            RepeatBehavior="Forever">
    <DoubleAnimation Storyboard.TargetName="Square"
                        Storyboard.TargetProperty="(Canvas.Left)"
                        From="-50"
                        To="50"
                        Duration="0:0:2"
                        AutoReverse="True"
                        RepeatBehavior="Forever">
        <DoubleAnimation.EasingFunction>
            <SineEase EasingMode="EaseInOut" />
        </DoubleAnimation.EasingFunction>
    </DoubleAnimation>
    <DoubleAnimation Storyboard.TargetName="Square"
                        Storyboard.TargetProperty="(Canvas.Top)"
                        From="-50"
                        To="50"
                        BeginTime="0:0:1"
                        Duration="0:0:2"
                        AutoReverse="True"
                        RepeatBehavior="Forever">
        <DoubleAnimation.EasingFunction>
            <SineEase EasingMode="EaseInOut" />
        </DoubleAnimation.EasingFunction>
    </DoubleAnimation>
</Storyboard>

Want some more?

There are a lot more optical illusions right here. Our sample project lives on GitHub.

Enjoy!

Creating Bindable Path Controls in UWP

In this article we’ll show some ways to create a bindable arc segment for UWP. We’ve been using elliptical arc elements relatively often. In most cases these where parts inside of user controls such as the percentage ring, the radial range indicator, and the radial gauge. We noticed that every time we used elliptical arcs, the hosting control’s code rapidly became cluttered with path calculation and drawing routines, making its core functionality unnecessarily hard to debug and extend. So we started looking for ways to abstract the calculation and drawing routines out of the user controls and into some reusable pattern.

Here’s a screenshot of the sample app that was created during the process:
CircleSegment

We targeted to implement the full Elliptical Arc SVG definition, including start and end point, radii, rotation angle, sweep direction, and a large arc indicator. Not only does this provide a mathematical context, it could also allow us to achieve compatibility with the SVG path syntax itself (e.g. for serialization). [Spoiler: We did not implement this in the sample app, because such a parser already exists in the CompositionProToolkit.]

For starters, here’s how a non-bindable elliptical arc path looks like in XAML (straight from the excellent documentation):

<Path Stroke="Black" StrokeThickness="1"  
  Data="M 10,100 A 100,50 45 1 0 200,100" />

XAML solution

The ‘classic’ way to draw an elliptical arc in UWP goes back to WPF, and is also implemented in the user controls that we just mentioned: in your XAML, place an ArcSegment as part of PathFigure in a Path control. The Path provides the Stroke properties (color, thickness, pattern), the PathFigure provides the start position, and the Arcsegment provides the rest.

Here’s how the XAML for a fixed arc segment looks like. It’s the exact same arc as the one in the previous code snippet, but without using the SVG mini-language:

<Path Stroke="Black" StrokeThickness="1">
  <Path.Data>
    <PathGeometry>
      <PathGeometry.Figures>
        <PathFigureCollection>
          <PathFigure StartPoint="10,100">
            <PathFigure.Segments>
              <PathSegmentCollection>
                <ArcSegment Size="100,50" RotationAngle="45" IsLargeArc="True" SweepDirection="CounterClockwise" Point="200,100" />
              </PathSegmentCollection>
            </PathFigure.Segments>
          </PathFigure>
        </PathFigureCollection>
      </PathGeometry.Figures>
    </PathGeometry>
  </Path.Data>
</Path>

This XAML structure looks a lot more binding-friendly than the Data string of the first Path definition. All we need to do is create an appropriate ViewModel for it, preferably one that implements INotifyPropertyChanged and exposes the properties through data types that are easy to bind to. Instead of Point or Vector instances for start position, end position, and radius, the ViewModel exposes numeric properties with the individual coordinates:

public class EllipticalArcViewModel : BindableBase
{
    public int RadiusX
    {
        get { return _radiusX; }
        set
        {
            SetProperty(ref _radiusX, value);
            OnPropertyChanged(nameof(Radius));
        }
    }

    public int RadiusY
    {
        get { return _radiusY; }
        set
        {
            SetProperty(ref _radiusY, value);
            OnPropertyChanged(nameof(Radius));
        }
    }

    public Size Radius => new Size(_radiusX, _radiusY);

    // ...
}

The enumerations for PenLineCap and SweepDirection are accessible through Boolean properties:

public bool IsClockwise
{
    get { return _isClockwise; }
    set
    {
        SetProperty(ref _isClockwise, value);
        OnPropertyChanged(nameof(SweepDirection));
    }
}

public SweepDirection SweepDirection => 
	_isClockwise ? SweepDirection.Clockwise : SweepDirection.Counterclockwise;

Here’s the class diagram for the first iteration of our ‘bindable arc’. The new ViewModel is on the left, and the XAML elements to which we will bind, are on the right:

EllipticalArcViewModel

The hosting page exposes an instance of the ViewModel in its code behind:

private EllipticalArcViewModel _viewModel = new EllipticalArcViewModel
{
    StartPointX = 10,
    StartPointY = 100,
    RadiusX = 100,
    RadiusY = 50,
    RotationAngle = 45,
    EndPointX = 200,
    EndPointY = 100
};

public EllipticalArcViewModel ViewModel => _viewModel;

And in the XAML we added {x:Bind} bindings to the elements:

<Path Stroke="{x:Bind ViewModel.Stroke, Mode=OneWay}"
        Height="400"
        Width="400"
        StrokeThickness="{x:Bind ViewModel.StrokeThickness, Mode=OneWay}"
        StrokeStartLineCap="{x:Bind ViewModel.StrokeLineCap, Mode=OneWay}"
        StrokeEndLineCap="{x:Bind ViewModel.StrokeLineCap, Mode=OneWay}">
    <Path.Data>
        <PathGeometry>
            <PathGeometry.Figures>
                <PathFigureCollection>
                    <PathFigure StartPoint="{x:Bind ViewModel.StartPoint, Mode=OneWay}">
                        <PathFigure.Segments>
                            <PathSegmentCollection>
                                <ArcSegment RotationAngle="{x:Bind ViewModel.RotationAngle, Mode=OneWay}"
                                            IsLargeArc="{x:Bind ViewModel.IsLargeArc, Mode=OneWay}"
                                            SweepDirection="{x:Bind ViewModel.SweepDirection, Mode=OneWay}"
                                            Point="{x:Bind ViewModel.EndPoint, Mode=OneWay}"
                                            Size="{x:Bind ViewModel.Radius, Mode=OneWay}" />
                            </PathSegmentCollection>
                        </PathFigure.Segments>
                    </PathFigure>
                </PathFigureCollection>
            </PathGeometry.Figures>
        </PathGeometry>
    </Path.Data>
</Path>

The ViewModel implements change propagation, so we can use Sliders (or other input controls) to modify its properties, and this will update the XAML elements:

<Slider Header="Rotation Angle"
        Value="{x:Bind ViewModel.RotationAngle, Mode=TwoWay}"
        Maximum="360" />
<ToggleSwitch Header="Sweep Direction"
                IsOn="{x:Bind ViewModel.IsClockwise, Mode=TwoWay}"
                OnContent="Clockwise"
                OffContent="Counterclockwise" />

Here’s how the corresponding page in the sample app looks like. It draws an ArcSegment with bindings to all of its SVG properties. Slider and ToggleSwitch controls allow you to modify the elliptical arc through the ViewModel:
ArcSegment

It’s relatively easy to make this solution more reusable by turning it into a user control. If you’re looking for an example of this, just check the code for the PieSlice and RingSlice controls from WinRT XAML Toolkit.

Composition solution

The last couple of years, we’ve seen an exciting increase in functionality and ease of use of the UWP Composition API. It has come to a point were apps can now relatively easy draw (and/or animate) anything directly in the Visual layer, a layer that is much closer to the fast DirectX drivers than the traditional XAML layer:
layers-win-ui-composition

The Visual Layer allows for fast drawing off the UI thread. Its API was recently enhanced with capabilities to draw paths. This makes it a logical next step in our attempt to create bindable paths.

In this second iteration, we packaged a ‘bindable arc’ as a user control. Its only XAML element is an empty grid which is only there to establish the link between the Visual (composition) Layer and the Framework (XAML) layer. Here’s that XAML:

<UserControl x:Class="XamlBrewer.Uwp.Controls.EllipticalArc">
    <Grid x:Name="Container" />
</UserControl>

Unsurprisingly, the new EllipticalArc control exposes the same list of properties as the EllipticalArcViewModel. Only this time they’re implemented as Dependency Properties:

public sealed partial class EllipticalArc : UserControl
{
    public static readonly DependencyProperty StartPointXProperty =
        DependencyProperty.Register(nameof(StartPointX), 
                                    typeof(int), 
                                    typeof(EllipticalArc), 
                                    new PropertyMetadata(0, new PropertyChangedCallback(Render)));

    public static readonly DependencyProperty StartPointYProperty =
        DependencyProperty.Register(nameof(StartPointY), 
                                    typeof(int), 
                                    typeof(EllipticalArc), 
                                    new PropertyMetadata(0, Render));

    public int EndPointX
    {
        get { return (int)GetValue(EndPointXProperty); }
        set { SetValue(EndPointXProperty, value); }
    }

    public int EndPointY
    {
        get { return (int)GetValue(EndPointYProperty); }
        set { SetValue(EndPointYProperty, value); }
    }

    // ...

}

Here’s the full API:

EllipticalArcControl

Every time a property changes, the Render method is called, to redraw the elliptical arc. It uses classes from the Composition API and Win2D (available through a NuGet package). Here are the key classes and their responsibility in that method:

Class Responsibility
Compositor Creates all Composition objects
Win2D CanvasPathBuilder Holds the path figure definitions
Win2D CanvasGeometry Draws geometric shapes in DirectX
CompositionPath Links the geometric shape to the Visual Layer
CompositionSpriteShape Holds the stroke and fill definitions
ShapeVisual Links the Visual Layer to the Framework Layer

Here’s the full Render method:

private static void Render(DependencyObject d, DependencyPropertyChangedEventArgs args)
{
    var arc = (EllipticalArc)d;
    if (arc.Container.ActualWidth == 0)
    {
        return;
    }

    var root = arc.Container.GetVisual();
    var compositor = Window.Current.Compositor;
    var canvasPathBuilder = new CanvasPathBuilder(new CanvasDevice());

    // Figure
    canvasPathBuilder.BeginFigure(new Vector2(arc.StartPointX, arc.StartPointY));
    canvasPathBuilder.AddArc(
        new Vector2(arc.EndPointX, arc.EndPointY),
        arc.RadiusX,
        arc.RadiusY,
        (float)(arc.RotationAngle * Math.PI / 180),
        arc.IsClockwise ? CanvasSweepDirection.Clockwise : CanvasSweepDirection.CounterClockwise,
        arc.IsLargeArc ? CanvasArcSize.Large : CanvasArcSize.Small);
    canvasPathBuilder.EndFigure(arc.IsClosed ? CanvasFigureLoop.Closed : CanvasFigureLoop.Open);

    // Path
    var canvasGeometry = CanvasGeometry.CreatePath(canvasPathBuilder);
    var compositionPath = new CompositionPath(canvasGeometry);
    var pathGeometry = compositor.CreatePathGeometry();
    pathGeometry.Path = compositionPath;
    var spriteShape = compositor.CreateSpriteShape(pathGeometry);
    spriteShape.FillBrush = compositor.CreateColorBrush(arc.Fill);
    spriteShape.StrokeThickness = (float)arc.StrokeThickness;
    spriteShape.StrokeBrush = compositor.CreateColorBrush(arc.Stroke);
    spriteShape.StrokeStartCap = arc.IsStrokeRounded ? CompositionStrokeCap.Round : CompositionStrokeCap.Flat;
    spriteShape.StrokeEndCap = arc.IsStrokeRounded ? CompositionStrokeCap.Round : CompositionStrokeCap.Flat;

    // Visual
    var shapeVisual = compositor.CreateShapeVisual();
    shapeVisual.Size = new Vector2((float)arc.Container.ActualWidth, (float)arc.Container.ActualHeight);
    shapeVisual.Shapes.Add(spriteShape);
    root.Children.RemoveAll();
    root.Children.InsertAtTop(shapeVisual);
}

Here’s how to use the control on a XAML page:

<controls:EllipticalArc x:Name="EllipticalArc"
                        StartPointX="10"
                        StartPointY="100"
                        RotationAngle="45"
                        RadiusX="100"
                        RadiusY="50"
                        EndPointX="200"
                        EndPointY="100"
                        Height="400"
                        Width="400" />

It supports two-way bindings, so you can update its properties through other controls – or through ViewModels if you prefer an MVVM approach:

<Slider Header="Rotation Angle"
        Value="{Binding RotationAngle, ElementName=EllipticalArc, Mode=TwoWay}"
        Maximum="360" />
<ToggleSwitch Header="Sweep Direction"
                IsOn="{Binding IsClockwise, ElementName=EllipticalArc, Mode=TwoWay}"
                OnContent="Clockwise"
                OffContent="Counterclockwise" />

Here’s how the corresponding page looks like in the sample app – it’s identical to the XAML version:
Composition

Reusable solution

The experiment with the light weight user control inspired us to move one step further, and create an easy-to-use circle segment control as part of a ‘reusable generic bindable path drawing framework for UWP’ (a.k.a. an abstract base class).

This circle segment control is intended for use inside (the template of) other controls – think of percentage ring, radial gauge, pie chart, and doughnut chart. All the common ‘bindable path’ members (such as stroke properties, start position, and closed figure indicator) are factored out into an abstract base class, while the CircleSegment itself only comes with a center, a radius, start end sweep angles. We also added a Boolean property to specify whether to add extra lines to the center and give it a pie slice shape. [We know: mathematically this property should have been called IsSector instead of IsPie.]

Here’s the class diagram for the last ‘bindable arc’ iteration:

CircleSegmentClassDiagram

As in the previous EllipticalArc, the user control’s only XAML element is a Grid to hook the composition drawings to the Visual Tree:

<UserControl
    x:Class="XamlBrewer.Uwp.Controls.CompositionPathHost">
    <Grid x:Name="Host" />
</UserControl>

On top of the property definitions, the control comes with an abstract Render method which is triggered on load and on property changes:

public static readonly DependencyProperty StartPointXProperty =
	DependencyProperty.Register(
		nameof(StartPointX), 
		typeof(double), 
		typeof(CompositionPathHost), 
		new PropertyMetadata(0d, new PropertyChangedCallback(Render)));

public double StartPointX
{
    get { return (double)GetValue(StartPointXProperty); }
    set { SetValue(StartPointXProperty, value); }
}

// ...

private void CompositionPathHost_Loaded(object sender, RoutedEventArgs e)
{
    Render(this, null);
}

protected static void Render(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var path = (CompositionPathHost)d;
    if (path.Container.ActualWidth == 0)
    {
        return;
    }

    path.Render();
}

protected abstract void Render();

The new CircleSegment control inherits from this abstract base class, and adds its own properties;

public class CircleSegment : CompositionPathHost
{
  public static readonly DependencyProperty CenterPointYProperty =
      DependencyProperty.Register(
		nameof(CenterPointY), 
		typeof(double), 
		typeof(CircleSegment), 
		new PropertyMetadata(0d, Render));

  public double CenterPointX
  {
    get { return (double)GetValue(CenterPointXProperty); }
    set { SetValue(CenterPointXProperty, value); }
  }

  // ...

}

And of course provides its own implementation of the Render method. It’s a specialized version of the one in EllipticalArc that’s based on a more convenient AddArc overload with a center point and start and sweep angles. The start point coordinates of the underlying figure are determined by the IsPie property. The figure starts at the circle’s center for a pie slice shape (sector), or at the beginning of the arc for a simple segment:

protected override void Render()
{
    var root = Container.GetVisual();
    var compositor = Window.Current.Compositor;
    var canvasPathBuilder = new CanvasPathBuilder(new CanvasDevice());
    if (IsStrokeRounded)
    {
        canvasPathBuilder.SetSegmentOptions(CanvasFigureSegmentOptions.ForceRoundLineJoin);
    }
    else
    {
        canvasPathBuilder.SetSegmentOptions(CanvasFigureSegmentOptions.None);
    }

    // Figure
    if (IsPie)
    {
        StartPointX = CenterPointX;
        StartPointY = CenterPointY;
    }
    else
    {
        StartPointX = Radius * Math.Cos(StartAngle * Degrees2Radians) + CenterPointX;
        StartPointY = Radius * Math.Sin(StartAngle * Degrees2Radians) + CenterPointY;
    }

    canvasPathBuilder.BeginFigure(new Vector2((float)StartPointX, (float)StartPointY));

    canvasPathBuilder.AddArc(
        new Vector2((float)CenterPointX, (float)CenterPointY),
        (float)Radius,
        (float)Radius,
        (float)(StartAngle * Degrees2Radians),
        (float)(SweepAngle * Degrees2Radians));

    canvasPathBuilder.EndFigure(IsClosed || IsPie ? CanvasFigureLoop.Closed : CanvasFigureLoop.Open);

    // Path
    var canvasGeometry = CanvasGeometry.CreatePath(canvasPathBuilder);
    var compositionPath = new CompositionPath(canvasGeometry);
    var pathGeometry = compositor.CreatePathGeometry();
    pathGeometry.Path = compositionPath;
    var spriteShape = compositor.CreateSpriteShape(pathGeometry);
    spriteShape.FillBrush = compositor.CreateColorBrush(Fill);
    spriteShape.StrokeThickness = (float)StrokeThickness;
    spriteShape.StrokeBrush = compositor.CreateColorBrush(Stroke);
    spriteShape.StrokeStartCap = IsStrokeRounded ? CompositionStrokeCap.Round : CompositionStrokeCap.Flat;
    spriteShape.StrokeEndCap = IsStrokeRounded ? CompositionStrokeCap.Round : CompositionStrokeCap.Flat;

    // Visual
    var shapeVisual = compositor.CreateShapeVisual();
    shapeVisual.Size = new Vector2((float)Container.ActualWidth, (float)Container.ActualHeight);
    shapeVisual.Shapes.Add(spriteShape);
    root.Children.RemoveAll();
    root.Children.InsertAtTop(shapeVisual);
}

Please observe we switched from integers to doubles for all coordinate properties. The calculation of the starting point revealed the rounding errors when using integers.

For a screenshot of the corresponding page in the sample app, scroll back to the beginning of this article. It’s basically the same as all the previous ones, only with better code behind. We also brought up the old SquareOfSquares control to host some CircleSegment instances with different sizes and random properties:

SquareOfSquares

Last but not least, we added an inspiring gallery page to demonstrate some potential usages of the control:

Gallery

All the code

The sample app lives here on GitHub.

Enjoy!

Drawing shapes with Windows Composition in UWP

This article explains how you can use the Composition API to display and animate geometric shapes in a UWP app. Before the April Windows 10 Update, this API was limited to drawing rectangles that you could fill with a brush or an image. The latest update does more than rectangles: it supports shapes from lines and ellipses to rounded rectangles and even SVG paths. This is good news for developers who want to use more of Windows Composition in their apps, e.g. for drawing controls or for animating content à la Lottie.

In this article I will not dive too much in detail on how to draw and animate Composition sprites in XAML. Please refer to one of my previous blog posts for an introduction to the core concepts. In this article we’ll stick to the new classes that were added to display shapes:

To demonstrate these new classes I built a Clock UserControl that’s made of ellipses and rounded rectangles drawn and animated by Windows Composition. This is how it looks like:
Standard

I didn’t have to start from scratch, I upgraded an existing project that used rectangles and images:
clock_original

Using the new API

The upgraded clock control is a custom user control that has

  • ellipses as background face and hour ticks,
  • rounded rectangles for hour and minute hands, and
  • a simple shape – a triangle- for the seconds hand.

Everything is done with the Composition API, the only XAML is

  • a ViewBox for easy calculations, with
  • an empty Canvas to host the visual:
<Viewbox Stretch="Uniform">
    <Canvas x:Name="Container"
            Background="Transparent"
            Height="200"
            Width="200">
    </Canvas>
</Viewbox>

The ContainerVisual that links the Composition elements to XAML is defined in the Loaded event of the hosting control, since that’s the first event where we have access to the Container canvas. For convenience, I still use an extension method to link the two worlds (maybe the new API has improvements here, which I didn’t notice):

_root = Container.GetVisual();
public static class UIElementExtensions
{
    public static ContainerVisual GetVisual(this UIElement element)
    {
        var hostVisual = ElementCompositionPreview.GetElementVisual(element);
        var root = hostVisual.Compositor.CreateContainerVisual();
        ElementCompositionPreview.SetElementChildVisual(element, root);
        return root;
    }
}

The Compositor is still used as a factory to create the rest of the instances.

Here’s the routine that draws the hour hand as a Win2D geometry and lifts it to the XAML stack – from CompositionRoundedRectangleGeometry to CompositionSpriteShape to ShapeVisual:

var roundedRectangle = _compositor.CreateRoundedRectangleGeometry();
roundedRectangle.Size = new Vector2(6.0f, 63.0f);
roundedRectangle.CornerRadius = new Vector2(3.0f, 3.0f);
_hourhandSpriteShape = _compositor.CreateSpriteShape(roundedRectangle);
_hourhandSpriteShape.FillBrush = _compositor.CreateColorBrush(Colors.DarkSlateGray);
_hourhandSpriteShape.Offset = new Vector2(97.0f, 40.0f);
_hourhandSpriteShape.CenterPoint = new Vector2(3.0f, 60.0f);
handShapeVisual = _compositor.CreateShapeVisual();
handShapeVisual.Size = new Vector2(200.0f, 200.0f);
handShapeVisual.Shapes.Add(_hourhandSpriteShape);
_root.Children.InsertAtTop(handShapeVisual);

The seconds hand is a triangle made out of three lines. Drawing it was a little bit harder than expected. The constructor of CompositionPath requires a IGeometrySource2D instance, which brings you pretty close to the Win2D primitives (and out of my enterprise XAML developer comfort zone). Fortunately I came across this blog post by Daren May where he shares an extension method on CanvasPathBuilder to build a path with lines:

public static class PathBuilderExtensions
{
    public static CanvasPathBuilder BuildPathWithLines(
        this CanvasPathBuilder builder,
        IEnumerable<Vector2> vectors,
        CanvasFigureLoop canvasFigureLoop)
    {
        var first = true;

        foreach (var vector2 in vectors)
        {
            if (first)
            {
                builder.BeginFigure(vector2);
                first = false;
            }
            else
            {
                builder.AddLine(vector2);
            }
        }

        builder.EndFigure(canvasFigureLoop);
        return builder;
    }

    public static CanvasPathBuilder BuildPathWithLines(
        this CanvasPathBuilder builder,
        IEnumerable<(float x, float y)> nodes,
        CanvasFigureLoop canvasFigureLoop)
    {
        var vectors = nodes.Select(n => new Vector2(n.x, n.y));
        return BuildPathWithLines(builder, vectors, canvasFigureLoop);
    }
}

Here’s the routine that draws the seconds hand from three line coordinates into a CompositionSpriteShape:

var canvasPathBuilder = new CanvasPathBuilder(new CanvasDevice());
canvasPathBuilder.BuildPathWithLines(new(float x, float y)[]
    {
        (0, 80),
        (3, 0),
        (6, 80)
    },
    CanvasFigureLoop.Closed);
var canvasGeometry = CanvasGeometry.CreatePath(canvasPathBuilder);
var compositionPath = new CompositionPath(canvasGeometry);
var pathGeometry = _compositor.CreatePathGeometry();
pathGeometry.Path = compositionPath;
_secondhandSpriteShape = _compositor.CreateSpriteShape(pathGeometry);

The –smooth- animations to redraw the hour, minute, and seconds hands act at CompositionObject level –the base class of all visual composition objects-, so there was no need to modify from the original clock here.

The old clock successfully upgraded from straight rectangles to rounded ones, ellipses, and lines. Let’s now get closer to real SVG paths.

Getting some help from CompositionProToolkit

When looking for more advanced extension methods for CanvasPathBuilder, I came across the CompositionProToolkit by Ratish Philip. This is a very rich collection of Win2D based helper classes and controls for Windows Composition. The helper classes allowed me to build a ‘pro’ version of the clock that comes with

  • ellipses for hour ticks and seconds hand,
  • an SVG-ish complex path as background face, and
  • a container shape for the hour and minute hands.

Here’s how that clock looks like:
Pro

Here’s how the seconds hand (just a circular dot) is drawn using one of the many extension methods on CanvasPathBuilder:

var canvasPathBuilder = new CanvasPathBuilder(new CanvasDevice());
canvasPathBuilder.AddCircleFigure(new Vector2(0.0f, 0.0f), 2.0f);
var canvasGeometry = CanvasGeometry.CreatePath(canvasPathBuilder);
var compositionPath = new CompositionPath(canvasGeometry);
var pathGeometry = _compositor.CreatePathGeometry();
pathGeometry.Path = compositionPath;
_secondhandSpriteShape = _compositor.CreateSpriteShape(pathGeometry);
_secondhandSpriteShape.FillBrush = _compositor.CreateColorBrush(Colors.Tomato);
_secondhandSpriteShape.Offset = new Vector2(100f, 5f);
_secondhandSpriteShape.CenterPoint = new Vector2(0.0f, 95.0f);

The soccer ball graphic at the end of the hour and minute hands is drawn from an SVG path. The background clock face uses the exact same code – with a different path of course. CompositionProToolkit comes with its own parser for the path mini language. Here’s how the path is transformed into a sprite:

pathData = "M12.255911,27.32522L8.0630484,27.526361z ... (rest of path omitted)";
var spriteShape = _compositor.CreateSpriteShape(pathData);
spriteShape.FillBrush = _compositor.CreateColorBrush(Colors.DarkSlateGray);
spriteShape.Offset = new Vector2(84.0f, 30f);

If you consider using Win2D or Windows Composition in your UWP apps, you should definitely take a look at CompositionProToolkit!

Using a CompositionContainerShape

This last version of the clock also comes with a nice use case for the new CompositionContainerShape. The hour and minute hands are each a ‘group’ of two sprites: a soccer ball and a line to the center of the clock. Both shapes are combined into one:

_hourContainerShape = _compositor.CreateContainerShape();
_hourContainerShape.CenterPoint = new Vector2(100f, 100f);
// ...
var spriteShape = _compositor.CreateSpriteShape(pathData);
// ...
_hourContainerShape.Shapes.Add(spriteShape);

var line = _compositor.CreateLineGeometry();
// ...
spriteShape = _compositor.CreateSpriteShape(line);
// ...
_hourContainerShape.Shapes.Add(spriteShape);

For the animation, we can now use that container as a target. So we don’t have to animate the individual shapes – very convenient:

_hourContainerShape.RotationAngleInDegrees = (float)now.TimeOfDay.TotalHours * 30;
_minuteContainerShape.RotationAngleInDegrees = now.Minute * 6;

The Sample Project

The sample project lives here on Github.

Enjoy!

A Fluent Button Flyout for UWP

In this article we describe how to build an elegantly designed and animated fluent ToggleButton-Popup combination for use in UWP apps as an alternative for the less fluent Button Flyout. The code is not mine, I merely reused code and components from the Continuity framework by Justin Liu. This frameworks contains a lot of helpers for implementing Fluent Design in your apps without needing Windows 10 Fall Creators Update.

The ToggleButton-Popup combo is an excellent widget to display extra information such as a help text.

Here’s how all of this may look like (styling is up to you of course). It starts with a stand-alone round ‘more’ button, with hover animations. When pressed, the button rotates toward the opening flyout, while changing its shape to look like a ‘close’ button that becomes connected to the content that was just revealed. In the mean time, the flyout itself opens with a scale animation as if it grew out of the button:

FluentFlyoutButton

Closing the button and dismissing the flyout come with reverse animations.

I’ve been looking for ways to animate opening and closing Flyout or ContentDialog instances. I went through many of the XAML animations techniques, but could not find a way. Flyout and ContentDialog don’t come with Visual States, so Visual Transitions are not an option. Although its name seems promising, PopInThemeAnimation is not applicable. And if it were, it doesn’t come with a scale animation (only opacity and translation).

I almost gave up, and tried to to accept that flyouts would always open and close like this:

DefaultFlyoutButton

Frankly, I don’t like the animations. But what’s worse: you can not even see which of the buttons was actually pressed…

Then I came across this sample Kliva design with exactly the user experience I was looking for:

ContinuityPanel

I decided to copy the relevant code into my own solution under Features/Continuity to get some Fluent-As-A-Service:

ContinuityClasses

The Kliva demo is not using a Button with its Flyout, but a ToggleButton with ‘just’ a Grid. My only mission was to replace the Grid with a Popup. After a few iterations, I came up with the following setup, which even allows the Popup to have IsLightDismissEnabled on.:

  • A toggle button with storyboarded animations is placed in a container
  • A Popup is placed next to it with
  • The content of the Popup (not the Popup itself) is given implicit Show and Hide Composition API animations.

Here’s the corresponding XAML definition:

<!-- Toggle Button -->
<continuity:CircularToggleButton x:Name="TheToggle"
                                    CheckedBackground="{StaticResource HighlightBrush}"
                                    CheckedCornerRadius="6 0 0 6"
                                    FontFamily="Segoe UI">
    <continuity:CircularToggleButton.CheckedContent>
        <ContentControl Margin="3"
                        Style="{StaticResource IconCloseStyle}" />
    </continuity:CircularToggleButton.CheckedContent>
    <ContentControl Style="{StaticResource IconMoreStyle}" />
</continuity:CircularToggleButton>
<!-- 'Flyout' -->
<Popup x:Name="ThePopup"
        IsOpen="{Binding IsChecked, ElementName=TheToggle, Mode=TwoWay}"
        IsLightDismissEnabled="False"
        HorizontalOffset="{Binding ActualWidth, ElementName=TheToggle}"
        VerticalOffset="-20">
    <Grid x:Name="TheGrid"
            Visibility="{Binding IsOpen, ElementName=ThePopup}">
        <!-- Content -->
    </Grid>
</Popup>

That’s it! Let’s dive into some details.

An animated circular toggle button

The CircularToggleButton adds a few dependency properties to ToggleButton, like content, background color and corner radius for the different states:

public sealed class CircularToggleButton : ToggleButton
{
    public static readonly DependencyProperty CheckedContentProperty =
        DependencyProperty.Register(
            "CheckedContent",
            typeof(object),
            typeof(CircularToggleButton),
            new PropertyMetadata(null));

    public object CheckedContent
    {
        get => GetValue(CheckedContentProperty);
        set => SetValue(CheckedContentProperty, value);
    }

    // ..
}

It also comes with a circular custom style and some nice visual effects and animations.

Here’s how the PointerOver VisualState pumps up the size of the content using a RenderTransform with a CompositeTransform on ScaleX and ScaleY:

<VisualState x:Name="PointerOver">
    <VisualState.Setters>
        <Setter Target="CheckedContentPresenter.(UIElement.RenderTransform).(CompositeTransform.ScaleX)"
                Value="1.1" />
        <Setter Target="CheckedContentPresenter.(UIElement.RenderTransform).(CompositeTransform.ScaleY)"
                Value="1.1" />
        <Setter Target="UncheckedContentPresenter.(UIElement.RenderTransform).(CompositeTransform.ScaleX)"
                Value="1.1" />
        <Setter Target="UncheckedContentPresenter.(UIElement.RenderTransform).(CompositeTransform.ScaleY)"
                Value="1.1" />
        <Setter Target="BackgroundVisual.Opacity"
                Value="0.9" />
    </VisualState.Setters>
</VisualState>

Here’s the VisualTransition with the StoryBoard on CornerRadius that turns the round button into its ‘connected’ shape when checked:

<VisualTransition GeneratedDuration="0:0:0.25"
                    To="Checked">
    <Storyboard>
        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.CornerRadius)"
                                        Storyboard.TargetName="BackgroundVisual">
            <DiscreteObjectKeyFrame KeyTime="0"
                                    Value="{Binding CheckedCornerRadius, RelativeSource={RelativeSource TemplatedParent}}" />
        </ObjectAnimationUsingKeyFrames>
    </Storyboard>
</VisualTransition>

If you want to know more about storyboarded animations, this circular toggle button style definition is a nice tutorial. Storyboarded animations are a way to change a dependency property over time. When there’s a visual impact of this, the animation may or may not require the UI thread. While the SDK’s evolve, more and more of these animations are internally rewritten to run on the composition thread. So there’s no need for you give up readable declarative Storyboards in XAML in favor of dark imperative code against the Composition API. But for the second part of this article, we’ll use the latter…

An animated Popup

The Popup control is a XAML container that allows you to place content on top of other content. You can not animate the Popup itself but fortunately you can animate its content. As promised we’ll use Composition animations for this. These are 60-frames-per-second animations that run independent of the UI thread. They’re a bit harder to write, but there are a lot of helpers available, like these from UWP Community Toolkit.

Here’s the Continuity helper that starts it all:

TheGrid.EnableFluidVisibilityAnimation(
    centerPoint: new Vector3(0.0f, 40.0f, 0.0f), 
    showFromScale: 0.2f, 
    hideToScale: 0.2f, 
    showDuration: 400, 
    hideDuration: 400);

Here’s part of its implementation.

A time based Composition API animation of the type Vector2KeyFrameAnimation is created with the Scale of the Visual as Target:

if (!showFromScale.Equals(1.0f))
{
    showeScaleAnimation = compositor.CreateVector2KeyFrameAnimation();
    showeScaleAnimation.InsertKeyFrame(0.0f, new Vector2(showFromScale));
    showeScaleAnimation.InsertKeyFrame(1.0f, Vector2.One);
    showeScaleAnimation.Duration = TimeSpan.FromMilliseconds(showDuration);
    showeScaleAnimation.DelayTime = TimeSpan.FromMilliseconds(showDelay);
    showeScaleAnimation.Target = "Scale.XY";
}

The different animations (scale, position, opacity, …) are placed together in an AnimationGroup (one for hide and one for show):

var showAnimationGroup = compositor.CreateAnimationGroup();
// ...
if (showeScaleAnimation != null)
{
    showAnimationGroup.Add(showeScaleAnimation);
}

These animation groups are then implicitly hooked to the Popup’s content, using SetImplicitShowAnimation and SetImplicitHideAnimation:

ElementCompositionPreview.SetImplicitShowAnimation(element, showAnimationGroup);

Under the hood, a lot of different animation techniques were used to create this user experience, but I love the result! And while it’s probably possible to forge this into a single custom control, I’m not sure if it would add much value….

The code

The ‘fluent button flyout’ sample lives here on Github, the inspiring Continuity by Justin Liu is right here.

Enjoy!

An Adaptive Menu Bar for UWP

This article demonstrates how to build an adaptive page header for UWP apps. The header contains a title, a horizontal tab-like menu bar, and an optional logo. On a wide screen all of these elements are positioned next to each other. When the screen narrows, the sizes of the title and the menu are fluidly scaled down. When the screen becomes too narrow, the tab control moves underneath the title in a fluid animation. Warning: there’s no rocket science involved, just some restyling and composition black magic.

Main menu and navigation

There is a consensus that he main navigation UI in a UWP app should be vertical menu on the left. Some time ago I wrote a blog post on how to build such navigation based on the SplitView control. Windows 10 Fall Creators Update introduces a new control for this: the NavigationView. It brings all of the top level navigation look-and-feel (menu, hamburger button, link to Settings, navigation service) in one control. For a good example on how to use it, create a Windows Template Studio Navigation Pane project and look at its source code.

In the sample project that I built for this article, I have reused the main menu UI and the Navigation service from the mentioned blog post: the so-called Shell page has a main menu on the left and a Frame that hosts the active user page on the right.

Secondary navigation

For commanding and secondary navigation UWP apps generally use horizontal menus or command bars. Some candidates for this are controls such as the different app bars, the UWP Toolkit Menu (which also supports vertical orientation), a future Ribbon that was promised in a recent Windows Community Standup and a lot of other controls that you may find in the field.

I decided to brew my own control: a light-weight horizontal menu that looks like the familiar Tab control. I put it together with the page title and an optional logo in a UserControl to be used as page header. The same header will appear on top of each content page that belongs to the same top level menu item. I did not introduce another Frame control and stayed close to the Pane-Header-Content paradigm of the already mentioned NavigationView.

Sample app

I built a small sample app with 12 content pages, unevenly spread over two main menu items. Here’s how the page header looks like with a title, 7 menu items (that’s what I target as a maximum) and no fixed logo:

AnimalsPage

Here’s a page with its title, a 5-items tab and a fixed logo at the right – that’s the default configuration in most of the apps that I’m currently building:

OthersPage
 

Building a lightweight tab control

The Tab control is nothing more than a styled ListView: a horizontal list of items, of which one can be selected:

<ListView x:Name="Menu"
            SelectionChanged="Menu_OnSelectionChanged"
            Style="{StaticResource MenuListViewStyle}"
            ItemContainerStyle="{StaticResource MenuListViewItemStyle}"
            ItemTemplate="{StaticResource MenuItemTemplate}"
            HorizontalAlignment="Left"
            Margin="20 10 10 0" />

In its custom Style we visually attach the tabs (items) to the content below by aligning the WrapGrid in the ItemsPanelTemplate to the bottom.

<Style x:Key="MenuListViewStyle"
        TargetType="ListView">
    <Setter Property="ItemsPanel">
        <Setter.Value>
            <ItemsPanelTemplate>
                <WrapGrid Orientation="Horizontal"
                            HorizontalAlignment="Right"
                            VerticalAlignment="Bottom" />
            </ItemsPanelTemplate>
        </Setter.Value>
    </Setter>
</Style>

Through the custom ItemContainerStyle we ensure that background colors of selected and non-selected tabs correspond to the background colors of header and content.

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

Finally the ItemTemplate makes the items look like menu buttons, with an SVG icon (of any size, unlike the standard AppBarButton) and a text:

<DataTemplate x:Key="MenuItemTemplate">
    <StackPanel Orientation="Vertical"
                Height="72"
                Width="80"
                Padding="4 4 4 0">
        <Border Background="Transparent"
                ToolTipService.ToolTip="{Binding Text}">
            <Path x:Name="Glyph"
                    Data="{Binding Glyph}"
                    VerticalAlignment="Center"
                    HorizontalAlignment="Center"
                    Height="40"
                    Width="40"
                    Fill="{StaticResource PageForegroundBrush}"
                    Stretch="Uniform" />
        </Border>
        <TextBlock Text="{Binding Text}"
                    Margin="0 4 0 0"
                    Foreground="{StaticResource PageForegroundBrush}"
                    VerticalAlignment="Center"
                    HorizontalAlignment="Center" />
    </StackPanel>
</DataTemplate>

This results in a clean UI that looks more or less like the familiar Tab control, but only works for a limited (<8) number of menu items. It you want more options in the same space, then I would suggest to restyle an instance of the UWP Toolkit Carousel to make a ‘rolling tab control’.

Let’s dive into the behavior. When you click a menu item, the selection changed event handler calls the navigation service in the exact same way as in the left hand main menu:

private void Menu_OnSelectionChanged(
	object sender, 
	SelectionChangedEventArgs e)
{
    if (e.AddedItems.First() is MenuItem menuItem 
	&& menuItem.IsNavigation)
    {
        Navigation.Navigate(menuItem.NavigationDestination);
    }
}

You navigate within the content frame to a new content page. That page contains the same page header (or another!). On the menu in the page header, the appropriate tab is selected:

/// <summary>
/// Highlights the (first) menu item that corresponds to the page.
/// </summary>
/// <param name="pageType">Type of the page.</param>
public void SetTab(Type pageType)
{
    // Lookup destination type in menu(s)
    var item = (from i in Menu.Items
                where (i as MenuItem).NavigationDestination == pageType
                select i).FirstOrDefault();
    if (item != null)
    {
        Menu.SelectedItem = item;
    }
    else
    {
        Menu.SelectedIndex = -1;
    }
}

Here’s the Tab Control in action:

TabNavigation

 

Making it Adaptive and Fluid

Initially, the title and the tab control each get half of the width of the page (minus the logo). This positions the first tab of the menu always at the same place, which gives a nice consistent UI. For a reasonable title and a submenu with a reasonable number of items, half the screen width should suffice. To deal with less reasonable content, each control is wrapped in a ViewBox that will stretch (only) down if needed.

<!-- Title -->
<GridViewItem VerticalAlignment="Stretch"
                VerticalContentAlignment="Center"
                HorizontalAlignment="Stretch"
                HorizontalContentAlignment="Left">
    <Viewbox x:Name="Title"
                Stretch="Uniform"
                StretchDirection="DownOnly"
                HorizontalAlignment="Left"
                VerticalAlignment="Center">
        <TextBlock Foreground="{StaticResource PageForegroundBrush}"
                    FontSize="48"
                    FontWeight="Light"
                    VerticalAlignment="Top"
                    HorizontalAlignment="Left"
                    Margin="48 8 0 0">
            <Run Text="Others" />
        </TextBlock>
    </Viewbox>
</GridViewItem>

<!-- Navigation -->
<GridViewItem HorizontalAlignment="Stretch"
                HorizontalContentAlignment="Stretch"
                VerticalAlignment="Stretch"
                VerticalContentAlignment="Bottom"
                Margin="0"
                Padding="0">
    <Viewbox x:Name="MenuBar"
                Stretch="Uniform"
                StretchDirection="DownOnly"
                HorizontalAlignment="Right"
                VerticalAlignment="Bottom"
                Margin="0">
        <ListView x:Name="Menu"
                    SelectionChanged="Menu_OnSelectionChanged"
                    Style="{StaticResource MenuListViewStyle}"
                    ItemContainerStyle="{StaticResource MenuListViewItemStyle}"
                    ItemTemplate="{StaticResource MenuItemTemplate}"
                    HorizontalAlignment="Left"
                    Margin="20 10 10 0" />
    </Viewbox>
</GridViewItem>

When the screen becomes too narrow, the elements are placed underneath each other. Most implementations for this scenario rely on a Visual State Trigger that changes the Orientation of a StackPanel. Unfortunately a StackPanel is not good in stretching its children, and I’m not sure whether its orientation change can be animated (Maybe it can, I just didn’t try it out). Instead we decided to place the title and menu as GridViewItems in a GridView with a WrapGrid as ItemsPanelTemplate. You can hook implicit animations to these items when their offset changes – more details in this blog post. The stretching and positioning of the GridView’s items are controlled by aligning the ItemWidth of the inner WrapGrid to theGridView’s own ActualWidth. I decided to use a SizeChanged event handler for this, but this might also be done through an element binding.

private void GridView_SizeChanged(object sender, SizeChangedEventArgs e)
{
    if (_itemsPanel == null)
    {
        return;
    }

    // Only react to change in Width.
    if (e.NewSize.Width != e.PreviousSize.Width)
    {
        AdjustItemTemplate();
    }
}

private void ItemsPanel_Loaded(object sender, RoutedEventArgs e)
{
    // Avoid walking the Visual Tree on each Size change.
    _itemsPanel = sender as WrapGrid;

    // Initialize item template.
    AdjustItemTemplate();
}

private void AdjustItemTemplate()
{
    if (ActualWidth > 800)
    {
        // Two rows.
        _itemsPanel.ItemWidth = ActualWidth / 2;
        _itemsPanel.MinWidth = ActualWidth;
        MenuBar.Margin = new Thickness(0, 0, 64, 0);
        Title.Margin = new Thickness(0);
    }
    else
    {
        // One row.
        _itemsPanel.ItemWidth = ActualWidth;
        _itemsPanel.Width = ActualWidth;
        MenuBar.Margin = new Thickness(0);
        Title.Margin = new Thickness(0, 0, 64, 0);
    }
}

By using a GridView to host the UI elements, I was able to reuse the animation from a previous blog post. [Well, almost: I removed the rotation, because you don’t want the tab control to look like a prancing pony when the screen resizes.]  Using the Composition API, we define an ImplicitAnimationCollection for the Offset, and apply it to the Visual for each of the GridView’s items:

        public static void RegisterImplicitAnimations(this ItemsControl itemsControl)
        {
            var compositor = ElementCompositionPreview.GetElementVisual(itemsControl as UIElement).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 (SelectorItem item in itemsControl.Items)
            {
                var elementVisual = ElementCompositionPreview.GetElementVisual(item);
                elementVisual.ImplicitAnimations = elementImplicitAnimation;
            }
        }

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

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

            // Add Animation to Animation group. 
            CompositionAnimationGroup animationGroup = compositor.CreateAnimationGroup();
            animationGroup.Add(offsetAnimation);

            return animationGroup;
        }

The Menu’s constructor declares the default menu items (the hosting page can override this, if needed) and registers the animations:

public OthersMenu()
{
    this.InitializeComponent();

    // Populate Menu.
    Menu.Items.Add(new MenuItem() {
        Glyph = Icon.GetIcon("AquariusIcon"),
        Text = "Aquarius",
        NavigationDestination = typeof(AquariusPage) });
    // More menu items ...

    // Animate Menu.
    GridView.RegisterImplicitAnimations();
}

That’s it! The content page should only host the user control in it’s XAML and does not need any code behind.

Here’s what all of this looks like in action:

MenuAnimation

The sample project lives here 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!

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!

BackDrop: a control for dynamic blur in UWP

This article presents a lightweight UWP control that creates a configurable blur and tint effect and applies it on the pixels behind it. It’s intended to be used as background of panels, flyouts and dialogs. Here’s how the control looks like in action – it’s the semitransparent red panel in the middle of the page:

Properties

I did not create the control myself, all credits should go the team that built the Composition API Samples on GitHub. When you open the menu of the sample gallery app, you immediately notice the SplitView Pane’s background. Mainly because it is freshly green, but also because it blurs whatever’s underneath it:

SplitViewMenu

When diving into the source code, I discovered the BackDrop control and immediately decided to give it a spin.

The BackDrop control is XAML-wise a very lightweight control: it derives from Control and has no UI elements of its own – so there’s no style or template for it.

public class BackDrop : Control
{
	// ...
}

The BackDrop control uses the Composition API. The dynamic blur effect comes from a CompositionBrush that is applied to a SpriteVisual. This visual is hooked in the control’s (empty) XAML tree with SetElementChildVisual. While a ‘regular’ brush applies to the pixels of its host, this one is defined using CreateBackDropBrush. So it applies to all pixels underneath the surface of its host control:

public BackDrop()
{
    var rootVisual = ElementCompositionPreview.GetElementVisual(this);
    _compositor = rootVisual.Compositor;
    _blurVisual = _compositor.CreateSpriteVisual();

    var brush = BuildBlurBrush();
    brush.SetSourceParameter("Source", _compositor.CreateBackdropBrush());
    _blurBrush = brush;
    _blurVisual.Brush = _blurBrush;

    ElementCompositionPreview.SetElementChildVisual(this, _blurVisual);
}

The main brush is a CompositionEffectBrush that combines not two but three effects. There’s obviously a GaussianBlur and a ColorSource effect involved. On top of these there’s a Saturation effect that enhances the background color by moving the colors away from grey. Here’s a screenshot from panels with a BackDrop with (on the right) and without (on the left) the saturation effect:

SaturationEffect

[If you prefer the panel on the left: the saturation intensity is configurable, and a value of ‘1’ means ‘no effect’.]

I modified the original code of the BackDrop control to expose all relevant parameters of its brush as dependency properties, so you can two-way-bind to these through XAML and C#:

BlurAmount double Gets or sets the amount of blur to apply on the background.
TintColor Color Gets or sets the color for the ColorSource effect. The Alpha value will be ignored.
TintAlpha int Gets or sets the transparency of the TintColor.
SaturationIntensity double Gets or sets the saturation. 0 is monochrome.

Here’s how the main brush is built up, with BlurAmount, Color, and Saturation as so-called animatable properties:

private CompositionEffectBrush BuildBlurBrush()
{
    var blurEffect = new GaussianBlurEffect()
    {
        Name = "Blur",
        BlurAmount = 0.0f,
        BorderMode = EffectBorderMode.Hard,
        Optimization = EffectOptimization.Balanced,
        Source = new CompositionEffectSourceParameter("Source")
    };

    var blendEffect = new BlendEffect
    {
        Background = blurEffect,
        Foreground = new ColorSourceEffect()
        {
            Name = "Color",
            Color = Color.FromArgb(90, 255, 255, 255)
        },
        Mode = BlendEffectMode.SoftLight
    };

    var saturationEffect = new SaturationEffect
    {
        Name = "Saturation",
        Source = blendEffect,
        Saturation = 1.75f
    };

    var factory = _compositor.CreateEffectFactory(
        saturationEffect,
        new[] { "Blur.BlurAmount", "Color.Color", "Saturation.Saturation" });

    return factory.CreateBrush();
}

Here are the PropertyChangedCallback delegates that update the effect parameters:

private static void OnBlurAmountChanged(
	DependencyObject d, 
	DependencyPropertyChangedEventArgs e)
{
    var backDrop = d as BackDrop;

    if (backDrop == null) return;

    backDrop._blurBrush.Properties.InsertScalar(
	"Blur.BlurAmount", 
	(float)(double)e.NewValue);
}

private static void OnTintColorChanged(
	DependencyObject d, 
	DependencyPropertyChangedEventArgs e)
{
    var backDrop = d as BackDrop;

    if (backDrop == null) return;

    var color = (Color)e.NewValue;
    color.A = (byte)backDrop.TintAlpha;

    backDrop._blurBrush.Properties.InsertColor("Color.Color", color);
}

private static void OnTintAlphaChanged(
	DependencyObject d, 
	DependencyPropertyChangedEventArgs e)
{
    var backDrop = d as BackDrop;

    if (backDrop == null) return;

    var color = backDrop.TintColor;
    color.A = (byte)(int)e.NewValue;

    backDrop._blurBrush.Properties.InsertColor("Color.Color", color);
}

The home page of my sample app allows you to play with all of the parameters. While experimenting, don’t forget to resize the app to observe the dynamic blur effect:

Properties

The main purpose of the BackDrop control is to be used as background for different panels, e.g. a Flyout. Here’s a screenshot of the Flyout page of the sample app. This page demonstrates the impact of BackDrop control. It shows the same content in two panels: the panel on the left has a BackDrop in its XAML, the panel on the right doesn’t.

Press the button to open a real Flyout and observe a cumulated blur and tint effect:

FlyOut

Here’s part of the XAML for the fixed panel on the left. To use a BackDrop, just put the control inside a transparent container:

<Grid Background="Transparent"">
    <controls:BackDrop BlurAmount="25"
                        TintColor="BlanchedAlmond" />
    <Grid>
        <StackPanel Padding="10">
        <!-- ... -->
        </StackPanel>
    </Grid>
</Grid>

Here’s how to hook a BackDrop control in a real Flyout. Don’t forget the Style Setter that makes the background transparent:

<Flyout>
    <Flyout.FlyoutPresenterStyle>
        <Style TargetType="FlyoutPresenter">
            <Setter Property="Background"
                    Value="Transparent" />
            <Setter Property="Padding"
                    Value="0" />
        </Style>
    </Flyout.FlyoutPresenterStyle>
    <Grid Background="Transparent"
            Width="300">
        <controls:BackDrop BlurAmount="25"
                            TintColor="BlanchedAlmond" />
        <Grid>
        <!-- ... -->
        </Grid>
    </Grid>
</Flyout>

While it’s easy to use the BackDrop control as a background for panels and Flyouts, using it inside a ContentDialog is a bit more challenging. The ContentDialog control is protected, in the sense that you can’t create a template for it in Visual Studio or Blend. You’re only supposed to modify the TitleTemplate and/or the ContentTemplate:

ContentDialogTemplate

To restyle an entire ContentDialog control, you can copy its style from the main Generic.xaml (which you find in \(Program Files)\Windows Kits\10\DesignTime\CommonConfiguration\Neutral\UAP\<SDK version>\Generic) and use a modified version of it as a XAML resource:

<Page.Resources>
    <!-- Custom style for Windows.UI.Xaml.Controls.ContentDialog -->
    <!-- Gives it a transparent background and adds a BackDrop -->
    <Style TargetType="ContentDialog">
        <Setter Property="Foreground"
                Value="{ThemeResource SystemControlPageTextBaseHighBrush}" />
        <Setter Property="Background"
                Value="Transparent" />
        <!-- ... -->
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ContentDialog">
                    <Border x:Name="Container">
                        <Grid x:Name="LayoutRoot">
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto" />
                            </Grid.RowDefinitions>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="Auto" />
                            </Grid.ColumnDefinitions>
                            <controls:BackDrop BlurAmount="15"
                                                TintColor="BurlyWood" />
                            <Border x:Name="BackgroundElement"
                                    Background="{TemplateBinding Background}"
                                    ...
                                    MinHeight="{TemplateBinding MinHeight}">
                                <Grid x:Name="DialogSpace"
                                        VerticalAlignment="Stretch"
                                        Padding="0 20">
                                <!-- ... -->
                                </Grid>
                            </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</Page.Resources>

This is the result – a modal dialog box with a blurred and tinted background:

Dialog

Of course I wanted to stress test the BackDrop control in my favorite control torture chamber: the SquareOfSquares. Here’s how a group of 21 differently configured BackDrop controls look like on a single page:

Squares

Here’s the same page on my phone:

BackDrop_Phone

When you look at the Memory Usage Diagnostic Tool in Visual Studio, you’ll notice that this page indeed consumes some extra memory, but it’s not a really significant amount of megabytes and most of it (not all!) is rapidly released. Anyway, this single page has more BackDrop controls than you’ll ever need in an app, so I assume it’s pretty safe to use it.

I didn’t feel the need to animate the effect. If you want to add your own animation (e.g. in the Loaded event), just take a look at the source page of this sample page from the Composition API Sample app:

BackDropSample

My version of the BackDrop control and its sample app live here on GitHub. The control is in its own library, for easy reuse. Make sure to have Anniversary Update or higher, and reference the Win2D UWP NuGet package.

Enjoy!

Building a UWP Rating Control using XAML and the Composition API

 

In this article we’ll build a XAML and C# Rating Control for the Windows Universal Platform which will be entirely drawn by the Composition API. It’s a custom Control that comes with the following dependency properties:

  • Maximum (int): the number of stars (or other images) to display,
  • StepFrequency (double): the rounding interval for the Value (a percentage, e.g. 0.25)
  • Value (double): the current value (from 0 to Maximum)
  • ItemHeight (int): height (and width) of each image in device independent pixels
  • ItemPadding (int): the number of pixels between images
  • FilledImage (uri): path to the filled image
  • EmptyImage (uri): path to the empty image
  • IsInteractive (bool): whether or not the control responds to user input (tapping or sliding)

The names of the core properties (Maximum, StepFrequency, and Value) are borrowed from the Slider class because after all –just like the slider- a Rating control is just a control to set and display a value within a range.

The Rating control’s behavior is also inspired by the slider:

  • tap on an image to set a value, and
  • slide horizontally over the control to decrease and increase the value with StepFrequency steps.

Here are some instances of the control in action:

WithoutLossOfImageQuality

An almost empty XAML template

The UI of the control is drawn entirely using the Composition API, so I kept the XAML template as simple as possible. I was tempted to use an ItemsControl as basis, but went for a Panel.. If the control were purely XAML, then a horizontal StackPanel would suffice as ControlTemplate. The star (or other) images will be displayed using the Composition API, in a layer on top of that StackPanel. This layer makes the panel itself unable to detect Tapped or ManipulationDelta events. The template contains extra Grid controls to put a ‘lid’ on the control.

The control template makes the distinction between the part that displays the images (PART_Images), and the part that deals with user input (PART_Interaction) through touch, pen, mouse or something else (like X-Box controller or Kinect – remember it’s a UWP app).

Here’s the default style definition in Themes/Generic.xaml:

<Style TargetType="local:Rating">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:Rating">
                <Grid>
                    <!-- Holds the images. -->
                    <StackPanel x:Name="PART_Items"
                                Orientation="Horizontal"
                                HorizontalAlignment="Center"
                                VerticalAlignment="Center" />
                    <!-- Interacts with touch and mouse and so. -->
                    <Grid x:Name="PART_Interaction"
                            ManipulationMode="TranslateX"
                            Background="Transparent" />
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

The code behind

Set-up

By convention, all named elements in the style start their name with “PART_” and are decorated with a TemplatePart:

[TemplatePart(Name = ItemsPartName, Type = typeof(StackPanel))]
[TemplatePart(Name = InteractionPartName, Type = typeof(UIElement))]
public class Rating : Control
{
    private const string ItemsPartName = "PART_Items";
    private const string InteractionPartName = "PART_Interaction";

    // ...
}

All properties are defined as Dependency Property, which allows two-way binding in XAML and automatic property change notification. All of the properties also have a default value, so that the control can be used immediately without specifying mandatory property values. And last but not least, all of the properties have a PropertyChanged callback in their PropertyMetadata, so the UI of the control is updated automatically at runtime when one of the properties changes. The dependency property registrations use the nameof() expression instead of a hard-coded string [which is still in the propdp code snippet].

Here’s how the ItemHeight property is registered:

public static readonly DependencyProperty ItemHeightProperty = 
DependencyProperty.Register(
    nameof(ItemHeight),
    typeof(int),
    typeof(Rating),
    new PropertyMetadata(12, OnStructureChanged));

In the OnApplyTemplate the control is drawn by a call to OnStructureChanged – the property changed callback that also redraws the control at runtime – and the event handlers for touch interaction –Tapped and ManipulationDelta– are registered:

protected override void OnApplyTemplate()
{
    // Ensures that ActualWidth is actually the actual width.
    HorizontalAlignment = HorizontalAlignment.Left;

    OnStructureChanged(this);

    var surface = this.GetTemplateChild(InteractionPartName) as UIElement;
    if (surface != null)
    {
        surface.Tapped += Surface_Tapped;
        surface.ManipulationDelta += Surface_ManipulationDelta;
    }

    base.OnApplyTemplate();
}

The OnstructureChanged method is called when the control is rendered initially, or whenever one of the main UI characteristics is changed (things like item height or padding, maximum, ore one of the images).

The method starts with verifying if the user provided custom images. If not, a default empty and full star image is taken from the control’s own resources. To my surprise, the initialization of the default image path did not work in the dependency property registration, nor in OnApplyTemplate:

private static void OnStructureChanged(DependencyObject d)
{
    Rating c = (Rating)d;

    if (c.EmptyImage == null)
    {
        c.EmptyImage = new Uri(
	"ms-appx:///XamlBrewer.Uwp.RatingControl/Assets/defaultStar_empty.png");
    }

    if (c.FilledImage == null)
    {
        c.FilledImage = new Uri(
	"ms-appx:///XamlBrewer.Uwp.RatingControl/Assets/defaultStar_full.png");
    }

    // ...
}

The next step in OnStructureChanged is to make sure that the StepFrequency falls in the expected range, which is greater than zero but maximum one:

if ((c.StepFrequency <= 0) || (c.StepFrequency > 1))
{
    c.StepFrequency = 1;
}

Loading the images

Then it’s time to load the two images. In the current version of the Composition API you’ll need some extra help for this. My favorite helper is the Microsoft.UI.Composition.Toolkit, a small C++ project that comes with the Windows UI Dev Labs samples on GitHub:

MSUICompositionToolkit

Every image is loaded once into a CompositionSurfaceBrush that we’ll reuse for each item in the list of rating images. Here’s the code that creates the two brushes:

var panel = c.GetTemplateChild(ItemsPartName) as StackPanel;
if (panel != null)
{
    // ...

    // Load images.
    var root = panel.GetVisual();
    var compositor = root.Compositor;
    var options = new CompositionImageOptions()
    {
        DecodeWidth = c.ItemHeight,
        DecodeHeight = c.ItemHeight
    };
    var imageFactory = 
	CompositionImageFactory.CreateCompositionImageFactory(compositor);
    var image = imageFactory.CreateImageFromUri(c.EmptyImage, options);
    var emptyBrush = compositor.CreateSurfaceBrush(image.Surface);
    image = imageFactory.CreateImageFromUri(c.FilledImage, options);
    var fullBrush = compositor.CreateSurfaceBrush(image.Surface);

    // ...
}

The reason why I prefer to use the Composition Toolkit for loading images is the fact that you can control the DecodeWidth and DecodeHeight. Alternatively, you can use the C# CompositionImageLoader project, also on GitHub. It comes with a NuGet package:

CompositionImageLoader

Here’s how the code looks like when you use this library:

// Load images.
var root = panel.GetVisual();
var compositor = root.Compositor;
var imageLoader = ImageLoaderFactory.CreateImageLoader(compositor);
var surface = imageLoader.LoadImageFromUri(c.EmptyImage);
var emptyBrush = compositor.CreateSurfaceBrush(surface);
surface = imageLoader.LoadImageFromUri(c.FilledImage);
var fullBrush = compositor.CreateSurfaceBrush(surface);

I had the intention to copy relevant code of the CompositionImageLoader into my project in order to create a full C# control with as few as possible external dependencies (only Win2D). But then I noticed a loss in image quality when using CompositionImageLoader. It looks like there’s a loss in DPI, even if you specify the size of the target image on load:

surface = imageLoader.LoadImageFromUri(
	c.FilledImage, 
	new Size(c.ItemHeight, c.ItemHeight));

Here’s a screenshot of the sample app using CompositionImageLoader:

LossOfImageQuality

And here’s the same app using Micsosoft.Composition.UI.Toolkit:

WithoutLossOfImageQuality

There’s a significant loss of quality in the devil and 3D star images. To see it, you may need to click on the screenshots to see them in full size, or try another monitor – the difference is not always obvious. Anyway, it made me hit the undo button in Source Control…

Rendering the control

The two composition surface brushes are loaded into SpriteVisual instances that are hooked to a padded Grid that is created for each item in the list of rating images. The full image will be drawn on top of the empty one. Based on the Value, we’ll calculate the clipping rectangle for each ‘full’ image. Here’s a 3D view on the structure. The yellow surface represents the StackPanel from the control’s template, the green rectangles are the root Grid elements for each image, and the images are … well … the images:

RatingStructure

At runtime, we’ll change the InsetClip values of the images on top, so the control maintains the references to these:

private List<InsetClip> Clips { get; set; } = new List<InsetClip>();

Here’s the code that creates all the layers – the full images are right-clipped at zero, so they don’t appear:

var rightPadding = c.ItemPadding;
c.Clips.Clear();

for (int i = 0; i < c.Maximum; i++)
{
    if (i == c.Maximum - 1)
    {
        rightPadding = 0;
    }

    // Create grid.
    var grid = new Grid
    {
        Height = c.ItemHeight,
        Width = c.ItemHeight,
        Margin = new Thickness(0, 0, rightPadding, 0)
    };
    panel.Children.Add(grid);
    var gridRoot = grid.GetVisual();

    // Empty image.
    var spriteVisual = compositor.CreateSpriteVisual();
    spriteVisual.Size = new Vector2(c.ItemHeight, c.ItemHeight);
    gridRoot.Children.InsertAtTop(spriteVisual);
    spriteVisual.Brush = emptyBrush;

    // Filled image.
    spriteVisual = compositor.CreateSpriteVisual();
    spriteVisual.Size = new Vector2(c.ItemHeight, c.ItemHeight);
    var clip = compositor.CreateInsetClip();
    c.Clips.Add(clip);
    spriteVisual.Clip = clip;
    gridRoot.Children.InsertAtTop(spriteVisual);
    spriteVisual.Brush = fullBrush;
}

We’re at the end of the OnstructureChanged code now. The control is rendered or re-rendered with the correct number of the correct images at the correct size and padding. It’s time to update the value:

OnValueChanged(c);

Changing the value

When the Value of the control is changed, we calculate the InsetClip for each image in the top layer (the ‘full’ stars). The images left of the value will be fully shown (clipped to the full width), the images right of the value will be hidden (clipped to zero). For the image in the middle, we calculate the number of pixels to be shown:

private static void OnValueChanged(DependencyObject d)
{
    Rating c = (Rating)d;

    var panel = c.GetTemplateChild(ItemsPartName) as StackPanel;
    if (panel != null)
    {
        for (int i = 0; i < c.Maximum; i++)
        {
            if (i <= Math.Floor(c.Value - 1))
            {
                // Filled image.
                c.Clips[i].RightInset = 0;
            }
            else if (i > Math.Ceiling(c.Value - 1))
            {
                // Empty image.
                c.Clips[i].RightInset = c.ItemHeight;
            }
            else
            {
                // Curtain.
                c.Clips[i].RightInset = 
	(float)(c.ItemHeight * (1 +  Math.Floor(c.Value) - c.Value));
            }
        }
    }
}

The images come from reusable brushes and are never reloaded at runtime, so I think that this rating control is very efficient in its resource usage.

The behavior

The Value property changes by sliding over the image. We have to round it to the nearestStepFrequency fraction. Here’s the rounding routine:

public static double RoundToFraction(double number, double fraction)
{
    // We assume that fraction is a value between 0 and 1.
    if (fraction <= 0) { return 0; }
    if (fraction > 1) { return number; }

    double modulo = number % fraction;
    if ((fraction - modulo) <= modulo)
        modulo = (fraction - modulo);
    else
        modulo *= -1;

    return number + modulo;
}

The behavior of the rating control is defined by two interactions:

  • tapping for fast initialization, and
  • sliding to adjust more precisely.

As already mentioned, the event handlers for the control’s interaction are defined on the entire control surface, not on each image. So when an image is tapped or clicked, we need to detect which one was actually hit. We then set the control to a new value which is rounded to the integer, so the whole tapped/clicked images becomes selected:

private void Surface_Tapped(object sender, TappedRoutedEventArgs e)
{
    if (!IsInteractive)
    {
        return;
    }

    Value = (int)(e.GetPosition(this).X / (ActualWidth + ItemPadding) * Maximum) + 1;
}

The calculation for deriving the Value from the the horizontal sliding manipulation is a bit more complex because we want the ‘curtain’ to closely follow the finger/pointer. We don’t change the control’s Value while sliding between the images, which creates a very natural user experience:

private void Surface_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
    if (!IsInteractive)
    {
        return;
    }

    // Floor.
    var value = Math.Floor(e.Position.X / (ActualWidth + ItemPadding) * Maximum);

    // Step.
    value += Math.Min(RoundToFraction(
	((e.Position.X - (ItemHeight + ItemPadding) * (value)) / (ItemHeight)), StepFrequency), 1);

    // Keep within range.
    if (value < 0)
    {
        value = 0;
    }
    else if (value > Maximum)
    {
        value = Maximum;
    }

    Value = value;
}

Using the Rating Control

When you want to use the rating control in your app, just declare its namespace in the XAML:

xmlns:controls="using:XamlBrewer.Uwp.Controls"

Then draw a Rating control and set its properties – as already mentioned: all of the properties have a default value:

<controls:Rating x:Name="Devils"
                    Maximum="4"
                    ItemHeight="60"
                    ItemPadding="24"
                    StepFrequency=".1"
                    EmptyImage="ms-appx:///Assets/RatingImages/devil_empty.png"
                    FilledImage="ms-appx:///Assets/RatingImages/devil_full.png" />

That’s all there is.

It’s a UWP control, so it runs on PC’s, tablets, Raspberry Pi, Xbox, and Hololens. Since I don’t own all of these (yet), here’s a screenshot from the phone:

RatingOnPhone

Source code

The XAML-and-Composition Rating Control for UWP lives here on GitHub, together with the sample app.

Enjoy!