Colin Eberhardt's Adventures in WPF

Developing a (very) Lookless Silverlight Radial Gauge Control

August 19th, 2010

This blog post describes the development of a lookless radial gauge control. In this post I will explore the use of an attached view model in order to move view specific properties and logic out of the control code in order to give a truly lookless control.

Today I had to get up far too early in order to catch an early morning flight to Copenhagen with a connection in Amsterdam. What to do for the six hours I would be travelling? Armed with a netbook and Visual Studio 2010 Express I thought it would be fun to have a go at developing a Silverlight gauge control. I know that there are already one or two free ones out there, with a decent looking one available on codeproject, however, it still felt like a good way to pass the time!

In order to make things a little more challenging I wanted to create a control that was truly lookless. So, what do I mean by this? Firstly a gauge control in its simplest sense displays the location of some indicator between a maximum and minimum value. There is nothing inherently circular about a gauge, thermometers are a good example of a linear gauge. So, I don’t want any ‘circular’ logic in the control itself. Secondly, custom controls often have certain expectations about the presence of named elements within their template. By this I mean that the template must contain, for example, a Path element called ‘needle’ which the control code will manipulate (The gauge published in the codeproject article above requires the presence of four named elements in the template). This forces certain constraints regarding how the control can be templated, this is really lookless is it?

The following example shows the gauge control which I created, and the rest of this post describes the implementation:

<local:GaugeControl Value="65" Width="200" Height="200"  
                    Maximum="100" Minimum="50"
                    x:Name="gauge">
  <local:GaugeControl.QualitativeRange>
    <local:QualitativeRanges>
      <local:QualitativeRange Color="Yellow" Maximum="75"/>
      <local:QualitativeRange Color="Orange" Maximum="90"/>
      <local:QualitativeRange Color="Red" Maximum="100"/>
    </local:QualitativeRanges>
  </local:GaugeControl.QualitativeRange>
</local:GaugeControl>

The Starting Point

The first step was to create a Gauge custom control with Value, Maximum and Minimum dependency properties. The only logic defined within the control itself is to set the DataContext of the root visual element to the control instance itself. This is quite a common approach to control design, allowing elements within the template to bind to the control properties:

public override void OnApplyTemplate()
{
  base.OnApplyTemplate();
 
  Grid root = GetTemplateChild("LayoutRoot") as Grid;
  root.DataContext = this;
}

The first thing I added to the control template was the ‘face’ of the radial gauge. This is simply an Ellipse with a pretty gradient fill and stroke:

<!-- dial background and outer border -->
<Ellipse Stretch="Fill" StrokeThickness="8">
  <Ellipse.Fill>
    <RadialGradientBrush Center="0.5,0.5">
      <GradientStop Color="#EEF"/>
      <GradientStop Color="#99B" Offset="0.9"/>
      <GradientStop Color="#335" Offset="1"/>
    </RadialGradientBrush>
  </Ellipse.Fill>
  <Ellipse.Stroke>
    <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
      <GradientStop Color="#BBD" Offset="0"/>
      <GradientStop Color="#003" Offset="1"/>
    </LinearGradientBrush>
  </Ellipse.Stroke>
</Ellipse>

Adding the Needle

The next thing I added to the control was a needle. This is rendered using a simple Path with a LinearGradient in order to give it some sense of depth. I want the needle to have a length of approximately 70% of the gauge’s radius, a simple way to achieve this is to construct it within a Grid that uses ‘star’ widths / heights to provide a proportional layout and configure the Path to stretch to fill the cell it occupies:

<!-- the needle path -->              
<Grid>
  <Grid.RowDefinitions>
    <RowDefinition Height="3*"/>
    <RowDefinition Height="7*"/>
    <RowDefinition Height="10*"/>
  </Grid.RowDefinitions>
  <Grid.ColumnDefinitions>
    <ColumnDefinition/>
    <ColumnDefinition/>
  </Grid.ColumnDefinitions>
 
  <Path Stretch="Uniform"
      Grid.Row="1" Grid.ColumnSpan="2"
      HorizontalAlignment="Center"
      Stroke="Black" StrokeThickness="0.5"
      Data="M 0,0 l 10,60 l -10, 40 l -10 -40">
    <Path.Fill>
      <LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
        <GradientStop Color="DarkRed" Offset="0"/>
        <GradientStop Color="DarkRed" Offset="0.45"/>
        <GradientStop Color="Red" Offset="0.55"/>
        <GradientStop Color="Red" Offset="1"/>
      </LinearGradientBrush>
    </Path.Fill>
  </Path>
</Grid>

Here you can see how the needle is scaled by its parent Grid:

Rotating the needle to reflect the current Gauge Value is achieved quite simply via RotateTransform. However, this needs to be converted into a rotation angle which depends on the Gauge Maximum /Minimum values together with the overall angle of sweep on the gauge. I initially approach this problem by applying bindings via value converters and multibindings, however I found myself repeating the same conversion logic in numerous places within the template in order to render the ticks etc… Ideally the angle of rotation would be something that the template could bind to. The template DataContext is bound to the Gauge control itself, however as stated earlier I do not want ‘circular’ concepts to leak into the control.

An attached View Model

The solution I came up with for this problem was to create a view model that lives entirely within the control template that acts as an adapter for the Gauge, supplementing its properties with the needed ‘circular’ concepts. In keeping with my aims I could not instantiate this view model within the Gauge control itself, so instead it is created via an attached behaviour within the control template:

<Style TargetType="local:GaugeControl">
  <Setter Property="FontSize" Value="10"/>
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="local:GaugeControl">
        <Grid x:Name="LayoutRoot" >
          <Grid>
            <!-- attached the view model -->
            <local:RadialGaugeControlViewModel.Attach>
              <local:RadialGaugeControlViewModel/>
            </local:RadialGaugeControlViewModel.Attach>
 
            <!-- ... control template goes here ...  -->
          </Grid>
        </Grid>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

The view model defines the Attach property and in its change handler performs the logic required to bind to the DatatContext of the parent (which is the Gauge control itself), and set itself as the DataContext of the Grid to which it is being attached. This allows the rest of the template to bind to properties of the RadialGaugeControlViewModel.

public class RadialGaugeControlViewModel : FrameworkElement, INotifyPropertyChanged
{
  #region Attach attached property
 
  public static readonly DependencyProperty AttachProperty =
      DependencyProperty.RegisterAttached("Attach", typeof(object), typeof(RadialGaugeControlViewModel),
          new PropertyMetadata(null, new PropertyChangedCallback(OnAttachChanged)));
 
  public static RadialGaugeControlViewModel GetAttach(DependencyObject d)
  {
    return (RadialGaugeControlViewModel)d.GetValue(AttachProperty);
  }
 
  public static void SetAttach(DependencyObject d, RadialGaugeControlViewModel value)
  {
    d.SetValue(AttachProperty, value);
  }
 
  /// <summary>
  /// Change handler for the Attach property
  /// </summary>
  private static void OnAttachChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  {
    Grid targetElement = d as Grid;
    RadialGaugeControlViewModel viewModel = e.NewValue as RadialGaugeControlViewModel;
 
    // handle the loaded event
    targetElement.Loaded += new RoutedEventHandler(Grid_Loaded);
 
  }
 
  /// <summary>
  /// Handle the Loaded event of the Grid to enable the attached
  /// view model to bind to properties of the Grid Parent element
  /// </summary>
  static void Grid_Loaded(object sender, RoutedEventArgs e)
  {
    FrameworkElement targetElement = sender as FrameworkElement;
    FrameworkElement parent = targetElement.Parent as FrameworkElement;
 
    // use the attached view model as the DataContext of the element it is attached to
    RadialGaugeControlViewModel attachedModel = GetAttach(targetElement);
    targetElement.DataContext = attachedModel;
 
    // bind the DataContext of the view model to the DataContext of the parent.
    attachedModel.SetBinding(RadialGaugeControlViewModel.DataContextProperty,
      new Binding("DataContext")
      {
        Source = parent
      });
  }
}

It is now possible to expose a property on the view model which provides the Gauge Value as an angle:

public double ValueAngle
{
  get
  {
    if (Gauge == null)
      return 0.0;
 
    return ValueToAngle(Gauge.Value);
  }
}
 
private double ValueToAngle(double value)
{
  double minAngle = -150;
  double maxAngle = 150;
  double angularRange = maxAngle - minAngle;
 
  return (value - Gauge.Minimum) / (Gauge.Maximum - Gauge.Minimum) *
      angularRange + minAngle;
}

This can then be bound to in the template in order to rotate the needle. For an extra ‘flourish’ a drop shadow is also added to the needle which binds to this same rotation angle in order to give a subtle ‘3D’ effect:

<Path Stretch="Uniform"
    Grid.Row="1" Grid.ColumnSpan="2"
    HorizontalAlignment="Center"
    Stroke="Black" StrokeThickness="0.5"
    Data="M 0,0 l 10,60 l -10, 40 l -10 -40"
    RenderTransformOrigin="0.5,1">
  <Path.RenderTransform>
    <!-- rotate the needle -->
    <RotateTransform Angle="{Binding Path=ValueAngle}"/>
  </Path.RenderTransform>
  <Path.Fill>
    <LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
      <GradientStop Color="DarkRed" Offset="0"/>
      <GradientStop Color="DarkRed" Offset="0.45"/>
      <GradientStop Color="Red" Offset="0.55"/>
      <GradientStop Color="Red" Offset="1"/>
    </LinearGradientBrush>
  </Path.Fill>
  <Path.Effect>
    <DropShadowEffect Color="Black" Direction="{Binding Path=ValueAngle}"
                      BlurRadius="3"
                      Opacity="0.6"
                      ShadowDepth="5"/>
  </Path.Effect>
