C# Code, Tutorials and Full Visual Studio Projects

Gradiently Coloring Cells in a WPF DataGrid

Posted by on Oct 11, 2011 in Code Snippets, WPF | 0 comments

Gradiently Coloring Cells in a WPF DataGrid

Many times I’ve had a list of data that my users would like color coded. You can do this in stepped increments such as 0-100 = light green and 101+ = dark green, but that doesn’t always meet the need.
Sometimes they want a column to be shaded gradiently, or every cell to shaded gradiently to show how it relates to another.

This example shows the later techinique, of gradiently shading each cell in a DataGrid based on the value in comparison to all the other values in the grid.

Here’s what the WPF DataGrid looks like. (click to make big)
C# WPF Gradient Color Converter

We start with a simple model:

public class MyModel
{
    public int[] Values { get; set; }

    public MyModel()
    {
        Values = new int[4];
    }
}

As you can see this model just holds an array of values. You could have each column written explicitly, the array is just a
shortcut to make the coding simplier, as we will assign one element of the array to each column. So there will be 4 columns.

Now we do the code behind, nothing special here we just build some sample data for our grid and set our DataContext. Our grid will be bound to our Rows collection.

using System;
using System.Collections.Generic;
using System.Windows;

namespace Jarloo
{
    public partial class MainWindow : Window
    {
        public List<MyModel> Rows { get; set; }

        public MainWindow()
        {
            InitializeComponent();

            DataContext = this;

            Rows = new List<MyModel>();

            Random rnd = new Random();

            for (int i = 0; i < 100; i++)
            {
                Rows.Add(new MyModel {Values = new []
                                                   {
                                                       rnd.Next(0, 3000), 
                                                       rnd.Next(0, 3000), 
                                                       rnd.Next(0, 3000), 
                                                       rnd.Next(0, 3000)
                                                   }});
            }
        }
    }

    public class MyModel
    {
        public int[] Values { get; set; }

        public MyModel()
        {
            Values = new int[4];
        }
    }
}

Lets setup our basic XAML structure:

<Window x:Class="Jarloo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        xmlns:Jarloo="clr-namespace:Jarloo" 
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <Style x:Key="NumberCell">
            <Setter Property="TextBlock.TextAlignment" Value="Right"/>
            <Setter Property="FrameworkElement.HorizontalAlignment" Value="Stretch"/>
        </Style>

        <Jarloo:CellColorConverter x:Key="CellColorConverter"/>
    </Window.Resources>
    
    <Grid>
        <DataGrid ItemsSource="{Binding Rows}" AutoGenerateColumns="False" CanUserAddRows="False" IsReadOnly="True" CanUserDeleteRows="False" >
            <DataGrid.Columns>

			...
			
            </DataGrid.Columns>

            <DataGrid.ItemsPanel>
                <ItemsPanelTemplate>
                    <VirtualizingStackPanel />
                </ItemsPanelTemplate>
            </DataGrid.ItemsPanel>
        </DataGrid>
    </Grid>
</Window>

So you can see we have a basic window with a DataGrid. Now the fun part, lets add a column!

<DataGridTextColumn Binding="{Binding Values[0]}" Header="Column 0" CellStyle="{StaticResource NumberCell}" >
    <DataGridTextColumn.ElementStyle>
        <Style TargetType="{x:Type TextBlock}">
            <Setter Property="Background">
                <Setter.Value>
                    <MultiBinding Converter="{StaticResource CellColorConverter}" ConverterParameter="0">
                        <Binding Path="." />
                        <Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type Jarloo:MainWindow}}" Path="Rows" />
                    </MultiBinding>
                </Setter.Value>
            </Setter>
        </Style>
    </DataGridTextColumn.ElementStyle>
</DataGridTextColumn>

There’s a bit to explain here. We created a TextColumn then we modify the style of the object and since it’s based on a TextBlock (when it’s not in edit mode) we can modify that
element. We change the Background style to get a value from a MultiBindingConverter. We give the converter a parameter of “0” which corresponds to our element in the array.
We also give it a copy of the current model “.”, and then a copy of all the models by referencing the Rows property on the window.

Lets take a look at the MultiBindingCoverter and those parameters should make more sense.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Windows.Data;
using System.Windows.Media;

