Click here to Skip to main content
Click here to Skip to main content
Technical Blog

Tagged as

Developing a Lookless Silverlight Gauge Control (part 2)

, 21 Oct 2010 CPOL
Rate this:
Please Sign up or sign in to vote.
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 prev

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:

<object width="300" height="240" data="data:application/x-silverlight," type="application/x-silverlight-2" > Get Microsoft Silverlight</object>

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

<span style="color: #0600FF;">public</span> <span style="color: #FF0000;">class</span> RadialGaugeControlViewModel <span style="color: #008000;">:</span> AttachedViewModelBase
<span style="color: #000000;">{</span>
  <span style="color: #0600FF;">public</span> RadialGaugeControlViewModel<span style="color: #000000;">(</span><span style="color: #000000;">)</span>
  <span style="color: #000000;">{</span>
    SweepAngle <span style="color: #008000;">=</span> <span style="color: #FF0000;">300</span><span style="color: #008000;">;</span>
  <span style="color: #000000;">}</span>
 
  <span style="color: #008080; font-style: italic;">/// <summary></span>
  <span style="color: #008080; font-style: italic;">/// Gets / sets the sweep angle of the radial gauge</span>
  <span style="color: #008080; font-style: italic;">/// </summary></span>
  <span style="color: #0600FF;">public</span> <span style="color: #FF0000;">double</span> SweepAngle
  <span style="color: #000000;">{</span> get<span style="color: #008000;">;</span> set<span style="color: #008000;">;</span> <span style="color: #000000;">}</span>
 
  <span style="color: #0600FF;">private</span> <span style="color: #FF0000;">double</span> ValueToAngle<span style="color: #000000;">(</span><span style="color: #FF0000;">double</span> value<span style="color: #000000;">)</span>
  <span style="color: #000000;">{</span>
    <span style="color: #FF0000;">double</span> minAngle <span style="color: #008000;">=</span> <span style="color: #008000;">-</span>SweepAngle <span style="color: #008000;">/</span> <span style="color: #FF0000;">2</span><span style="color: #008000;">;</span>
    <span style="color: #FF0000;">double</span> maxAngle <span style="color: #008000;">=</span> SweepAngle <span style="color: #008000;">/</span> <span style="color: #FF0000;">2</span><span style="color: #008000;">;</span>
    <span style="color: #FF0000;">double</span> angularRange <span style="color: #008000;">=</span> maxAngle <span style="color: #008000;">-</span> minAngle<span style="color: #008000;">;</span>
 
    <span style="color: #0600FF;">return</span> <span style="color: #000000;">(</span>value <span style="color: #008000;">-</span> Gauge.<span style="color: #0000FF;">Minimum</span><span style="color: #000000;">)</span> <span style="color: #008000;">/</span> <span style="color: #000000;">(</span>Gauge.<span style="color: #0000FF;">Maximum</span> <span style="color: #008000;">-</span> Gauge.<span style="color: #0000FF;">Minimum</span><span style="color: #000000;">)</span> <span style="color: #008000;">*</span>
        angularRange <span style="color: #008000;">+</span> minAngle<span style="color: #008000;">;</span>
  <span style="color: #000000;">}</span>
 
  ...
<span style="color: #000000;">}</span>

This allows it to be set in the control template of our re-templated gauge control where the view model is instantiated and attached:

<span style="color: #009900;"><span style="color: #000000; font-weight: bold;"><Style</span> <span style="color: #000066;">TargetType</span>=<span style="color: #ff0000;">"local:GaugeControl"</span> <span style="color: #000066;">x:Key</span>=<span style="color: #ff0000;">"themedGauge"</span><span style="color: #000000; font-weight: bold;">>

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:

<span style="color: #009900;"><span style="color: #000000; font-weight: bold;"><Grid<span style="color: #000000; font-weight: bold;">></span></span></span>
<span style="color: #808080; font-style: italic;"><!-- attach the view model --></span>
<span style="color: #009900;"><span style="color: #000000; font-weight: bold;"><local:RadialGaugeControlViewModel.Attach<span style="color: #000000; font-weight: bold;">>

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:

<span style="color: #009900;"><span style="color: #000000; font-weight: bold;"><ItemsControl.ItemTemplate<span style="color: #000000; font-weight: bold;">></span></span></span>
  <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"><DataTemplate<span style="color: #000000; font-weight: bold;">></span></span></span>
    <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"><Grid</span> <span style="color: #000066;">Width</span>=<span style="color: #ff0000;">"50"</span> <span style="color: #000066;">Height</span>=<span style="color: #ff0000;">"20"</span><span style="color: #000000; font-weight: bold;">></span></span>
      <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"><Grid.RenderTransform<span style="color: #000000; font-weight: bold;">></span></span></span>
        <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"><TransformGroup<span style="color: #000000; font-weight: bold;">></span></span></span>
          <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"><TranslateTransform</span> <span style="color: #000066;">X</span>=<span style="color: #ff0000;">"-25"</span> <span style="color: #000066;">Y</span>=<span style="color: #ff0000;">"-10"</span><span style="color: #000000; font-weight: bold;">/></span></span>
          <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"><TranslateTransform</span> <span style="color: #000066;">Y</span>=<span style="color: #ff0000;">"{Binding Path=Parent.GridHeight,</span>
<span style="color: #009900;">              Converter={StaticResource ScaleFactorConverter}, ConverterParameter=-0.69}"</span><span style="color: #000000; font-weight: bold;">/></span></span>
          <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"><RotateTransform</span> <span style="color: #000066;">Angle</span>=<span style="color: #ff0000;">"{Binding Path=Angle}"</span><span style="color: #000000; font-weight: bold;">/></span></span>
        <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"></TransformGroup<span style="color: #000000; font-weight: bold;">></span></span></span>
      <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"></Grid.RenderTransform<span style="color: #000000; font-weight: bold;">></span></span></span>
      <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"><TextBlock</span> <span style="color: #000066;">Text</span>=<span style="color: #ff0000;">"{Binding Path=Value}"</span> </span>
<span style="color: #009900;">            <span style="color: #000066;">VerticalAlignment</span>=<span style="color: #ff0000;">"Center"</span> <span style="color: #000066;">HorizontalAlignment</span>=<span style="color: #ff0000;">"Center"</span></span>
<span style="color: #009900;">            <span style="color: #000066;">RenderTransformOrigin</span>=<span style="color: #ff0000;">"0.5, 0.5"</span><span style="color: #000000; font-weight: bold;">></span></span>
        <span style="color: #808080; font-style: italic;"><!-- rotate the labels by '-Angle' to return to their original orientation --></span>
        <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"><TextBlock.RenderTransform<span style="color: #000000; font-weight: bold;">></span></span></span>
          <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"><RotateTransform</span> <span style="color: #000066;">Angle</span>=<span style="color: #ff0000;">"{Binding Path=Angle,</span>
<span style="color: #009900;">                Converter={StaticResource ScaleFactorConverter}, ConverterParameter=-1}"</span> <span style="color: #000000; font-weight: bold;">/></span></span>
        <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"></TextBlock.RenderTransform<span style="color: #000000; font-weight: bold;">></span></span></span>
      <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"></TextBlock<span style="color: #000000; font-weight: bold;">></span></span></span>
    <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"></Grid<span style="color: #000000; font-weight: bold;">></span></span></span>
  <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"></DataTemplate<span style="color: #000000; font-weight: bold;">></span></span></span>
<span style="color: #009900;"><span style="color: #000000; font-weight: bold;"></ItemsControl.ItemTemplate<span style="color: #000000; font-weight: bold;">></span></span></span>

The finished semi-circular gauge can be seen here next to my original radial gauge control:

<object width="400" height="240" data="data:application/x-silverlight," type="application/x-silverlight-2" > Get Microsoft Silverlight</object>

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:

<span style="color: #009900;"><span style="color: #000000; font-weight: bold;"><Style</span> <span style="color: #000066;">TargetType</span>=<span style="color: #ff0000;">"local:GaugeControl"</span> <span style="color: #000066;">x:Key</span>=<span style="color: #ff0000;">"bulletGraphGauge"</span><span style="color: #000000; font-weight: bold;">>

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:

<span style="color: #0600FF;">public</span> <span style="color: #FF0000;">class</span> BulletGraphGaugeViewModel <span style="color: #008000;">:</span> AttachedViewModelBase
<span style="color: #000000;">{</span>
  <span style="color: #0600FF;">public</span> BulletGraphGaugeViewModel<span style="color: #000000;">(</span><span style="color: #000000;">)</span>
  <span style="color: #000000;">{</span>
  <span style="color: #000000;">}</span>
 
  <span style="color: #0600FF;">private</span> GaugeControl Gauge
  <span style="color: #000000;">{</span>
    <span style="color: #008080; font-style: italic;">// the view model’s DataContext is bound to the Gauge</span>
    get <span style="color: #000000;">{</span> <span style="color: #0600FF;">return</span> DataContext <span style="color: #008000;">!=</span> <span style="color: #0600FF;">null</span> <span style="color: #008000;">?</span> <span style="color: #000000;">(</span>GaugeControl<span style="color: #000000;">)</span>DataContext <span style="color: #008000;">:</span> null<span style="color: #008000;">;</span> <span style="color: #000000;">}</span>
  <span style="color: #000000;">}</span>
 
  <span style="color: #0600FF;">public</span> <span style="color: #FF0000;">double</span> FeaturedMeasureLength
  <span style="color: #000000;">{</span>
    get
    <span style="color: #000000;">{</span>
      <span style="color: #0600FF;">if</span> <span style="color: #000000;">(</span>Gauge <span style="color: #008000;">==</span> <span style="color: #0600FF;">null</span><span style="color: #000000;">)</span>
        <span style="color: #0600FF;">return</span> <span style="color: #FF0000;">0</span><span style="color: #008000;">;</span>
 
      <span style="color: #0600FF;">return</span> ValueToWidth<span style="color: #000000;">(</span>Gauge.<span style="color: #0000FF;">Value</span> <span style="color: #008000;">-</span> Gauge.<span style="color: #0000FF;">Minimum</span><span style="color: #000000;">)</span><span style="color: #008000;">;</span>
    <span style="color: #000000;">}</span>
  <span style="color: #000000;">}</span>
 
  <span style="color: #008080; font-style: italic;">/// <summary></span>
  <span style="color: #008080; font-style: italic;">/// Converts the given value (which should be between Gauge.Maximum / Gauge.Minimum)</span>
  <span style="color: #008080; font-style: italic;">/// into suitable width for rendering within the view.</span>
  <span style="color: #008080; font-style: italic;">/// </summary></span>
  <span style="color: #0600FF;">private</span> <span style="color: #FF0000;">double</span> ValueToWidth<span style="color: #000000;">(</span><span style="color: #FF0000;">double</span> value<span style="color: #000000;">)</span>
  <span style="color: #000000;">{</span>
    <span style="color: #FF0000;">double</span> range <span style="color: #008000;">=</span> Gauge.<span style="color: #0000FF;">Maximum</span> <span style="color: #008000;">-</span> Gauge.<span style="color: #0000FF;">Minimum</span><span style="color: #008000;">;</span>
    <span style="color: #0600FF;">return</span> <span style="color: #000000;">(</span>value<span style="color: #000000;">)</span> <span style="color: #008000;">/</span> range <span style="color: #008000;">*</span> ElementWidth<span style="color: #008000;">;</span>
  <span style="color: #000000;">}</span>
<span style="color: #000000;">}</span>

Next we’ll add the ‘qualitative range’ scale:

<span style="color: #808080; font-style: italic;"><!-- Qualitative ranges --></span>
<span style="color: #009900;"><span style="color: #000000; font-weight: bold;"><ItemsControl</span> <span style="color: #000066;">ItemsSource</span>=<span style="color: #ff0000;">"{Binding Path=Ranges}"</span></span>
<span style="color: #009900;">              <span style="color: #000066;">Grid.Row</span>=<span style="color: #ff0000;">"1"</span> <span style="color: #000066;">Grid.RowSpan</span>=<span style="color: #ff0000;">"3"</span><span style="color: #000000; font-weight: bold;">></span></span>
  <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"><ItemsControl.ItemsPanel<span style="color: #000000; font-weight: bold;">></span></span></span>
    <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"><ItemsPanelTemplate<span style="color: #000000; font-weight: bold;">></span></span></span>
      <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"><StackPanel</span> <span style="color: #000066;">Orientation</span>=<span style="color: #ff0000;">"Horizontal"</span><span style="color: #000000; font-weight: bold;">/></span></span>
    <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"></ItemsPanelTemplate<span style="color: #000000; font-weight: bold;">></span></span></span>
  <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"></ItemsControl.ItemsPanel<span style="color: #000000; font-weight: bold;">></span></span></span>
  <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"><ItemsControl.ItemTemplate<span style="color: #000000; font-weight: bold;">></span></span></span>
    <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"><DataTemplate<span style="color: #000000; font-weight: bold;">></span></span></span>
      <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"><Rectangle</span> <span style="color: #000066;">Width</span>=<span style="color: #ff0000;">"{Binding Path=Width}"</span></span>
<span style="color: #009900;">                  <span style="color: #000066;">Stroke</span>=<span style="color: #ff0000;">"Black"</span> <span style="color: #000066;">StrokeThickness</span>=<span style="color: #ff0000;">"0.2"</span></span>
<span style="color: #009900;">                  <span style="color: #000066;">Fill</span>=<span style="color: #ff0000;">"{Binding Path=Color, Converter={StaticResource ColorToBrushConverter}}"</span><span style="color: #000000; font-weight: bold;">/></span></span>
    <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"></DataTemplate<span style="color: #000000; font-weight: bold;">></span></span></span>
  <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"></ItemsControl.ItemTemplate<span style="color: #000000; font-weight: bold;">></span></span></span>
<span style="color: #009900;"><span style="color: #000000; font-weight: bold;"></ItemsControl<span style="color: #000000; font-weight: bold;">></span></span></span>
<span style="color: #009900;"><span style="color: #000000; font-weight: bold;"><Border</span> <span style="color: #000066;">BorderBrush</span>=<span style="color: #ff0000;">"Black"</span> <span style="color: #000066;">BorderThickness</span>=<span style="color: #ff0000;">"1.0"</span></span>
<span style="color: #009900;">        <span style="color: #000066;">Grid.Row</span>=<span style="color: #ff0000;">"1"</span> <span style="color: #000066;">Grid.RowSpan</span>=<span style="color: #ff0000;">"3"</span> <span style="color: #000066;">Margin</span>=<span style="color: #ff0000;">"-1"</span><span style="color: #000000; font-weight: bold;">/></span></span>

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:

<span style="color: #0600FF;">public</span> IEnumerable<span style="color: #008000;"><</span>RangeItem<span style="color: #008000;">></span> Ranges
<span style="color: #000000;">{</span>
  get
  <span style="color: #000000;">{</span>
    <span style="color: #0600FF;">if</span> <span style="color: #000000;">(</span>Gauge <span style="color: #008000;">==</span> <span style="color: #0600FF;">null</span><span style="color: #000000;">)</span>
      yield break<span style="color: #008000;">;</span>
 
    <span style="color: #0600FF;">for</span> <span style="color: #000000;">(</span><span style="color: #FF0000;">int</span> i <span style="color: #008000;">=</span> <span style="color: #FF0000;">0</span><span style="color: #008000;">;</span> i <span style="color: #008000;"><</span> Gauge.<span style="color: #0000FF;">QualitativeRange</span>.<span style="color: #0000FF;">Count</span><span style="color: #008000;">;</span> i<span style="color: #008000;">++</span><span style="color: #000000;">)</span>
    <span style="color: #000000;">{</span>
      var range <span style="color: #008000;">=</span> Gauge.<span style="color: #0000FF;">QualitativeRange</span><span style="color: #000000;">[</span>i<span style="color: #000000;">]</span><span style="color: #008000;">;</span>
      <span style="color: #0600FF;">if</span> <span style="color: #000000;">(</span>i <span style="color: #008000;">==</span> <span style="color: #FF0000;">0</span><span style="color: #000000;">)</span>
      <span style="color: #000000;">{</span>
        <span style="color: #008080; font-style: italic;">// first RangeItem, width is determined from the first range value</span>
        yield <span style="color: #0600FF;">return</span> <span style="color: #008000;">new</span> RangeItem<span style="color: #000000;">(</span><span style="color: #000000;">)</span>
        <span style="color: #000000;">{</span>
          Color <span style="color: #008000;">=</span> range.<span style="color: #0000FF;">Color</span>,
          Width <span style="color: #008000;">=</span> ValueToWidth<span style="color: #000000;">(</span>range.<span style="color: #0000FF;">Maximum</span> <span style="color: #008000;">-</span> Gauge.<span style="color: #0000FF;">Minimum</span><span style="color: #000000;">)</span>
        <span style="color: #000000;">}</span><span style="color: #008000;">;</span>
      <span style="color: #000000;">}</span>
      <span style="color: #0600FF;">else</span>
      <span style="color: #000000;">{</span>
        <span style="color: #008080; font-style: italic;">// subsequent items, width computed as the difference between the</span>
        <span style="color: #008080; font-style: italic;">// current value and its predecessor</span>
        var previousRange <span style="color: #008000;">=</span> Gauge.<span style="color: #0000FF;">QualitativeRange</span><span style="color: #000000;">[</span>i <span style="color: #008000;">-</span> <span style="color: #FF0000;">1</span><span style="color: #000000;">]</span><span style="color: #008000;">;</span>
        yield <span style="color: #0600FF;">return</span> <span style="color: #008000;">new</span> RangeItem<span style="color: #000000;">(</span><span style="color: #000000;">)</span>
        <span style="color: #000000;">{</span>
          Color <span style="color: #008000;">=</span> range.<span style="color: #0000FF;">Color</span>,
          Width <span style="color: #008000;">=</span> ValueToWidth<span style="color: #000000;">(</span>range.<span style="color: #0000FF;">Maximum</span> <span style="color: #008000;">-</span> previousRange.<span style="color: #0000FF;">Maximum</span><span style="color: #000000;">)</span>
        <span style="color: #000000;">}</span><span style="color: #008000;">;</span>
      <span style="color: #000000;">}</span>
    <span style="color: #000000;">}</span>
  <span style="color: #000000;">}</span>
<span style="color: #000000;">}</span>
 
...
 
<span style="color: #0600FF;">public</span> <span style="color: #FF0000;">class</span> RangeItem
<span style="color: #000000;">{</span>
  <span style="color: #0600FF;">public</span> <span style="color: #FF0000;">double</span> Width <span style="color: #000000;">{</span> get<span style="color: #008000;">;</span> set<span style="color: #008000;">;</span> <span style="color: #000000;">}</span>
  <span style="color: #0600FF;">public</span> Color Color <span style="color: #000000;">{</span> get<span style="color: #008000;">;</span> set<span style="color: #008000;">;</span> <span style="color: #000000;">}</span>
<span style="color: #000000;">}</span>

Finally, the scales are added:

<span style="color: #808080; font-style: italic;"><!-- upper scale --></span>
<span style="color: #009900;"><span style="color: #000000; font-weight: bold;"><ItemsControl</span> <span style="color: #000066;">ItemsSource</span>=<span style="color: #ff0000;">"{Binding Path=MajorTicks}"</span></span>
<span style="color: #009900;">              <span style="color: #000066;">VerticalAlignment</span>=<span style="color: #ff0000;">"Center"</span></span>
<span style="color: #009900;">              <span style="color: #000066;">Grid.Row</span>=<span style="color: #ff0000;">"0"</span></span>
<span style="color: #009900;">              <span style="color: #000066;">ItemsPanel</span>=<span style="color: #ff0000;">"{StaticResource CanvasTemplate}"</span><span style="color: #000000; font-weight: bold;">></span></span>
  <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"><ItemsControl.ItemTemplate<span style="color: #000000; font-weight: bold;">></span></span></span>
    <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"><DataTemplate<span style="color: #000000; font-weight: bold;">></span></span></span>
      <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"><Grid</span> <span style="color: #000066;">Width</span>=<span style="color: #ff0000;">"50"</span> <span style="color: #000066;">Height</span>=<span style="color: #ff0000;">"20"</span><span style="color: #000000; font-weight: bold;">></span></span>
        <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"><Grid.RenderTransform<span style="color: #000000; font-weight: bold;">></span></span></span>
          <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"><TransformGroup<span style="color: #000000; font-weight: bold;">></span></span></span>
            <span style="color: #808080; font-style: italic;"><!-- centre the labels --></span>
            <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"><TranslateTransform</span> <span style="color: #000066;">X</span>=<span style="color: #ff0000;">"-25"</span> <span style="color: #000066;">Y</span>=<span style="color: #ff0000;">"-10"</span><span style="color: #000000; font-weight: bold;">/></span></span>
            <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"><TranslateTransform</span> <span style="color: #000066;">X</span>=<span style="color: #ff0000;">"{Binding Path=Position}"</span><span style="color: #000000; font-weight: bold;">/></span></span>
          <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"></TransformGroup<span style="color: #000000; font-weight: bold;">></span></span></span>
        <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"></Grid.RenderTransform<span style="color: #000000; font-weight: bold;">></span></span></span>
        <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"><TextBlock</span> <span style="color: #000066;">Text</span>=<span style="color: #ff0000;">"{Binding Path=Label}"</span></span>
<span style="color: #009900;">                    <span style="color: #000066;">VerticalAlignment</span>=<span style="color: #ff0000;">"Center"</span></span>
<span style="color: #009900;">                    <span style="color: #000066;">HorizontalAlignment</span>=<span style="color: #ff0000;">"Center"</span><span style="color: #000000; font-weight: bold;">/></span></span>
      <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"></Grid<span style="color: #000000; font-weight: bold;">></span></span></span>
    <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"></DataTemplate<span style="color: #000000; font-weight: bold;">></span></span></span>
  <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"></ItemsControl.ItemTemplate<span style="color: #000000; font-weight: bold;">></span></span></span>
<span style="color: #009900;"><span style="color: #000000; font-weight: bold;"></ItemsControl<span style="color: #000000; font-weight: bold;">></span></span></span>
 
<span style="color: #808080; font-style: italic;"><!-- major ticks --></span>
<span style="color: #009900;"><span style="color: #000000; font-weight: bold;"><ItemsControl</span> <span style="color: #000066;">ItemsSource</span>=<span style="color: #ff0000;">"{Binding Path=MajorTicks}"</span></span>
<span style="color: #009900;">              <span style="color: #000066;">VerticalAlignment</span>=<span style="color: #ff0000;">"Bottom"</span></span>
<span style="color: #009900;">              <span style="color: #000066;">Grid.Row</span>=<span style="color: #ff0000;">"0"</span></span>
<span style="color: #009900;">              <span style="color: #000066;">ItemsPanel</span>=<span style="color: #ff0000;">"{StaticResource CanvasTemplate}"</span><span style="color: #000000; font-weight: bold;">></span></span>
  <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"><ItemsControl.ItemTemplate<span style="color: #000000; font-weight: bold;">></span></span></span>
    <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"><DataTemplate<span style="color: #000000; font-weight: bold;">></span></span></span>
      <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"><Line</span> <span style="color: #000066;">X1</span>=<span style="color: #ff0000;">"{Binding Path=Position}"</span> <span style="color: #000066;">Y1</span>=<span style="color: #ff0000;">"0"</span> <span style="color: #000066;">X2</span>=<span style="color: #ff0000;">"{Binding Path=Position}"</span> <span style="color: #000066;">Y2</span>=<span style="color: #ff0000;">"-5"</span></span>