</Path>

Adding a Scale

The gauge control needs to have tick marks and labels render around the dial face at regularly spaced intervals between the Maximum and Minimum values. Here the view model comes into its own by providing a list of ‘Tick’ value objects, each of which provide the view with the required information to render tick marks and their labels:

public IEnumerable<Tick> MajorTicks
{
  get
  {
    if (Gauge == null)
      yield break;
 
    double tickSpacing = (Gauge.Maximum - Gauge.Minimum) / 10;
    for (double tick = Gauge.Minimum; tick <= Gauge.Maximum; tick += tickSpacing)
    {
      yield return new Tick()
      {
        Angle = ValueToAngle(tick),
        Value = tick.ToString("N0"),
        Parent = this
      };
    }
  }
}
 
public class Tick
{
  public double Angle { get; set; }
  public string Value { get; set; }
  public RadialGaugeControlViewModel Parent { get; set; }
}

The XAML which renders the major tick marks uses an ItemsControl to create each tick instance:

<!-- major ticks -->
<ItemsControl ItemsSource="{Binding Path=MajorTicks}"
              VerticalAlignment="Center" HorizontalAlignment="Center">
  <ItemsControl.ItemsPanel>
    <ItemsPanelTemplate>
      <Canvas></Canvas>
    </ItemsPanelTemplate>
  </ItemsControl.ItemsPanel>
  <ItemsControl.ItemTemplate>
    <DataTemplate>
      <Ellipse Fill="Black" Width="8" Height="8">
        <Ellipse.RenderTransform>
          <TransformGroup>
            <!-- centre the ellipse -->
            <TranslateTransform X="-4" Y="-4"/>
            <!-- offset to the edge of the gauge -->
            <TranslateTransform X="0"
                Y="{Binding Path=Parent.GridHeight, Converter={StaticResource ScaleFactorConverter},
                                                                 ConverterParameter=-0.37}"/>
            <!-- rotate -->
            <RotateTransform Angle="{Binding Angle}"/>
          </TransformGroup>
        </Ellipse.RenderTransform>
      </Ellipse>
    </DataTemplate>
  </ItemsControl.ItemTemplate>
</ItemsControl>

As you can see each tick is simply an ellipse. The clever part is how each Ellipse is transformed to position it appropriately. It is first centre to make subsequent transforms a little simpler, it is then translated y an offset which moves it to the edge of the gauge face. The offset factor is computed as some fraction of the overall size of the gauge control. In order to achieve this I reluctantly had to angle SizeChanged events on the template Grid in order to expose its ActualHeight / ActualWidth, this is because ElementName binding on these properties appears to be broken.

On attachment the view model handles SizeChanged events as follows:

/// <summary>
/// Handle SizeChanged events from the grid so that we can inform elements
/// of changes in the ActualHeight / ActualWidth
/// </summary>
private void Grid_SizeChanged(object sender, SizeChangedEventArgs e)
{
  OnPropertyChanged("GridHeight");
  OnPropertyChanged("GridWidth");
 
  Grid.Clip = new EllipseGeometry()
  {
    RadiusX = _grid.ActualWidth / 2,
    RadiusY = _grid.ActualHeight / 2,
    Center = new Point(_grid.ActualWidth / 2, _grid.ActualHeight / 2)
  };
}
 
public double GridWidth
{
  get { return _grid.ActualWidth; }
}
 
public double GridHeight
{
  get { return _grid.ActualHeight; }
}

Adding tick labels and minor tick marks both use a simple variation on the above described approach:

I also added a ‘qualitative value’ range which renders a colour coded band beneath the needle. Again, this uses variations on the same approach, with the view model adapting the properties of the Gauge control and the template binding to these properties, together with the Grid size information in order to provide any required scaling.

The last flourish was to add a ‘glass’ effect to the Gauge. This was ‘borrowed’ directly from this fantastic codeproject article on creating ‘round glassy buttons’.

The finished Gauge is shown below, where its value is bound to a Slider control:

Conclusions

I am pretty happy with how this Gauge control turned out, visually I think it looks pretty good. I am also happy that I have succeeded in my initial aim of making it completely lookless. The attached view model within the control template is an interesting approach that moves view specific concepts (in this case angular properties) into the view, which is where the belong.

This radial Gauge control could certainly be improved to allow a more flexible scale calculation. Also, the RadialGaugeControlViewModel could also expose some of the view specific properties such as the radial sweep angle (currently hard-coded to 300 degrees) allowing them to be set in the template. For now, I think I will leave this control as it is.

Tomorrow I am on another early flight, this time heading back home. I might take this as an opportunity to provide a view for this control making use of a different attached view model.

