Monthly Archives: December 2016

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:


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">
            <ControlTemplate TargetType="local:PercentageRing">
                <Border Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}">
                        <Grid x:Name="PART_Container"

                            <!-- 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"
                                <TextBlock Name="PART_ValueText"
                                            Foreground="{TemplateBinding ValueBrush}"
                                            Text="{TemplateBinding Value}"
                                            TextAlignment="Center" />

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): 


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

<!-- Trail -->
<Path Name="PART_Trail"
        Stroke="{TemplateBinding TrailBrush}"
        StrokeThickness="{TemplateBinding ScaleWidth}"
        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:


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).