Monday 2 January 2012

Variables In Animation

I'm not sure if you have had this, but one of the most annoying things with animation design in WPF is the fact that you can't use bindings. Being able to animate something to move across the whole width of an element is an obvious desire and yet is not easily supported when done just within XAML.

So here I'm going to show you the standard technique to work around this which can be reused on any style/control you're designing. For this we'll be using the arithmetic converter codebase from the previous post as well (which I'll include in the posted solution).

The key to this whole problem is to use a coordinate transformation (see here for some classics) so that we can factor out our binding value. This means we animate a variable in a consistant manner, such as changing it from 0 to 1, then the property we actually want to animate s bound to this variable.

These variables will just be attached properties, which can be reused on any element we need them for. The code below shows two (X and Y) but you can define as many as you require.
public class Variables
{
public static readonly DependencyProperty XProperty = DependencyProperty.RegisterAttached(
"X", typeof(double), typeof(Variables), new FrameworkPropertyMetadata(null));

public static void SetX(UIElement element, double value)
{
element.SetValue(XProperty, value);
}

public static double GetX(UIElement element)
{
return (Double)element.GetValue(XProperty);
}

public static readonly DependencyProperty YProperty = DependencyProperty.RegisterAttached(
"Y", typeof(double), typeof(Variables), new FrameworkPropertyMetadata(null));

public static void SetY(UIElement element, double value)
{
element.SetValue(YProperty, value);
}

public static double GetY(UIElement element)
{
return (Double)element.GetValue(YProperty);
}
}

There is really nothing special about these. They just provide us a way to start ancillary data for our animations (or really any requirement you have).

So let's try this out. I'm going to implement the iPhone-styled checkbox which acts like a sliding toggle button. To aid this, I'm attaching a style package which allows the configurability of extra properties such as state text, state backgrounds, toggle width and toggle background. I'll not show the code here as it really is just some more appropriately named attached properties.

There are going to be four components
  • the left rectangle holding the checked information
  • the central toggle
  • the right rectangle holding the unchecked information
  • A framing border around the component
As the user checks and unchecks the component, we're going to animate a render transform on the grid holding the top three components and move it from left to right. This will be moving a distance of width - toggle width. You can now see why the arithmetic converter comes in handy.

So lets take a look at each of these components individually, firstly the left rectangle:
<Border Background="{TemplateBinding Styles:ToggleCheckBox.CheckedBackground}"
HorizontalAlignment="Left" Height="{Binding Height, RelativeSource={RelativeSource TemplatedParent}}">
<Border.Width>
<MultiBinding Converter="{ArithmeticConverter:ArithmeticConverter}" ConverterParameter="x-y">
<Binding Path="Width" RelativeSource="{RelativeSource TemplatedParent}"/>
<Binding Path="(Styles:ToggleCheckBox.ToggleWidth)"
RelativeSource="{RelativeSource TemplatedParent}"/>
</MultiBinding>
</Border.Width>
<TextBlock FontFamily="{TemplateBinding FontFamily}"
Foreground="{TemplateBinding Styles:ToggleCheckBox.CheckedForeground}"
HorizontalAlignment="Center" VerticalAlignment="Center" FontWeight="{TemplateBinding FontWeight}"
Text="{TemplateBinding Styles:ToggleCheckBox.CheckedText}" FontSize="{TemplateBinding FontSize}"/>
</Border>

Here you can see how we bind to the style properties that we've defined (each which can then be configured on the checkbox itself). There isn't really much to this except to note the usage of the arithmetic converter to set the width to width - toggle width. This is just to make sure the text centres on the rectangle.

Next, the toggle
<Border Background="{TemplateBinding Styles:ToggleCheckBox.ToggleBackground}"
HorizontalAlignment="Left" x:Name="Toggle" Width="{TemplateBinding Styles:ToggleCheckBox.ToggleWidth}"
Height="{Binding Height, RelativeSource={RelativeSource TemplatedParent}}"
BorderBrush="#FF939393" BorderThickness="1" CornerRadius="1">
<Border.RenderTransform>
<TranslateTransform>
<TranslateTransform.X>
<MultiBinding Converter="{ArithmeticConverter:ArithmeticConverter}" ConverterParameter="x-y">
<Binding Path="Width" RelativeSource="{RelativeSource TemplatedParent}"/>
<Binding Path="(Styles:ToggleCheckBox.ToggleWidth)"
RelativeSource="{RelativeSource TemplatedParent}"/>
</MultiBinding>
</TranslateTransform.X>
</TranslateTransform>
</Border.RenderTransform>
</Border>
Again, nothing really exciting. The main thing to notice here is that we're using a render transform to move the position of the toggle relative to the grid it's contained in. The reason for this is to make sure that it gets rendered (even if hidden). Given that we're using render transforms to change visibility of the components then if you don't do this, some of them will never actually be rendered.