<span style="color: #009900;">            <span style="color: #000066;">Stroke</span>=<span style="color: #ff0000;">"Black"</span><span style="color: #000000; font-weight: bold;">/></span></span>
    <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"></DataTemplate<span style="color: #000000; font-weight: bold;">></span></span></span>
  <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"></ItemsControl.ItemTemplate<span style="color: #000000; font-weight: bold;">></span></span></span>
<span style="color: #009900;"><span style="color: #000000; font-weight: bold;"></ItemsControl<span style="color: #000000; font-weight: bold;">></span></span></span>
 
<span style="color: #808080; font-style: italic;"><!-- minor ticks --></span>          
<span style="color: #009900;"><span style="color: #000000; font-weight: bold;"><ItemsControl</span> <span style="color: #000066;">ItemsSource</span>=<span style="color: #ff0000;">"{Binding Path=MinorTicks}"</span></span>
<span style="color: #009900;">              <span style="color: #000066;">VerticalAlignment</span>=<span style="color: #ff0000;">"Bottom"</span></span>
<span style="color: #009900;">              <span style="color: #000066;">Grid.Row</span>=<span style="color: #ff0000;">"0"</span></span>
<span style="color: #009900;">              <span style="color: #000066;">ItemsPanel</span>=<span style="color: #ff0000;">"{StaticResource CanvasTemplate}"</span><span style="color: #000000; font-weight: bold;">></span></span>
  <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"><ItemsControl.ItemTemplate<span style="color: #000000; font-weight: bold;">></span></span></span>
    <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"><DataTemplate<span style="color: #000000; font-weight: bold;">></span></span></span>
      <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"><Line</span> <span style="color: #000066;">X1</span>=<span style="color: #ff0000;">"{Binding}"</span> <span style="color: #000066;">Y1</span>=<span style="color: #ff0000;">"0"</span> <span style="color: #000066;">X2</span>=<span style="color: #ff0000;">"{Binding}"</span> <span style="color: #000066;">Y2</span>=<span style="color: #ff0000;">"-3"</span></span>
<span style="color: #009900;">            <span style="color: #000066;">Stroke</span>=<span style="color: #ff0000;">"Black"</span><span style="color: #000000; font-weight: bold;">/></span></span>
    <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"></DataTemplate<span style="color: #000000; font-weight: bold;">></span></span></span>
  <span style="color: #009900;"><span style="color: #000000; font-weight: bold;"></ItemsControl.ItemTemplate<span style="color: #000000; font-weight: bold;">></span></span></span>
<span style="color: #009900;"><span style="color: #000000; font-weight: bold;"></ItemsControl<span style="color: #000000; font-weight: bold;">></span></span></span>

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:

<object width="400" height="240" data="data:application/x-silverlight," type="application/x-silverlight-2" > Get Microsoft Silverlight</object>

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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Colin Eberhardt
Architect Scott Logic
United Kingdom United Kingdom
I am CTO at ShinobiControls, a team of iOS developers who are carefully crafting iOS charts, grids and controls for making your applications awesome.
 
I am a Technical Architect for Visiblox which have developed the world's fastest WPF / Silverlight and WP7 charts.
 
I am also a Technical Evangelist at Scott Logic, a provider of bespoke financial software and consultancy for the retail and investment banking, stockbroking, asset management and hedge fund communities.
 
Visit my blog - Colin Eberhardt's Adventures in .NET.
 
Follow me on Twitter - @ColinEberhardt
 
-
Follow on   Twitter   Google+

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.141216.1 | Last Updated 21 Oct 2010
Article Copyright 2010 by Colin Eberhardt
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid