Category Archives: User Controls

A Radial Range Indicator control for UWP

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

Here’s how a RadialRangeIndicator looks like:

RadialRangeIndicator

Its main constituents are

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

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

Here’s the list of configurable properties:


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

Gets or sets the brush for the displayed value range.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Gallery

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

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

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

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

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

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

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

HopDetails

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

SquareOfSquares

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

Enjoy!

A Percentage Ring control for UWP

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

PercentageRing

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

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

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

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

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

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

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

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

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

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

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

Gallery

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

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

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

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

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

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

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

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

Squares

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

PercentageRing_Phone Gallery_Phone Squares_Phone

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

Enjoy!

BackDrop: a control for dynamic blur in UWP

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

Properties

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

SplitViewMenu

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

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

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

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

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

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

    ElementCompositionPreview.SetElementChildVisual(this, _blurVisual);
}

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

SaturationEffect

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

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

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

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

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

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

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

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

    return factory.CreateBrush();
}

Here are the PropertyChangedCallback delegates that update the effect parameters:

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

    if (backDrop == null) return;

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

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

    if (backDrop == null) return;

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

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

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

    if (backDrop == null) return;

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

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

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

Properties

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

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

FlyOut

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

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

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

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

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

ContentDialogTemplate

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

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

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

Dialog

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

Squares

Here’s the same page on my phone:

BackDrop_Phone

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

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

BackDropSample

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

Enjoy!

Building a UWP Rating Control using XAML and the Composition API

 

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

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

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

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

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

Here are some instances of the control in action:

WithoutLossOfImageQuality

An almost empty XAML template

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

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

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

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

The code behind

Set-up

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

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

    // ...
}

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

Here’s how the ItemHeight property is registered:

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

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

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

    OnStructureChanged(this);

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

    base.OnApplyTemplate();
}

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

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

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

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

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

    // ...
}

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

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

Loading the images

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

MSUICompositionToolkit

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

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

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

    // ...
}

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

CompositionImageLoader

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

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

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

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

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

LossOfImageQuality

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

WithoutLossOfImageQuality

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

Rendering the control

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

RatingStructure

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

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

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

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

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

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

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

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

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

OnValueChanged(c);

Changing the value

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

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

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

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

The behavior

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

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

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

    return number + modulo;
}

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

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

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

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

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

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

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

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

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

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

    Value = value;
}

Using the Rating Control

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

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

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

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

That’s all there is.

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

RatingOnPhone

Source code

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

Enjoy!

A Floating Panel Control for UWP

Windows Apps need to run in an increasing number of window sizes and resolutions and fortunately the Windows 10 ecosystem helps us with things like the RelativePanel and Visual State Triggers. But not all positioning issues can or should be solved by responsive layout techniques. Sometimes it’s just better to let the user decide where a control should be positioned. In a UI that shows a diagram –for example- it’s hard to predict where the bars or lines or shapes will appear. For that same diagram it’s also hard to predict the size of the legend: it depends on the number of series and the length of their names. In this case it would be appropriate to let the user drag the legend panel to a ‘free’ area in the diagram.

In this article we’ll build a XAML Control for the Universal Platform that can be dragged around the screen. Its position can optionally be restrained to a parent control’s rectangle or to the window itself. The control is decorated with an icon to indicate its ‘draggability’ behavior to the user.

Here’s how to use it:

<controls:FloatingContent Boundary="Parent">
    <!-- Any Content Here ... -->
</controls:FloatingContent>

As usual, we built a control together with a sample app. This app contains some instances of the so-called FloatingContent that are differently configured:

  • Joy is unbound, she can be dragged off the screen, 
  • Disgust is bound to the Window,
  • Sadness is bound to [the root control of] the Page (as you will see, this makes a subtle difference), 
  • Anger is bound to the red Border and doesn’t like it, and
  • Fear is also bound to the red Border, but he has a Margin.

Here’s how that app looks like:

FloatingContentControl

Style

The control is yet another templated control. Here’s the ControlTemplate:

<ControlTemplate TargetType="local:FloatingContent">
    <!-- This Canvas never covers other controls -->
    <Canvas Background="Transparent"
            Height="0"
            Width="0"
            VerticalAlignment="Top"
            HorizontalAlignment="Left">
        <!-- This Border handles the dragging -->
        <Border x:Name="PART_Border"
                ManipulationMode="TranslateX, TranslateY, TranslateInertia">
            <Grid>
                <!-- Content -->
                <ContentPresenter />
                <!-- Overlay -->
                <!-- Anything with an Opacity will do here... -->
                <Path x:Name="PART_Overlay"
                        Data="...(long Path Data omitted)..."
                        Stretch="Uniform"
                        Opacity="0"
                        Fill="White"
                        Width="24"
                        Height="24"
                        Margin="8"
                        VerticalAlignment="Top"
                        HorizontalAlignment="Left" />
            </Grid>
        </Border>
    </Canvas>