I'm leaving the right rectangle and framing border out as they pretty much follow the same details. Now the key to this whole enterprise, the grid that will actually be getting animated (containing the left, toggle and right components).
<Grid x:Name="Container">
<Grid.RenderTransform>
<TranslateTransform>
<TranslateTransform.X>
<MultiBinding Converter="{ArithmeticConverter:ArithmeticConverter}" ConverterParameter="x*(y-z)">
<Binding Path="(Packages:Variables.X)" RelativeSource="{RelativeSource TemplatedParent}"/>
<Binding Path="Width" RelativeSource="{RelativeSource TemplatedParent}"/>
<Binding Path="(Styles:ToggleCheckBox.ToggleWidth)"
RelativeSource="{RelativeSource TemplatedParent}"/>
</MultiBinding>
</TranslateTransform.X>
</TranslateTransform>
</Grid.RenderTransform>
Here is where we actually see the binding to our variable. Since we're moving this left when unchecked then we'll be setting it between 0 and -1 (you could of course do 0 to 1 and just rejig the maths). So we want our relative position of the grid to be X * (Width - Toggle Width) so that it will move the components far enough left or right to hide one of the sides. The converter above does just that. Yay!

Now all we need is the animation, which should be nice and simple
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="False">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(Packages:Variables.X)"
From="0" To="-1" FillBehavior="HoldEnd" Duration="0:0:0.25" DecelerationRatio="0.5"/>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(Packages:Variables.X)"
From="-1" To="0" FillBehavior="HoldEnd" Duration="0:0:0.25" DecelerationRatio="0.5"/>
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</ControlTemplate.Triggers>
All we need to do is animate our attached variable X between 0 and -1 when the checkbox state changes and we'll slide it the correct distance left and right, as though it was bound to the width itself.

Here are some screenshots of it in action:

And the XAML:
<Window x:Class="ItsAllAboutTheXaml.DynamicAnimation.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:Styles="clr-namespace:ItsAllAboutTheXaml.DynamicAnimation.Controls.Styles" Title="Window1" Height="300" Width="300">
<StackPanel HorizontalAlignment="Left">
<CheckBox Styles:ToggleCheckBox.CheckedText="ACT" Styles:ToggleCheckBox.UncheckedText="OOM"
Style="{StaticResource ToggleCheckBoxStyle}" Margin="4"/>
<CheckBox FontWeight="Normal" Styles:ToggleCheckBox.CheckedText="ON"
Styles:ToggleCheckBox.UncheckedText="OFF" Style="{StaticResource ToggleCheckBoxStyle}"
Width="70" Height="25" Margin="4"/>
<CheckBox Styles:ToggleCheckBox.CheckedForeground="LightGray" FontSize="10"
Styles:ToggleCheckBox.ToggleWidth="50" Styles:ToggleCheckBox.CheckedText="IN"
Styles:ToggleCheckBox.UncheckedText="OUT" Style="{StaticResource ToggleCheckBoxStyle}"
Width="200" Margin="4"/>
<CheckBox Styles:ToggleCheckBox.CheckedBackground="Red" Style="{StaticResource ToggleCheckBoxStyle}"
Width="75" Margin="4"/>
</StackPanel>
</Window>

It's as simple as that and with multiple variables and the arithmetic converter you can really create some incredibly customised animations to fit all your requirements.

You can get the code here (this should include the dll for the ArithmeticConverter).

Wednesday 2 November 2011

Arithmetic Converter

So before we get started on some more of the data grid functionality just wanted to upload a converter I've been working on yesterday.

You'll have probably seen a number of attempts at this before so why have I bothered writing my own? Well there are three reasons:
  1. I haven't written many parsers before so thought I would give it a go. It's the best way to learn.
  2. Most of the ones out there are not easily extendable so wanted to see if I could make on that was.
  3. Most good arithmetic expression parsers available on the market cost money.
I'm really writing this because I need to use it for some of the deeper dives into styling within WPF and XAML that I'll be doing later. Although it is obviously possible to write a converter for each and every styling equation you come across (and when you get slightly more complex there are quite a few of them) it is a lot easier to understand if all in one place and a lot easier to write as well.

If you have style designers, they can focus completely on the XAML without any requirement of c# knowledge or having to be bogged down by the unit testing that comes with that as well.

Here you will find the code that incorporates the arithmetic converter codebase, plus the associated unit test and an example of it's usage which looks something like this:


All of the rows in this view are calculated via bindings. No magic here!

So what does the converter allow at the moment?
  • Constants: Currently supported are digits and Pi.
  • Bound Variables: The converter can be used with either a binding or multi-binding. There is nothing stopping these being referenced multiple times.
    • Binding - the variable can be referenced either by x, a or {0}
    • MultiBinding - The first four bindings can be referenced either by x,y,z,t or a,b,c,d. It is also possible to use {n} to reference the nth element. 
  • Unary Operators: Either a negative or positive sign in front of a variable or constant.
  • Binary Operators: Support for addition, subtraction, division and multiplication in standard precedence order.
  • Brackets: Brackets to control evalation order.
  • Trigonometric Functions: Currently can support the application of sine, cosine and tangent functions.
  • Absolute Function: Can be used to evaluate the absolute value of an expression.
This has grown slightly organically over the last few hours but here's the basic components:

ArithmeticConverter.cs
This is focused on the management of type conversion between the bound sources and the target. It uses the parser to evaluate the operator tree and calculate the result from this.

ArithmeticExpressionParser.cs
Firstly will maintain a cache of any operator trees for a given expression. This means that our performance of parsing the expression can take a slight hit as this will all be evaluated up front for us.

So lets take a look at the key part of this class, the parse method. This is a recursively called method which each of the tokens have access to call if they need to parse the expression at a certain precedence level (i.e, when evaluating the right hand side of multiplication, we don't want to be taking into account additions as they'll take control)

   1:  public IArithmeticOperator Parse(string parseExpression, ref int position, int precedence, ref bool needsClosed)
   2:  {
   3:      IArithmeticOperator currentOperator = null;
   4:   
   5:      while (position < parseExpression.Length)
   6:      {
   7:          SkipWhiteSpace(parseExpression, ref position);
   8:   
   9:          var applied = false;
  10:          var highPrecedenceTokens = from t in Tokens where t.Precedence() > precedence select t;
  11:          foreach (var token in highPrecedenceTokens)
  12:          {
  13:              if (token.Apply(this, parseExpression, ref currentOperator, ref position, ref needsClosed))
  14:              {
  15:                  applied = true;
  16:                  break;
  17:              }
  18:          }
  19:   
  20:          // Keep returning until we no longer need to be closed.
  21:          if (needsClosed)
  22:              return currentOperator;
  23:   
  24:          if (applied) 
  25:              continue;
  26:   
  27:          if (precedence == (int)ArithmeticTokenPrecedence.LowestPrecendence)
  28:              throw new ArgumentException(String.Format("Failed to parse expression {0}", parseExpression));
  29:   
  30:          return currentOperator;
  31:      }
  32:   
  33:      return currentOperator;
  34:  }
 
The key things of note here are the precedence settings which are used to control which tokens can take over control from the current one. So for example, if we're currently parsing an addition but then come across a multiplication, it will take precedence.

If it was the other way round, then the current parse loop would be stopped (since addition wouldn't be a token to check) and the multiplication operator would be closed out. We would then go on to evaluate the addition afterwards.

The other main point is the needs closed parameter which is used for bracketing expressions. When a close bracket is found, we'll wriggle our way all the way back to the opening bracket and close out any open operators along the way. This is done by all tokens returning false when the expression needs closed until we get back to the bracket token, which will reset the variable so we can continue on our merry way.

The operators are relatively simple so I won't go into detail on them but what I will talk about is an example token so you can get a feel for how they work (and understand how to write your own if it so desires you). For our example we'll use a BinaryArithmeticToken (which all the basic operations like addition, subtraction, multiplication and division inherit from).

   1:  public abstract class BinaryArithmeticToken : BaseArithmeticToken
   2:  {
   3:      protected abstract char OperatorCharacter { get; }
   4:      protected abstract BinaryArithmeticOperator GetOperator(IArithmeticOperator leftOperator, IArithmeticOperator rightOperator);
   5:   
   6:      public override bool Apply(ArithmeticExpressionParser parser, string expression, ref IArithmeticOperator arithmeticOperator, ref int position)
   7:      {
   8:          if (arithmeticOperator == null)
   9:              return false;
  10:   
  11:          if (expression[position] != OperatorCharacter)
  12:              return false;
  13:   
  14:          position++;
  15:   
  16:          arithmeticOperator = GetOperator(arithmeticOperator, parser.Parse(expression, ref position, Precedence()));
  17:          return true;
  18:      }
  19:  }
 
So what is going on here? Well the operator character just refers to the grammer that we use in the expression like + or *. The key is in the application.

It first checks if arithmetic operator is null and doesn't process it if it is. The reason being that all binary operators (not functions if you're thinking of implementing it) have an expression on the left hand and on the right hand side. If the arithmetic operator is currently null, then no left hand side must exist so we'll pass this down to the unary tokens.

Then we obviously check the character matches up with the value in the expression. If so, we move the position forward passed this character and do our processing. All this means is that we set the arithmetic operator to a new BinaryOperator, where the left hand side is the currently evaluated operator, and the right hand side is left to the parser.

We set a precedence level so that no lowly operators can try to take the power from us.

So all of that is well and good, but lets be honest, most of the time you just want to hook up a new function. Well it's as simple as anything. say I want to write a new function which changes degrees to radians. What do I need to do?

Well given that there is a UnaryFunctionArithmeticToken already out there, you just need to inherit from that and fill in the pieces. Once you save it down, the parser will automatically pick it up and attempt to parse the expression with it! The code ain't too hard either.

   1:  public class DegsToRadsFunctionArithmeticToken : UnaryFunctionArithmeticToken
   2:  {
   3:      protected override IEnumerable<string> FunctionNames
   4:      {
   5:          get
   6:          {
   7:              yield return "radians";
   8:              yield return "rad";                
   9:          }
  10:      }
  11:   
  12:      protected override UnaryFunctionArithmeticOperator GetOperator(IArithmeticOperator arithmeticOperator)
  13:      {
  14:          return new UnaryFunctionArithmeticOperator(arithmeticOperator, x => (Math.PI / 180) * x);
  15:      }
  16:  }
 
All you need to know is how to use it. Well that isn't too much either. You can add the following XAML into the example provided to convert our entered value from degrees into radians

   1:  <Label Content="{Binding Text, ElementName=Text, Converter={ArithmeticConverter:ArithmeticConverter}, ConverterParameter=rad(x), UpdateSourceTrigger=PropertyChanged}"/>

And of course you can combine this in anyway you like with other operators or functions in your expression.

I want to stress one major thing here. I highly recommend NOT putting business logic into your XAML. It may seem very easy to do but should still focus on keeping the view seperate from the model using the MVVM pattern.

We will only be using this for styling purposes and as a visual helper in the later posts. Anyway, now that we have this, we can get on to some of the more cooler styling techniques. I'll also be looking to use the MediaFire site to host all the code written in this blog.

Saturday 29 October 2011

Getting to grips with DataGrid (Styles Cont.)

So continuing on from the last post, here's a quick update on some more styling of your cells and grid. In this post we're going to show:
  • Changing the brush and hiding the vertical or horizontal grid lines.
  • Changing the foreground and background font brushes.
  • Playing around with text alignment (for the moment just left, center and right) in both vertical and horizontal.
So first of all, we're going to slightly expand on our test object to add some new values. Here's the one to use now:

namespace AllInTheXaml.DataGrid.BasicStyling
{
    public class TestObject
    {
        public string Test { get; set; }
        public double Value { get; set; }
        public double Bid { get; set; }
        public double Ask { get; set; }
    }
}
 
Here we're just introducing two new values which we can play around with.

Grid Lines

Lets start with the easy one. The DataGrid already exposes the brush for both the vertical and horizontal direction as VerticalGridLinesBrush and HorizontalGridLinesBrush respectively. Great thing is that you can set these to Transparent to make them invisible! It's as easy as that to hide them across the whole grid.


Here's a quick example:


<Window x:Class="AllInTheXaml.DataGrid.BasicStyling.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:Collections="clr-namespace:System.Collections;assembly=mscorlib" 
    xmlns:BasicStyling="clr-namespace:AllInTheXaml.DataGrid.BasicStyling" 
    xmlns:Controls="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit" 
    Title="Window1" Height="300" Width="300">
    <Window.Resources>
        <Collections:ArrayList x:Key="data">
            <BasicStyling:TestObject Test="positive" Value="2.3" Bid="100" Ask="102"/>
            <BasicStyling:TestObject Test="negative" Value="-1.7" Bid="99" Ask="103"/>
            <BasicStyling:TestObject Test="zero" Value="0"/>
        </Collections:ArrayList>
        <BasicStyling:DoubleSignConverter x:Key="DoubleSignConverter"/>
    </Window.Resources>    
    <Controls:DataGrid CanUserAddRows="False" 
                       AutoGenerateColumns="False"                        
                       HeadersVisibility="Column" 
                       VerticalGridLinesBrush="Transparent"
                       ItemsSource="{Binding Source={StaticResource data}}">
        <Controls:DataGrid.Columns>
            <Controls:DataGridTextColumn Header="Test" Binding="{Binding Test}"/>
            <Controls:DataGridTemplateColumn Header="Change">
                <Controls:DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="*"/>
                                <ColumnDefinition Width="Auto"/>
                            </Grid.ColumnDefinitions>
                            <TextBlock Grid.Column="0" Text="{Binding Value}" HorizontalAlignment="Right"/> 
                            <Image Margin="2,0,0,0" Width="10" Height="10" x:Name="valueImage" Grid.Column="1">  
                                <Image.Style>
                                    <Style TargetType="{x:Type Image}">
                                        <Style.Triggers>
                                            <DataTrigger Binding="{Binding Value, Converter={StaticResource DoubleSignConverter}}" Value="1">
                                                <Setter Property="Source" Value="greenUpArrow_16.png"/>
                                            </DataTrigger>
                                            <DataTrigger Binding="{Binding Value, Converter={StaticResource DoubleSignConverter}}" Value="-1">
                                                <Setter Property="Source" Value="redDownArrow_16.png"/>
                                            </DataTrigger>
                                            <DataTrigger Binding="{Binding Value, Converter={StaticResource DoubleSignConverter}}" Value="0">
                                                <Setter Property="Source" Value="{x:Null}"/>
                                            </DataTrigger>
                                        </Style.Triggers>
                                    </Style>
                                </Image.Style>                                    
                            </Image>
                        </Grid>
                    </DataTemplate>
                </Controls:DataGridTemplateColumn.CellTemplate>
            </Controls:DataGridTemplateColumn>
        </Controls:DataGrid.Columns>
    </Controls:DataGrid>
</Window>
 
Which will produce a window like this with no vertical grid lines


Foreground Cell Brush

For this one we're going to keep playing around with the change column. Instead of having the negative sign, we'll show the absolute value of the change but add green or red colouring to indicate the direction.

First, you'll obviously need a new converter to get the absolute value, so lets put that together.

