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!

Advertisements

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!

Improving the accessibility of a UWP control

In this article we’ll show some techniques to improve the accessibility of a custom control in UWP. We’ll focus on three aspects:

  • Support for High Contrast themes,
  • Narrator compatibility, and
  • Keyboard input capability.

We’ll use the Radial Gauge control from Windows Community Toolkit as an example – the High Contrast and Narrator code in this article was recently merged into the source.

Supporting High Contrast themes

Adding Theme Dictionaries

With Settings –> Ease of access –> High Contrast, a Windows user can switch his color scheme to a small palette of contrasting colors. There may be medical reasons for this (like cataract or diabetic retinopathy) but it could also be set as to deal with working conditions (like direct sunlight on the screen, or insufficient lighting).

In a UWP app, all of the built-in native controls (the “XAML Common Controls”) respect this user setting and will update their UI. As an app developer you have to make sure

  • not to override that behavior of the native controls that you use, and
  • to provide a similar behavior for the XAML elements that you write yourself: pages and custom controls.

Basically this boils down to never hard coding colors.

When building a custom control, you should create a ThemeDictionary that includes an entry for ‘High Contrast’. Here’s a XAML snippet from the previous style definition from the Radial Gauge control. It came with direct assignment of colors:

<Style TargetType="local:RadialGauge"> 
   <Setter Property="NeedleBrush" 
           Value="{ThemeResource SystemControlBackgroundAccentBrush}" /> 
   <Setter Property="TrailBrush" 
           Value="{ThemeResource SystemControlBackgroundAccentBrush}" />  
   <Setter Property="TickBrush" 
           Value="{ThemeResource SystemControlForegroundBaseHighBrush}" />  
   <Setter Property="ScaleTickBrush" 
           Value="Transparent" /> 
   <!-- ... --/>
</Style>

The brushes were pulled from theme resources to support Light an Dark themes. That’s a good start, but it doesn’t cover the High Contrast scenario. You should stick to a restricted palette of only 8 system colors in the High Contrast section of the theme dictionary:

Key Initial default
SystemColorButtonFaceColor #FFF0F0F0
SystemColorButtonTextColor #FF000000
SystemColorGrayTextColor #FF6D6D6D
SystemColorHighlightColor #FF3399FF
SystemColorHighlightTextColor #FFFFFFFF
SystemColorHotlightColor #FF0066CC
SystemColorWindowColor #FFFFFFFF
SystemColorWindowTextColor #FF000000

To support High Contrast in the Radial Gauge, an extra layer of abstraction was added in the color brush assignments by means of a resource dictionary:

<ResourceDictionary.ThemeDictionaries>
    <ResourceDictionary x:Key="Default">
        <SolidColorBrush x:Key="RadialGaugeNeedleBrush"
                         Color="{ThemeResource SystemChromeHighColor}" />
        <SolidColorBrush x:Key="RadialGaugeTrailBrush"
                         Color="{ThemeResource SystemChromeHighColor}" />
        <SolidColorBrush x:Key="RadialGaugeScaleBrush"
                         Color="{ThemeResource SystemBaseMediumLowColor}" />
        <SolidColorBrush x:Key="RadialGaugeScaleTickBrush"
                         Color="{ThemeResource SystemBaseMediumLowColor}" />
        <SolidColorBrush x:Key="RadialGaugeTickBrush"
                         Color="{ThemeResource SystemBaseHighColor}" />
        <SolidColorBrush x:Key="RadialGaugeForeground"
                         Color="{ThemeResource SystemBaseHighColor}" />
    </ResourceDictionary>
    <ResourceDictionary x:Key="Light">
        <!-- ... -->
    </ResourceDictionary>
    <ResourceDictionary x:Key="Dark">
        <!-- ... -->
    </ResourceDictionary>
    <ResourceDictionary x:Key="HighContrast">
        <!-- ... -->
    </ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>

The default style of the control was updated to refer to resources from that dictionary:

<Style TargetType="local:RadialGauge">
    <Setter Property="NeedleBrush"
            Value="{ThemeResource RadialGaugeNeedleBrush}" />
    <Setter Property="TrailBrush"
            Value="{ThemeResource RadialGaugeTrailBrush}" />
    <Setter Property="ScaleBrush"
            Value="{ThemeResource RadialGaugeScaleBrush}" />
    <Setter Property="ScaleTickBrush"
            Value="{ThemeResource RadialGaugeScaleTickBrush}" />
    <Setter Property="TickBrush"
            Value="{ThemeResource RadialGaugeTickBrush}" />
    <Setter Property="Foreground"
            Value="{ThemeResource RadialGaugeForeground}" />
    <!-- ... -->
</Style>

The resource dictionary has an entry for ‘High Contrast’ with only brushes out of the limited set of system colors:

<ResourceDictionary x:Key="HighContrast">
    <SolidColorBrush x:Key="RadialGaugeNeedleBrush"
                     Color="{ThemeResource SystemColorHotlightColor}" />
    <SolidColorBrush x:Key="RadialGaugeTrailBrush"
                     Color="{ThemeResource SystemColorHotlightColor}" />
    <SolidColorBrush x:Key="RadialGaugeScaleBrush"
                     Color="{ThemeResource SystemColorWindowColor}" />
    <SolidColorBrush x:Key="RadialGaugeScaleTickBrush"
                     Color="{ThemeResource SystemColorWindowColor}" />
    <SolidColorBrush x:Key="RadialGaugeTickBrush"
                     Color="{ThemeResource SystemColorWindowTextColor}" />
    <SolidColorBrush x:Key="RadialGaugeForeground"
                     Color="{ThemeResource SystemColorWindowTextColor}" />