</ControlTemplate>

The Border control inside the template handles the dragging: through its ManipulationMode it is set to react to  combined horizontal and vertical manipulation with inertia. That border is wrapped in a zero-height, zero-width Canvas. That’s because the Border’s position is changed by updating its Canvas.Top and Canvas.Left attached properties. Inside the Border there’s the ContentPresenter that … presents the content. On top of the content there’s an overlay that displays a ‘draggability’ icon.

Class Definition

The class is decorated with TemplatePart attributes for the Border and the Overlay, since we use these in the code. [Note to template designers: the code also heavily relies on the outer Canvas in the template, so don’t drop that.]

In most cases we let a custom control inherit from Control, but this time ContentControl is a better parent:

/// <summary>
/// A Content Control that can be dragged around.
/// </summary>
[TemplatePart(Name = BorderPartName, Type = typeof(Border))]
[TemplatePart(Name = OverlayPartName, Type = typeof(UIElement))]
public class FloatingContent : ContentControl
{
    private const string BorderPartName = "PART_Border";
    private const string OverlayPartName = "PART_Overlay";

    private Border border;
    private UIElement overlay;

The FloatingContent control has just one dependency propertyBoundary-, which is a value from the FloatingBoundary enumeration. Here are the relevant code snippets:

public enum FloatingBoundary
{
    None,
    Parent,
    Window
}
public static readonly DependencyProperty BoundaryProperty =
    DependencyProperty.Register(
        "Boundary",
        typeof(FloatingBoundary),
        typeof(FloatingContent),
        new PropertyMetadata(FloatingBoundary.None));

public FloatingBoundary Boundary
{
    get { return (FloatingBoundary)GetValue(BoundaryProperty); }
    set { SetValue(BoundaryProperty, value); }
}

During the initialization of the control’s look and feel in the OnApplyTemplate method

  • we first lookup the Border in the template, and throw an Exception if it’s not found (after all: there’s no floating behavior without that border).
  • Then we register an event handler for ManipulationDelta which will manage the movement of the control when it is dragged around.
  • Last but not least, we make sure that any initial positioning of the control is maintained, and
  • in order to keep the boundary hit detection algorithm simple, we replace the control’s Margin by its Border’s Padding:
protected override void OnApplyTemplate()
{
    // Border
    this.border = this.GetTemplateChild(BorderPartName) as Border;
    if (this.border != null)
    {
        this.border.ManipulationDelta += Border_ManipulationDelta;

        // Move Canvas properties from control to border.
        Canvas.SetLeft(this.border, Canvas.GetLeft(this));
        Canvas.SetLeft(this, 0);
        Canvas.SetTop(this.border, Canvas.GetTop(this));
        Canvas.SetTop(this, 0);

        // Move Margin to border.
        this.border.Padding = this.Margin;
        this.Margin = new Thickness(0);
    }
    else
    {
        // Exception
        throw new Exception("Floating Control Style has no Border.");
    }

    this.Loaded += Floating_Loaded;
}

When the control is loaded, we register an event handler to the SizeChanged of its parent. When that parent is resized, we may need to update the FloatingControl’s position:

private void Floating_Loaded(object sender, RoutedEventArgs e)
{
    FrameworkElement el = GetClosestParentWithSize(this);
    if (el == null)
    {
        return;
    }

    el.SizeChanged += Floating_SizeChanged;
}

We have to pick the right parent for this: by crawling up the Visual Tree we look for the closest control with an actual size, since that is the one that will decently respond to SizeChanged

/// <summary>
/// Gets the closest parent with a real size.
/// </summary>
private FrameworkElement GetClosestParentWithSize(FrameworkElement element)
{
    while (element != null && 
	(element.ActualHeight == 0 || element.ActualWidth == 0))
    {
        // Crawl up the Visual Tree.
        element = element.Parent as FrameworkElement;
    }

    return element;
}

Runtime behavior

Dragging the control

The most important feature of the FloatingContent control, is following the pointer – finger, pen or mouse pointer, or other. This movement is triggered through the ManipulationDelta event handler. It calculates the theoretical –unbound- position of the control as a rectangle with top, left, width and height. It then calls the AdjustCanvasPosition() routine that will effectively update the control’s position:

private void Border_ManipulationDelta(object sender, 
	ManipulationDeltaRoutedEventArgs e)
{
    var left = Canvas.GetLeft(this.border) + e.Delta.Translation.X;
    var top = Canvas.GetTop(this.border) + e.Delta.Translation.Y;

    Rect rect = new Rect(
	left, 
	top, 
	this.border.ActualWidth, 
	this.border.ActualHeight);
    var moved = AdjustCanvasPosition(rect);

    // Not intuitive:
    //if (!moved)
    //{
    //    // We hit the boundary. Stop the inertia.
    //    e.Complete();
    //}
}

The move-the-control code returns a Boolean to let the caller know whether or not the manipulation did actually move the control. He may want to react on this. The commented code in the previous snippet implements some kind of auto-docking feature that stops all movement when a boundary is hit.

As already mentioned, the floating control is moved by updating its Canvas.Top and Canvas.Left attached properties and is constrained by its Boundary type:

  • When Boundary is None, no checks are done: the theoretical position becomes the actual position.
  • When Boundary is Parent, we’ll look up the closest parent with an actual size in the Visual Tree, and apply a collision detection algorithm.
  • When the Boundary is Window, we look up the relative position of the control in the app’s Window through TransformToVisual and Window.Current, and then adjust when a collision was detected.

Here’s the whole routine, except for the collision detection calculation which is fairly obvious:

/// <summary>
/// Adjusts the canvas position according to the Boundary property.
/// </summary>
/// <returns>True if there was a move, otherwise False.</returns>
private bool AdjustCanvasPosition(Rect rect)
{
    // Free floating.
    if (this.Boundary == FloatingBoundary.None)
    {
        Canvas.SetLeft(this.border, rect.Left);
        Canvas.SetTop(this.border, rect.Top);

        return true;
    }

    FrameworkElement el = GetClosestParentWithSize(this);

    // No parent
    if (el == null)
    {
        // We probably never get here.
        return false;
    }

    var position = new Point(rect.Left, rect.Top); ;

    if (this.Boundary == FloatingBoundary.Parent)
    {
        Rect parentRect = new Rect(0, 0, el.ActualWidth, el.ActualHeight);
        position = AdjustedPosition(rect, parentRect);
    }

    if (this.Boundary == FloatingBoundary.Window)
    {
        var ttv = el.TransformToVisual(Window.Current.Content);
        var topLeft = ttv.TransformPoint(new Point(0, 0));
        Rect parentRect = new Rect(topLeft.X, topLeft.Y, 
	Window.Current.Bounds.Width - topLeft.X, 
	Window.Current.Bounds.Height - topLeft.Y);
        position = AdjustedPosition(rect, parentRect);
    }

    // Set new position
    Canvas.SetLeft(this.border, position.X);
    Canvas.SetTop(this.border, position.Y);

    return position == new Point(rect.Left, rect.Top);
}

Note: when the Boundary is set to Window and you’re using a SplitView on the page, then the FloatingContent will remain on the window as expected, but it can be dragged under the Splitview’s Pane. That’s what we did with Disgust in the screenshots. If you don’t want that behavior, then use Parent for Boundary and place the control in the page’s root – that’s how we configured Sadness.

Showing the Draggability Indicator

When the user hovers over the control, when he taps on it, and when he’s dragging the control around, the FloatingContent displays a ‘dragging indicator’ in its upper left corner. It’s the white icon in Anger -not the red arrow- in the following screenshot:

FloatingContentControlOverlay

That so-called Overlay is defined in the control’s style, with a zero Opacity. We let the icon appear and disappear by animating this Opacity. That’s why we define it as UIElement instead of the Path in the style definition: the designer of a custom template can choose any element that comes with Opacity.

The overlay

All event handlers are registered in the OnApplyTemplate:

protected override void OnApplyTemplate()
{
    // Border
    this.border = this.GetTemplateChild(BorderPartName) as Border;
    if (this.border != null)
    {
        this.border.ManipulationStarted += Border_ManipulationStarted;
        this.border.ManipulationCompleted += Border_ManipulationCompleted;
        this.border.Tapped += Border_Tapped;
        this.border.PointerEntered += Border_PointerEntered;
    }
    else
    {
        // Exception
        throw new Exception("Floating Control Style has no Border.");
    }

    // Overlay
    this.overlay = GetTemplateChild(OverlayPartName) as UIElement;
}

The dragging icon appears and disappears smoothly. We defined a StoryBoard with a DoubleAnimation on Opacity. Here’s the code for one of the event handlers:

private void Border_ManipulationStarted(object sender, 
	ManipulationStartedRoutedEventArgs e)
{
    if (this.overlay != null)
    {
        var ani = new DoubleAnimation()
        {
            From = 0.0,
            To = 1.0,
            Duration = new Duration(TimeSpan.FromSeconds(1.5))
        };
        var storyBoard = new Storyboard();
        storyBoard.Children.Add(ani);
        Storyboard.SetTarget(ani, overlay);
        ani.SetValue(Storyboard.TargetPropertyProperty, "Opacity");
        storyBoard.Begin();
    }
}

The other event handlers are similar. The flash effect is the same as the fade-in, but with AutoReverse to True.

It’s Universal

Here’s how the control looks like on Windows 10 Mobile. In the right screenshot, Sadness is showing the dragging overlay. Also observe that when the Boundary is set to Screen –look at Disgust– the control can be dragged under the phone’s StatusBar. Again, if you don’t want this to happen, configure the control like Sadness and use the page’s root as boundary:

FloatingContentControlPhone FloatingContentControlPhoneOverlay

Here’s how the sample app looks like on the Raspberry Pi:

FloatingContentControlRaspberryPi

[Yep: in my house, the smallest computer gets the biggest screen.]

The code

The control and its sample app live here on GitHub. The FloatingContent control has its own project.

Enjoy!

Binding a Slider to an Enumeration in UWP

Very often an Enumeration type represents a sequence or a ranking, like Offline-Connecting-Online or Bad-Good-Better-Best. In a user interface it would make sense to bind a property of such an Enum to a Slider control and let it more or less behave like a rating control.

In this article we’ll build a templatable custom control that

