In a previous blog post I described the process of creating a lookless gauge control. I introduced the concept of an attached view model which separates view specific concepts from the control. In this post I demonstrate how this allows for great flexibility when re-templating the control.
In my previous post I described the development of a radial gauge control where I removed any view-specific logic and properties from the control by introducing an attached view model into the control’s template. My reasons for doing this were to create a completely lookless control in the order that this would permit greater flexibility when re-templating or themeing. The control I created was a gauge control, which in my previous blog post I rendered as a radial gauge. The ‘radial concepts’ of angle, and other derived properties are contained within the attached view model, with the Gauge control itself only containing concepts that are common across all gauges (radial or otherwise). The control is shown below:
In this post I will create two different templates for this control. The first is a small variation on the radial gauge which is rendered as a semi-circle. The second is very different, a bullet graph, where the gauge is rendered as a linear indicator. With each, an attached view model is introduced into the controls template in order to compute properties that aid in the rendering of the control.

A Semi-circular Gauge
The original attached view model I created was hard-coded to create a radial control where the needle has a sweep angle of 300 degrees. To support a semi-circular gauge we need to be able to configure this parameter. I achieved this by simply making SweepAngle a public property of the attached view model. This property is used wherever the view model needs to convert a gauge value to a radial location (i.e. when computing the tick mark rotation angles):
public class RadialGaugeControlViewModel : AttachedViewModelBase { public RadialGaugeControlViewModel() { SweepAngle = 300; } /// <summary> /// Gets / sets the sweep angle of the radial gauge /// </summary> public double SweepAngle { get; set; } private double ValueToAngle(double value) { double minAngle = -SweepAngle / 2; double maxAngle = SweepAngle / 2; double angularRange = maxAngle - minAngle; return (value - Gauge.Minimum) / (Gauge.Maximum - Gauge.Minimum) * angularRange + minAngle; } ... } |
This allows it to be set in the control template of our re-templated gauge control where the view model is instantiated and attached:
<Style TargetType="local:GaugeControl" x:Key="themedGauge"> <Setter Property="FontSize" Value="8"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="local:GaugeControl"> <Grid x:Name="LayoutRoot" > <Grid> <!-- attach the view model --> <local:RadialGaugeControlViewModel.Attach> <local:RadialGaugeControlViewModel SweepAngle="180"/> </local:RadialGaugeControlViewModel.Attach> <!-- gauge template goes here! --> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style> |
This means that the various properties of the attached view model that the view binds to in order to render the various control parts, such as the needle, are now computed with a 180 degree sweep.
For the semi-circular gauge, I used a similar technique to the original radial gauge for creating the required layout, where a grid with various rows / columns with star widths / heights create a proportional layout within which elements such as the needle are placed:
<Grid> <!-- attach the view model --> <local:RadialGaugeControlViewModel.Attach> <local:RadialGaugeControlViewModel SweepAngle="180" Clip="false"/> </local:RadialGaugeControlViewModel.Attach> <!-- the gauge outline --> <Path Stroke="Black" StrokeThickness="3" StrokeLineJoin="Round" Stretch="Uniform"> <Path.Data> <PathGeometry> <PathGeometry.Figures> <PathFigure IsClosed="True" StartPoint="0.5,0"> <PathFigure.Segments> <ArcSegment Size="0.5,0.5" RotationAngle="45" IsLargeArc="False" SweepDirection="Clockwise" Point="1,0.5"/> <LineSegment Point="1,0.55"/> <LineSegment Point="0,0.55"/> <LineSegment Point="0,0.5"/> <ArcSegment Size="0.5,0.5" RotationAngle="45" IsLargeArc="False" SweepDirection="Clockwise" Point="0.5,0"/> </PathFigure.Segments> </PathFigure > </PathGeometry.Figures> </PathGeometry> </Path.Data> </Path> <Grid ShowGridLines="true"> <Grid.RowDefinitions> <RowDefinition Height="3.5*"/> <RowDefinition Height="4*"/> <RowDefinition Height=".6*"/> <RowDefinition Height=".6*"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="8.0*"/> <ColumnDefinition Width=".7*"/> <ColumnDefinition Width=".7*"/> <ColumnDefinition Width="8.0*"/> </Grid.ColumnDefinitions> <!-- needle --> <Path Stretch="Uniform" Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" Grid.RowSpan="2" Fill="Black" HorizontalAlignment="Center" Stroke="Black" StrokeThickness="0.5" Data="M 0,0 l 5,60 l -10, 0" RenderTransformOrigin="0.5,1"> <Path.RenderTransform> <RotateTransform Angle="{Binding Path=ValueAngle}"/> </Path.RenderTransform> </Path> <!-- needle cover --> <Ellipse Fill="White" Stroke="Black" StrokeThickness="2" Width="13" Height="13" HorizontalAlignment="Center" VerticalAlignment="Bottom" Grid.Row="2" Grid.Column="1" Grid.RowSpan="2" Grid.ColumnSpan="2"/> </Grid> |
You can see the various gridlines below:

The code for creating the major / minor ticks and qualitative ranges are much the same as before. However, for a bit of variation I did not want the labels on this gauge to be rotated. In order to achieve this I used the same technique as before where each label is initially constructed at the same location, then a translate / rotate transform is used to move it to the correct location on the dial. In this control, the text label is rotated a second time in the opposite direction in order to bring it back to its original orientation:
<ItemsControl.ItemTemplate> <DataTemplate> <Grid Width="50" Height="20"> <Grid.RenderTransform> <TransformGroup> <TranslateTransform X="-25" Y="-10"/> <TranslateTransform Y="{Binding Path=Parent.GridHeight, Converter={StaticResource ScaleFactorConverter}, ConverterParameter=-0.69}"/> <RotateTransform Angle="{Binding Path=Angle}"/> </TransformGroup> </Grid.RenderTransform> <TextBlock Text="{Binding Path=Value}" VerticalAlignment="Center" HorizontalAlignment="Center" RenderTransformOrigin="0.5, 0.5"> <!-- rotate the labels by '-Angle' to return to their original orientation --> <TextBlock.RenderTransform> <RotateTransform Angle="{Binding Path=Angle, Converter={StaticResource ScaleFactorConverter}, ConverterParameter=-1}" /> </TextBlock.RenderTransform> </TextBlock> </Grid> </DataTemplate> </ItemsControl.ItemTemplate> |
The finished semi-circular gauge can be seen here next to my original radial gauge control:
So, the original control has proven to be pretty versatile in that it can be re-templated to look quite different. To allow this, the attached view model was modified to expose a few view-centric properties that could be set when it is created and attached within the template.
However, the example above is still a radial gauge. A much bigger challenge would be to re-template the control to look completely different. That was my next challenge!
A Bullet Graph Template
Radial gauge controls look very pretty; however they eat up a lot of screen real-estate, and are not the best way of visualising a one-dimensional metric. A much clearer visualisation can be achieved by using a linear gauge, a bullet-graph for example. This section describes how a new attached view model can be applied to the gauge control, to support a template which produces a bullet graph.
Using the same process as in the above example, the basic structure of the control is defined using a Grid:

<Style TargetType="local:GaugeControl" x:Key="bulletGraphGauge"> <Setter Property="FontSize" Value="8"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="local:GaugeControl"> <Grid x:Name="LayoutRoot" Background="LightBlue"> <Grid ShowGridLines="True"> <!-- attach the view model --> <local:RadialGaugeControlViewModel.Attach> <local:BulletGraphGaugeViewModel/> </local:RadialGaugeControlViewModel.Attach> <!--the grid layout --> <Grid.RowDefinitions> <RowDefinition Height="3*"/> <RowDefinition Height="*"/> <RowDefinition Height="2*"/> <RowDefinition Height="*"/> <RowDefinition Height="3*"/> </Grid.RowDefinitions> <!-- featured measure indicator --> <Rectangle Fill="Black" Grid.Row="2" HorizontalAlignment="Left" Width="{Binding Path=FeaturedMeasureLength}"/> </Grid> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style> |
The ‘featured measure’, which is a thermometer-like bar which indicates the current value of the gauge has its Width property bound to the view model which is attached at the top of the template. To simplify the code, I refactored the attached view model concept to extract a base class which is shared by both the radial and bullet-graph subclasses. This base class takes care of attaching to its parents DataContext, adapting the control’s properties, exposing the actual width / height of the control and the other bits and pieces I described in my previous blog post. The bullet graph view model is then much simpler as a result. In the code below you can see how the FeaturedMeasureLength is computed in the view model:
public class BulletGraphGaugeViewModel : AttachedViewModelBase { public BulletGraphGaugeViewModel() { } private GaugeControl Gauge { // the view model’s DataContext is bound to the Gauge get { return DataContext != null ? (GaugeControl)DataContext : null; } } public double FeaturedMeasureLength { get { if (Gauge == null) return 0; return ValueToWidth(Gauge.Value - Gauge.Minimum); } } /// <summary> /// Converts the given value (which should be between Gauge.Maximum / Gauge.Minimum) /// into suitable width for rendering within the view. /// </summary> private double ValueToWidth(double value) { double range = Gauge.Maximum - Gauge.Minimum; return (value) / range * ElementWidth; } } |
Next we’ll add the ‘qualitative range’ scale:

<!-- Qualitative ranges --> <ItemsControl ItemsSource="{Binding Path=Ranges}" Grid.Row="1" Grid.RowSpan="3"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <StackPanel Orientation="Horizontal"/> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemTemplate> <DataTemplate> <Rectangle Width="{Binding Path=Width}" Stroke="Black" StrokeThickness="0.2" Fill="{Binding Path=Color, Converter={StaticResource ColorToBrushConverter}}"/> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> <Border BorderBrush="Black" BorderThickness="1.0" Grid.Row="1" Grid.RowSpan="3" Margin="-1"/> |
Once again, the attached view model is used to adapt the properties exposed by the control in order to provide properties which are easier to bind to in order to create the required UI. Here the Gauge controls QualitativeRange is adapted to expose a Range property, which the ItemsControl in the XAML above binds to, this property is an enumeration of RangeItems which each have the correct width and colour for our UI:
public IEnumerable<RangeItem> Ranges { get { if (Gauge == null) yield break; for (int i = 0; i < Gauge.QualitativeRange.Count; i++) { var range = Gauge.QualitativeRange[i]; if (i == 0) { // first RangeItem, width is determined from the first range value yield return new RangeItem() { Color = range.Color, Width = ValueToWidth(range.Maximum - Gauge.Minimum) }; } else { // subsequent items, width computed as the difference between the // current value and its predecessor var previousRange = Gauge.QualitativeRange[i - 1]; yield return new RangeItem() { Color = range.Color, Width = ValueToWidth(range.Maximum - previousRange.Maximum) }; } } } } ... public class RangeItem { public double Width { get; set; } public Color Color { get; set; } } |
Finally, the scales are added:

<!-- upper scale --> <ItemsControl ItemsSource="{Binding Path=MajorTicks}" VerticalAlignment="Center" Grid.Row="0" ItemsPanel="{StaticResource CanvasTemplate}"> <ItemsControl.ItemTemplate> <DataTemplate> <Grid Width="50" Height="20"> <Grid.RenderTransform> <TransformGroup> <!-- centre the labels --> <TranslateTransform X="-25" Y="-10"/> <TranslateTransform X="{Binding Path=Position}"/> </TransformGroup> </Grid.RenderTransform> <TextBlock Text="{Binding Path=Label}" VerticalAlignment="Center" HorizontalAlignment="Center"/> </Grid> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> <!-- major ticks --> <ItemsControl ItemsSource="{Binding Path=MajorTicks}" VerticalAlignment="Bottom" Grid.Row="0" ItemsPanel="{StaticResource CanvasTemplate}"> <ItemsControl.ItemTemplate> <DataTemplate> <Line X1="{Binding Path=Position}" Y1="0" X2="{Binding Path=Position}" Y2="-5" Stroke="Black"/> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> <!-- minor ticks --> <ItemsControl ItemsSource="{Binding Path=MinorTicks}" VerticalAlignment="Bottom" Grid.Row="0" ItemsPanel="{StaticResource CanvasTemplate}"> <ItemsControl.ItemTemplate> <DataTemplate> <Line X1="{Binding}" Y1="0" X2="{Binding}" Y2="-3" Stroke="Black"/> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> |
Again, exactly the same pattern is employed, with the MajorTicks and MinorTicks properties exposed by the attached view model, each providing an enumeration of simple value objects with a Position and Label properties, which are bound to the three ItemsControls above to create the required UI.
The complete bullet graph template is shown below next to the original radial gauge:
In conclusion, the use of an attached view model provides a mechanism for exposing view-specific properties to the view logic within the XAML template. This enables the construction of a control which really is entirely lookless.
You can download the sourcecode for this project: GaugeControl.zip
Regards, Colin E.


email: ceberhardt@scottlogic.co.uk
on Google+



Got this working in a phone app in no time. Thanks!
I forgot another problem I still encounter, the ticks (minor, major and labels) aren’t rendered, even in your blog page
can’t find why, any clue ?
I found the reason why labels and ticks doesn’t appear
The ScaleFactorConverter converter always return DependencyProperty.UnsetValue (cf my previous comment)
the problem exist on a computer with a CultureInfo.CurrentCulture wich doesn’t take a dot for decimal (fr-FR in my case)
the solution I found is to do the TryParse this way :
if(value is double == true && double.TryParse(parameter.ToString(), NumberStyles.Float, new CultureInfo(“en-US”), out factor) == true)
return (double)value * factor;
You also can use strongly typed values in xaml :
- add a namespace to mscorlib :
xmlns:sys=”clr-namespace:System;assembly=mscorlib”
- declare a resource for each value you need :
-0.37
- use the resource instead of “string” parameter :
ConverterParameter={StaticResource Value-0.37}
- and in the converter, a simple cast :
if(value is double && parameter is double)
return (double)value * (double)parameter;
Wow – many thanks for looking into this issue and solving it – you are a star!
Thanks to you for sharing great coding
Glad to help anytime I can
Thanks for the updated code below – sorry, I have no clue why the ticks are not rendering for you.
I built your solution under VS2010 with Silverlight 4 SDK installed
The control crashed so I looked after the reason and found it into your converters
I suggest you to implement them in way they don’t throw exception
and return DependencyProperty.UnsetValue instead of null when value or parameter are invalid
ie:
public class ScaleFactorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
double factor;
if(value is double == true && double.TryParse(parameter.ToString(), out factor) == true)
return (double)value * factor;
return DependencyProperty.UnsetValue;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Hi Colin,
How do I set the animation (storyboard) for the needle, please suggest
Thanks for really great sample. I am new to silverlight, I have another usercontrol where I am placing these guage and bullet usercontrols and I also have a textbox and button, when I click the button I would like to set the needle based on the textbox value for the radial guage and bullet graph. How do I do that…please advice !
Hi Lucky,
You have two options …
(1) Use the MVVM (Model View ViewModel) pattern, binding the textbox and gauge to two separate properties of your view model. The button is then bound to a command that copies the value from one view model property to the other. The binding will then ensure that the view is synchronised
(2) The simple way, just add an event handler to your button click event, grab the TextBox.Text property, parse it and set the gauge value.
It is up to you which technique you use!
Regards,
Colin E.
[...] Developing a Lookless Silverlight Gauge Control (part 2) [...]
If anyone is interested, I wrote step by step instructions on how to create a bullet graph in my new book:
http://www.amazon.com/Silverlight-4-Business-Intelligence-Software/dp/1430230606/ref=sr_1_2?ie=UTF8&s=books&qid=1288027777&sr=8-2
It is over 50 pages. My approach is different than the one taken here and actually implements Stephen Few’s “official bullet graph specification” (he is the guy who invented the thing).
http://www.perceptualedge.com/articles/misc/Bullet_Graph_Design_Spec.pdf
The problem with the approach above is that, by not using dependency properties you won’t be able to animate, element bind etc. the control properly using Blend 4 tooling.
Hi Bart,
An interesting book. Your observation about not being able to animate the gauge properties is not entirely correct. If you animate the DPs exposed by the Gauge, it will result in the animation of the ‘derived’ properties in the attached view model.
Animated bullet graphs? what would Stephen Few say to that!
Regards, Colin E.
Hi Colin,
really great sample!
Sorry this stupid question – but I’m right in the beginning of learning Silverlight:
I’m wondering why you don’t use Dependency-Properties in these controls (for example for FeaturedMeasureLength)? When have I to use Dependency-Properties? Or better: Why don’t you use Dependency-Properties in your example?
It’s a little bit confusing for me to understand when I have to use Dependency-Properties and when I don’t have to use them…
Cheers,
Stone
Hi Stone,
Good question, and one that people struggle with. To my mind the answer is actually very simple! Just look at the features that a Dependency Property provides:
+ Value inheritence (default values, etc…)
+ Attachment (i.e. attached properties)
+ Databinding (as a target, CLR properties can be a source)
+ Styles (a dependency property can be set via a Style)
If you don’t need any of these features, use a CLR property. If you do, use a Dependency Property!
Regards, Colin E.
Hi Colin,
thank you so much for your answer.
Ok – now I understand.
Thank you!
Excellent. I am sure I can use this in my next LightSwitch application
Hi Michael, thanks – give me a shout when you do use it.
I’m trying to set up a gauge for a small pet project I’ve been working on. Looking forward to learning from the outcomes of both this and the previous post!
Thanks Drew
Developing a Lookless Silverlight Gauge Control (part 2) | Colin Eberhardt’s Adventures in WPF…
Thank you for submitting this cool story – Trackback from DotNetShoutout…