</ResourceDictionary>

The control now supports High contrast themes, and we didn’t even need to change the code behind.

Ignoring local properties

When a developer places our control on a page, he can locally assign brushes to the control’s color settings – after all that’s why we decorated it with properties. It makes sense for the control to ignore these assignments when a High Contrast theme is applied. For that we need to detect whether we’re in High contrast mode or not, and find a way to get notified when the user enables or disables the feature.

This is were the AccessibilitySettings class enters the picture, with a HighContrast property and a HighContrastChanged event. When the app starts, and whenever the event fires, we update the color scheme if necessary. We first create an instance of the class:

private static readonly AccessibilitySettings ThemeListener = new AccessibilitySettings();

And then register an event handler. That handler may force a redraw of the control –to update the colors- so the control’s Visual Tree should be assembled (but not yet displayed) when we register it. That makes the OnApplyTemplate event the best choice to host this code:

ThemeListener.HighContrastChanged += ThemeListener_HighContrastChanged;

Let’s take a look at the code to change the color scheme. When the app starts, we create a cache to remember the local values for the different colors. All the brushes are defined as dependency properties. To detect whether a dependency property is locally overridden (in XAML or C#, but not by a theme dictionary), we can use ReadLocalValue. It returns the local value or the so-called UnSetValue that indicates that no value was assigned:

private SolidColorBrush _needleBrush;
private Brush _trailBrush;
private Brush _scaleBrush;
// More brushes ...
_needleBrush = ReadLocalValue(NeedleBrushProperty) as SolidColorBrush;
_trailBrush = ReadLocalValue(TrailBrushProperty) as SolidColorBrush;
_scaleBrush = ReadLocalValue(ScaleBrushProperty) as SolidColorBrush;
// More assignments ...

When we enter a High Contrast theme, we unbind the local values from the dependency properties so that the entries of the theme dictionary are used. And when we enter a non High Contrast theme, we reassign the local values from the cache to reinstall the original color scheme:

private void OnColorsChanged()
{
    if (ThemeListener.HighContrast)
    {
        // Apply High Contrast Theme.
        ClearBrush(_needleBrush, NeedleBrushProperty);
        ClearBrush(_trailBrush, TrailBrushProperty);
        ClearBrush(_scaleBrush, ScaleBrushProperty);
        // More of these ...
    }
    else
    {
        // Apply User Defined or Default Theme.
        RestoreBrush(_needleBrush, NeedleBrushProperty);
        RestoreBrush(_trailBrush, TrailBrushProperty);
        RestoreBrush(_scaleBrush, ScaleBrushProperty);
        // More of these ...
    }

    // ...
}

To clear and restore the brushes, we use ClearValue and SetValue respectively:

private void ClearBrush(Brush brush, DependencyProperty prop)
{
    if (brush != null)
    {
        ClearValue(prop);
    }
}

private void RestoreBrush(Brush source, DependencyProperty prop)
{
    if (source != null)
    {
        SetValue(prop, source);
    }
}

For a deeper dive into Dependency Properties, check this documentation.

In the small sample app that I wrote, I encapsulated all accessibility related code behind (there’s more to come further in this article) in a separate partial class file:

FileStructure

To get these file under the xaml.cs file in Visual Studio’s explorer, I tweaked the project file:

ProjectFile

That sample app has a page with three radial gauge controls. The one in the middle has a local assignment for some colors:

<controls:RadialGauge Unit="Mississippi"
                      Value="20"
                      NeedleBrush="MediumOrchid"
                      TrailBrush="Indigo"
/>

Here’s how that page looks like in a non High Contrast theme. On the left side of the screen there’s the Settings app; the right side is main page of the sample app:

HighContrast_Off

Here’s the app in High Contrast mode. Observe the limited color palette, and the fact that we successfully ignored the local colors for the middle gauge:

HighContrast

The UI Automation API

Another way to improve the accessibility of a custom UWP control is to provide screen reader support. Narrator lets you use your device to complete common tasks without mouse or touch input. It reads and interacts with elements on the screen, like text and buttons. Of course that only works if you stick to a protocol. That protocol is Microsoft UI Automation, an API in Windows that enables your apps to provide (and consume) programmatic information about its user interface. Providing programmatic access to most UI elements on the desktop enables assistive technology products, such as Narrator and Magnifier, to provide this information to the end users, e.g. by speech.

One of the most important components of Windows Automation is the so-called UI Automation Tree. This is the hierarchical representation of your desktop window, its child elements (open windows) and their UI components (menus, buttons, lists, …). The UI Automation Tree makes a difference between Control elements (interactive) and Content elements, but it makes no difference in technology: it works against all stacks: WinForms, Web, XAML, …

Supporting Narrator

Creating and registering an Automation Peer

An Automation Peer is a class that helps exposing the content of a UI element class to Windows UI Automation. It ensures that the UI Automation Tree does not have to rely on high-level assumptions, so it improves the service of components like Narrator and Magnifier.

To create an automation peer for a class, you just create a descendant from AutomationPeerFrameworkAutomationPeer is a good parent for a XAML control- and specify the described class (the ‘owner’) in the constructor:

public class RadialGaugeAutomationPeer : FrameworkElementAutomationPeer
{
    public RadialGaugeAutomationPeer(RadialGauge owner)
        : base(owner)
    { }

    // ...
}

To activate the automation peer, you also have to override the OnCreateAutomationPeer in the owner class (RadialGauge in our case):

protected override AutomationPeer OnCreateAutomationPeer()
{
    return new RadialGaugeAutomationPeer(this);
}

Basic overrides

There are two methods that you would probably override in every automation peer. The first one is GetChildrenCore(). It returns the list of child elements that you want to expose to UI Automation. In the case of the RadialGauge, we want to expose the control to as a single element and hide its details. So the override returns no children:

protected override IList<AutomationPeer> GetChildrenCore()
{
    return null;
}

The second important automation peer method is GetNameCore() which returns the main description of the component. If you want more than the fully qualified class name, then you should override it. Here’s what we did in the RadialGauge to make it return the class name and unit measure:

protected override string GetNameCore()
{
    var gauge = (RadialGauge)Owner;
    return "radial gauge. " + (string.IsNullOrWhiteSpace(gauge.Unit) ? "no unit specified. " : "unit " + gauge.Unit + ". ");
}

Tip: provide punctuation and pauses (blanks) in the result string to create a natural description. When Narrator reads the screen, it respects all of these.

After the name, Narrator tells the type of control. Custom is the default return value for GetAutomationControlTypeCore(), so this is actually an obsolete override:

protected override AutomationControlType GetAutomationControlTypeCore()
{
    return AutomationControlType.Custom;
}

I just want to point out that the type comes from the AutomationControlType enumeration. So you can not add your own here, but you can still expose a lot more details of your control.

Refining the Automation Peer

To find out more details about your control, Windows Automation will call GetPatternCore() a few times to see if it supports some protocols. One of these protocols is RangeValue which describes the controls that have a Value that is between a Minimum and a Maximum, controls such as … the RadialGauge.

To fulfill the protocol we have to implement the IRangeValueProvider interface:

public class RadialGaugeAutomationPeer :
    FrameworkElementAutomationPeer,
    IRangeValueProvider
{
    // ...
}
public double Value => ((RadialGauge)Owner).Value;
public double Minimum => ((RadialGauge)Owner).Minimum;
public double SmallChange => ((RadialGauge)Owner).StepSize;
public bool IsReadOnly => !((RadialGauge)Owner).IsInteractive;
// ...

Then we have to override GetPatternCore() to return a reference for each protocol that we implemented:

protected override object GetPatternCore(PatternInterface patternInterface)
{
    if (patternInterface == PatternInterface.RangeValue)
    {
        // Expose RangeValue properties.
        return this;
    }

    return base.GetPatternCore(patternInterface);
}

That’s is! Narrator will now say the description of our control, followed by the ValueRange information (Value, Minimum and Maximum). If you activate Narrator (in Settings) and TAB to the different gauges in the sample app, they will be nicely described. Here’s a –silent- screenshot:

Narrator

You can use the Inspect tool from the Windows SDK to discover and visualize all the information that Windows Automation is able to extract from your control:

Inspect_Narrator

Supporting keyboard input

A lot of users don’t use the mouse or a touch screen to interact with their computer, but only the keyboard. So it makes sense for a UWP control to accept keyboard input when it has the focus. The RadialGauge has a handler for the KeyDown event. Its value can be increased and decreased with the right and left arrow keys respectively. The Control key makes the difference between a large change of 5 units and a small change of 1:

private void RadialGauge_KeyDown(object sender, KeyRoutedEventArgs e)
{
    var step = 1;
    var ctrl = Window.Current.CoreWindow.GetKeyState(VirtualKey.Control);
    if (ctrl.HasFlag(CoreVirtualKeyStates.Down))
    {
        step = 5;
    }

    if (e.Key == VirtualKey.Left)
    {
        Value = Math.Max(Minimum, Value - step);
        e.Handled = true;
        return;
    }

    if (e.Key == VirtualKey.Right)
    {
        Value = Math.Min(Maximum, Value + step);
        e.Handled = true;
    }
}

This article described three techniques to improve the accessibility of a UWP Custom Control. If you want to know more on this topic, check this excellent starting page on the DevCenter.  You’ll quickly learn that spending some time and effort on accessibility makes your controls and apps better for everyone.

If you want to play with the code, the small sample app with the accessible RadialGauge 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!

Using a TreeView Control for Navigation in UWP

In this article we’ll show how to use a TreeView as the main navigation control in a UWP app. We will stay as close as possible to the Fluent Design principles and other current UI guidelines.

Our first approach was to rely on the NavigationView control, since it comes with a full user experience (navigation, hamburger, page title, secondary commands, responsive and fluent design). Unfortunately it is entirely focused on (read: limited to) a flat list of menu items. So we decided to design and build the entire user experience from scratch.

We admit that we expected to run into issues. None of the native UWP apps that could clearly use treeview based navigation –such as the Mail client and the OneNote app- actually have one. So we were a bit suspicious of the TreeView’s capabilities. Fortunately this was just unfounded fear.

We created a sample app with these main navigation components in a Shell page:

  • a SplitView,
  • a TreeView for primary navigation,
  • a Frame to host the pages on a background with parallax effect,
  • a Hamburger button, and
  • a CommandBar for secondary navigation.

Here’s how that app looks like:
TreeViewNavigation

Building the User Experience

Here’s the main XAML content of the Shell page:

<!-- SplitView -->
<SplitView x:Name="ShellSplitView">
    <SplitView.Pane>
        <Grid x:Name="SplitViewPane">
            <Grid.RowDefinitions>
                <RowDefinition Height="*" />
                <RowDefinition Height="auto" />
            </Grid.RowDefinitions>
            <!-- Navigation Tree -->
            <TreeView x:Name="NavigationTree"
                        ItemInvoked="TreeView_ItemInvoked"
                        Style="{StaticResource TreeViewStyle1}" />
            <!-- Command Bar -->
            <CommandBar>
                <!-- AppBarButtons -->
            </CommandBar>
        </Grid>
    </SplitView.Pane>
    <SplitView.Content>
        <Grid>
            <!-- Does not seem to work. -->
            <ParallaxView Source="{Binding ElementName=NavigationTree}"
                            VerticalShift="100">
                <!-- Background Image -->
            </ParallaxView>

            <!-- Navigation Frame -->
            <Frame x:Name="SplitViewFrame"
                    Navigated="SplitViewFrame_OnNavigated"/>
        </Grid>
    </SplitView.Content>
</SplitView>

<!-- Title Bar -->
<TextBlock x:Name="AppTitle" />

<!-- Hamburger Button -->
<Button x:Name="HamburgerButton">
    <!-- ... -->
</Button>

SplitView

We used a classic SplitView control to host the main components: the TreeView on the left and a Frame to hold the current page on the right. The SplitView Pane can be opened or closed with a Hamburger button that sits on top of it (z-index wise).

A SplitView supports four display modes for the Pane, but the NavigationView only uses three display modes for the menu:

  • Minimal: panel shows if needed and on top of content,
  • Compact: panel shows a narrow band of icons, and
  • Expanded: panel shows alongside the content.

We didn’t use icons in the TreeView’s ItemTemplate – and it would anyway be impossible to  to show a multi-level tree in a narrow vertical band. So we decided to skip compact mode in the visual states. As a result the SplitView Pane has always the same width (when visible). It will just toggle between Inline and Expanded according to the app’s width.

Following the guidelines for Acrylic, we made the navigation pane background

  • 60% in-app acrylic in overlay mode, and
  • 60% background acrylic in side-by-side mode.

When using acrylic brushes in your app, don’t forget to provide a fallback color. It’s the solid color that is shown when the composition brushes are not used – i.e. when the app loses focus, or when the user settings or the machine hardware prevent it. Here’s a screenshot of the app when it does not have the focus:

Unfocused

Here’s the definition of the corresponding brushes:

<!-- Splitview Pane -->
<AcrylicBrush x:Key="SplitViewInlineBackgroundBrush"
                BackgroundSource="HostBackdrop"
                TintOpacity="0.6"
                FallbackColor="#ffe6ff" />
<AcrylicBrush x:Key="SplitViewOverlayBackgroundBrush"
                BackgroundSource="Backdrop"
                TintOpacity="0.6"
                FallbackColor="#ffe6ff" />

And here’s how they are declaratively applied in the Visual State Manager:

<!-- VisualState to be triggered when window width is >=1007 effective pixels -->
<VisualState x:Name="Expanded">
    <VisualState.StateTriggers>
        <AdaptiveTrigger MinWindowWidth="1007" />
    </VisualState.StateTriggers>
    <VisualState.Setters>
        <!-- SplitView pane shows as overlay -->
        <Setter Target="ShellSplitView.DisplayMode"
                Value="Inline" />
        <Setter Target="ShellSplitView.PaneBackground"
                Value="{StaticResource SplitViewInlineBackgroundBrush}" />
    </VisualState.Setters>
</VisualState>

The Shell page -including the SplitView and its content- is blended into the title area:

// Blends the app into the title bar.
var coreTitleBar = CoreApplication.GetCurrentView().TitleBar;
coreTitleBar.ExtendViewIntoTitleBar = true;
var titleBar = ApplicationView.GetForCurrentView().TitleBar;
titleBar.ButtonBackgroundColor = Colors.Transparent;
titleBar.ButtonInactiveBackgroundColor = Colors.Transparent;
titleBar.ButtonForegroundColor = (Color)Application.Current.Resources["TitlebarButtonForegroundColor"];

For more info on this code and its consequences, check the TitleBar Customization documentation.

TreeView

The TreeView control is the obvious candidate for displaying a hierarchical menu. It is also one of the UWP controls that comes with built-in Reveal highlight effect, a lighting effect that highlights interactive elements, such as command bar buttons, listview items, or treeview nodes when the user moves the pointer near them. Check the bottom left part of the first screenshot in this article for an impression.

We tweaked the TreeViewItem style a little bit to decorate the selected node with a thin vertical bar, just like the NavigationView does (but without an animation). Here’s what we changed :

<!-- Added -->
<Grid x:Name="SelectionGrid"
        Width="4"
        Margin="8"
        HorizontalAlignment="Left" />

<VisualState x:Name="Selected">
    <VisualState.Setters>
        <!-- Changed -->
        <Setter Target="SelectionGrid.Background"
                Value="{ThemeResource TreeViewItemBackgroundSelected}" />
        <Setter Target="ContentPresenter.Foreground"
                Value="{ThemeResource TreeViewItemForegroundSelected}" />
        <Setter Target="ContentPresenterGrid.BorderBrush"
                Value="{ThemeResource TreeViewItemBorderBrushSelected}" />
    </VisualState.Setters>
</VisualState>

As part of the process, we also changed the different accent colors so that we don’t rely on the user’s personal accent color:

<Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.ThemeDictionaries>
            <!-- AccentColor overrides -->
            <!-- Shades of OrangeRed -->
            <ResourceDictionary x:Key="Default">
                <Color x:Key="SystemAccentColor">#ff4500</Color>
                <Color x:Key="SystemAccentColorLight1">#ff4500</Color>
                <Color x:Key="SystemAccentColorLight2">#ff8f66</Color>
                <Color x:Key="SystemAccentColorLight3">#b33000</Color>
                <Color x:Key="SystemAccentColorDark1">#ff4500</Color>
                <Color x:Key="SystemAccentColorDark2">#ff8f66</Color>
                <Color x:Key="SystemAccentColorDark3">#b33000</Color>
            </ResourceDictionary>
            <!-- ... -->

Here’s how all of this looks like – before and after the template and color changes:
WithoutVisualChanges

WithVisualChanges

Notice that we also moved the whole TreeView control some pixels to the left, to vertically align the chevron icons with the Hamburger button.

Hamburger button

We decided to give the Hamburger button the same size and padding as the system buttons (minimize, maximize, and close) and place it into the title bar in the top left corner. If you place input controls in the title bar, then you should override the default interactive part of the title bar (the part that responds to dragging and double clicking) – otherwise your control will not be reachable.

Here’s the XAML for the top left corner:

<!-- Hamburger Button -->
<Button x:Name="HamburgerButton"
        Margin="0"
        Width="48"
        Height="32"
        <!-- ... -->

<!-- Title Bar -->
<Grid x:Name="AppTitleBar"
        Background="Transparent"
        Height="32"
        VerticalAlignment="Top"
        HorizontalAlignment="Stretch">
    <TextBlock x:Name="AppTitle"
                xmlns:appmodel="using:Windows.ApplicationModel"
                Text="{x:Bind appmodel:Package.Current.DisplayName}"
                Style="{StaticResource CaptionTextBlockStyle}"
                IsHitTestVisible="False"
                Margin="48 8 0 0" />
</Grid>

Here’s how the default title bar zone is overridden:

Window.Current.SetTitleBar(AppTitleBar);

The code behind the Hamburger button is straightforward. It just needs to toggle IsPaneOpen on the SplitView:

/// <summary>
/// Toggles the SplitView pane.
/// </summary>
private void HamburgerButton_Click(object sender, RoutedEventArgs e)
{
    ShellSplitView.IsPaneOpen = !ShellSplitView.IsPaneOpen;
}

Secondary commands

For the secondary commands at the bottom of the navigation panel, we opted for a CommandBar with regular app bar buttons. It’s one of the controls that comes with the Reveal effect built in, and it has the correct size. We considered buttons of the same dimensions as the Hamburger and the system buttons, but this size is to small for buttons that are used often and that have more complex icons.

Navigation

Our focus of the sample app was mainly on the user experience, so we kept the functional part -the navigation itself- as simple as possible. All the related code is placed directly in the Shell page, albeit in a partial class file. In a real-world app you would build a Navigation Service and then find and call it using singletons, dependency injection, service locator, or any other kind of blood magic. For more info, please check my older blog post on SplitView navigation.

To separate at least some of the concerns, we did create a NavigationMenuItem class that allows to define a hierarchic navigation structure (with the destination page type and parameter, and a Children collection):
NavigationMenuItem

Here’s a part of the logical navigation menu in the sample app:

private ObservableCollection<NavigationMenuItem> MainMenu => 
    new ObservableCollection<NavigationMenuItem>
        {
            new NavigationMenuItem
            {
                Text = "Edged Weapons",
                NavigationDestination = typeof(BladesPage),
                Children = new ObservableCollection<MenuItem>
                {
                    new NavigationMenuItem
                    {
                        Text = "Arakh",
                        NavigationDestination = typeof(BladesPage),
                        NavigationParameter = "Arakh"
                    },
                    new NavigationMenuItem
                    {
                        Text = "Dragonglass",
                        NavigationDestination = typeof(BladesPage),
                        NavigationParameter = "Dragonglass"
                    },
                    // ...

The Navigate method uses this information to … well … navigate:

/// <summary>
/// Navigates to the specified source page type.
/// </summary>
public bool Navigate(Type sourcePageType, object parameter = null)
{
    return SplitViewFrame.Navigate(sourcePageType, parameter);
}

When the app starts up, TreeView nodes are created from the menu structure – with a little help from a recursive extension method:

internal static TreeViewNode AsTreeViewNode(this NavigationMenuItem menuItem)
{
    var result = new TreeViewNode
    {
        Content = menuItem
    };

    foreach (NavigationMenuItem subItem in menuItem.Children)
    {
        result.Children.Add(subItem.AsTreeViewNode());
    }

    return result;
}
/// <summary>
/// Populates the TreeView from the Menu.
/// </summary>
private void PopulateTreeView()
{
    // Populate the tree.
    foreach (var item in MainMenu)
    {
        NavigationTree.RootNodes.Add(item.AsTreeViewNode());
    }
}

Each node in the TreeView now has a NavigationMenuItem as its Content, so it knows which page to load and with which parameter. Here’s what happens when a node is clicked:

/// <summary>
/// Navigates to the corresponding treeview selection.
/// </summary>
private void TreeView_ItemInvoked(TreeView sender, TreeViewItemInvokedEventArgs args)
{
    if (args.InvokedItem is TreeViewNode node)
    {
        if (node.Content is NavigationMenuItem menuItem)
        {
            var target = menuItem.NavigationDestination;
            if (target != null)
            {
                Navigate(menuItem.NavigationDestination, menuItem.NavigationParameter);
            }
        }
    }
}

The secondary command buttons call the same method, but without a parameter:

private void SettingsButton_Tapped(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e)
{
    Navigate(typeof(SettingsPage));
}

Just to prove that the parameter from the NavigationMenuItem is correctly passed to the Page, here’s how the different pages in the sample app update their subtitle in the top right corner:

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    if (e.Parameter != null)
    {
        SubTitle.Text = e.Parameter.ToString();
    }

    base.OnNavigatedTo(e);
}

The Back Button

The hierarchical menu on the left -together with the secondary commands at the bottom- give the user immediate access to any page in the app. So personally we’re not in favor of providing a back button. But just for the sake of completeness, we also implemented it.

This implies a method to navigate back:

/// <summary>
/// Returns to the previous page.
/// </summary>
public void GoBack()
{
    if (SplitViewFrame.CanGoBack)
    {
        SplitViewFrame.GoBack();
    }
}

And you also need to make the system button visible:

/// <summary>
/// Shows the system back button.
/// </summary>
public void EnableBackButton()
{
    var navManager = SystemNavigationManager.GetForCurrentView();
    navManager.AppViewBackButtonVisibility = AppViewBackButtonVisibility.Visible;
    navManager.BackRequested -= (s, e) => GoBack();
    navManager.BackRequested += (s, e) => GoBack();
}

We placed the Hamburger button in the top left corner. When the system back button (dis)appears, we need to shift that button together with the text block holding the app title:

// Update the title bar when the back button (dis)appears or resizes.
Window.Current.CoreWindow.SizeChanged += (s, e) => UpdateAppTitle();
coreTitleBar.LayoutMetricsChanged += (s, e) => UpdateAppTitle();
/// <summary>
/// Updates the title bar when the back button (dis)appears or resizes.
/// </summary>
private void UpdateAppTitle()
{
    var full = (ApplicationView.GetForCurrentView().IsFullScreenMode);
    var left = (full ? 0 : CoreApplication.GetCurrentView().TitleBar.SystemOverlayLeftInset);
    HamburgerButton.Margin = new Thickness(left, 0, 0, 0);
    AppTitle.Margin = new Thickness(left + HamburgerButton.Width + 12, 8, 0, 0);
}

We also added the code to unselect the current TreeView node when the Back or a secondary command button is pressed. Since we weren’t successful in programmatically selecting and unselecting nodes, we applied the old trick of toggling the SelectionMode:

/// <summary>
/// Makes the TreeView lose its selection when there is no corresponding main menu item.
/// </summary>
/// <remarks>This works, but I don't know why...</remarks>
private void SplitViewFrame_OnNavigated(object sender, NavigationEventArgs e)
{
    NavigationTree.SelectionMode = TreeViewSelectionMode.None;
    NavigationTree.SelectionMode = TreeViewSelectionMode.Single;
}

We assumed it would be necessary to add some extra code to determine whether the navigation was triggered by the TreeView or not, before deciding to unselect the current node. It turns out that unconditionally toggling the selection mode seems to do the job already. But honestly: this feels like a coincidence.

NavigationTreeView, first of his name ?

We just created a great looking fluent design compatible TreeView based navigation UI. The development was easier than we expected, and the results look better than we expected. Some of you may be tempted to componentize all of this into a user experience just like the current NavigationView control. We think that this can be done with not too much extra effort, but personally we prefer the more granular control that we have in the sample app.

That sample app lives here on GitHub.

Enjoy!

Displaying Dynamic SQL Results in a UWP DataGrid

In this article we’ll show how you can display the result of any query against a SQL Server database directly in a UWP DataGrid. The grid control that we use is the new Windows Community Toolkit DataGrid. The companion sample app hosts 3 different TSQL queries (feel free to add your own), but only one instance of the DataGrid. Here’s how it looks like:

Grid_1

We’ll show you how to

  • connect to a SQL Server database from UWP,
  • read a dynamic TSQL statement into a DataTable,
  • modify the content of the DataTable progammatically – if needed,
  • bind the DataTable to a DataGrid, and
  • sort the grid on clicking a header column.

This type of dynamic data binding and visualization is particularly useful in the parts of an app that deal with administration, diagnostics, monitoring, or trouble shooting – where you don’t necessarily know upfront which queries to launch and don’t have the possibility (or resources, or priority) to define a full entity model and/or specialized UI controls.

Get that DataGrid

The DataGrid XAML Control is a port from the popular Silverlight control with the same name. Most -but not all- of the functionality has been migrated. This article does NOT focus on its full feature set, but only on dynamic data binding. Please check the official documentation and the source code of a complete sample app for more info. Its documentation is not yet fully uploaded, so some of the hyperlinks in this article will point to the (2011!) Silverlight version.

The DataGrid Control is distributed as a NuGet package within Windows Community Toolkit, currently as a prerelease :

Grid_NuGet

Here’s how the data grid is defined in the sample app. With no columns specified and AutoGenerateColumns to false it is ready for dynamic data binding:

<controls:DataGrid x:Name="ResultsGrid"
                    RowDetailsTemplate="{StaticResource DetailsTemplate}"
                    RowDetailsVisibilityMode="VisibleWhenSelected"
                    CanUserSortColumns="True"
                    Sorting="ResultsGrid_Sorting"
                    BorderThickness="2"
                    BorderBrush="DarkSlateGray"
                    AlternatingRowBackground="BlanchedAlmond"
                    GridLinesVisibility="All"
                    AutoGenerateColumns="False"
                    SelectionMode="Single" />

So let’s create some tabular data.

Populating a DataTable

DataTable is one of the core classes of the ADO.NET library. It represents an in-memory, database-agnostic structure of rows and strongly typed columns. An easy way to populate a DataTable is calling Fill() against a SqlDataAdapter that executes a SqlCommand on a SqlConnection. Here’s how this looks like in the sample app:

using (var connection = new SqlConnection(connectionString))
{
    await connection.OpenAsync();
    var command = connection.CreateCommand();
    command.CommandText = query;
    dataTable = new DataTable();

    using (var dataAdapter = new SqlDataAdapter(command))
    {
        dataAdapter.Fill(dataTable);
    }
}

Defining the DataGrid structure

In WPF, it would suffice to set AutoGenerateColumns to true and then run the following to populate the DataGrid from the DataTable:

ResultsGrid.ItemsSource = dataTable;

[Actually just data binding would even do the trick.]

The Silverlight -and hence UWP- version of the control doesn’t allow this, so there’s a tiny bit more programming required.

We’ll create a list of DataGridTextColumn instances -one for each column in the DataTable- and assign their Header text and and index Binding:

for (int i = 0; i < table.Columns.Count; i++)
{
        grid.Columns.Add(new DataGridTextColumn()
        {
            Header = table.Columns[i].ColumnName,
            Binding = new Binding { Path = new PropertyPath("[" + i.ToString() + "]") }
        });
}

If you would want to rename columns here, or skip some, or add some new ones, then this would be the place to do these changes.

Populating the DataGrid

The DataGrid is now ready to accept content. This content comes from an ObservableCollection that we populate with the ItemArray of each row in the DataTable. The collection is then set as ItemsSource of the DataGrid:

var collection = new ObservableCollection<object>();
foreach (DataRow row in table.Rows)
{
    collection.Add(row.ItemArray);
}

grid.ItemsSource = collection;

Here’s another view on the result. Same grid, same code, different query:

Grid_2

Tweaking the content

In the sample app, we manipulated the content of one of the columns. Some of the queries contain the text of a TSQL query, a long string that may contain visual formatting with tabs, carriage returns and series of blanks. That’s good to display in the details template – which is what we do. For a regular column it makes more sense to truncate the content, and get rid of the carriage returns.

Fortunately it’s easy to iterate through the rows of a DataTable and update one or more of its columns). Here’s how we remove obsolete white space from a string (using a Split() with StringSplitOptions.RemoveEmptyEntries), and truncate the SQL statements:

if (table.Columns.Contains("SQL Statement"))
{
    var column = table.Columns["SQL Statement"];

    foreach (DataRow row in table.Rows)
    {
        string sqlStatement = ((row[column] as string) ?? string.Empty).Trim();
        row[column] = string.Join(' ', sqlStatement.Split(default(string[]), StringSplitOptions.RemoveEmptyEntries)).Substring(0, 80) + "...";
    }

    table.AcceptChanges();
}

The call to AcceptChanges() is not strictly necessary but we do it to save memory. The call locally commits the changes inside the DataTable and clears the –potentially bloated- row versioning information.

Here’s how the sample app looks like for a query that has a “SQL Statement” column. The manipulated value goes to a regular column and the full text appears in the details of the selected row:

Grid_3

Sorting

Last but not least, we implemented the canonical single-column-sorting-on-header-click behavior. This is only enabled when CanUserSort on the column or CanUserSortColumns on the DataGrid is set to true. In the current release, the DataGrid only covers the visual part: it displays an arrow in the column header according the current SortDirection. We have to implement the sort ourselves in a handler hooked to the Sorting event that exposes the column and the sort direction. We set that sort direction for the clicked column and reset I for the others:

var currentSortDirection = e.Column.SortDirection;

foreach (var column in ResultsGrid.Columns)
{
    column.SortDirection = null;
}

var sortOrder = "ASC";

if ((currentSortDirection == null || currentSortDirection == DataGridSortDirection.Descending))
{
    e.Column.SortDirection = DataGridSortDirection.Ascending;
}
else
{
    sortOrder = "DESC";
    e.Column.SortDirection = DataGridSortDirection.Descending;
}

Then we sort the content itself. We assign the Sort property of the DataTable’s DefaultView, transform the resulting DataView back to a DataTable, and update the binding:

var dataView = dataTable.DefaultView;
dataView.Sort = e.Column.Header + " " + sortOrder;
dataTable = dataView.ToTable();

RefreshContents(dataTable, ResultsGrid);

Code

The sample app lives here on GitHub. When playing with it, don’t forget to bring your own connection string to MainPage.xaml.cs.

Enjoy!

How to build a SQL Connection Dialog in UWP

This article explains how to build a dialog in UWP that sets up a connection from your app to any SQL Server database on your network or in the cloud. Since the Windows Fall Creators Update we can -finally- directly connect to SQL Server from UWP. In that SDK .net core was extended with a large portion of the object model of ye olde ADO.NET, hosted in the System.Data.SqlClient namespace. In this article we’ll use some of these to build a connection dialog to SQL Server, with the following features:

  • You can type a server name or select a recently use one.
  • You can select the security mode.
  • You can enter your credentials when SQL Authentication is used.
  • You can select the database from a list.
  • You have direct access to the connection string.
  • You can test the connection.

When the dialog closes, it will serve you with a validated connection string (unless you cancelled the operation).

Here’s how the dialog looks like in its default mode, with different input fields:

SqlDialogDefaultInput

And here’s how it looks like in direct access mode, where you can edit the connection string:

SqlDialogDirectInput

Basic structure

The SQL Connection Dialog is based on a ContentDialog. Under the title (which has a nice SVG Icon – thanks to TheNounProject) there’s a ProgressBar from which we toggle the IsIndeterminate property to indicate whether the control is busy – i.e. establishing a connection or fetching the database names. The first row in the input form is a custom editable combobox for the server name. Then come some regular input controls –ComboBox, TextBox, PasswordBox– for the security parameters. At the bottom of the input zone there’s a ComboBox that hosts the names of the databases. All text fields on the input form have their IsSpellCheckEnabled set to false – we assume you don’t want red squigglies under server names or user accounts.

All input fields are bound to properties of a SqlConnectionBuilder. As an example, here’s the code for the UserId field:

private SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder();
private string userId;

/// <summary>
/// Gets or sets the SQL user identifier.
/// </summary>
public string UserId
{
    get { return userId; }
    set
    {
        userId = value;
        builder.UserID = userId;
        OnPropertyChanged();
    }
}

And its corresponding XAML:

<TextBox Text="{x:Bind UserId, Mode=TwoWay}"
         IsEnabled="{x:Bind SqlSecurity, Mode=OneWay}" />

The ellipsis button in the top left corner sets the control in direct input mode, showing a large multi-row textbox to manipulate the connection string.

A ContentDialog can have maximum three buttons at the bottom, and we’re using all of these. The PrimaryButton allows to test the current connectionstring, the SecondaryButton returns a validated connectstring to the caller and closes the dialog, and the CloseButton … well … closes.

An Editable ComboBox

The name of the server to connect to can be chosen from a dropdown with recently used successful connections (stored in LocalSettings) or it can be typed in manually. Now UWP does not come with an editable ComboBox, so we needed to simulate one through an AutoSuggestBoxToggleButton combination. The toggle button opens the AutoSuggestBox’s dropdown. Here’s the XAML:

<AutoSuggestBox x:Name="SuggestBox"
                Style="{StaticResource AutoSuggestBoxNoSpellCheckingStyle}"
                Text="{x:Bind Server, Mode=TwoWay}"
                ItemsSource="{x:Bind MostRecentConnections}"
                VerticalAlignment="Stretch"
                IsSuggestionListOpen="{Binding IsChecked, ElementName=Toggle, Mode=TwoWay}"
                FontFamily="Segoe UI" />
<ToggleButton x:Name="Toggle"
                Grid.Column="1"
                VerticalAlignment="Stretch"
                Width="32"
                Background="Transparent"
                FontFamily="Segoe UI">
    <FontIcon Foreground="{ThemeResource ComboBoxDropDownGlyphForeground}"
                FontSize="12"
                FontFamily="Segoe MDL2 Assets"
                Glyph=""
                IsHitTestVisible="False" />
</ToggleButton>

A few classes from .NET SQL Server ecosystem were not ported to .net core. One of these is SqlDataSourceEnumerator, which allows to lookup SQL Server instances on the local network. So we can’t do this on UWP.

Maintaining the size

The UWP ContentDialog is quite stubborn when it comes to setting and resetting its size. It prefers to decide its height and width depending on its children and its host, and tends to ignore hard-coding values. We decided not to fight the dialog, and let it define its own size – based on the default look with the multiple input fields. But we don’t want the dialog to shrink when switching to direct input mode (with only one textbox). All the content is always in place so the dialog never feels the need to resize. We’re just playing with Opacity and Z-Order when switching the mode. A Grid has no ZIndex attached property for its content, so we reverse the Children collection instead. Here’s what happens when we switch from default mode to direct mode:

DefaultGrid.Opacity = 1;
DirectGrid.Opacity = 0;
Host.Children.Remove(DirectGrid);
Host.Children.Insert(0, DirectGrid);

Of course there’s similar code the other way around. It keeps the dialog the same size, and it freezes the mode switch button at its position, making it easy to rapidly check the connection string and come back.

Testing the connection

For testing the connection, we create a SqlConnection instance from the current connection string, and try to Open it:

using (SqlConnection con = new SqlConnection(builder.ConnectionString))
{
    await con.OpenAsync();
}

We’re updating the UI during the call (remember there’s a ProgressBar), so it’s a good idea to use OpenAsync here. The Using statement will make sure that we close the connection automatically.

When the connection is successful, we’ll show it to the user, with a MessageDialog:

SqlDialogTest

And when the connection is not successful, we display the error:

SqlDialogTestFail

Fetching the database list

The list of database names on a SQL Server Instance can only be fetched through a successful connection to the server. Knowing that every unsuccessful connection attempt waists at least a few seconds, we don’t want to try to connect on every property change. The list of databases is fetched in the combo box’s own DropDownOpened event:

<ComboBox x:Name="DatabaseComboBox"
          DropDownOpened="DatabaseComboBox_DropDownOpened"
          ItemsSource="{x:Bind Databases, Mode=OneWay}"
          SelectionChanged="Database_SelectionChanged" />

Here’s the code. We create a new SqlCommand on the open connection, execute it, and loop though the result with a SqlDataReader:

using (var connection = new SqlConnection(builder.ConnectionString))
{
    await connection.OpenAsync();

    using (var command = connection.CreateCommand())
    {
        command.CommandText = "SELECT [name] FROM sys.databases ORDER BY [name]";

        using (SqlDataReader reader = await command.ExecuteReaderAsync())
        {
            while (await reader.ReadAsync())
            {
                databases.Add(reader.GetString(0));
            }
        }
    }
}

Again, we used the asynchronous versions of the operations (ExecureReaderAsync and ReadAsync) to keep the UI responsive.

Achievement unlocked: connected

When the user clicks the Connect button, we validate the current connection string in just the same way as for the Test button. If the connection is successful, we store the current server name of top of the most recent servers – the list that appears in the editable combobox on top. Then we assign the dialog’s ConnectionString property for the caller to grab. As mentioned, the SqlConnectionDialog does not return a connection, but a validated connection string:

try
{
    using (SqlConnection con = new SqlConnection(builder.ConnectionString))
    {
        await con.OpenAsync();
    }

    MostRecentConnection = Server;
    ConnectionString = builder.ConnectionString;
}

How to use it

Here’s how a client app uses the dialog. It opens the dialog, waits for the result, checks whether the user cancelled or not, and finally grabs the connection string:

var dialog = new ConnectionDialog();
var result = await dialog.ShowAsync();

// User Cancelled
if (result == ContentDialogResult.None)
{
    return;
}

var connectionString = dialog.ConnectionString;

The sample client app uses the provided connection string to fetch the list of tables from the select database with their number of rows:

SqlDialogResult

The sample app with the SqlConnectionDialog lives here on GitHub.

Enjoy!