  • looks and behaves like a Slider,
  • can be two-way bound to most Enum types,
  • supports {x:Bind} and {Binding} declarative binding as well as programmatic binding,
  • provides a tooltip while sliding,
  • respects the Display data annotation attribute to support custom text, and
  • does not crash the XAML designer (!).

This is a screenshot from the sample app. It shows three instances of the EnumSlider control. Each of the sliders is bound in a different way to the same Importance property in the viewmodel:

EnumSlider

Style

It is possible to derive from the Slider base class, but we went for a templatable control. Here’s the default template, it contains just a Slider:

<Style TargetType="local:EnumSlider">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:EnumSlider">
                <Slider Name="PART_Slider"
                        SnapsTo="StepValues"
                        StepFrequency="1" />
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Class Definition

Properties and Initialization

The class definition holds a private field to store the Enum type, and a Value dependency property of type Object:

// The Enum Type to which we are bound.
private Type _enum;

// Value Dependency Property.
public static readonly DependencyProperty ValueProperty =
    DependencyProperty.Register(
        "Value",
        typeof(object),
        typeof(EnumSlider),
        new PropertyMetadata(null, OnValueChanged));

/// <summary>
/// Gets or sets the Value.
/// </summary>
public object Value
{
    get { return GetValue(ValueProperty); }
    set { SetValue(ValueProperty, value); }
}

When the template is applied to the control, we initialize the Slider:

/// <summary>
/// Called when the (default or custom) style template is applied.
/// </summary>
protected override void OnApplyTemplate()
{
    if (_enum == null)
    {
        // Keep the XAML Designer happy.
        return;
    }

    InitializeSlider();
}

During that initialization,

  • we set the slider’s range to the number of constants in the enumerator list,
  • we register an event handler to ValueChanged,
  • we update its Value by casting to integer, and
  • we assign a ThumbToolTipValueConverter so that the tooltip –while  sliding- shows text instead of the numerical value:
/// <summary>
/// Configures the internal Slider.
/// </summary>
private void InitializeSlider()
{
    var slider = this.GetTemplateChild(SliderPartName) as Slider;
    if (slider != null)
    {
        slider.ValueChanged += Slider_ValueChanged;
        slider.Maximum = Enum.GetNames(this._enum).Count() - 1;
        slider.ThumbToolTipValueConverter = new DoubleToEnumConverter(_enum);
        slider.Value = (int)this.Value;
    }
}

The range of the slider now goes from zero to the number of items in the enumeration (minus one), and we’re using the default Enum-to-int cast to map the values. This technique does NOT work when the enumeration does not use an integral type as its underlying type. It also does NOT work for enumerations that override their sequence, like this:

enum Importance
{
    None = -1,  // Does not work.
    Trivial,
    Moderate,
    Important = 10, // Does not work.
    Critical
};

In this version of the EnumSlider, we’ll NOT work around these issues. We assume that an enumeration without linear sequence numbers is probably not a good candidate to be bound to a linear slider. We like to keep this control lightweight, and hence only focus on the ‘regular’ enumerations.

If you *do* want a version of a slider control that deals with custom sequence numbers and underlying data types, please check this older version that we wrote a couple of years ago. Its mapping logic is built upon an internal dictionary with the enumeration’s text values.

Keeping the XAML Designer happy

The OnApplyTemplate is not only called at runtime. It may also be called at design time, by the XAML Designer. At that moment the Enum type is not yet known, so we should not try to configure the slider then. That’s why there is a null-check on _enum in the start of OnApplyTemplate. If we would trigger the slider initialization there, it would fail. The code would compile and run, but the IDE will not be able to preview any page that hosts the control. You’ll end up with an empty designer, and some squiggly lines in the XAML:

EnumSlider_DesignModeFail

Here’s the same IDE after we added the null-check:

EnumSlider_DesignMode

If you want developers to use your control in their apps, make sure it does not crash the XAML designer.

Runtime behavior

When the Value property of our control changes, all we need to do is updating the internal slider. When app starts, the very first update(s) may occur before the control is ready (i.e. before OnApplyTemplate is called). So the OnValueChanged event handler starts with a check to see whether or not the slider still needs to be initialized. This is also the place where we get our hands on the real Enum type to which the control is bound. At the end of the method, we update the slider’s value with that same cast from Enum to Integer:

/// <summary>
/// Called when the Value changed, e.g. through data binding.
/// </summary>
private static void OnValueChanged(DependencyObject d, 
	DependencyPropertyChangedEventArgs e)
{
    var _this = d as EnumSlider;

    if (_this != null)
    {
        // Initialize the Enum Type.
        if (e.OldValue == null)
        {
            if (e.NewValue is Enum)
            {
                _this._enum = e.NewValue.GetType();
                _this.InitializeSlider();
                return; // Slider got its value.
            }
        }

        var slider = _this.GetTemplateChild(SliderPartName) as Slider;

        if (slider != null)
        {
            slider.Value = (int)_this.Value;
        }
    }
}

Converters, converters, converters

Dealing with data annotations

In the tooltip that is shown when the user manipulates the slider, we’re going to show the text value that corresponds to the current slider value (a Double).  Unfortunately, enumerator constants cannot contain special characters or spaces, and they’re also not localizable. It’s however possible to decorate them with a Display data annotation attribute, like this:

enum Importance
{
    None,
    Trivial,
    Moderate,
    Important,
    [Display(Name = "O M G")]
    Critical
};

The lookup of the corresponding text value is done in the converter instance that we assigned in the slider’s initialization. The string representation of the enumerator –also its name in the type definition- is found with a call to Enum.ToObject(). Then we apply some reflection –with GetRuntimeFields and GetCustomAttribute– to get to the Display attribute’s value: 

/// <summary>
/// Converts the value of the internal slider into text.
/// </summary>
/// <remarks>Internal use only.</remarks>
internal class DoubleToEnumConverter : IValueConverter
{
    private Type _enum;