You can download the full source for this article: GaugeControl.zip

(Apologies for the lack of project structure, this code was written using VS 2010 Express).

Regards, Colin E.

Exposing and Binding to a Silverlight ScrollViewer’s Scrollbars

July 21st, 2010

The Silverlight ScrollViewer exposes readonly properties which indicate the current vertical and horizontal scroll offset, and methods for setting the current offset. In this blog post I demonstrate a simple attached behaviour that exposes these offsets as read / write dependency properties allowing them to be bound to.

The Silverlight ScrollViewer is a very useful control, if you have some content that is larger than the space available in your application, just sit it inside a ScrollViewer and it will automatically add vertical or horizontal scrollbars as required. Simple.

The ScrollViewer exposes readonly properties which indicate the current vertical and horizontal scroll offset. I have been used a ScrollViewer on many occasions without finding this to be an issue, however, recently I was creating an MVVM application which contained a list of items within a ScrollViewer. When the user clicks on an item, they are taken to its ‘details’ page, they then have the option to return to the list. I wanted the list to have the same state when the user returned to it, i.e. the same scroll location. Naturally, with this being an MVVM application, the scroll position belongs on the ViewModel and should be relayed to the View via a binding. However, with the offset properties on the ScrollViewer being readonly, this was not possible. Time to get creative!

The template of the ScrollViewer contains two ScrollBar instances, one horizontal and one vertical. The basic idea behind my solution is to add an attached behaviour (i.e. an attached property, that on attachment performs some logic on the target element) which walks the visual tree to find these scrollbars exposing their offset values.

Firstly, we’ll start by defining an attached property called VerticalOffset, which will be used to expose the position of the vertical scrollbar. Dependency property boiler plate code follows:

public static class ScrollViewerBinding
{
  #region VerticalOffset attached property
 
  /// <summary>
  /// Gets the vertical offset value
  /// </summary>
  public static double GetVerticalOffset(DependencyObject depObj)
  {
    return (double)depObj.GetValue(VerticalOffsetProperty);
  }
 
  /// <summary>
  /// Sets the vertical offset value
  /// </summary>
  public static void SetVerticalOffset(DependencyObject depObj, double value)
  {
    depObj.SetValue(VerticalOffsetProperty, value);
  }
 
  /// <summary>
  /// VerticalOffset attached property
  /// </summary>
  public static readonly DependencyProperty VerticalOffsetProperty =
      DependencyProperty.RegisterAttached("VerticalOffset", typeof(double),
      typeof(ScrollViewerBinding), new PropertyMetadata(0.0, OnVerticalOffsetPropertyChanged));
 
  #endregion
}

We’ll also define a private attached property of type ScrollBar, which will be used to store a reference to the scrollbar once it has been located within the ScrollViewers visual tree:

/// <summary>
/// An attached property which holds a reference to the vertical scrollbar which
/// is extracted from the visual tree of a ScrollViewer
/// </summary>
private static readonly DependencyProperty VerticalScrollBarProperty =
    DependencyProperty.RegisterAttached("VerticalScrollBar", typeof(ScrollBar),
    typeof(ScrollViewerBinding), new PropertyMetadata(null));

Because this attached property is private, I haven’t bothered to create CLR property accessors which are only used for convenience.

The VerticalOffset attached property has a changed event handler. When the property is first attached to a ScrollViewer, this allows us to walk the visual tree constructed from the ScrollViewer’s template and extract the scrollbars. It is this extra logic that adds new functionality to the ScrollViewer which is what makes this an attached behaviour, as opposed to a regular attached property which merely holds state.

When an attached property is first associated with its target, the visual tree described by the target’s template has not yet been constructed. With WPF, you would typically register for the Loaded event on the target, and on handling this event walk the visual tree that will have been constructed. However, in Silverlight there is a very subtle difference, apparently the Loaded event is not guaranteed to occur after the template is applied. Oh dear. The only other alternative is to handle the LayoutUpdated which is fired whenever changes are made to the visual tree. However, there is another complication! The LayoutUpdated event always has a null sender, this might look like a bug, but it is actually by design. This means that if we handle the LayoutUpdated event on the ScrollViewer element which the property is attached to, the method invoked as the event handler no longer has a reference to the ScrollViewer!

In order to get around this problem, the attached property changed handler is as follows:

/// <summary>
/// Invoked when the VerticalOffset attached property changes
/// </summary>
private static void OnVerticalOffsetPropertyChanged(DependencyObject d,
    DependencyPropertyChangedEventArgs e)
{
  ScrollViewer sv = d as ScrollViewer;
  if (sv != null)
  {
    // check whether we have a reference to the vertical scrollbar
    if (sv.GetValue(VerticalScrollBarProperty) == null)
    {
      // if not, handle LayoutUpdated, which will be invoked after the
      // template is applied and extract the scrollbar
      sv.LayoutUpdated += (s, ev) =>
        {
          if (sv.GetValue(VerticalScrollBarProperty) == null)
          {
            GetScrollBarsForScrollViewer(sv);
          }
        };
    }
    else
    {
      // update the scrollviewer offset
      sv.ScrollToVerticalOffset((double)e.NewValue);
    }
  }
}

