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.

No comments:

Post a Comment