    public DoubleToEnumConverter(Type type)
    {
        _enum = type;
    }

    public object Convert(object value, 
			Type targetType, 
			object parameter, 
			string language)
    {
        var _name = Enum.ToObject(_enum, (int)(double)value);

        // Look for a 'Display' attribute.
        var _member = _enum
            .GetRuntimeFields()
            .FirstOrDefault(x => x.Name == _name.ToString());
        if (_member == null)
        {
            return _name;
        }

        var _attr = (DisplayAttribute)_member
			.GetCustomAttribute(typeof(DisplayAttribute));
        if (_attr == null)
        {
            return _name;
        }

        return _attr.Name;
    }

    public object ConvertBack(object value, 
		Type targetType, 
		object parameter, 
		string language)
    {
        return value; // Never called
    }
}

[Remember to run your app from time to time in release mode, especially when using reflection!]

Here’s the result. The tooltip of the middle slider shows ‘O M G’ instead of ‘Critical’:

EnumSliderDisplay

Alternatively you can use the Display value for looking up a localized value from a resource. For an example of this, check this article by Marco Minerva.

Dealing with {x:Bind}

Regular bindings are evaluated at runtime, but when you declaratively bind with the newer {x:Bind} construction, then some of the binding code is generated at compile-time. For a two-way binding, the compiler will verify the compatibility between the viewmodel’s property (in our case: the enumeration) and the control’s property (in our case: the Value property of type Object). The compiler will not be happy if we define the binding like this:

<controls:EnumSlider Value="{
	x:Bind ViewModel.Importance, 
	Mode=TwoWay />

Here’s the complaint:

xBindIssue

To reassure the compiler, we are forced to plug in a converter. Here’s the full code of that converter:

/// <summary>
/// Facilitates two-way binding to an Enum.
/// </summary>
public class EnumConverter : IValueConverter
{
    private Type _enum;

    public object Convert(
	object value, 
	Type targetType, 
	object parameter, 
	string language)
    {
        _enum = value.GetType();
        return value;
    }

    public object ConvertBack(
	object value, 
	Type targetType, 
	object parameter, 
	string language)
    {
        if (_enum == null)
        {
            return null;
        }

        return Enum.ToObject(_enum, (int)value);
    }
}

Here’s the working version of the {x:Bind} declaration in XAML:

<controls:EnumSlider Value="{
	x:Bind ViewModel.Importance, 
	Mode=TwoWay, 
	Converter={StaticResource EnumConverter}}" />

For a regular –old school- binding, the converter is not necessary:

<controls:EnumSlider 
	DataContext="{x:Bind ViewModel}"
	Value="{Binding Importance, Mode=TwoWay}" />

OK, we covered the two declarative binding techniques. Let’s now take a look at programmatic binding. Here’s the XAML:

<controls:EnumSlider x:Name="CodeBehindSlider" />

And here’s the C# bit:

this.CodeBehindSlider.BindTo(ViewModel, "Importance");

For convenience, the EnumSlider has a helper method that facilitates setting up a two-way binding to its Value:

/// <summary>
/// Sets up a two-way data binding.
/// </summary>
public bool BindTo(object viewModel, string path)
{
    try
    {
        Binding b = new Binding();
        b.Source = viewModel;
        b.Path = new PropertyPath(path);
        b.Mode = BindingMode.TwoWay;
        // b.Converter = new EnumConverter();
        this.SetBinding(EnumSlider.ValueProperty, b);
    }
    catch (Exception)
    {
        return false;
    }

    return true;
}

