Colin Eberhardt's Technology Adventures

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.

BindingGroups for Total View Validation

January 26th, 2009

Over the weekend Sacha published a new article on codeproject, Total View Validation (does Sacha ever sleep?). This article addresses some of the perceived problems with the WPF binding framework, firstly, that the standard solution of using the ValidatesOnDataErrors property forces you to place validation logic into your bound business objects, and secondly, that there is no way to perform validation across multiple objects. His solution to both these problems was to construct a view model which contains the validation rules and applies them to all the objects within the form. A collection of validation failures are associated with the view model. These can either be rendered in their entirety or each control within the view can render errors relating to their context.

This blog post describes how BindingGroups can be used to address the problem of applying validation rules to multiple objects.

BindingGroups were introduced as part of .NET 3.5 SP1, Vincent Sibal provides a good introduction to this new features on his blog. I have previously blogged on the subject of BindingGroups, where I demonstrated how they can be used to provide improved error messages when type conversion fails during the binding process.

In this post I will take the application from Sacha’s article and modify it to use BindingGroups. The following is a screenshot of the application:

form

The form displays the details of two People (two instances of the Person class). The user inputs data using the above controls then clicks the Validate button to run all the Form’s validation rules. Most of the validation rules relate to a single property of the bound object, e.g. FullName cannot be empty. However there is one trick validation rule:

“If Person 1′s age is less than 18, Person 2′s age cannot be zero”

This rule cannot be evaluated by assocaiting it with the Age property of either person. It requires access to both Person instances, hence it really belongs at the scope of the form itself. This is where BindingGroups come in …

The XAML below shows our form which consists of a pair of StackPanels that contain fields bound to our Person instances by setting their DataContext properties in the code-behind. The ‘root’ element of the Window contains our BindingGroup.

<StackPanel Name="RootElement" Orientation="Vertical">
    <!-- the binding group for this form -->
    <StackPanel.BindingGroup>
        <BindingGroup Name="FormBindingGroup">
            <BindingGroup.ValidationRules>
                <local:PersonValidationRule
                            ValidationStep="CommittedValue"/>
            </BindingGroup.ValidationRules>
        </BindingGroup>
    </StackPanel.BindingGroup>
 
    <Border>
 
        <StackPanel Orientation="Vertical">
 
            <!-- Person One fields -->
            <StackPanel Name="personOnePanel" Orientation="Vertical">
                <Label Content="Person1"/>
 
                <!-- FirstName Property-->
                <StackPanel Orientation="Horizontal" >
                    <Label Content="FirstName" />
                    <TextBox Text="{Binding Path=FirstName, 
                                           BindingGroupName=FormBindingGroup,
                                           ValidatesOnDataErrors=true}" />                            
                </StackPanel>
 
                <!-- LastName Property-->
                ...
            </StackPanel>
 
            <!-- Person Twofields -->
            <StackPanel Name="personTwoPanel"  Orientation="Vertical">
                <Label Content="Person2"/>
 
                <!-- FirstName Property-->
                <StackPanel Orientation="Horizontal" >
                    <Label Content="FirstName" />
                    <TextBox Text="{Binding Path=FirstName, 
                                           BindingGroupName=FormBindingGroup,
                                           ValidatesOnDataErrors=true}" />                            
                </StackPanel>
 
                <!-- LastName Property-->
                ...
            </StackPanel>
 
        </StackPanel>
 
    </Border>
 
    <StackPanel Orientation="Vertical">
 
        <Button x:Name="btnValidate" Content="Validate" Margin="5"  />
 
    </StackPanel>
</StackPanel>

Rules associated with a BindingGroup have access to both their associated binding expressions, and the source objects used by these bindings. A BindingGroup becomes associated with a collection of binding expressions if it shares the same DataContext. However, in the above example we have a pair of objects, therefore we cannot share a DataContext across all the elements in our XAML (this is not strictly true, we could construct a view model for this purpose, however I really want to illustrate one of the other features of BindingGroups). BindingGroups can also be explicitly associated with a Binding via its BindingGroupName property.

So just what exactly can we do with our BindingGroup? It is possible to execute this rule at various different steps of the binding process, for example, it is possible to evaluate the rule before type conversion has occurred. In this instance, we are only interested in the converted value, hence the ValidationStep property is set to CommittedValue. The rule itself is very simple:

public class PersonValidationRule : ValidationRule
{
    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
        BindingGroup bindingGroup = (BindingGroup)value;
 
        // extract the two bound Person instances.
        Person personOne = bindingGroup.Items[0] as Person;
        Person personTwo = bindingGroup.Items[1] as Person;
 
        if (personTwo.Age==0 && personOne.Age<18)
        {
            return new ValidationResult(false,
                     "Person 2 Age can't be zero if Person1 Age < 18");
        }           
 
        return ValidationResult.ValidResult;
    }
}

We simply retrieve both Person instances, apply our rule raising an error if our condition is not met. Any validation errors returned by the BindingGroup become associated with the Validation.Errors attached property of the element which the BindingGroup is associated with. Therefore they can be accessed and styled in the ‘standard’ way, for example:

<Style TargetType="{x:Type TextBox}">
    <Style.Triggers>
        <Trigger Property="Validation.HasError" Value="true">
            <Setter Property="ToolTip"
                Value="{Binding RelativeSource={RelativeSource Self}, 
                       Path=(Validation.Errors)[0].ErrorContent}"/>
        </Trigger>
    </Style.Triggers>
</Style>

See the excellent article Validation in WPF for details.

However, BindingGroups offer one further advantage, validation errors from the associated binding expressions are also placed in the Validation.Errors collection, therefore we can output all the validation errors for our form by binding to this property as follows:

<ItemsControl ItemsSource="{Binding Path=(Validation.Errors), ElementName=RootElement}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Label Foreground="Red" Content="{Binding Path=ErrorContent}"/>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

The following screenshot shows the form where multiple validation errors are present:

errors

Conclusions

So … which approach is better? BindingGroups or Sacha’s ViewModel technique?

Personally, I don’t think there is one best approach – and this blog post is certainly not an attack on Sacha’s article, which presents an elegant solution. My intention is to simply show that it is possible to perform validation on multiple objects at a ‘wider’ scope with the standard WPF framework.

To quote Mike Hillberg, the Poo is Optional.

A sample project with the code from this post is available for download: bindinggroupsglobalvalidation.zip.

Regards, Colin E.

WPF DataGrid – Committing changes cell-by-cell

January 21st, 2009

In my recent codeproject article on the DataGrid I described a number of techniques for handling the updates to DataTables which are bound to the grid. These examples all worked on the assumption that you want to keep your database synchronised with the DataGrid, with changes being committed on a row-by-row basis, i.e. when the user finishes editing a row the changes are written to the database. The WPF DataGrid operates in a row-oriented manner making this a relatively straightforward scenario to implement.

However, what if you want to commit changes on a cell-by-cell basis? Firstly, lets have a look at the problem in a bit more detail. The following code displays a DataGrid, together with a ‘details’ view. Note that IsSynchronizedWithCurrentItem is set to true so that the details view and currently selected item within the grid will remain synchronised:

<DockPanel DataContext="{Binding Source={StaticResource PersonData}}">
 
  <Border DockPanel.Dock="Bottom" Padding="10">
    <Grid x:Name="RootElement">
      <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="1.8*"/>
      </Grid.ColumnDefinitions>
 
      <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition/>
        <RowDefinition/>
      </Grid.RowDefinitions>
 
      <Label Content="Forename:"/>
      <TextBox Grid.Column="1" Text="{Binding Forename}"/>
 
      <Label Grid.Row="1" Content="Surname:"/>
      <TextBox  Grid.Row="1" Grid.Column="1" Text="{Binding Surname}"/>
 
      <Label Grid.Row="2"  Content="Age:"/>
      <TextBox Grid.Row="2"   Grid.Column="1" Text="{Binding Age}"/>
    </Grid>
  </Border>
 
  <dg:DataGrid ItemsSource="{Binding}" Name="dataGrid"
         IsSynchronizedWithCurrentItem="true"/>
</DockPanel>

The ‘PersonData’ in this case is a DataTable that is constructed in the code-behind.

Now, with this example, if you make changes to a persons surname, then click on the age cell and make changes there, the changes in surname are not reflected in the details view below:

rownotcommitted

In the above example the user has entered the surname “Blunt” and has moved onto the age cell, however the changes are not reflected in the details view below.

Why is this?

The reason is that when you bind to a DataTable, you are actually binding to your DataTable’s DefaultView, which is of type DataView. As a result, each row of your table will be bound to a DataRowView. If you look at the documentation for DataRowView you will find that it implements the IEditableObject interface which is the significant factor here. This interface allows you to perform trasnactional changes to your object, i.e. you can change the object’s properties within a ‘transaction’, then commit then all in a single atomic action. By default, when you bind to a DataGrid this occurs when the user finishes editing a row, either by moving focus or hitting Enter. In order to allow cell-by-cell changes, we need to commit each time the user moves from one cell to the next in the currently selected row.

The DataGrid exposes a CellEditEnding event which looks like a good candidate for this, from the event arguments we can locate the current EditingElement (i.e. the TextBox which now occupies or cell that is in edit mode), the cell’s Column and Row, and from here we can locate the Row.Item whcih is our bound DataRowView. You might think that we can just commit the change in this event handler, however, this is an ‘Ending’ event, not an ‘Ended’ event. In other words the value has not yet been written to the row. This catches me out far too often – as does its RowEditEnding counterpart!

So, if we cannot commit the edit here, how about the CurrentCellChanged event? however this event does not tell us which cell we just left. Although a simple combination of the two provides the effect we are after:

private DataRowView rowBeingEdited = null;
 
private void dataGrid_CellEditEnding(object sender,
                                  DataGridCellEditEndingEventArgs e)
{
    DataRowView rowView = e.Row.Item as DataRowView;
    rowBeingEdited = rowView;
}
 
private void dataGrid_CurrentCellChanged(object sender, EventArgs e)
{
    if (rowBeingEdited != null)
    {
        rowBeingEdited.EndEdit();
    }
}

With this change in place, changes are committed cell-by-cell and the two view remain synchronised:

cellsynchronized

You can download an example project, wpfdatagridcellbycellcommits, changing the file extension from .doc to .zip.

Regards, Colin E.

LayoutTransform vs. RenderTransform – What’s the Difference?

December 19th, 2008

I have answered a few forum posts about the WPF transforms recently, mostly regarding confusion between RenderTransform and LayoutTransform. This brief blog post illustrates the difference between the two. The WPF layout system comprises, of two steps, followed by the rendering of the user interface (UI):

  • Measure
  • Arrange
  • Render

In the Measure step, the DesiredSize of each element is computed; in the Arrange step the position of child elements within their parents is determined; finally, in the Render step, the result user interface is rendered to the screen. Layout transforms and Render transforms are computed at different stages of the layout/render process:

  • LayoutTransform
  • Measure
  • Arrange
  • RenderTransform
  • Render

As a result, any transformations associated with an elements LayoutTransform property will have an impact on the subsequent Measure and Arrange steps. Whereas a RenderTransform will not have any impact on the layout process and will only effect rendering. The difference is probably best illustrated by an example:

transforms

In the above examples, it can be seen that when a LayoutTransform is applied, neighbouring elements are re-positioned to accommodate the transformed elements, whereas with the RenderTransform they are not. One common use of WPF transforms is to rotate ListView column headings. With the above examples it should be obvious that a LayoutTransform is required to achieve this effect.

You can download the demo project, wpfrenderandlayouttransforms, changing the file extension from .doc to .zip.

Regards, Colin E.