Firstly, we check whether we have already got a reference to the scrollbar, if not, we attached a LayoutUpdated event handler. But instead of defining this handler as a method, it is written as an lambda expression (i.e. an anonymous delegate), so that the ScrollViewer reference is captured using closure.

When the LayoutUpdated event is fired the following method is invoked:

/// <summary>
/// Attempts to extract the scrollbars that are within the scrollviewers
/// visual tree. When extracted, event handlers are added to their ValueChanged events.
/// </summary>
private static void GetScrollBarsForScrollViewer(ScrollViewer scrollViewer)
{
  ScrollBar scroll = GetScrollBar(scrollViewer, Orientation.Vertical);
  if (scroll != null)
  {
    // save a reference to this scrollbar on the attached property
    scrollViewer.SetValue(VerticalScrollBarProperty, scroll);
 
    // scroll the scrollviewer
    scrollViewer.ScrollToVerticalOffset(
      ScrollViewerBinding.GetVerticalOffset(scrollViewer));
 
 
    // handle the changed event to update the exposed VerticalOffset
    scroll.ValueChanged += (s, e) =>
      {
        SetVerticalOffset(scrollViewer, e.NewValue);
      };
  }
}
 
/// <summary>
/// Searches the descendants of the given element, looking for a scrollbar
/// with the given orientation.
/// </summary>
private static ScrollBar GetScrollBar(FrameworkElement fe, Orientation orientation)
{
  return fe.Descendants()
            .OfType<ScrollBar>()
            .Where(s => s.Orientation == orientation)
            .SingleOrDefault();
 
}

The GetScrollBar method is invoked, which then uses Linq-to-VisualTree to walk the descendants of the ScrollViewer in an attempt to find scrollbars of a given orientation. If one is found, it is stored in the private attached property. We also subscribe to the events which the scrollbar raises when its value changes, in order to update the VerticalOffsetProperty. Again, a lambda expression is used to capture the ScrollViewer which this scrollbar is associated with, so that we do not have to store a relationship from scrollbar to scrollviewer.

As an aside, it would have been a bit more elegant to use binding to connect our VerticalScrollOffset to the scrollbar value as follows:

scroll.SetBinding(ScrollBar.ValueProperty,
  new Binding("(ScrollViewerBinding.VerticalOffset)")
  {
    Source = scrollViewer
  });

However, it appears that there is a bug in Silverlight which makes binding to custom attached properties in code behind fail with a critical error. Until this is fixed, we have to take care of the ‘binding’, by handling change events from both properties, manually.

Using this attached behaviour is as simple as the following:

<ScrollViewer 
    local:ScrollViewerBinding.VerticalOffset="{Binding YPosition, Mode=TwoWay}"
    local:ScrollViewerBinding.HorizontalOffset="{Binding XPosition, Mode=TwoWay}">
    <!-- Big content goes here! -->
</ScrollViewer>

The following little demo binds the X & Y offset of the scrollviewer to two CLR properties (with property changed notifications) defined in code behind. These same properties are bound to the text fields below:

You can download the complete sourcecode for this blog post: ScrollOffsetBinding.zip

Regards, Colin E.

A Universal Value Converter for WPF

July 9th, 2010

This post provides a simple IValueConverter implementation that makes use of the framework type converters in order to convert between a large range of source / target types. This converter can be used both within bindings and in code-behind to give more concise property setters.

Introduction

One of the great features of the XAML language is that it is flexible, concise and expressive (yes, I know that XML can be a little verbose, but if you try to create a complex UI purely in code-behind I think you will agree with my observations!). For example, you can set the fill of a rectangle by simply specifying the named color:

<Rectangle Fill="Green"/)

or … you can specify the RGB values directly:

<Rectangle Fill="#00FF00"/)

Looking at the above examples, you might be fooled into thinking that the Fill property is of type Color. People who are new to WPF often find that this is not the case the first time they try to bind a property of type Color to the Fill property (they would never set the Fill property directly in code behind, because that would be a cardinal sin!). The Fill property is actually of type Brush, and the XAML parser is performing some cunning type conversions in order to make the above markup work.

The solution to this problem of binding a Color to the Fill property is to create a value converter:

public class ColorToBrushConverter : IValueConverter
{
  public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
  {
    if (value is Color)
    {
      return new SolidColorBrush((Color)value);
    }
    return null;
  }
 
  public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
  {
    throw new Exception("The method or operation is not implemented.");
  }
}

Which can be used as follows:

<Rectangle Fill="{Binding Path=MyColorProperty,
                                  Converter={StaticResource ColorToBrushConverter}} "/)

If you search google for ColorToBrushConverter, you can see that there are a great many people who have implemented this simple little converter. But what happens if you want to bind to a string representation of color? or you want to bind to a stroke dash property or path geometry? Whilst value converters are simple to implement, it is a shame that you have to create so many of them!

A Universal Value Converter

Wouldn’t it be great to have a single value converter that has the same flexibility as the XAML parser? It is actually very simple to create such as converter (and after creating probably my 5th ColorToBrushConverter I have no idea why it took so long before I realised this!). The .NET framework has had an API for conversion between different types via TypeConverters for a long time. They used extensively in .NET technologies for databinding and designer support, and much more.

A value converter can obtain a suitable TypeConverter for the target property then perform the required conversion:

public class UniversalValueConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        // obtain the conveter for the target type
        TypeConverter converter = TypeDescriptor.GetConverter(targetType);
 
        try
        {
            // determine if the supplied value is of a suitable type
            if (converter.CanConvertFrom(value.GetType()))
            {
                // return the converted value
                return converter.ConvertFrom(value);
            }
            else
            {
                // try to convert from the string representation
                return converter.ConvertFrom(value.ToString());
            }
        }
        catch (Exception)
        {
            return value;
        }
 
    }
 
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

Note, that this value converter first tries to convert directly from the source to the target type, if this is not possible it then tried to convert via a string representation (I am not sure whether it is more correct to obtain a TypeConverter for the String type, then use this rather that invoking ToString on the value being converted, but the above works for me :-) ).

You can see this converter in action below where a range of type conversions are demonstrated:

<TextBlock Text="Converting String to Brush ...."/>
<Rectangle Fill="{Binding ElementName=colorTextBox, Path=Text, Converter={StaticResource UniversalValueConverter}}"/>
<TextBox x:Name="colorTextBox"
             Text="Red"/>
 
<TextBlock Text="Converting String to Geometry ...."/>
<Path Data="{Binding ElementName=geometryText, Path=Text, Converter={StaticResource UniversalValueConverter}}"/>
<TextBox x:Name="geometryText"                  
             Text="M 10,20 C 10,2.5 40,35 40,17 H 28"/>
 
<TextBlock Text="Converting String to DoubleCollection (stroke dash) ...."/>
<Line StrokeDashArray="{Binding ElementName=dashText, Path=Text, Converter={StaticResource UniversalValueConverter}}"/>
<TextBox x:Name="dashText"                  
            Text="2 2 4 5"/>