[It turns out that the converter is optional, so this helper method does not actually add much value…]

Wait, there’s more: … another converter

The logic in the internal converter that looks up the value for the Display attribute, is so useful that we decided to expose it. The control’s project has a public EnumDisplayConverter that converts an enumeration instance to its display value. The code is roughly the same as in the DoubleToEnumConverter, and it can be used to bind a text to the enum property. This is the XAML for the text block in the upper right corner of the page:

<TextBlock Text="{
	x:Bind ViewModel.Importance, 
	Mode=OneWay, 
	Converter={StaticResource EnumDisplayConverter}}" />

Alternatively, we could have exposed the logic as an extension method of Enum, but then it would be less discoverable.

It’s Universal

We haven’t tested the project on a Raspberry Pi yet, but here’s how it looks like on the phone:

EnumSlider_Phone1 EnumSlider_Phone2

Takeaways

Here are some takeaways for custom control builders:

  • Write defensive code in OnApplyTemplate and OnValueChanged event handlers. You never know in which order they are called.
  • Make sure to not crash the XAML Designer.
  • Test your controls against all types of data binding.
  • Test your controls in release mode from time to time.
  • If your control contains logic that could be useful to the client, expose that logic.

Source

The sample app and the control live here on GitHub. The EnumSlider control has its own project, for easy reuse.

Enjoy!

Building a custom UWP control with XAML and the Composition API

In this article we’ll build a custom UWP control that will be drawn partly by the XAML engine and partly by the Composition API. We’ll start from scratch … well almost. We’ll build yet another new version of the Modern Radial Gauge. Here’s how it will look like. The ‘old’ full-XAML gauge is on the left, the new version is on the right:

CompositionGauge_Comparison

The design of this radial gauge is timeless, thanks to Arturo Toledo. Unfortunately its implementations are not as timeless. The ‘old’ gauge goes back to early versions of Windows 8 and the continuous upgrades to more recent platforms have stretched its limits. Literally. Over the last couple of years, device screens have improved and the XAML engine was adapted to run on more and more platforms. We noticed that on some screens, the rotations and translations of the XAML elements are starting to suffer from rounding errors. As a result the radial gauge does not look crisp anymore in every size and resolution. The screenshot proves why it’s time for a new version, and why we could use some help from the Composition API:

CompositionGauge_Errors

Anatomy of a Radial Gauge

A gauge is an indicator that displays a value within a range – so it needs to have things like Minimum, Maximum, Value, and probably a Unit. A radial gauge is a gauge that looks like an analog clock – so it comes with things like a Scale, Ticks, and a Needle.

Here’s an overview of the important UI parts of the Modern Radial Gauge:

CompositionGauge_Anatomy

Here’s the list of properties that are defined in the actual control:

  • Minimum: minimum value on the scale (double, default 0)
  • Maximum: maximum value on the scale (double, default 100)
  • Value: the value to represent (double, default 0)
  • ValueStringFormat: StringFormat to apply to the displayed value (string)
  • Unit: unit measure to display (string)
  • TickSpacing: spacing -in value units- between ticks (int)
  • NeedleBrush: color of the needle (SolidColorBrush)
  • TickBrush: color of the outer ticks (SolidColorBrush)
  • ScaleWidth: thickness of the scale in pixels – relative to the control’s default size (double, default 26)
  • ScaleBrush: background color of the scale (Brush)
  • ScaleTickBrush: color of the ticks on the scale (SolidColorBrush)
  • TrailBrush: color of the trail following the needle (Brush)
  • ValueBrush: color of the value text (Brush)
  • UnitBrush: color of the unit measure text (Brush)

Using the gauge on a page should be as easy as the following one-liner:

<controls:RadialGauge Value="{Binding Temperature}" Unit="°C" />

But the user of the control should be able to fully customize the looks – like this:

<controls:RadialGauge Value="85"
                        Unit="bottles of beer on the wall"
                        TickBrush="Transparent"
                        ScaleTickBrush="Transparent"
                        NeedleBrush="{StaticResource DarkBrown}"
                        TrailBrush="{StaticResource DarkBrown}"
                        UnitBrush="{StaticResource VeryDarkBrown}"
                        ValueBrush="{StaticResource DarkBrown}"
                        Margin="4">
    <controls:RadialGauge.ScaleBrush>
        <SolidColorBrush Color="#FFFFD9AA"
                            Opacity=".6" />
    </controls:RadialGauge.ScaleBrush>
</controls:RadialGauge>

On top of that, the control is templatable: a developer can hook the gauge to a custom template (provided or not by a designer) as long as some requirements are met.

Project Structure