namespace Jarloo
{
    public class CellColorConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            {
                try
                {
                    MyModel currentRow = (MyModel) values[0];
                    List<MyModel> rows = (List<MyModel>) values[1];
                    int index = System.Convert.ToInt32(parameter);

                    if (currentRow == null) return new SolidColorBrush(Color.FromArgb(0, 255, 255, 255));
                    if (rows == null) return new SolidColorBrush(Color.FromArgb(0, 255, 255, 255));


                    int max = rows.Max(w => w.Values.Max());
                    int min = rows.Min(w => w.Values.Min());

                    decimal v1 = currentRow.Values[index] - min;
                    decimal v2 = max - min;

                    decimal percentage = v1/v2;
                    byte alpha = System.Convert.ToByte(percentage*255);
                    return new SolidColorBrush(Color.FromArgb(alpha, 73, 128, 11));
                }
                catch 
                {
                    return new SolidColorBrush(Color.FromArgb(0, 255, 255, 255));
                }
            }
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

Ok so the first bit just takes the data we passed out of the generic object etc so we have niced typed versions of what were playing with.

A bit of safty code there case we get a null we return a white background.

Now here is the reason for passing the whole Rows collection. We need a way to get the Min and Max values of all the elements in each
array. This is so we can determine where our row is in the order. Basically what we are doing is trying to take the matrix of cells and
order them from smallest to highest. Once that is done we can apply a percentage value to each to say where it lies in the order then we can
give it the shade of color applicable.

You’ll see I’m cheating and not actually calcuating a gradient, but instead modifying the alpha based on the percentage. The max Alpha can be 255 and 0 is the lowest. When it’s 0 it’s completely invisible, when 255 it’s fully opaque.

Here is the whole XAML code for those that want the converter syntax and all the columns:

<Window x:Class="Jarloo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        xmlns:Jarloo="clr-namespace:Jarloo" 
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <Style x:Key="NumberCell">
            <Setter Property="TextBlock.TextAlignment" Value="Right"/>
            <Setter Property="FrameworkElement.HorizontalAlignment" Value="Stretch"/>
        </Style>

        <Jarloo:CellColorConverter x:Key="CellColorConverter"/>
    </Window.Resources>
    
    <Grid>
        <DataGrid ItemsSource="{Binding Rows}" AutoGenerateColumns="False" CanUserAddRows="False" IsReadOnly="True" CanUserDeleteRows="False" >
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding Values[0]}" Header="Column 0" CellStyle="{StaticResource NumberCell}" >
                    <DataGridTextColumn.ElementStyle>
                        <Style TargetType="{x:Type TextBlock}">
                            <Setter Property="Background">
                                <Setter.Value>
                                    <MultiBinding Converter="{StaticResource CellColorConverter}" ConverterParameter="0">
                                        <Binding Path="." />
                                        <Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type Jarloo:MainWindow}}" Path="Rows" />
                                    </MultiBinding>
                                </Setter.Value>
                            </Setter>
                        </Style>
                    </DataGridTextColumn.ElementStyle>
                </DataGridTextColumn>

                <DataGridTextColumn Binding="{Binding Values[1]}" Header="Column 1" CellStyle="{StaticResource NumberCell}" >
                    <DataGridTextColumn.ElementStyle>
                        <Style TargetType="{x:Type TextBlock}">
                            <Setter Property="Background">
                                <Setter.Value>
                                    <MultiBinding Converter="{StaticResource CellColorConverter}" ConverterParameter="1">
                                        <Binding Path="." />
                                        <Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type Jarloo:MainWindow}}" Path="Rows" />
                                    </MultiBinding>
                                </Setter.Value>
                            </Setter>
                        </Style>
                    </DataGridTextColumn.ElementStyle>
                </DataGridTextColumn>

                <DataGridTextColumn Binding="{Binding Values[2]}" Header="Column 2" CellStyle="{StaticResource NumberCell}" >
                    <DataGridTextColumn.ElementStyle>
                        <Style TargetType="{x:Type TextBlock}">
                            <Setter Property="Background">
                                <Setter.Value>
                                    <MultiBinding Converter="{StaticResource CellColorConverter}" ConverterParameter="2">
                                        <Binding Path="." />
                                        <Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type Jarloo:MainWindow}}" Path="Rows" />
                                    </MultiBinding>
                                </Setter.Value>
                            </Setter>
                        </Style>
                    </DataGridTextColumn.ElementStyle>
                </DataGridTextColumn>

                <DataGridTextColumn Binding="{Binding Values[3]}" Header="Column 3" CellStyle="{StaticResource NumberCell}" >
                    <DataGridTextColumn.ElementStyle>
                        <Style TargetType="{x:Type TextBlock}">
                            <Setter Property="Background">
                                <Setter.Value>
                                    <MultiBinding Converter="{StaticResource CellColorConverter}" ConverterParameter="3">
                                        <Binding Path="." />
                                        <Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type Jarloo:MainWindow}}" Path="Rows" />
                                    </MultiBinding>
                                </Setter.Value>
                            </Setter>
                        </Style>
                    </DataGridTextColumn.ElementStyle>
                </DataGridTextColumn>

            </DataGrid.Columns>

            <DataGrid.ItemsPanel>
                <ItemsPanelTemplate>
                    <VirtualizingStackPanel />
                </ItemsPanelTemplate>
            </DataGrid.ItemsPanel>
        </DataGrid>
    </Grid>
</Window>

Gradiently Coloring a Single Column

The same technique can be applied to a single column if you wish by changing the following lines in the converter:

int max = rows.Max(w =>; w.Values.Max());
int min = rows.Min(w =>; w.Values.Min());

to

decimal max = rows.Select(w =>; w.Values[index]).Max();
decimal min = rows.Select(w =>; w.Values[index]).Min();

Now the min and max are calculated for just that column, and the current cell is calculated in that range instead of being compared to every cell.

Leave a Comment

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>