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.

Silverlight ClipToBounds – Can I Clip It?, Yes You Can!

May 12th, 2009

With Silverlight, Panels do not clip their contents by default. See the following example:

noclip

Where we have a Grid containing another Grid which itself contains an ellipse, and a Canvas which contains an ellipse:

<Grid x:Name="LayoutRoot" Background="White">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
 
    <Grid Grid.Column="0" Background="Blue" Margin="20">                        
        <Grid Background="Yellow" Margin="20,40,-20,20">
            <Ellipse Fill="LightGreen" Width="80" Height="80" Margin="-40, -40, 0, 0"/>
        </Grid>
    </Grid>
 
    <Canvas Grid.Column="1"  Background="Aqua" Margin="20" >
        <Ellipse Fill="Red" Canvas.Top="-10" Canvas.Left="-10" Width="130" Height="130"/>
    </Canvas>
</Grid>

Often this is not the desired effect (although it is actually quite a useful feature of Canvas; You can simply add a Canvas to your visual tree without explicitly or implicitly setting its Size and use it as a mechanism for absolute positioning its children).

Fortunately Silverlight provides a Clip property on UIElement, allowing you to provide the clipping geometry for the element:

<Grid Width="200" Height="100">       
    <Grid.Clip>
        <RectangleGeometry Rect="0, 0, 200, 100"/>
    </Grid.Clip>
</Grid>

The above example creates a clipping geometry which matches the rectangular geometry of the Grid itself. Clearly more funky clipping geometries can be created, allowing for really cool effects, however most of the time I simply want my Panel clipped so that its children cannot escape!

The above example has a few problems, Firstly, it is a bit long-winded having to explicitly create the geometry each time I want to clip; Secondly, if my Grid’s size is calculated from its parent’s layout, how can I define the clip geometry in my XAML?; Finally, if my Grid’s geometry changes, its clipped geometry does not change.

In order to solve this problem I created a simple little attached behaviour, which allows you to define the clipping using the attached property Clip.ToBounds as illustrated below:

<UserControl x:Class="SilverlightClipToBounds.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:util="clr-namespace:Util" Width="300" Height="200">
    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
 
        <Grid Grid.Column="0" Background="Blue" Margin="20" util:Clip.ToBounds="true">       
            <Grid Background="Yellow" Margin="20,40,-20,20" util:Clip.ToBounds="true">
                <Ellipse Fill="LightGreen" Width="80" Height="80" Margin="-40, -40, 0, 0"/>
            </Grid>
        </Grid>
 
        <Canvas Grid.Column="1"  Background="Aqua" Margin="20" util:Clip.ToBounds="true">
            <Ellipse Fill="Red" Canvas.Top="-10" Canvas.Left="-10" Width="130" Height="130"/>
        </Canvas>
    </Grid>
</UserControl>

The result can be seen below:

crop

And here is the code for the attached behaviour itself:

public class Clip
{
    public static bool GetToBounds(DependencyObject depObj)
    {
        return (bool)depObj.GetValue(ToBoundsProperty);
    }
 
    public static void SetToBounds(DependencyObject depObj, bool clipToBounds)
    {
        depObj.SetValue(ToBoundsProperty, clipToBounds);
    }
 
    /// <summary>
    /// Identifies the ToBounds Dependency Property.
    /// <summary>
    public static readonly DependencyProperty ToBoundsProperty =
        DependencyProperty.RegisterAttached("ToBounds", typeof(bool),
        typeof(Clip), new PropertyMetadata(false, OnToBoundsPropertyChanged));
 
 
    private static void OnToBoundsPropertyChanged(DependencyObject d,
        DependencyPropertyChangedEventArgs e)
    {
        FrameworkElement fe = d as FrameworkElement;
        if (fe != null)
        {
            ClipToBounds(fe);
 
            // whenever the element which this property is attached to is loaded
            // or re-sizes, we need to update its clipping geometry
            fe.Loaded += new RoutedEventHandler(fe_Loaded);
            fe.SizeChanged += new SizeChangedEventHandler(fe_SizeChanged);
 
        }
    }
 
    /// <summary>
    /// Creates a rectangular clipping geometry which matches the geometry of the
    /// passed element
    /// </summary>
    private static void ClipToBounds(FrameworkElement fe)
    {
        if (GetToBounds(fe))
        {
            fe.Clip = new RectangleGeometry()
            {
                Rect = new Rect(0, 0, fe.ActualWidth, fe.ActualHeight)
            };
        }
        else
        {
            fe.Clip = null;
        }
    }
 
    static void fe_SizeChanged(object sender, SizeChangedEventArgs e)
    {
        ClipToBounds(sender as FrameworkElement);
    }
 
    static void fe_Loaded(object sender, RoutedEventArgs e)
    {
        ClipToBounds(sender as FrameworkElement);
    }    
}

When the ToBounds property is associated with an element, ClipToBounds is invoked to create a rectangular clip geometry. We also add event handlers for Loaded, which is a very useful event which is fired when an element has been laid out and rendered, in other words it’s size will have been computed, and SizeChanged. In the event handlers for both we simply update the clipping geometry.

This can be seen in action here, where clicking on the Grids or Canvas increases their size, with the clipping geometry growing accordingly:

You can download a demo project here: sliverlightcliptobounds.zip

Can I Clip It?, Yes You Can!

Regards, Colin E.

Using CSS Selectors for Styling in WPF

March 11th, 2009

When I first encountered WPF I was really impressed by its styling and templating features which are more powerful than anything else I had previously seen for desktop software development. The property-value pairing within styles instantly reminded me of CSS, however the WPF styles lack the most powerful feature of CSS – the selector. This blog post describes an attached behaviour for styling WPF application using CSS selectors.

Let’s first look at the anatomy of CSS. A CSS stylesheet is composed of a number of rules, each rule is composed of a selector which provides a pattern which the CSS selector engine matches against the target HTML document to locate nodes which to apply the style to. The styling information itself is defined within a declaration block, which consists of a number of comma-separated declarations enclosed within braces. The declaration block looks quite similar to a style within WPF …

div {
   font-size: 10px;
}
<Style TargetType="TextBlock">
    <Setter Property="FontSize" Value="10"/>
</Style>

So let’s ignore the declaration block for the moment and concentrate on the interesting part, the CSS selector.

Selectors define a pattern which is matched, the information which the selector contains can include the element (tag) type, class or ID. More complex selectors might also include element attribute values or use pseudo-classes. We can easily find parallel concepts to these within WPF …

  • HTML element type (e.g. div, p, a) => dependency object type (e.g. TextBlock, Button)
  • HTML ID (#menu) => dependency object Name (e.g. x:Name=”menu”)
  • HTML class (.title) => attached class property (e.g. css:Css.Class=”menu”)

The only concept for which there really is no correspondent in WPF is CSS class. This can easily be introduced via an attached property.

So now that we have the concept mapping, all we need now is a CSS selector engine that can apply our selector in order to extract matching objects from our visual / logical tree. Luckily I found a .NET CSS selector engine implementation in codeplex called Fizzler, you can read about it on the authors blog, or go straight to codeplex to download it. The author of this engine, Colin Ramsay, had done a great job of the implementation and had also written an exhaustive set of unit tests. The engine itself was tightly coupled to HtmlDocumentNodes, however I was able to slide a simple interface between the selector engine and the HTML document types as follows:

public interface IDocumentNode
{
    IAttributeCollection Attributes { get; }
    List<IDocumentNode> ChildNodes { get; }
    IDocumentNode ParentNode { get; }
    IDocumentNode PreviousSibling { get; }
    string Id { get; }
    string Class { get; }
    string Name { get; }
    bool IsElement { get; }
}
 
public interface IAttributeCollection
{
    IAttribute this[string name] { get; }
}
 
public interface IAttribute
{
    string Value { get; }
}

Again, thanks to the extensive unit tests, this refactor only took ~ 40 minutes.

The next job was to create an implementation of the above interfaces that walks the visual / logical trees, both were pretty easy to implement using the LogicalTreeHelper and VisualTreeHelper classes. With these classes implemented, I was able to query the visual tree using selectors as follows:

SelectorEngine engine = new SelectorEngine();
IList<IDocumentNode> matchingNodes = engine.Parse(".form TextBlock.warning");
 
// apply the style to all matching nodes
foreach (DependencyObjectNode matchingNode in matchingNodes)
{
   ...
}

In the above example, the selector engine returns any TextBlock with the class ‘warning’, which is a descendant of any element with the class ‘form’. This is certainly a novel way of querying the visual tree!

The next step is to define the declaration block and apply the style which they define to any matching elements.

public class StyleDeclarationBlock : List<StyleDeclaration>
{
}
 
public class StyleDeclaration
{
    public string Property { get; set; }
    public object Value { get; set; }
}

We can construct style declarations within XAML as follows:

<css:StyleDeclarationBlock>
    <css:StyleDeclaration Property="BorderThickness" Value="2"/>
    <css:StyleDeclaration Property="BorderBrush" Value="Black"/>
    <css:StyleDeclaration Property="CornerRadius" Value="5"/>
    <css:StyleDeclaration Property="Margin" Value="10,10,10,10"/>
</css:StyleDeclarationBlock>

The process for applying a style is pretty straightforward, the only subtle complication is that styles, once applied are frozen (i.e. immutable). Therefore, each time an element is selected via our selector engine, we clone the existing style then merge it with the new one. See the attached source-code for details.

Putting it all together involves the creation of an Attached Behaviour which associates a stylesheet with an element within our XAML. When the element is loaded, the rules within the stylesheet are applied. Let’s look at a simple demo …

Here is a simple UI defined in XAML. Note the absence of any styling information:

<Grid>
    <Border css:Css.Class="form">                
        <StackPanel Orientation="Vertical">
            <TextBlock css:Css.Class="title" Text="User Details"/>
            <Grid VerticalAlignment="Top" >
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*"/>    
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>  
 
                <Grid.RowDefinitions>
                    <RowDefinition/>
                    <RowDefinition/>
                    <RowDefinition/>
                    <RowDefinition/>
 
                </Grid.RowDefinitions>        
 
 
                <TextBlock css:Css.Class="mandatory" Text="Name"/>
                <TextBox  css:Css.Class="mandatory" Grid.Column="1" Text="Anthony Gatto"/>
 
                <TextBlock Grid.Row="1" Text="Age"/>
                <TextBox Grid.Row="1"  Grid.Column="1" Text="36"/>
 
                <TextBlock Grid.Row="2" Text="Profession"/>
                <TextBox Grid.Row="2"  Grid.Column="1" Text="Performer"/>
 
                <Button Grid.Row="3" Grid.Column="1" >Submit</Button>
            </Grid> 
        </StackPanel>
    </Border>
</Grid>

The resulting UI looks like this:

no-style

Now we apply our stylesheet to the root element in our XAML:

<css:StyleSheet x:Key="cssStyles">
    <css:StyleSheet.Rules>
 
        <css:StyleRule Selector=".form Grid *" SelectorType="LogicalTree">
            <css:StyleRule.DeclarationBlock>
                <css:StyleDeclarationBlock>
                    <css:StyleDeclaration Property="Margin" Value="4,4,4,4"/>
                </css:StyleDeclarationBlock>
            </css:StyleRule.DeclarationBlock>
        </css:StyleRule>
 
        <css:StyleRule Selector=".form TextBlock.mandatory">
            <css:StyleRule.DeclarationBlock>
                <css:StyleDeclarationBlock>
                    <css:StyleDeclaration Property="Foreground" Value="Red"/>
                </css:StyleDeclarationBlock>
            </css:StyleRule.DeclarationBlock>
        </css:StyleRule>
 
        <css:StyleRule Selector="Border.form">
            <css:StyleRule.DeclarationBlock>
                <css:StyleDeclarationBlock>
                    <css:StyleDeclaration Property="BorderThickness" Value="2"/>
                    <css:StyleDeclaration Property="BorderBrush" Value="Black"/>
                    <css:StyleDeclaration Property="CornerRadius" Value="5"/>
                    <css:StyleDeclaration Property="Margin" Value="10,10,10,10"/>
                </css:StyleDeclarationBlock>
            </css:StyleRule.DeclarationBlock>
        </css:StyleRule>
 
        <css:StyleRule Selector=".form .title">
            <css:StyleRule.DeclarationBlock>
                <css:StyleDeclarationBlock>
                    <css:StyleDeclaration Property="HorizontalAlignment" Value="Stretch"/>
                    <css:StyleDeclaration Property="HorizontalContentAlignment" Value="Center"/>
                    <css:StyleDeclaration Property="Background" Value="DarkBlue"/>
                    <css:StyleDeclaration Property="Foreground" Value="White"/>
                    <css:StyleDeclaration Property="FontSize" Value="13"/>
                    <css:StyleDeclaration Property="Padding" Value="3,3,3,3"/>
                    <css:StyleDeclaration Property="FontWeight" Value="Bold"/>
                </css:StyleDeclarationBlock>
            </css:StyleRule.DeclarationBlock>
        </css:StyleRule>
 
    </css:StyleSheet.Rules>
</css:StyleSheet>
 
...
 
<Grid css:Css.StyleSheet="{StaticResource cssStyles}">

Which styles our WPF application as follows:

styled

Let’s look at those selector in a little more detail. The first one, <css:StyleRule Selector=".form Grid *" SelectorType="LogicalTree"> is quite interesting. Here we are selecting everything within the Grid within our form and applying a margin. Note that here we are using a selector on the logical tree, otherwise we will also match elements within the control template of our TextBoxes and Buttons and will hence apply the margin internally within these elements also. However, in other contexts the ability to be able to apply styles within a controls template is a powerful concept.

The next selector <css:StyleRule Selector=".form TextBlock.mandatory"> styles our mandatory fields, the others styling the form title and borders.

The above is a pretty simple demonstration. The Fizzler CSS engine implements most of the CSS2.1 selectors and also some CSS3, so much more complex selector logic is possible. You can also include multiple comma-separated selectors on a rule.

Currently this code is not what I would call production-ready. It is just an idea that has been bugging me for a while that I wanted to get out of my head and share. I think this approach has a number of merits and it overcomes some of the restrictions in WPF styling. It provides a much more flexible mechanism for styling your UI, WPF gives you explicit styles and implicit styles, neither of which match the power of the CSS selector. This approach also adds the ability to merge styles, so if an element is matched by multiple rules these styles are merged. Furthermore, this approach does not impose the restriction that styles must have a TargetType, if the matching element does not have a corresponding dependency property, it simply does not pick up that piece of style information.

However, this implementation is not quite complete. I have not considered how to apply styles to dynamically generated content, ItemsControls for example.

I would be very interested to hear peoples opinions on this idea. Do you thin kit has potential? Perhaps if it was ported to Silverlight – it could even include mapping rules form ‘real’ CSS documents so that your Silverlight application could share style information with the rest of the web page which hosts it. But that is a job for another day …

You can download the code for this blog post here: wpfcssstyling.zip

UPDATE – the concept of using CSS to style WPF and Silvelight applications has been discussed on the WPF Discisples Group.

Regards, Colin E.