In Visual Studio 2015, we created a class library and added a new item of the type ‘templated control’. The RadialGauge class inherits from Control, and comes with a Style definition the Themes/Generic.xaml file. Here’s how that looks like in the IDE:

CompositionGauge_VS

Control template

The generic.xaml file is a XAML ResourceDictionary that contains the default style with the ControlTemplate for the custom control – it can be overridden. We defined the radial gauge control as a 200 by 200 pixels grid, inside a ViewBox. All internal calculations are conveniently done against that 200×200 grid, but the ViewBox will stretch the gauge to any size:

<Style TargetType="local:RadialGauge">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:RadialGauge">
                <Viewbox>
                    <Grid x:Name="PART_Container"
                            Height="200"
                            Width="200">

Here’s how to decide what elements to put in the style. For the elements that are drawn by the Composition API, it’s simple: there’s no markup available for them (yet?), so they will always be drawn in code-behind. Any rectangle that is filled with a solid color or an image is a candidate for rendering through the Composition API and hence will not appear in the control template. For the radial gauge these are the ticks inside and outside the scale, and the needle.

If you’re not into DirectX then the remaining parts will be drawn in XAML. For these elements you still have the choice. You can declare them in the style, or program them in code-behind. For the radial gauge, the XAML parts are [the border of] two ArcSegment instances and the text blocks that display the value and the unit.

Here’s a visual overview of the radial gauge parts by technology:

CompositionGauge_Parts 

We can not define the arc segment fully in XAML: the width of the background scale is configurable, and the end point of the trail depends on the current value. So both arc segments need to be drawn programmatically. The corresponding Path elements are declared in the XAML control template, but only with their color properties set – no path or dimensions. The Stroke and StrokeThickness are bound through a TemplateBinding to properties that are defined in the class itself.

Here’s the XAML load of the control template:

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

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

<!-- Value and Unit -->
<StackPanel VerticalAlignment="Bottom"
            HorizontalAlignment="Center">
    <TextBlock Name="PART_ValueText"
                Foreground="{TemplateBinding ValueBrush}"
                FontSize="20"
                FontWeight="SemiBold"
                Text="{TemplateBinding Value}"
                TextAlignment="Center"
                Margin="0 0 0 2" />
    <TextBlock Foreground="{TemplateBinding UnitBrush}"
                FontSize="16"
                TextAlignment="Center"
                Text="{TemplateBinding Unit}"
                Margin="0" />
</StackPanel>

Observe that all named elements follow the PART_Xxx naming convention which is typical for templated controls.

Class Definition

The C# class definition is decorated with TemplatePart attributes for those same named elements. Again, this is just a convention. It indicates to the template (re-)designer that the code will break when these parts are not provided by the template:

[TemplatePart(Name = ScalePartName, Type = typeof(Path))]
[TemplatePart(Name = TrailPartName, Type = typeof(Path))]
[TemplatePart(Name = ValueTextPartName, Type = typeof(TextBlock))]
public class RadialGauge : Control
{

Dependency Properties

The class definition also contains the properties that are exposed by the gauge: the Minimum, Maximum and current Value, and a set of colors. These are implemented as Dependency Properties, which makes them automagically available for data binding, animation, and change notification. A dependency property registration declares a name, a type, and optionally a default value and a change event handler.

Here’s part of the Value property definition:

public static readonly DependencyProperty ValueProperty = 
DependencyProperty.Register(
    "Value",
    typeof(double),
    typeof(RadialGauge),
    new PropertyMetadata(0.0, OnValueChanged));

public double Value
{
    get { return (double)GetValue(ValueProperty); }
    set { SetValue(ValueProperty, value); }
}

private static void OnValueChanged(DependencyObject d, 
DependencyPropertyChangedEventArgs e)
{
    // Redraw trail, rotate needle, and update value text.
    // ...
}

The rest of the properties have similar declarations. They’re all generated by the same built-in code snippet: ‘propdp’.

Initial look

At runtime the OnApplyTemplate override is the first place where we have programmatic access to the fully templated control. All XAML elements are available there. If necessary, we can now refine the initial look. We will draw the scale and the ticks, and also the needle in its initial position. For each part, we call GetTemplateChild() to get a reference to the element in the template. It’s good practice to check if the part actually exists. After all, as a custom control developer we only provide a default template. We’re never sure of the actual template that will be applied.

Here’s the code for drawing the scale – we’ll spare you the details of the ArcSegment drawing routine:

var scale = this.GetTemplateChild(ScalePartName) as Path;
if (scale != null)
{
    var pg = new PathGeometry();
    var pf = new PathFigure();
    // ArcSegment details omitted.
    // ...
    pf.Segments.Add(seg);
    pg.Figures.Add(pf);
    scale.Data = pg;
}

So far for the XAML part, let’s move to the Composition API. In that same OnApplyTemplate() method we draw the ticks outside and inside the scale, and the needle. For an introduction to drawing with the Composition API, please check this article. The code presented there draws an analog clock with ticks and hands, so it is extremely similar to the radial gauge. With the Composition API you’ll always need

Here’s the code that draws the needle. The ticks and scale ticks are drawn the same way – they’re all just colored rectangles:

var container = this.GetTemplateChild(ContainerPartName) as Grid;
_root = container.GetVisual();
_compositor = _root.Compositor;

_needle = _compositor.CreateSpriteVisual();
_needle.Size = new Vector2(NeedleWidth, NeedleHeight);
_needle.Brush = _compositor.CreateColorBrush(NeedleBrush.Color);
_needle.CenterPoint = new Vector3(NeedleWidth / 2, NeedleHeight, 0);
_needle.Offset = new Vector3(100 - NeedleWidth / 2, 100 - NeedleHeight, 0);
_root.Children.InsertAtTop(_needle);

At the end of OnApplyTemplate() the gauge is properly initialized and ready to shine.

Changing the value

When the Value property changes, it fires the event handler that was registered in the dependency property definition. That handler is a static method –as imposed by the dependency property infrastructure- so you can’t use ‘this’  in it. The reference to the gauge comes in as a parameter of the type DependencyObject, so you have to cast it. I don’t remember why I called the corresponding variable ‘c’  in the following code, but it’s actually a common practice to call it ‘_this’, or ‘that’.

Here’s the code to update needle’s rotation, draw the trail, and update the text to its new value. The pattern is always the same: get a reference to the UI element from the control template, check if it’s really there, and then update it:

private static void OnValueChanged(DependencyObject d)
{
    RadialGauge c = (RadialGauge)d;
    if (!Double.IsNaN(c.Value))
    {
        var middleOfScale = 100 - ScalePadding - c.ScaleWidth / 2;
        c.ValueAngle = c.ValueToAngle(c.Value);

        // Needle
        if (c._needle != null)
        {
            c._needle.RotationAngleInDegrees = (float)c.ValueAngle;
        }

        // Trail
        var trail = c.GetTemplateChild(TrailPartName) as Path;
        if (trail != null)
        {
            if (c.ValueAngle > MinAngle)
            {
                trail.Visibility = Visibility.Visible;
                var pg = new PathGeometry();
                var pf = new PathFigure();
                // ArcSegment drawing omitted.
                // ...
                pf.Segments.Add(seg);
                pg.Figures.Add(pf);
                trail.Data = pg;
            }
            else
            {
                trail.Visibility = Visibility.Collapsed;
            }
        }

        // Value Text
        var valueText = c.GetTemplateChild(ValueTextPartName) as TextBlock;
        if (valueText != null)
        {
            valueText.Text = c.Value.ToString(c.ValueStringFormat);
        }
    }
}

In a production version of this gauge, it would make sense to also listen to changes in other properties. If you want to use this gauge to follow up the daily evolution of a stock quote, then the Minimum and Maximum values will change at runtime. This version assumes that all properties except Value are assigned in the XAML, and do not change after that.

Cleaning up the code

In the code snippets, we omitted the beef of the calculations. I admit that in earlier versions these calculations were hard to understand, and hence hard to maintain. In this version of the gauge, we took the time to finally factor out all relevant parameters, and defined them as constants with a decent name: 

private const double MinAngle = -150.0;
private const double MaxAngle = 150.0;
private const float TickHeight = 18.0f;
private const float TickWidth = 5.0f;
private const float ScalePadding = 23.0f;
private const float ScaleTickWidth = 2.5f;
private const float NeedleWidth = 5.0f;
private const float NeedleHeight = 100.0f;

Not only does this make the calculations easier to understand and more maintainable, it also reveals good candidate dependency properties for future versions of the control. I definitely want to experiment with a scale from –180 to +90 degrees, like the ones in this car:

A5_gauges

[For the record: this is *not* my car – my speedometer stops at 280 and I have more fuel]

Testing the looks

The SquareOfSquares control is an ideal container to check the looks of the radial gauge with different settings for size, colors, and scale width. Here’s how the gauge looks like in a variety of partially random configurations:

CompositionGauge_Square

Of course it also makes sense to create a gallery with some representative looks, like this:

CompositionGauge_Gallery

You may consider the SquareOfSquares and the Gallery as a kind of ‘visual unit tests’.

Keep it Universal

Since the control is meant to be Universal, we must test it on all relevant device families. Here’s how the pages of the sample app look like on the phone:

CompositionGauge_Comparison_Phone CompositionGauge_Parts_Phone CompositionGauge_Square_Phone CompositionGauge_Gallery_Phone

Source Code

The upgraded version of the Modern Radial Gauge looks crisp again in any size and resolution. Its Visual Tree is a lot smaller, and the needle rotation is not done by the XAML engine anymore. That should result in better performance. That may be important e.g. in a dashboard with a huge amount of gauge instances. Last but not least, we identified some useful enhancements for future versions.

All source code is available. The sample app and all related controls live here on GitHub.

Enjoy!