using System;
using System.Globalization;
using System.Windows.Data;

namespace AllInTheXaml.DataGrid.BasicStyling
{
    public class AbsConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if(value == null)
                return null;

            return Math.Abs((double) value);
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotSupportedException("Only able to support one way binding for AbsConverter");
        }
    }
}
And now all we need to do is add the right styling to our TextBlock to make sure it colours correctly. We can use the same sign converter as we did for the arrows for this.

<Window x:Class="AllInTheXaml.DataGrid.BasicStyling.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:Collections="clr-namespace:System.Collections;assembly=mscorlib" 
    xmlns:BasicStyling="clr-namespace:AllInTheXaml.DataGrid.BasicStyling" 
    xmlns:Controls="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit" 
    Title="Window1" Height="300" Width="300">
    <Window.Resources>
        <Collections:ArrayList x:Key="data">
            <BasicStyling:TestObject Test="positive" Value="2.3" Bid="100" Ask="102"/>
            <BasicStyling:TestObject Test="negative" Value="-1.7" Bid="99" Ask="103"/>
            <BasicStyling:TestObject Test="zero" Value="0"/>
        </Collections:ArrayList>
        <BasicStyling:DoubleSignConverter x:Key="DoubleSignConverter"/>
        <BasicStyling:AbsConverter x:Key="AbsConverter"/>
    </Window.Resources>    
    <Controls:DataGrid CanUserAddRows="False" 
                       AutoGenerateColumns="False"                        
                       HeadersVisibility="Column" 
                       VerticalGridLinesBrush="Transparent"
                       ItemsSource="{Binding Source={StaticResource data}}">
        <Controls:DataGrid.Columns>
            <Controls:DataGridTextColumn Header="Test" Binding="{Binding Test}"/>
            <Controls:DataGridTemplateColumn Header="Change">
                <Controls:DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="*"/>
                                <ColumnDefinition Width="Auto"/>
                            </Grid.ColumnDefinitions>
                            <TextBlock Grid.Column="0" Text="{Binding Value, Converter={StaticResource AbsConverter}}" HorizontalAlignment="Right">
                                <TextBlock.Style>
                                    <Style TargetType="{x:Type TextBlock}">
                                        <Style.Triggers>
                                            <DataTrigger Binding="{Binding Value, Converter={StaticResource DoubleSignConverter}}" Value="1">
                                                <Setter Property="Foreground" Value="#FF66C872"/>
                                            </DataTrigger>
                                            <DataTrigger Binding="{Binding Value, Converter={StaticResource DoubleSignConverter}}" Value="-1">
                                                <Setter Property="Foreground" Value="#FFBD182C"/>
                                            </DataTrigger>
                                            <DataTrigger Binding="{Binding Value, Converter={StaticResource DoubleSignConverter}}" Value="0">
                                                <Setter Property="Foreground" Value="#FF000000"/>
                                            </DataTrigger>
                                        </Style.Triggers>
                                    </Style>
                                </TextBlock.Style>
                            </TextBlock>
                            <Image Margin="2,0,0,0" Width="10" Height="10" x:Name="valueImage" Grid.Column="1">  
                                <Image.Style>
                                    <Style TargetType="{x:Type Image}">
                                        <Style.Triggers>
                                            <DataTrigger Binding="{Binding Value, Converter={StaticResource DoubleSignConverter}}" Value="1">
                                                <Setter Property="Source" Value="greenUpArrow_16.png"/>
                                            </DataTrigger>
                                            <DataTrigger Binding="{Binding Value, Converter={StaticResource DoubleSignConverter}}" Value="-1">
                                                <Setter Property="Source" Value="redDownArrow_16.png"/>
                                            </DataTrigger>
                                            <DataTrigger Binding="{Binding Value, Converter={StaticResource DoubleSignConverter}}" Value="0">
                                                <Setter Property="Source" Value="{x:Null}"/>
                                            </DataTrigger>
                                        </Style.Triggers>
                                    </Style>
                                </Image.Style>                                    
                            </Image>
                        </Grid>
                    </DataTemplate>
                </Controls:DataGridTemplateColumn.CellTemplate>
            </Controls:DataGridTemplateColumn>
        </Controls:DataGrid.Columns>
    </Controls:DataGrid>
</Window> 
 
Just look how easy this is. We get to actually work with the value itself and format it exactly how we want with touching code and it's so easy to plug and play our converters and different controls to create really custom styles to the grid. This just shows one of the real great powers of WPF in action. Here's what we get


Background Cell Brush and Text Alignment Together!


There is a reason I want to show these together and it's mainly to show the limitation of some of the controls in the DataGrid. This actually is not possible to do with the DataGridTextColumn. The default control it seems that underlines this is a TextBlock (which makes sense) but if you've worked much with it you'll know that it doesn't have a HorizontalContentAlignment property. This means you can't have the control itself stretch the whole cell and align the text.

I must admit, I found this quite a bad oversight from the DataGrid developers as it seems like reasonably obvious functionality. However, given we have the DataGridTemplateColumn all is not lost! We can design it ourselves. Hoozah!

All we really need to do is add a Border around the TextBlock. We can align the TextBlock as we want with a transparent background and then use the Border (which stretches by default) to control our background brush.

Before we put that all in motion just going to introduce one last converter so that if the bid or ask values are zero they don't show up (this will also show how to hide error or null values in the grid)
using System;
using System.Globalization;
using System.Windows.Data;

namespace AllInTheXaml.DataGrid.BasicStyling
{
    public class IsZeroOrNullConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if(value == null)
                return null;

            return ((double) value) == 0;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotSupportedException("Only able to support one way binding for IsZeroOrNullConverter");
        }
    }
}
And now the XAML

<Window x:Class="AllInTheXaml.DataGrid.BasicStyling.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:Collections="clr-namespace:System.Collections;assembly=mscorlib" 
    xmlns:BasicStyling="clr-namespace:AllInTheXaml.DataGrid.BasicStyling" 
    xmlns:Controls="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit" 
    Title="Window1" Height="300" Width="300">
    <Window.Resources>
        <Collections:ArrayList x:Key="data">
            <BasicStyling:TestObject Test="positive" Value="2.3" Bid="100" Ask="102"/>
            <BasicStyling:TestObject Test="negative" Value="-1.7" Bid="99" Ask="103"/>
            <BasicStyling:TestObject Test="zero" Value="0"/>
        </Collections:ArrayList>
        <BasicStyling:DoubleSignConverter x:Key="DoubleSignConverter"/>
        <BasicStyling:AbsConverter x:Key="AbsConverter"/>
        <BasicStyling:IsZeroOrNullConverter x:Key="IsZeroOrNullConverter"/>
    </Window.Resources>    
    <Controls:DataGrid CanUserAddRows="False" 
                       AutoGenerateColumns="False"                        
                       HeadersVisibility="Column" 
                       VerticalGridLinesBrush="Transparent"
                       ItemsSource="{Binding Source={StaticResource data}}">
        <Controls:DataGrid.Columns>
            <Controls:DataGridTextColumn Header="Test" Binding="{Binding Test}"/>
            <Controls:DataGridTemplateColumn Header="Change">
                <Controls:DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="*"/>
                                <ColumnDefinition Width="Auto"/>
                            </Grid.ColumnDefinitions>
                            <TextBlock Grid.Column="0" Text="{Binding Value, Converter={StaticResource AbsConverter}}" HorizontalAlignment="Right">
                                <TextBlock.Style>
                                    <Style TargetType="{x:Type TextBlock}">
                                        <Style.Triggers>
                                            <DataTrigger Binding="{Binding Value, Converter={StaticResource DoubleSignConverter}}" Value="1">
                                                <Setter Property="Foreground" Value="#FF66C872"/>
                                            </DataTrigger>
                                            <DataTrigger Binding="{Binding Value, Converter={StaticResource DoubleSignConverter}}" Value="-1">
                                                <Setter Property="Foreground" Value="#FFBD182C"/>
                                            </DataTrigger>
                                            <DataTrigger Binding="{Binding Value, Converter={StaticResource DoubleSignConverter}}" Value="0">
                                                <Setter Property="Foreground" Value="#FF000000"/>
                                            </DataTrigger>
                                        </Style.Triggers>
                                    </Style>
                                </TextBlock.Style>
                            </TextBlock>
                            <Image Margin="2,0,0,0" Width="10" Height="10" x:Name="valueImage" Grid.Column="1">  
                                <Image.Style>
                                    <Style TargetType="{x:Type Image}">
                                        <Style.Triggers>
                                            <DataTrigger Binding="{Binding Value, Converter={StaticResource DoubleSignConverter}}" Value="1">
                                                <Setter Property="Source" Value="greenUpArrow_16.png"/>
                                            </DataTrigger>
                                            <DataTrigger Binding="{Binding Value, Converter={StaticResource DoubleSignConverter}}" Value="-1">
                                                <Setter Property="Source" Value="redDownArrow_16.png"/>
                                            </DataTrigger>
                                            <DataTrigger Binding="{Binding Value, Converter={StaticResource DoubleSignConverter}}" Value="0">
                                                <Setter Property="Source" Value="{x:Null}"/>
                                            </DataTrigger>
                                        </Style.Triggers>
                                    </Style>
                                </Image.Style>                                    
                            </Image>
                        </Grid>
                    </DataTemplate>
                </Controls:DataGridTemplateColumn.CellTemplate>
            </Controls:DataGridTemplateColumn>
            <Controls:DataGridTemplateColumn Width="40" Header="Bid">
                <Controls:DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <Border Margin="-1">
                            <TextBlock Text="{Binding Bid}" HorizontalAlignment="Center">
                            <TextBlock.Style>
                                <Style TargetType="{x:Type TextBlock}">
                                    <Style.Triggers>
                                        <DataTrigger Binding="{Binding Bid, Converter={StaticResource IsZeroOrNullConverter}}" Value="true">
                                            <Setter Property="Foreground" Value="Transparent"/>
                                        </DataTrigger>
                                    </Style.Triggers>
                                </Style>
                            </TextBlock.Style>
                            </TextBlock>
                            <Border.Style>
                                <Style TargetType="{x:Type Border}">
                                    <Style.Triggers>
                                        <DataTrigger Binding="{Binding Bid, Converter={StaticResource IsZeroOrNullConverter}}" Value="false">
                                            <Setter Property="Background" Value="#FFAAC9E9"/>
                                        </DataTrigger>
                                    </Style.Triggers>
                                </Style>
                            </Border.Style>
                        </Border>
                    </DataTemplate>
                </Controls:DataGridTemplateColumn.CellTemplate>
            </Controls:DataGridTemplateColumn>
            <Controls:DataGridTemplateColumn Width="40" Header="Ask">
                <Controls:DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <Border Margin="-1">
                            <TextBlock Text="{Binding Ask}" HorizontalAlignment="Center">
                            <TextBlock.Style>
                                <Style TargetType="{x:Type TextBlock}">
                                    <Style.Triggers>
                                        <DataTrigger Binding="{Binding Ask, Converter={StaticResource IsZeroOrNullConverter}}" Value="true">
                                            <Setter Property="Foreground" Value="Transparent"/>
                                        </DataTrigger>
                                    </Style.Triggers>
                                </Style>
                            </TextBlock.Style>
                            </TextBlock>
                            <Border.Style>
                                <Style TargetType="{x:Type Border}">
                                    <Style.Triggers>
                                        <DataTrigger Binding="{Binding Ask, Converter={StaticResource IsZeroOrNullConverter}}" Value="false">
                                            <Setter Property="Background" Value="#FFFFA4A4"/>
                                        </DataTrigger>
                                    </Style.Triggers>
                                </Style>
                            </Border.Style>
                        </Border>
                    </DataTemplate>
                </Controls:DataGridTemplateColumn.CellTemplate>
            </Controls:DataGridTemplateColumn>
        </Controls:DataGrid.Columns>
    </Controls:DataGrid>
</Window> 
 
Those with the observant eye will have noticed the little introduction of margins on the border. These are due to the whitespace that the data grid cell automatically adds around your template. I don't particularly like this so using margins to override them. I'm using a constant margin around the whole border so that this wouldn't look odd if you started moving the columns around.

Here's how it looks

So that's pretty much it for me today. Look forward to some more data grids tomorrow!

Thursday 27 October 2011

Getting to grips with DataGrid (Styles)

Due to work has been a while since I've posted here, but finally got a bit more free time. I've decided to invest a bit of time looking at the possibilities of the WPF Toolkit's DataGrid and really see how far we can push it.

So first thing is first, basic stylings. I want to be able to show an arrow that indicates the direction a value is going in. A simple premise that was painful to do in WinForms.

All ready? Then lets get started! First will create a mock object that I'll look to use throughout this series so it's easy for you to recreate the data. I'll also upload the code at some point as well.

namespace AllInTheXaml.DataGrid.BasicStyling
{
    public class TestObject
    {
        public string Test { get; set; }
        public double Value { get; set; }
    }
}
 
Great, now we'll start with the most basic implementation of the data grid and see what we get.
<Window x:Class="AllInTheXaml.DataGrid.BasicStyling.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
    xmlns:Collections="clr-namespace:System.Collections;assembly=mscorlib"  
    xmlns:BasicStyling="clr-namespace:AllInTheXaml.DataGrid.BasicStyling" 
    xmlns:Controls="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit"  
    Title="Window1" Height="300" Width="300">
    <Window.Resources>
        <Collections:ArrayList x:Key="data">
            <BasicStyling:TestObject Test="positive" Value="2.3"/>
            <BasicStyling:TestObject Test="negative" Value="-1.7"/>
            <BasicStyling:TestObject Test="zero" Value="0"/>
        </Collections:ArrayList>        
    </Window.Resources>
    <Controls:DataGrid CanUserAddRows="False" HeadersVisibility="Column" 
        ItemsSource="{Binding Source={StaticResource data}}"/>        
</Window>
Well that seemed simple enough, so how does that look?

 Not too bad at all. 

Now lets come to styling. As you would imagine for the Microsoft provided grid model, it is very WPF focused. That means we can achieve a lot of what we want to do in XAML. In fact, nearly everything can be done without a single piece of code. Lets take a look at some of this XAML then.
 
<Window x:Class="AllInTheXaml.DataGrid.BasicStyling.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:Collections="clr-namespace:System.Collections;assembly=mscorlib" 
    xmlns:BasicStyling="clr-namespace:AllInTheXaml.DataGrid.BasicStyling" 
    xmlns:Controls="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit" 
    Title="Window1" Height="300" Width="300">
    <Window.Resources>
        <Collections:ArrayList x:Key="data">
            <BasicStyling:TestObject Test="positive" Value="2.3"/>
            <BasicStyling:TestObject Test="negative" Value="-1.7"/>
            <BasicStyling:TestObject Test="zero" Value="0"/>
        </Collections:ArrayList>        
    </Window.Resources>    
        <Controls:DataGrid Margin="0,10,0,0" CanUserAddRows="False"  
            AutoGenerateColumns="False" HeadersVisibility="Column"  
            ItemsSource="{Binding Source={StaticResource data}}">
            <Controls:DataGrid.Columns>
                <Controls:DataGridTextColumn Header="Test" Binding="{Binding Test}"/>
                <Controls:DataGridTemplateColumn Header="Value">
                    <Controls:DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <Grid>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="*"/>
                                    <ColumnDefinition Width="Auto"/>
                                </Grid.ColumnDefinitions>
                                <TextBlock Grid.Column="0" Text="{Binding Value}" HorizontalAlignment="Right"/>
                                <Image Margin="2,0,0,0" Width="10" Height="10" x:Name="valueImage" 
                                    Grid.Column="1" Source="greenUpArrow_16.png"/>
                        </Grid>
                        </DataTemplate>
                    </Controls:DataGridTemplateColumn.CellTemplate>
                </Controls:DataGridTemplateColumn>
                </Controls:DataGrid.Columns>
        </Controls:DataGrid>
</Window> 
 
The DataGrid provides a number of different types of template columns that you can use for rendering your cell. As you can see here, we have used a basic text one to show the header (given that we don't really want this to change from the default) but using the DataGridTemplateColumn for the value. This control gives us the ability to set exactly how the cell should look when being read. It also can be defined to show what it looks like when it is being edited as well! But that's for another time. Here we now show a text block (right aligned like all numbers should be) and an image of our arrow. So what does this look like then?


Ahh yes, well I've only used a single image. Well the reason was I wanted you to understand the templating first before we actually went to complete our goal. So I did say that almost all of it could be achieved in XAML, well here comes the little bit of code. We need to actually write a converter which will return us the sign of the value so we can predicate which arrow to use. Positive will mean an up green arrow, negative will mean a down red arrow and zero will mean set it to null. Simples! So here's the code.
using System;
using System.Globalization;
using System.Windows.Data;

namespace AllInTheXaml.DataGrid.BasicStyling
{
    public class DoubleSignConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if(value == null)
                return null;

            return Math.Sign((double) value);
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotSupportedException("Only able to support one way binding for DoubleSingConverter");
        }
    }
}

And the XAML to go with this

<Window x:Class="AllInTheXaml.DataGrid.BasicStyling.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:Collections="clr-namespace:System.Collections;assembly=mscorlib" 
    xmlns:BasicStyling="clr-namespace:AllInTheXaml.DataGrid.BasicStyling" 
    xmlns:Controls="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit" 
    Title="Window1" Height="300" Width="300">
    <Window.Resources>
        <Collections:ArrayList x:Key="data">
            <BasicStyling:TestObject Test="positive" Value="2.3"/>
            <BasicStyling:TestObject Test="negative" Value="-1.7"/>
            <BasicStyling:TestObject Test="zero" Value="0"/>
        </Collections:ArrayList>
        <BasicStyling:DoubleSignConverter x:Key="DoubleSignConverter"/>
    </Window.Resources>    
    <Controls:DataGrid Margin="0,10,0,0" CanUserAddRows="False" 
        AutoGenerateColumns="False" HeadersVisibility="Column"  
        ItemsSource="{Binding Source={StaticResource data}}">
        <Controls:DataGrid.Columns>
            <Controls:DataGridTextColumn Header="Test" Binding="{Binding Test}"/>
            <Controls:DataGridTemplateColumn Header="Value">
                <Controls:DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="*"/>
                                <ColumnDefinition Width="Auto"/>
                            </Grid.ColumnDefinitions>
                            <TextBlock Grid.Column="0" Text="{Binding Value}" HorizontalAlignment="Right"/>
                            <Image Margin="2,0,0,0" Width="10" Height="10" x:Name="valueImage" Grid.Column="1">  
                                <Image.Style>
                                    <Style TargetType="{x:Type Image}">
                                        <Style.Triggers>
                                            <DataTrigger Binding="{Binding Value, Converter={StaticResource DoubleSignConverter}}" Value="1">
                                                <Setter Property="Source" Value="greenUpArrow_16.png"/>
                                            </DataTrigger>
                                            <DataTrigger Binding="{Binding Value, Converter={StaticResource DoubleSignConverter}}" Value="-1">
                                                <Setter Property="Source" Value="redDownArrow_16.png"/>
                                            </DataTrigger>
                                            <DataTrigger Binding="{Binding Value, Converter={StaticResource DoubleSignConverter}}" Value="0">
                                                <Setter Property="Source" Value="{x:Null}"/>
                                            </DataTrigger>
                                        </Style.Triggers>
                                    </Style>
                                </Image.Style>                                    
                            </Image>
                        </Grid>
                    </DataTemplate>
                </Controls:DataGridTemplateColumn.CellTemplate>
            </Controls:DataGridTemplateColumn>
            </Controls:DataGrid.Columns>
    </Controls:DataGrid>
</Window> 
 

And we get a lovely column looking like this
   
So I hope that this shows just how much can be achieved with the DataGrid from a styling perspective very easily from just a few basic pieces of XAML.