Colin Eberhardt's Adventures in WPF

Adding a Location Crosshair to Silverlight charts (again!)

March 23rd, 2009

Silverlight is moving fast. Really fast. The recent MIX09 conference saw the release of Silverlight 3 (Beta) and also a new release of the Silverlight Toolkit. All this change is making it hard for us bloggers to keep up!

Just over a month ago I posted an article on this blog about how to add a location crosshair to the Silverlight Toolkit Charts. This blog post briefly discusses how the charts have changed with the release last week, and updates the code accordingly.

If you want to know how the general approach works, I would recommend reading the previous blog post, this post serves as an update and discussion on how the chart template has changed.

There have been a number of minor changes to the charts that affect my previous implementation, including a change of namespace and orientation type on the chart axes. However, the biggest change is in the template of the chart control itself. Previously the chart template was structured via a Grid as shown below:

<ControlTemplate TargetType="charting:Chart">
    <Grid Name="ChartArea" Style="{TemplateBinding ChartAreaStyle}">
 
        <!-- ##### chart control adds column / row definitions and axes here #### -->
 
        <Grid Height="250" x:Name="PlotArea" Style="{TemplateBinding PlotAreaStyle}">
 
            <!-- the standard chart template components -->
            <Grid x:Name="GridLinesContainer" />
            <Grid x:Name="SeriesContainer"/>
            <Border BorderBrush="#FF919191" BorderThickness="1" />
 
            <!-- ##### I added cross hair and legend here #### -->
        </Grid>
    </Grid>
</ControlTemplate>

After loading the template the chart adds the axes and the appropriate column / row definitions. This leaves us free to add new visual content withing the ‘PlotArea’ grid. Inspecting the visual tree, with Silverlight Spy, it look like the following:

visualtree-before

The content I added in order to construct a crosshair and legend are highlighted in red.

The March release of the Silverlight toolkit modifies the way in which axes are added to the chart by the introduction of a new layout panel, the EdgePanel (think DockPanel!). This panel allows you to locate (or dock) elements at one of the four edges of a panel or the panel centre. The modified control template is show below:

<ControlTemplate TargetType="charting:Chart" x:Key="ChartTemplate">
    <Grid x:Name="ChartRoot" Style="{TemplateBinding PlotAreaStyle}">
 
        <chartingprimitives:EdgePanel x:Name="ChartArea" Style="{TemplateBinding ChartAreaStyle}">                                   
 
            <Grid Canvas.ZIndex="1" Style="{TemplateBinding PlotAreaStyle}" />
            <Border Canvas.ZIndex="1" BorderBrush="#FF919191" BorderThickness="1" />
 
            <!-- ##### I added cross hair and legend here #### -->
 
            <!-- ##### chart control adds column / row definitions and axes here #### -->
 
        </chartingprimitives:EdgePanel>
    </Grid>
</ControlTemplate>

I have indicated the location where I add my own visual content and also the location where the chart control adds the axes. The resulting visual tree is shown below:

visualtree-after

The significant difference here is that all the components of our chart are contained within the same EdgePanel, with their location dictated by their EdgePanel.Edge property. One problem this does introduce for us is that of Z-ordering, previously our additional content was top of the stack, now it sits somewhere in between. Fortunately EdgePanel honours the Canvas.ZIndex attached property allowing us to push our content to the top. The modified template is given below in full:

<ControlTemplate TargetType="charting:Chart" x:Key="ChartTemplate">
    <Grid x:Name="ChartRoot" Style="{TemplateBinding PlotAreaStyle}">
 
        <chartingprimitives:EdgePanel x:Name="ChartArea" Style="{TemplateBinding ChartAreaStyle}">                                   
 
            <Grid Canvas.ZIndex="-1" Style="{TemplateBinding PlotAreaStyle}" />
            <Border Canvas.ZIndex="3" BorderBrush="#FF919191" BorderThickness="1" />
 
            <!-- a location crosshair -->
            <Grid Name="CrosshairContainer" Canvas.ZIndex="1" Background="Transparent"
                 MouseMove="CrosshairContainer_MouseMove" MouseEnter="CrosshairContainer_MouseEnter"
                 MouseLeave="CrosshairContainer_MouseLeave" >
                <Grid Name="Crosshair">
                    <Line Name="Vertical" X1="{Binding Path=X}" Y1="0" X2="{Binding Path=X}" Y2="400" Stroke="Black"/>
                    <Line Name="Horizontal" X1="0" Y1="{Binding Path=Y}" X2="400" Y2="{Binding Path=Y}" Stroke="Black"/>
                </Grid>
            </Grid>
 
            <!-- a location 'legend' -->
            <Border Canvas.ZIndex="2" Name="LocationIndicator" Visibility="Collapsed" Style="{StaticResource LocationLegendStyle}">
                <StackPanel Orientation="Horizontal" Margin="5">
                    <TextBlock Text="Location: "/>
                    <TextBlock Text="{Binding Path=Key,
                                Converter={StaticResource FormattingConverter}, ConverterParameter=hh:mm:ss}"/>
                    <TextBlock Text=", "/>
                    <TextBlock Text="{Binding Path=Value,
                                Converter={StaticResource FormattingConverter}, ConverterParameter=0.00}"/>
                </StackPanel>
            </Border>
 
        </chartingprimitives:EdgePanel>
    </Grid>
