Colin Eberhardt's Adventures in WPF

Implementing RelativeSource binding in Silverlight

February 27th, 2009

In my previous post I demonstrated how an the WPF ElementName style binding can be emulated with Silverlight via an attached behaviour. As a brief recap, the technique involved creating an attached property, which when bound, adds a handler for the elements Loaded event. When the element is loaded, the even handler locates the named element and constructs a binding via a relay object. Here is the attached property in use:

<Rectangle Height="20" Fill="Green">
    <local:BindingHelper.Binding>
        <local:BindingProperties ElementName="Slider" SourceProperty="Value" TargetProperty="Width"/>
    </local:BindingHelper.Binding>
</Rectangle>
<Slider x:Name="Slider" Value="20" Minimum="0" Maximum="300"/>

Where the Rectangle’s Width is bound to the Slider’s Value. For details, and sourcecode, visit this blog post. Here I am going to extend this attached behaviour in order to emulate WPF’s RelativeSource binding.

For much of the time you will want to bind your visual elements to your data objects via their DataContext property. However, there are often times when you want too perform a binding for pure presentation purposes, for example, binding the width of two elements together. In this context WPFs RelativeSource and ElementName binding prove to be powerful and useful features of the binding framework.

Here I will extend the attached behaviour I described in my previous post to add RelativeSource binding capabilities. The properties of the attached property type have been extended:

public class BindingProperties
{
    public string SourceProperty { get; set; }
    public string ElementName { get; set; }
    public string TargetProperty { get; set; }
    public IValueConverter Converter { get; set; }
    public object ConverterParameter { get; set; }
    public bool RelativeSourceSelf { get; set; }
    public string RelativeSourceAncestorType { get; set; }
    public int RelativeSourceAncestorLevel { get; set; }
 
    public BindingProperties()
    {
        RelativeSourceAncestorLevel = 1;
    }
}

With the RelativeSourceSelf, RelativeSourceAncestorType and RelativeSourceAncestorLevel properties being used for relative source bindings. Taking RelativeSourceSelf as our first example, within WPF a RelativeSource.Self property indicates that the source of a binding should be the element which the binding is associated with. (I know – it sounds a bit crazy, but search google, it is surprisingly useful!).

private static void OnBinding(
    DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
    FrameworkElement targetElement = depObj as FrameworkElement;
 
    targetElement.Loaded += new RoutedEventHandler(TargetElement_Loaded);
}
 
private static void TargetElement_Loaded(object sender, RoutedEventArgs e)
{
    FrameworkElement targetElement = sender as FrameworkElement;
 
    // get the value of our attached property
    BindingProperties bindingProperties = GetBinding(targetElement);
 
    if (bindingProperties.ElementName != null)
    {
        // perform our 'ElementName' lookup
        ...
    }
    else if (bindingProperties.RelativeSourceSelf)
    {
        // bind an element to itself.
        CreateRelayBinding(targetElement, targetElement, bindingProperties);
    }
}

When the attached property becomes attached to our target element it adds a handler for the elements Loaded event (this is the attached behaviour). Within the event handler, we determine whether this is a relative source binding. If this is the case, the CreateRelayBinding method is invoked where the source and target element parameters are the same element. For details of how the CreateRelayBinding method works, see the previous blog post. An example of a relative-source self binding is show below, where a TextBox’s Width property is bound to its Text, if you trype in a new text value, the TextBox Width adjusts accordingly.

<TextBox Text="200" Margin="0,5,0,0">
    <local:BindingHelper.Binding>
        <local:BindingProperties TargetProperty="Width" SourceProperty="Text"
                                 RelativeSourceSelf="True"/>                                
    </local:BindingHelper.Binding>
</TextBox>

The next type of RelativeSource binding I am going to tackle is the FindAncestor mode. You use this type of binding when you want to bind to an element of a specific type that is located further up the visual tree that the target element. The following code snippet shows how the attached behaviour achieves this type of binding:

private static void TargetElement_Loaded(object sender, RoutedEventArgs e)
{
    FrameworkElement targetElement = sender as FrameworkElement;
 
    // get the value of our attached property
    BindingProperties bindingProperties = GetBinding(targetElement);
 
    if (bindingProperties.ElementName != null)
    {
        // perform our 'ElementName' lookup
        ...
    }
    else if (bindingProperties.RelativeSourceSelf)
    {
        // bind an element to itself.
        ...
    }
    else if (!string.IsNullOrEmpty(bindingProperties.RelativeSourceAncestorType))
    {
 
        // navigate up the tree to find the type
        DependencyObject currentObject = targetElement;
 
        int currentLevel = 0;
        while (currentLevel < bindingProperties.RelativeSourceAncestorLevel)
        {
            do
            {
                currentObject = VisualTreeHelper.GetParent(currentObject);
            }
            while (currentObject.GetType().Name != bindingProperties.RelativeSourceAncestorType);
            currentLevel++;
        }
 
        FrameworkElement sourceElement = currentObject as FrameworkElement;
 
        // bind them
        CreateRelayBinding(targetElement, sourceElement, bindingProperties);
    }
}

The code above simply navigates up the visual tree to find the n’th element of a given type. When the given type has been located, the relay binding between the two is constructed.

This type of binding is incredibly flexible, the following shows a number of examples:

<UserControl x:Class="SilverlightBinding.PageTwo">
 
    <UserControl.Resources>
        <local:VisibilityConverter x:Key="VisibilityConverter"/>
    </UserControl.Resources>
 
    <StackPanel x:Name="TheOuterStackPanel" Background="White"> 
        <StackPanel Name="theInnerStackPanel" Width="400">
            <!-- bind the textbox width to its text property -->
            <TextBox Text="200" Margin="0,5,0,0">
                <local:BindingHelper.Binding>
                    <local:BindingProperties TargetProperty="Width" SourceProperty="Text"
                                             RelativeSourceSelf="True"/>                                
                </local:BindingHelper.Binding>
            </TextBox>
            <!-- bind the textbox text to this UserControls width property -->
            <TextBox Margin="0,5,0,0">
                <local:BindingHelper.Binding>
                    <local:BindingProperties TargetProperty="Text" SourceProperty="Width"
                                             RelativeSourceAncestorType="PageTwo"/>                                
                </local:BindingHelper.Binding>
            </TextBox>
            <!-- bind the textbox text to the inner stack panels name property -->
            <TextBox Margin="0,5,0,0">
                <local:BindingHelper.Binding>
                    <local:BindingProperties TargetProperty="Text" SourceProperty="Name"
                                             RelativeSourceAncestorType="StackPanel"/>                                
                </local:BindingHelper.Binding>
            </TextBox>
            <!-- bind the textbox text to the outer stack panels name property -->
            <TextBox Margin="0,5,0,0">
                <local:BindingHelper.Binding>
                    <local:BindingProperties TargetProperty="Text" SourceProperty="Name"
                                             RelativeSourceAncestorType="StackPanel"
                                             RelativeSourceAncestorLevel="2"/>                                
                </local:BindingHelper.Binding>
            </TextBox>
            <!-- bind a slider's value to its parent grid's height property  -->
            <Grid Height="30"  Margin="0,5,0,0" Background="CadetBlue" HorizontalAlignment="Left" >
                <Slider Width="400"  HorizontalAlignment="Left" Minimum="10" Maximum="40">
                    <local:BindingHelper.Binding>
                        <local:BindingProperties TargetProperty="Value" SourceProperty="Height"
                                             RelativeSourceAncestorType="Grid"/>
                    </local:BindingHelper.Binding>
                </Slider>
            </Grid>
        </StackPanel>     
    </StackPanel>
</UserControl>

You can see in the above XAML, examples of relative-source self and find ancestor bindings with a variety of types and ancestor-levels. And here it is in action:

If you type in a new value in the top text box its width will change, or move the slider to see the height of its parent grid changing. And all without any code-behind of course!

Many of these effects shown above could be performed via element-name bindings. However, you are not always the creator, or template provider for all the visual elements rendered to screen. One common example is that of the ItemsControl, the ListBox being an example of this type of control. Here you supply a DataTemplate, with your visual elements being rendered inside a ListBoxItem container. Therefore, there is no way to bind to the ListBoxItem via an element name or otherwise. However, you can reach the ListBoxItem by navigating up the tree using a relative-source binding as show below:

<ListBox Name="grid" Width="200" HorizontalAlignment="Left">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <Grid Width="180">
                <TextBlock Text="{Binding Forename}"/>
 
                <Ellipse Width="10" Height="10" Fill="Red" HorizontalAlignment="Right">
                    <local:BindingHelper.Binding>
                        <local:BindingProperties TargetProperty="Visibility" SourceProperty="IsSelected"
                                                 Converter="{StaticResource VisibilityConverter}"
                                                 RelativeSourceAncestorType="ListBoxItem"/>
                    </local:BindingHelper.Binding>
                </Ellipse>
            </Grid>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

In the above example we have a DataTemplate which contains an ellipse. As the ListBox generates each ‘row’, it creates a ListBoxItem and populates it with the visual elements from our data template. When our ellipse is created, and loaded, the attached behaviour fires, navigating up the visual tree to find the first ListBoxItem it encounters. When it finds it, it creates a single instance of our relay object, binding both the ListBoxItem.IsSelected and Ellipse.Visibilty (via a suitable value converter) together via the relay object.

And here it is in action (click an item to see the ellipse) …

You can download a visual studio project with all the sourcecode: silverlightrelativesourcebinding.zip.

Enjoy, Colin E.

ElementName binding in Silverlight via Attached Behaviours

February 22nd, 2009

As a relative newcomer to Silverlight I was happily greeted by the warm feeling of familiarity when I started developing. It is surprisingly easy to make the transition from WPF to Silverlight developer, with most of the core concepts being just the same. However, there are some parts of the WPF framework that you start to miss. One of those is ElementName binding.

For those of you not familiar with the concept I will give a very brief overview. When binding the properties of your visual elements within XAML, the source of this binding will be the object associated with the elements DataContext. This works just fine for binding your business objects to the UI that exposes their properties. However, with WPF, binding gives you so much more than just a mechanism for exposing your business data, it also allows you to bind properties between visual elements. This is a powerful concept that allows you to create complex layouts, beyond that which is possible by the framework provided Panels (for some examples of this, see my article on creating a Bullet Graph). In order to enable this, WPF provides ElementName and RelativeSource bindings, giving you a powerful mechanism for locating other elements within your visual tree to bind to. A simple example, where a rectangle’s Width is bound to a named slider is given below:

<Rectangle Width="{Binding Path=Value, ElementName=MySlider}" Height="20" Fill="Green"/>
<Slider x:Name="MySlider" Value="25" Minimum="0" Maximum="300"/>

Unfortunately Silverlight does not have this capability.

My first thought was to simply point the DataContext of the target element to the source element in order to allow property binding between them. However, much to my surprise, Silverlight dependency properties do not support property changed notification.

A common solution to this problem is to employ a Relay Object, as described in a number of blog posts. An object with a single property, Value, which implement INotifyPropertyChanged is bound to the two visual elements. A simple example is illustrated below:

<UserControl.Resources>
    <local:RelayObject x:Key="Relay">        
        <local:RelayObject.Value>
            <system:Double>20</system:Double>
        </local:RelayObject.Value>
    </local:RelayObject>
</UserControl.Resources>
 
<StackPanel x:Name="LayoutRoot" Background="White">
    <Rectangle Width="{Binding Path=Value, Source={StaticResource Relay}, Mode=TwoWay" Height="20" Fill="Green"/>
    <Slider Value="{Binding Path=Value, Source={StaticResource Relay}, Mode=TwoWay}" Minimum="0" Maximum="300"/>       
</StackPanel>

Here an instance of our RelayObject is bound to both the Rectangle’s Width and the Slider’s Value, effectively binding these to properties together. This works just fine, however it does not really feel like the WPF ElementName binding, furthermore, you have to add a new RelayObject instance for each binding.

My solution makes use of the Attached Behaviour pattern which is becoming very popular in WPF and Silverlight. First we define an attached property which uses a type which contains the information we need to create our binding, i.e source and target properties, and the name of the element which we wish to bind to.

public class BindingProperties
{
    public string SourceProperty { get; set; }
    public string ElementName { get; set; }
    public string TargetProperty { get; set; }
}
 
public static class BindingHelper
{
    public static BindingProperties GetBinding(DependencyObject obj)
    {
        return (BindingProperties)obj.GetValue(BindingProperty);
    }
 
    public static void SetBinding(DependencyObject obj, BindingProperties value)
    {
        obj.SetValue(BindingProperty, value);
    }
 
    public static readonly DependencyProperty BindingProperty =
        DependencyProperty.RegisterAttached("Binding", typeof(BindingProperties), typeof(BindingHelper),
        new PropertyMetadata(null, OnBinding));
 
 
    /// <summary>
    /// property change event handler for SelectAllButtonTemplate
    /// </summary>
    private static void OnBinding(
        DependencyObject depObj, DependencyPropertyChangedEventArgs e)
    {
        FrameworkElement targetElement = depObj as FrameworkElement;
 
        targetElement.Loaded += new RoutedEventHandler(TargetElement_Loaded);
    }
 
    private static void TargetElement_Loaded(object sender, RoutedEventArgs e)
    {
        FrameworkElement targetElement = sender as FrameworkElement;
 
        // get the value of our attached property
        BindingProperties bindingProperties = GetBinding(targetElement);
 
        // perform our 'ElementName' lookup
        FrameworkElement sourceElement = targetElement.FindName(bindingProperties.ElementName) as FrameworkElement;
 
        // bind them
        CreateRelayBinding(targetElement, sourceElement, bindingProperties);
    }    
}

What the above code does is it defines the attached property of type BindingProperties. The OnBinding method is invoked whenever this property is attached to a dependency object. Within this event handler, we add a handler to the element’s Loaded event. This event occurs when the element is laid out within the visual tree and ready for action, it is at this point that we can perform our ElementName lookup.

Within the TargetElement_Loaded event handler we use the FrameworkElement.FindName to look-up the named source element for our binding. This method locates any element with the given name that is within the same XAML namescope as itself. Interestingly, this is the same method that Visual Studio uses for creating member variables within your code-behind file from the named elements within your XAML. Once, the named element has been located, a relay binding is constructed between them, as follows:

private static readonly BindingFlags dpFlags = BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy;
 
private static void CreateRelayBinding(FrameworkElement targetElement, FrameworkElement sourceElement,
    string targetProperty, string sourceProperty, IValueConverter converter)
{
     // create a relay binding between the two elements
     ValueObject relayObject = new ValueObject();
 
     // find the source dependency property
     FieldInfo[] sourceFields = sourceElement.GetType().GetFields(dpFlags);
     FieldInfo sourceDependencyPropertyField = sourceFields.First(i => i.Name == sourceProperty + "Property");
     DependencyProperty sourceDependencyProperty = sourceDependencyPropertyField.GetValue(null) as DependencyProperty;
 
     // initialise the relay object with the source dependency property value 
     relayObject.Value = sourceElement.GetValue(sourceDependencyProperty);
 
     // create the binding for our target element to the relay object, this binding will
     // include the value converter
     Binding targetToRelay = new Binding();
     targetToRelay.Source = relayObject;
     targetToRelay.Path = new PropertyPath("Value");
     targetToRelay.Mode = BindingMode.TwoWay;
     targetToRelay.Converter = converter;
 
     // find the target dependency property
     FieldInfo[] targetFields = targetElement.GetType().GetFields(dpFlags);
     FieldInfo targetDependencyPropertyField = targetFields.First(i => i.Name == targetProperty + "Property");
     DependencyProperty targetDependencyProperty = targetDependencyPropertyField.GetValue(null) as DependencyProperty;
 
     // set the binding on our target element
     targetElement.SetBinding(targetDependencyProperty, targetToRelay);
 
     // create the binding for our source element to the relay object
     Binding sourceToRelay = new Binding();
     sourceToRelay.Source = relayObject;
     sourceToRelay.Path = new PropertyPath("Value");
     sourceToRelay.Mode = BindingMode.TwoWay;
 
     // set the binding on our source element
     sourceElement.SetBinding(sourceDependencyProperty, sourceToRelay);        
}

The above code simply creates our relay object, initialising its value from the sourtce element, then constructs the bindings from relay-to-source and relay-to-target. The only ‘tricky’ part of the above code is the use of reflection to locate the static dependency properties of each element.

Using the above attached property (behaviour), an element name binding can be constructed as follows:

<Rectangle Height="20" Fill="Green">
    <local:BindingHelper.Binding>
        <local:BindingProperties ElementName="Slider" SourceProperty="Value" TargetProperty="Width"/>
    </local:BindingHelper.Binding>
</Rectangle>
<Slider x:Name="Slider" Value="20" Minimum="0" Maximum="300"/>

The advantages of this approach are twofold, firstly, we do not have to explicitly create a relay object for each ElementName bindings, secondly, the source property value is used to initialise the relay object directly. It is also straightforward exercise to extend the above to add ValueConverters into the binding.

But what if we want to bind two different properties to our slider? If for example we want to bind the Widths of two rectangles, we would need to ensure that a single relay object is constructed which is shared by the Slider and both Rectangles. A simple solution to this problem is to maintain a dictionary of the source bindings to their associated relay objects. If we bind more than one target property to a particular source property, the relay object is re-used. Any value converters are specified on the target binding, therefore we can bind multiple properties to the source with different converters.

The following example shows a couple of Rectangles bound to our slider where their Widths are scaled by different factors:

<Rectangle Height="20" Fill="Green">
    <local:BindingHelper.Binding>
        <local:BindingProperties ElementName="Slider" SourceProperty="Value"
            TargetProperty="Width" Converter="{StaticResource ScalingConverter}" ConverterParameter="2"/>
    </local:BindingHelper.Binding>
</Rectangle>
<Rectangle Height="20" Fill="Green">
    <local:BindingHelper.Binding>
        <local:BindingProperties ElementName="Slider" SourceProperty="Value"
            TargetProperty="Width" Converter="{StaticResource ScalingConverter}" ConverterParameter="4"/>
    </local:BindingHelper.Binding>
</Rectangle>
<Slider x:Name="Slider" Value="20" Minimum="0" Maximum="300"/>

And here is a demonstration of the above implemented both manually with relay objects and with this attached behaviour:

For details of the above, please refer to the attached project download.

This approch does have a few downsides – unfortunately the syntax is a little verbose. Also, in its present form you can only create on element name binding per visual element. However, the interesting feature of this technique is that it could be readily adapted to search for the source element in other ways, for example emulating the WPF relative source bindings. Perhaps I will have a go at that one next week ….

You can download the sourcecode for this blog post.

Styling hard-to-reach elements in control templates with attached behaviours

February 10th, 2009

OK, the title of this blog post is not very snappy, but it is not an easy problem to describe in a few short words. Here’s the rub, the WPF DataGrid has a select-all button located in the top-left corner, just like Excel and many other grid controls / applications. However, with the default style, this button is barely visible and I would not be surprised if a user of the grid failed to see it.

select-all

Unfortunately restyling this button is not all that straightforward. The more complex WPF controls like the DataGrid and ListView typically expose the template used to construct, and the style applied to their various visual elements.  However, the DataGrid does not expose a style or template for the select-all button.

Fortunately, all is not lost. With WPF, if a control does not expose a style for one of its component parts, you can replicate and replace its entire template. Sometimes this approach feels a little heavy-handed … “I can’t seem to find a way to fit those flashy alloy wheels to my car, so I’ll just by a new car that already has the wheels I like”.

I want to explore a more lightweight approach.

Whilst it is usually preferable to modify a controls template from XAML, in cases like this, I prefer the more concise approach of modifying them in code-behind. In order to do this, you need to handle the FrameworkElement.Loaded event on the control whos template you wish to modify. This event is fired after the control’s visual tree has been constructed, to verify this add a breakpoint in the event handler and inspect the visual tree with Mole. The image below shows the visual tree of my DataGrid which I have expanded to locate the select-all button.

datagrid-mole

You can see that the button is the first element, four steps down the visual tree, therefore it should be easy to locate by walking the visual tree. Once it has been reached we can simply replace its template to the one which we desire:

private void Grid_Loaded(object sender, RoutedEventArgs e)
{
    DependencyObject dep = sender as DependencyObject;
 
    // Navigate down the visual tree to the button
    while (!(dep is Button))
    {
        dep = VisualTreeHelper.GetChild(dep, 0);
    }
    Button button = dep as Button;
 
    // apply our new template
    object res = FindResource("SelectAllButtonTemplate");
    button.Template = res as ControlTemplate
}

The template SelectAllButtonTemplate is simply defined as a resource of our Window.

This works just fine for our single grid, but what if we want to use the same trick on multiple DataGrids? (Besides, we have code-behind, which in WPF is a bit of a dirty word!).

The Attached Behaviour pattern is a useful WPF pattern that proves very useful in this situation. In brief, attached properties add state to a your WPF elements, whereas attached behaviours add behaviour. When an attached property becomes associated with a dependency object, an event is raised. We can handle this event and register handlers on the events of the object being attached to, in this case, our DataGrid’s Loaded event.

The following code is our attached behaviour in its entirety:

public static class DataGridStyleBehaviour
{
    #region attached property
 
    public static ControlTemplate GetSelectAllButtonTemplate(DataGrid obj)
    {
        return (ControlTemplate)obj.GetValue(SelectAllButtonTemplateProperty);
    }
 
    public static void SetSelectAllButtonTemplate(DataGrid obj, ControlTemplate value)
    {
        obj.SetValue(SelectAllButtonTemplateProperty, value);
    }
 
    public static readonly DependencyProperty SelectAllButtonTemplateProperty =
        DependencyProperty.RegisterAttached("SelectAllButtonTemplate",
        typeof(ControlTemplate), typeof(DataGridStyleBehaviour),
        new UIPropertyMetadata(null, OnSelectAllButtonTemplateChanged));
 
    #endregion
 
    #region property behaviour
 
    // property change event handler for SelectAllButtonTemplate
    private static void OnSelectAllButtonTemplateChanged(
        DependencyObject depObj, DependencyPropertyChangedEventArgs e)
    {
        DataGrid grid = depObj as DataGrid;
        if (grid == null)
            return;
 
        // handle the grid's Loaded event
        grid.Loaded += new RoutedEventHandler(Grid_Loaded);
    }
 
    // Handles the DataGrid's Loaded event
    private static void Grid_Loaded(object sender, RoutedEventArgs e)
    {
        DataGrid grid = sender as DataGrid;
        DependencyObject dep = grid;
 
        // Navigate down the visual tree to the button
        while (!(dep is Button))
        {
            dep = VisualTreeHelper.GetChild(dep, 0);
        }
        Button button = dep as Button;
 
        // apply our new template
        ControlTemplate template = GetSelectAllButtonTemplate(grid);
        button.Template = template;
    }
 
    #endregion
}

This attached property can be used as illustrated below, where we apply the SelectAllButtonTemplate from our Window’s resources to a DataGrid:

<Window ...
    Title="BBC News RSS" Height="300" Width="400">
 
    <Window.Resources>
        <XmlDataProvider x:Key="NewsFeed"
                     Source="http://newsrss.bbc.co.uk/rss/newsonline_uk_edition/front_page/rss.xml"  
                     XPath="//item" />
 
        <ControlTemplate x:Key="SelectAllButtonTemplate" TargetType="{x:Type Button}">
            <Grid>
                <Rectangle  x:Name="Border"
                            Fill="Pink" 
                            SnapsToDevicePixels="True" />
                <Polygon   x:Name="Arrow"
                           HorizontalAlignment="Right"
                           VerticalAlignment="Bottom"
                           Margin="8,8,3,3"
                           Opacity="0.15"
                           Fill="Black"
                           Stretch="Uniform"
                           Points="0,10 10,10 10,0" />
            </Grid>            
        </ControlTemplate>
    </Window.Resources>
 
    <Grid>
        <dg:DataGrid Name="dataGrid" AutoGenerateColumns="False" 
                     local:DataGridStyleBehaviour.SelectAllButtonTemplate="{StaticResource SelectAllButtonTemplate}"
                     ItemsSource="{Binding Source={StaticResource NewsFeed}}" RowHeaderWidth="25">
            <dg:DataGrid.Columns>
                <dg:DataGridTextColumn Header="Title" Binding="{Binding XPath=title}" Width="*"/>
                <dg:DataGridHyperlinkColumn   Header="Url" Binding="{Binding XPath=link}" Width="*"/>
                <dg:DataGridTextColumn Header="Date" Binding="{Binding XPath=pubDate}" Width="*"/>
            </dg:DataGrid.Columns>
        </dg:DataGrid>
    </Grid>
</Window>

The result is a beautiful pink select-all button that I don’t think any user would miss in a hurry:

pinkbutton

And a clean code-behind file for our Window :-)

You can download the demo project for this blog post in the following zip file, wpfstylinghiddenelements.

One last thing … if you are implementing a WPF / Silverlight control, please expose the styles and templates for all component parts of your control!

Regards, Colin E.

Adding a Location Crosshair to Silverlight Charts

February 4th, 2009
UPDATE – The March09 update of the Silverlight toolkit is incompatible with the code detailed below. For an up-to-date version see the following blog post.

This blog post describes how to add a location crosshair to your Silverlight charts as shown below:

The chart itself is rendered using the charting component of the recently release Silverlight Toolkit. Creating and displaying a simple line chart is as simple as referencing the correct silverlight toolkit namespaces and placing a chart with an associated lineseries into the XAML for your page:

<charting:Chart Name="Chart" PlotAreaStyle="{StaticResource PlotAreaStyle}">
    <charting:LineSeries
		IndependentValueBinding="{Binding Path=Key}"
		DependentValueBinding="{Binding Path=Value}"/>
</charting:Chart>

In this example, the data is provided in the form of an XML file as shown in the snippet below:

<?xml version="1.0" encoding="UTF-8"?>
<history generated="2009/02/03 10:51:02 GMT+0000">
  <dataPoints>
    <dataPoint date="2009/02/03 08:01:00 GMT+0000" change="21.61" changePercent="0.36" value="6073.99"/>
    <dataPoint date="2009/02/03 08:02:45 GMT+0000" change="16.11" changePercent="0.27" value="6068.49"/>
    ...
    <dataPoint date="2009/02/03 10:50:45 GMT+0000" change="0.4" changePercent="0.01" value="6052.78"/>
  </dataPoints>
</history>

This data shows the performance of the FTSE 100 index on a relatively uneventful morning. The XML file is read using LINQ to XML into a collection of KeyValuePairs. The LineSeries’ ItemsSource is set to the result of this query, with the bindings in the above XAML binding the Key and Value properties of each KeyValuePair instance.

protected LineSeries LineSeries
{
    get
    {
        return ((LineSeries)Chart.Series[0]);
    }
}
 
public Page()
{
    InitializeComponent();
 
    XDocument doc = XDocument.Load("chartData.xml");
    var elements = from dataPoint in doc.Descendants("dataPoint")                           
                   select new KeyValuePair<DateTime, double>
                   (
                       DateTime.Parse(dataPoint.Attribute("date").Value.Substring(0, 19)),
                       Double.Parse(dataPoint.Attribute("value").Value)
                   );
 
    LineSeries.ItemsSource = elements;
}

This results in the following (rather ugly!) chart:

ftse100

Whilst the background fade effect and line series datapoints can be modified by styling the chart, the removal of unwanted elements such as the legend require us to delve into the the Chart’s control template. The modified control template is given below:

<charting:Chart.Template>
    <ControlTemplate TargetType="charting:Chart">
        <Grid Name="ChartArea" Style="{TemplateBinding ChartAreaStyle}">
 
            <!-- NOTE: the chart legend and title have been removed -->
            <Grid Height="250" x:Name="PlotArea" Style="{TemplateBinding PlotAreaStyle}"
                  MouseMove="PlotArea_MouseMove" MouseEnter="PlotArea_MouseEnter" MouseLeave="PlotArea_MouseLeave">
 
                <!-- the standard chart template components -->
                <Grid x:Name="GridLinesContainer" />
                <Grid x:Name="SeriesContainer"/>
                <Border BorderBrush="#FF919191" BorderThickness="1" />
 
                <!-- a location crosshair -->
                <Grid Name="Crosshair" Visibility="Collapsed">
                    <Line Name="Vertical" X1="{Binding Path=X}" Y1="0" X2="{Binding Path=X}" Y2="250" Stroke="Black"/>
                    <Line Name="Horizontal" X1="0" Y1="{Binding Path=Y}" X2="400" Y2="{Binding Path=Y}" Stroke="Black"/>
                </Grid>
 
                <!-- a location 'legend' -->
                <Border Name="LocationIndicator" Visibility="Collapsed" Style="{StaticResource LocationLegendStyle}">
                    <StackPanel Orientation="Horizontal" Margin="5">
                        <TextBlock Text="Location: "/>
                        <TextBlock Text="{Binding Path=Key}"/>
                        <TextBlock Text=", "/>
                        <TextBlock Text="{Binding Path=Value}"/>
                    </StackPanel>
                </Border>
            </Grid>
        </Grid>
    </ControlTemplate>
</charting:Chart.Template>

In the above template you can see a couple of Grids, one named GridLinesContainer, and the other names SeriesContainer. When constructing the chart, the chart control will navigate the visual tree constructed from its control template to find elements of these names. It will then add the grid lines and series to these containers accordingly. This gives us the freedom to modify the chart’s control template whilst still allowing it to function normally. In the above template I have added two new elements, a cross hair and a location indicator. I have also added handlers for a few of the mouse events on the plot area.

The mouse move event handler in the code behind finds the current mouse position within the PlotArea grid. Firstly, the coordinates of this point are found within the chart coordinate system (more on this later), following this, the DataContexts for the Crosshair and PlotArea are set to the mouse position and location within the chart coordinate system respectively. Looking in the above XAML we can see that the Crosshair contains a pair of lines which are bound to the X and Y properties of their DataContext, ensuring that the two lines intersect at the current mouse location. The LocationIndicator will inherit the PlotArea’s DataContext, allowing it to output the current location in the chart coordinate system.

private void PlotArea_MouseMove(object sender, MouseEventArgs e)
{
    if (LineSeries.ItemsSource == null)
        return;
 
    Point mousePos = e.GetPosition(PlotArea);
    KeyValuePair<DateTime, double> crosshairLocation = GetPlotAreaCoordinates(mousePos);
 
    PlotArea.DataContext = crosshairLocation;
    Crosshair.DataContext = mousePos;
    PlotArea.Cursor = Cursors.None;
}
 
protected Grid PlotArea
{
    get { return ChartArea.FindName("PlotArea") as Grid; }
}
 
protected Grid Crosshair
{
    get { return ChartArea.FindName("Crosshair") as Grid; }
}
 
protected Grid ChartArea
{
    get
    {
        // chart area is within a different namescope to this page, therefore
        // we must navigate the visual tree to locate it
        return VisualTreeHelper.GetChild(Chart, 0) as Grid;
    }
}

One thing worth noting in the above code is that way in which the PlotArea and Crosshair elements are located. Usually it is possible to simply name elements within your XAML using their Name attribute then refer to them directly in the code-behind or locate them via the DependencyObject.FindName method. However, FindName relies on the named element being in the same namescope. Control template are in a different namescope to the XAML page which they are defined within, therefore we have to navigate the visual tree to find the root element of the chart control, then invoke FindName from there. See the MSDN page on XAML namescopes for more details.

The GetPlotAreaCoordinates method is given below:

private KeyValuePair<DateTime, double> GetPlotAreaCoordinates(Point position)
{
    Range<IComparable> yAxisHit = ((IRangeAxis)YAxis).GetPlotAreaCoordinateValueRange(PlotArea.Height - position.Y);
    Range<IComparable> xAxisHit = ((IRangeAxis)XAxis).GetPlotAreaCoordinateValueRange(position.X);
 
    return new KeyValuePair<DateTime, double>((DateTime)xAxisHit.Minimum, (double)yAxisHit.Minimum);
}

The silverlight chart axes actually provides methods which can be used to translate from screen coordinates to chart coordinates via the IRangeAxis interface, however they are hidden by explicit interface implementation of these interface methods by the concrete axis classes. It is a shame that such useful methods are hidden! I have never really seen the value of explicit interface implementation.

In conclusion, this blog post has shown how simple it is to add a crosshair to your silverlight linecharts. What I personally find interesting is the way in which you can not only customise the appearance of Silverlight (and WPF) controls, but also add behaviour without the need to subclass the controls themselves. I think that this makes life easier for the users of Silverlight/WPF controls, in that they can use the same techniques to extend any control rather than relying on the control having built in extension points (it is often a source of frustration when developing with Windows Forms (or similar technologies) when you wish to customise some aspect of an existing control, however the control author has not provided events, or virtual methods for this purpose). It also of course makes life easier for the control developer in that they do not have to spend so much time thinking about how their control might be extended and adapted by its users.

You can download a visual studio project with the code from this blog post, sllinechartcrosshair.zip.

Regards, Colin E.