For the first conversion, string to brush, you can use named colors, and the hex notation in its range of abbreviated forms (#AF7, #AAFF77, #FFAAFF77 …). You can also use this converter to convert from string to their corresponding enum values, for example binding the string “Collapsed” to the Visbility property.

Value conversion in code behind

The above converter really is swiss army knife for bindings, but what about code-behind? You are still constrained by the type requirements of the property being set:

rect1.Fill = new SolidColorBrush()
{
    Color = Colors.Red
};

Value converters, whilst typically used in binding, can also be used directly in code-behind. The following extension method extends SetValue method for setting dependency properties to make use of the above value converter:

/// <summary>
/// Sets the given dependency property, applying type conversion where required
/// </summary>
public static void SetValueEx(this DependencyObject element, DependencyProperty property, object value)
{
    var conv = new UniversalValueConverter();
    var convertedValue = conv.Convert(value, property.PropertyType, null, CultureInfo.InvariantCulture);
    element.SetValue(property, convertedValue);
}

Which provides a more flexible mechanism for setting property values:

rect1.SetValueEx(Rectangle.FillProperty, Colors.Red);
rect2.SetValueEx(Rectangle.FillProperty, "Blue");
rect3.SetValueEx(Rectangle.FillProperty, "#FFEE55");
rect4.SetValueEx(Rectangle.FillProperty, new SolidColorBrush(Colors.Orange));

… and Silverlight?

Unfortunately Silverlight lacks the TypeDescriptor class which is used to obtain TypeConveters. I am guessing that type conversion within Silverlight is ‘baked-in’ to the XAML parser, which means that it is not possible to re-use this logic :-(

You can download the full source for this blog post: UniversalValueConverter.zip

Regards,
Colin E.

Modal dialogs in cross-platform WPF/Silverlight applications

June 18th, 2010

This blog post looks at the problem of showing modal dialog windows in applications that target both the Silverlight and WPF platforms. A solution is provided which allows modal dialogs to be written that work well for both technologies.

Silverlight is, roughly speaking, a subset of Windows Presentation Foundation (WPF). This means that it is possible to write applications that target both frameworks, allowing you to develop applications that work both on the desktop and on the web using the same codebase.

However, Silverlight is not a strict subset of WPF, the differences between the two frameworks can be summed up as follows:

  1. Things that WPF has that Silverlight does not
  2. Things that Silverlight has that WPF has not
  3. Things that both framework have but are different!

There are a great many things that the WPF framework has that Silverlight does not, mostly due to the size constraints of Silverlight being a browser plugin (1). There are a few things that Silverlight has, such as Out Of Browser (OOB) support and local storage which are not in WPF because they relate to the browser context (2). If these areas are avoided, the creation of a cross-platform application can be straightforward, however it is point (3) that causes problems.

When developing an application which targets both frameworks it makes sense to do most of your development primarily in Silverlight in order to avoid accidentally using WPF features. In practical terms the way most people  achieve this is to create two projects, one Silverlight, one WPF. The WPF project will include links to the files within the Silverlight project (This is achieved by selecting “Add existing item …”, then selecting the “Add as link” option). The following diagram highlights the linked files in the attached project:

The first obstacle to overcome is the entry point into the application. Silverlight applications render a single UserControl as the RootVisual of the instantiated application, wheres WPF applications will create a Window instance when launched. This problem is easily solved by simply hosting the UserControl, which is the starting point for the Silverlight application, within a Window:

<Window x:Class="ModalDialogDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:ModalDialogDemo"
        Title="Modal dialog demo" SizeToContent="WidthAndHeight">
    <Grid>
        <local:MainControl/>
    </Grid>
</Window>

With this small obstacle cleared, you can create some pretty decent cross-framework applications (as long as you avoid the temptation of looking at all the WPF controls you are missing out on!).

I have created quite a few WPF / Silverlight applications in this manner, and the process, although a little clunky (I usually have two Visual Studio instances open so that I can keep the two versions in synch), works pretty well. However, the next major obstacle I experienced is that of creating a modal dialog. This falls firmly into category (3).

Modal dialog functionality was added in Silverlight 3. To pop-up a modal dialog, create a page which inherits from ChildWindow (rather than the usual UserControl). This class provides a Show method which pops up the dialog, and a Closed event which is raised when the user hits the OK or Cancel. For example, you might create an error dialog and use it as follows:

ErrorDialog dlg = new ErrorDialog ();
dlg.Closed += new EventHandler(OnErrorDialogClosed);
dlg.Show();

Visual Studio has a template for the creating of modal dialogs via ChildWindow:

However, with WPF things are a bit different. Whilst Silverlight modal dialogs are constrained to live within the Silverlight container on your web page, WPF dialogs are proper windows that you can move around your desktop (Interestingly, someone on stackoverflow has emulated Silverlight’s ChildWindow in WPF, although I am not convinced that this will look right on a desktop application). Visual Studio does not have a template for creating modal dialogs in WPF, however, there is more than enough documentation regarding the various types of dialog on MSDN.

With WPF a Window can be shown as modal as follows:

ErrorDialog dlg = new ErrorDialog ();
dlg.ShowDialog();

There are a few differences here, firstly Window has a Show method just like Silverlight’s ChildWindow, however, this will result in showing a modeless window. Instead we invoke the ShowDialog method which shows a modal dialog, but there is another subtle difference, whilst ChildWindow.Show returns immediately, with an event handler required to capture the modal dialog being closed, WPF’s Window.ShowDialog blocks until the dialog is closed.

So … each framework has different classes for modal dialogs, and different interfaces for each. How do we resolve these differences?

Early in this blog post we saw how the UserControl which is the entry point into our Silverlight application can be hosted in a WPF Window. The same approach can be used for modal dialogs. We can create our modal dialog as a UserControl:

<UserControl x:Class="ModalDialogDemo.ModalDialog"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">    
    <Grid x:Name="LayoutRoot">
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
 
        <TextBlock Text="What is your favourite colour? " VerticalAlignment="Center"/>
        <TextBox x:Name="colourInput" Grid.Column="1" VerticalAlignment="Center" Width="80"/>
    </Grid>
</UserControl>

With the above control hosted in a ChildWindow or Window depending on the platform. The problem is, we cannot explicitly make reference to either of these types in our common code (i.e. the code shared by WPF / Silverlight via file linking). This problem can be solved by employing the Gang of Four Adapter pattern.

We define an interface that gives the functionality that we require for hosting our UserControl as a modal dialog:

/// <summary>
/// An interface which provides a host for some content which should be rendered
/// as a modal dialog
/// </summary>
public interface IModalDialogHost
{
    /// <summary>
    /// Gets or sets the content to host
    /// </summary>
    UserControl HostedContent { get; set; }
 
    /// <summary>
    /// Gets the dialog title
    /// </summary>
    string Title { set; }
 
    /// <summary>
    /// Gets the dialog result (i.e. OK, Cancel)
    /// </summary>
    bool? DialogResult { get; }
 
    /// <summary>
    /// Shows the dialog, invoking the given callback when it is closed
    /// </summary>
    void Show(DialogClosed closedCallback);        
}
 
/// <summary>
/// A callback which is invoked when a modal dialog is closed.
/// </summary>
public delegate void DialogClosed(IModalDialogHost host);

Within the Silverlight project we create a concrete implementation of this interface based on ChildWindow:

<controls:ChildWindow x:Class="ModalDialogDemo.ModalDialogHost"
           xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
           xmlns:local="clr-namespace:ModalDialogDemo"
           xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls">
    <Grid x:Name="LayoutRoot" Margin="2">
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
 
        <!-- the hosted content -->
        <ContentPresenter x:Name="contentHost"/>
 
        <Button x:Name="CancelButton" Content="Cancel" Click="CancelButton_Click" Width="75"
                Height="23" HorizontalAlignment="Right" Margin="0,12,0,0" Grid.Row="1" />
        <Button x:Name="OKButton" Content="OK" Click="OKButton_Click" Width="75" Height="23"
                HorizontalAlignment="Right" Margin="0,12,79,0" Grid.Row="1" />
    </Grid>
</controls:ChildWindow>

With the interface implemented in code-behind as follows:

/// <summary>
/// A Silverlight implementation of IModalDialogHost
/// </summary>
public partial class ModalDialogHost : ChildWindow, IModalDialogHost
{
    ...
 
    public UserControl HostedContent
    {
        get
        {
            return (UserControl)contentHost.Content;
        }
        set
        {
            contentHost.Content = value;
        }
    }
 
    public void Show(DialogClosed closedCallback)
    {
        Show();
 
        // handle the Closed event, invoking the callback provided by the client
        Closed += (s, e) =>
            {
                closedCallback(this);
            };
    }
 
    public new string Title
    {
        set { base.Title = value; }
    }
}

Within WPF we have a similar implementation based on Window:

<Window x:Class="ModalDialogDemo.ModalDialogHost"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"        
        SizeToContent="WidthAndHeight"
        ShowInTaskbar="False">
    <Grid Margin="10">
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
 
        <!-- the hosted content -->
        <ContentPresenter x:Name="contentHost" Margin="10"/>
 
        <Button x:Name="CancelButton" Content="Cancel" Click="CancelButton_Click" Width="75"
                Height="23" HorizontalAlignment="Right" Margin="0,12,0,0" Grid.Row="1" />
        <Button x:Name="OKButton" Content="OK" Click="OKButton_Click" Width="75"
                Height="23" HorizontalAlignment="Right" Margin="0,12,79,0" Grid.Row="1" />
    </Grid>
</Window>

Note that with WPF we have a few extra configuration options to consider, such as the ShowInTaskbar property. The code-behind again implements our adapter interface:

/// <summary>
/// A WPF implementation of IModalDialogHost
/// </summary>
public partial class ModalDialogHost : Window, IModalDialogHost
{
    ...
 
    public UserControl HostedContent
    {
        get
        {
            return (UserControl)contentHost.Content;
        }
        set
        {
            contentHost.Content = value;
        }
    }
 
    public void Show(DialogClosed closedCallback)
    {
        ShowDialog();
 
        // invoke the callback when the dialog is closed
        closedCallback(this);
    }
}

So, within each project we have a framework specific implementation of this host. This allows us to create modal dialogs from the common / core code as follows:

IModalDialogHost dlg = new ModalDialogHost();
dlg.Title = "Pick a Colour";
dlg.HostedContent = new ModalDialog();
dlg.Show(DialogClosedHandler);
 
...
 
private void DialogClosedHandler(IModalDialogHost dialogHost)
{
    string colour = ((ModalDialog)dialogHost.HostedContent).SelectedColour;
    resultTextBox.Text = colour;                        
}

Here is a Silverlight application with a dialog implemented using this hosting concept:

And here is a screenshot of the equivalent WPF application, where you can see the modal dialog is popped up as a window:

(You can download the sourcecode below if you want to have a go with the WPF app)

One final note … I have deliberately steered clear of the whole patterns business in this blog post. As you are probably aware, the Model-View-ViewModel (MVVM) pattern is very popular within Silverlight and WPF, and often other patterns such as Service Locator and Dependency Injection are thrown into the mix. However, the community has been struggling a little with how best to model modal dialog behaviours from within an MVVM ViewModel. Interestingly a couple of developers have supplied solutions on codeproject and personal blogs, both faced questions and even criticisms that their approaches were not correct and unit testable (one of the central tenets of MVVM) and quickly followed up with articles demonstrating that you could in fact unit test their code (published here and here)

Whilst the use of a pattern such as MVVM which enforces a strong separation of application logic from the view will certainly help to solve problems such as WPF / Silverlight framework differences, in this particular instance I wanted to show a simple technical solution rather than walk into the awaiting patterns minefield!

You can download the full sourcecode for this article here: ModalDialogDemo.zip

Regards, Colin E.