</ControlTemplate>

One more subtle point, the event handlers for MouseMove, MouseLeave etc.. when associated with a Grid only work if the background is not null, hence the need for a transparent background. I have no idea why, believe me … it just works!

One final important change is the method used to translate points from screen coordinates into a position within the chart coordinate system. Previously I used a method called GetPlotAreaCoordinateValueRange on the ‘hidden IRangeAxis interface. This has now been renamed to GetValueAtPosition and returns and takes a UnitValue type as its input (presumably for API consistency with the pie charts). Here is the code:

/// <summary>
/// Transforms the supplied position on the plot area grid into a point within
/// the plot area coordinate system
/// </summary>
private KeyValuePair<DateTime, double> GetPlotAreaCoordinates(Point position)
{
    IComparable yAxisHit = ((IRangeAxis)YAxis).GetValueAtPosition(
        new UnitValue(PlotArea.ActualHeight - position.Y, Unit.Pixels));
 
    IComparable xAxisHit = ((IRangeAxis)XAxis).GetValueAtPosition(
        new UnitValue(position.X, Unit.Pixels));
 
    return new KeyValuePair<DateTime, double>((DateTime)xAxisHit, (double)yAxisHit);
}

With these changes in place, our crosshair is fully functional once more:

… until the next release of the toolkit ;-)

You can download the project sourcecode: sllinechartcrosshairupdated.zip.

Regards, Colin E.

Silverlight 3 is here! (but is it a flash killer yet?)

March 19th, 2009

Less than 24 hours ago Microsoft’s MIX09 conference kicked of in Las Vegas with Silverlight 3 taking centre stage.  For those of you who have followed the conference build-up, this comes as no surprise, with twice as many Silverlight sessions (31) advertised than any other technology (WPF has a measly 4!).

You didn’t have to be at Las Vegas to feel the build up. The teams at Microsoft were working hard the day and night before with the quiet arrival of a new silverlight blog, the Silverlight  3 beta 1 API docs and most importantly the appearance of the Silverlight 3 SDK on the Microsoft download site. All carefully timed so that after Scott Guthrie made his announcement, developers around the world could get to work and spread the word.

Incidentally, this is the first time I have watched a MIX keynote and I wasn’t quite prepared for what I saw! Scott Guthrie’s arrival on stage was preceded by a comedy sketch involving Scott endorsing a Cola product, blind programming in a superhero costume, bear wrestling and disco dancing!

disco

It would seem that Las Vegas is certainly the appropriate venue for this event which has far more glitz and glamour than any other technical conference you might attend. Anyhow enough of this digression …

Yes, Silverlight 3 is here folks. But what does it all mean? The best place to get the cool-hard facts is Time Heuer’s blog.

Probably the most hyped new feature is the capability for out of browser / offline experience, where a user can right click a silverlight application to install it into a local sandbox. This certainly puts a spanner in the works for JavaFX whose drag-and-drop deployment to the desktop seemed to be its only unique selling point. Incidentally, you can already see a demo of Silverlight 3 out-of-browser on Shaun Wildermuth’s blog.

Other new features include a host of new controls, 3D support, deep linking and other search engine optimisations, Bitmap manipulation API, and other features familiar to WPF developers including validation, merged resource dictionaries and an improved binding framework (which renders my own ElementName binding implementation totally redundant!)

And all of this within a runtime that is now 40KBytes smaller!

However, the feature I am most interested in is the new GPU hardware acceleration and modern video codec support. Now I work primarily within the financial domain, so why on earth am I interested in video codec support? Well, it is not the feature itself that interests me, more the effect that it will have.

I feel that the most of these new features, great though they are, will excite an audience who are already sold on the idea of Silverlight. I have talked to no end of developers who are just itching to get to work with the technology (myself included) but who are hampered by the fact that it is just not a viable option right now because their end users do not have the plugin.

Currently Silverlight penetration is low, just less that 25% according to current stats (if you can believe a site which uses Flash charts ;-) . Currently it is video which is driving the consumer uptake of RIA technologies, with YouTube (with its flash player) currently the 3rd most popular site on the internet. If Microsoft are going to bring Silverlight to the masses, video is currenlty the way to do it.

Silverlight has already made some headway in this market, with the UK broadcasting company ITV using a Silverlight video player for their video-on-demand service, ITV catchup. However, disappointingly, the ITV decided to support both Silverlight and Flash video players, with people visiting the site without a Silverlight plugin still being able to watch video in Flash. This does little to help the push for Silverlight adoption.

More good new for Silverlight at the MIX keynote came from Perkins Miller of NBC who broke the news that coverage of the 2010 Winter Olympics in Vancouver would be available in HD online through a Silverlight player. Now let’s hope Microsoft got an exclusive deal here!

Now go forth, download Silverlight 3, and get started!

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.