Change DataGrid cell colour based on values

175,415

Solution 1

If you try to set the DataGrid.CellStyle the DataContext will be the row, so if you want to change the colour based on one cell it might be easiest to do so in specific columns, especially since columns can have varying contents, like TextBlocks, ComboBoxes and CheckBoxes. Here is an example of setting all the cells light-green where the Name is John:

<DataGridTextColumn Binding="{Binding Name}">
    <DataGridTextColumn.ElementStyle>
        <Style TargetType="{x:Type TextBlock}">
            <Style.Triggers>
                <Trigger Property="Text" Value="John">
                    <Setter Property="Background" Value="LightGreen"/>
                </Trigger>
            </Style.Triggers>
        </Style>
    </DataGridTextColumn.ElementStyle>
</DataGridTextColumn>

A Screenshot


You could also use a ValueConverter to change the colour.

public class NameToBrushConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        string input = (string)value;
        switch (input)
        {
            case "John":
                return Brushes.LightGreen;
            default:
                return DependencyProperty.UnsetValue;
        }
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotSupportedException();
    }
}

Usage:

<Window.Resources>
    <local:NameToBrushConverter x:Key="NameToBrushConverter"/>
</Window.Resources>
...
<DataGridTextColumn Binding="{Binding Name}">
    <DataGridTextColumn.ElementStyle>
        <Style TargetType="{x:Type TextBlock}">
            <Setter Property="Background" Value="{Binding Name, Converter={StaticResource NameToBrushConverter}}"/>
        </Style>
    </DataGridTextColumn.ElementStyle>
</DataGridTextColumn>

Yet another option is to directly bind the Background to a property which returns the respectively coloured brush. You will have to fire property change notifications in the setters of properties on which the colour is dependent.

e.g.

public string Name
{
    get { return _name; }
    set
    {
        if (_name != value)
        {
            _name = value;
            OnPropertyChanged(nameof(Name));
            OnPropertyChanged(nameof(NameBrush));
        }
    }
}

public Brush NameBrush
{
    get
    {
        switch (Name)
        {
            case "John":
                return Brushes.LightGreen;
            default:
                break;
        }

        return Brushes.Transparent;
    }
}

Solution 2

If you need to do it with a set number of columns, H.B.'s way is best. But if you don't know how many columns you are dealing with until runtime, then the below code [read: hack] will work. I am not sure if there is a better solution with an unknown number of columns. It took me two days working at it off and on to get it, so I'm sticking with it regardless.

C#

public class ValueToBrushConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        int input;
        try
        {
            DataGridCell dgc = (DataGridCell)value;
            System.Data.DataRowView rowView = (System.Data.DataRowView)dgc.DataContext;
            input = (int)rowView.Row.ItemArray[dgc.Column.DisplayIndex];
        }
        catch (InvalidCastException e)
        {
            return DependencyProperty.UnsetValue;
        }
        switch (input)
        {
            case 1: return Brushes.Red;
            case 2: return Brushes.White;
            case 3: return Brushes.Blue;
            default: return DependencyProperty.UnsetValue;
        }
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotSupportedException();
    }
}

XAML

<UserControl.Resources>
    <conv:ValueToBrushConverter x:Key="ValueToBrushConverter"/>
    <Style x:Key="CellStyle" TargetType="DataGridCell">
        <Setter Property="Background" Value="{Binding RelativeSource={RelativeSource Self}, Converter={StaticResource ValueToBrushConverter}}" />
    </Style>
</UserControl.Resources>
<DataGrid x:Name="dataGrid" CellStyle="{StaticResource CellStyle}">
</DataGrid>

Solution 3

This may be of help to you. It isn't the stock WPF datagrid however.

I used DevExpress with a custom ColorFormatter behaviour. I couldn't find anything on the market that did this out of the box. This took me a few days to develop. My code attaached below, hopefully this helps someone out there.

Edit: I used POCO view models and MVVM however you could change this to not use POCO if you desire.

Example

Viewmodel.cs

namespace ViewModel
{
    [POCOViewModel]
    public class Table2DViewModel
    {
        public ITable2DView Table2DView { get; set; }

        public DataTable ItemsTable { get; set; }


        public Table2DViewModel()
        {
        }

        public Table2DViewModel(MainViewModel mainViewModel, ITable2DView table2DView) : base(mainViewModel)
        {
            Table2DView = table2DView;   
            CreateTable();
        }

        private void CreateTable()
        {
            var dt = new DataTable();
            var xAxisStrings = new string[]{"X1","X2","X3"};
            var yAxisStrings = new string[]{"Y1","Y2","Y3"};

            //TODO determine your min, max number for your colours
            var minValue = 0;
            var maxValue = 100;
            Table2DView.SetColorFormatter(minValue,maxValue, null);

            //Add the columns
            dt.Columns.Add(" ", typeof(string));
            foreach (var x in xAxisStrings) dt.Columns.Add(x, typeof(double));

            //Add all the values
            double z = 0;
            for (var y = 0; y < yAxisStrings.Length; y++)
            {
                var dr = dt.NewRow();
                dr[" "] = yAxisStrings[y];
                for (var x = 0; x < xAxisStrings.Length; x++)
                {
                    //TODO put your actual values here!
                    dr[xAxisStrings[x]] = z++; //Add a random values
                }
                dt.Rows.Add(dr);
            }
            ItemsTable = dt;
        }


        public static Table2DViewModel Create(MainViewModel mainViewModel, ITable2DView table2DView)
        {
            var factory = ViewModelSource.Factory((MainViewModel mainVm, ITable2DView view) => new Table2DViewModel(mainVm, view));
            return factory(mainViewModel, table2DView);
        }
    }

}

IView.cs

namespace Interfaces
    {
        public interface ITable2DView
        {
            void SetColorFormatter(float minValue, float maxValue, ColorScaleFormat colorScaleFormat);
        }
    }

View.xaml.cs

namespace View
{
    public partial class Table2DView : ITable2DView
    {
        public Table2DView()
        {
            InitializeComponent();
        }

        static ColorScaleFormat defaultColorScaleFormat = new ColorScaleFormat
        {
            ColorMin = (Color)ColorConverter.ConvertFromString("#FFF8696B"),
            ColorMiddle = (Color)ColorConverter.ConvertFromString("#FFFFEB84"),
            ColorMax = (Color)ColorConverter.ConvertFromString("#FF63BE7B")
        };

        public void SetColorFormatter(float minValue, float maxValue, ColorScaleFormat colorScaleFormat = null)
        {
            if (colorScaleFormat == null) colorScaleFormat = defaultColorScaleFormat;
            ConditionBehavior.MinValue = minValue;
            ConditionBehavior.MaxValue = maxValue;
            ConditionBehavior.ColorScaleFormat = colorScaleFormat;
        }
    }
}

DynamicConditionBehavior.cs

namespace Behaviors
{
    public class DynamicConditionBehavior : Behavior<GridControl>
    {
        GridControl Grid => AssociatedObject;

        protected override void OnAttached()
        {
            base.OnAttached();
            Grid.ItemsSourceChanged += OnItemsSourceChanged;
        }

        protected override void OnDetaching()
        {
            Grid.ItemsSourceChanged -= OnItemsSourceChanged;
            base.OnDetaching();
        }

        public ColorScaleFormat ColorScaleFormat { get; set;}
        public float MinValue { get; set; }
        public float MaxValue { get; set; }

        private void OnItemsSourceChanged(object sender, EventArgs e)
        {
            var view = Grid.View as TableView;

            if (view == null) return;

            view.FormatConditions.Clear();

            foreach (var col in Grid.Columns)
            {
                view.FormatConditions.Add(new ColorScaleFormatCondition
                {
                    MinValue = MinValue,
                    MaxValue = MaxValue,
                    FieldName = col.FieldName,
                    Format = ColorScaleFormat,
                });
            }

        }
    }
}

View.xaml

<UserControl x:Class="View"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:dxmvvm="http://schemas.devexpress.com/winfx/2008/xaml/mvvm" 
             xmlns:ViewModels="clr-namespace:ViewModel"
             xmlns:dxg="http://schemas.devexpress.com/winfx/2008/xaml/grid"
             xmlns:behaviors="clr-namespace:Behaviors"
             xmlns:dxdo="http://schemas.devexpress.com/winfx/2008/xaml/docking"
             DataContext="{dxmvvm:ViewModelSource Type={x:Type ViewModels:ViewModel}}"
             mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="800">

    <UserControl.Resources>
        <Style TargetType="{x:Type dxg:GridColumn}">
            <Setter Property="Width" Value="50"/>
            <Setter Property="HorizontalHeaderContentAlignment" Value="Center"/>
        </Style>

        <Style TargetType="{x:Type dxg:HeaderItemsControl}">
            <Setter Property="FontWeight" Value="DemiBold"/>
        </Style>
    </UserControl.Resources>

        <!--<dxmvvm:Interaction.Behaviors>
            <dxmvvm:EventToCommand EventName="" Command="{Binding OnLoadedCommand}"/>
        </dxmvvm:Interaction.Behaviors>-->
        <dxg:GridControl ItemsSource="{Binding ItemsTable}"
                     AutoGenerateColumns="AddNew"
                     EnableSmartColumnsGeneration="True">

        <dxmvvm:Interaction.Behaviors >
            <behaviors:DynamicConditionBehavior x:Name="ConditionBehavior" />
            </dxmvvm:Interaction.Behaviors>
            <dxg:GridControl.View>
                <dxg:TableView ShowGroupPanel="False"
                           AllowPerPixelScrolling="True"/>
            </dxg:GridControl.View>
        </dxg:GridControl>
  </UserControl>

Solution 4

In my case convertor must return string value. I don't why, but it works.

*.xaml (common style file, which is included in another xaml files)

<Style TargetType="DataGridCell">
        <Setter Property="Background" Value="{Binding RelativeSource={RelativeSource Self}, Converter={StaticResource ValueToBrushConverter}}" />
</Style>

*.cs

public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
    Color color = VSColorTheme.GetThemedColor(EnvironmentColors.ToolWindowBackgroundColorKey);
    return "#" + color.Name;
}

Solution 5

Just put instead

<Style TargetType="{x:DataGridCell}" >

But beware that this will target ALL your cells (you're aiming at all the objects of type DataGridCell ) If you want to put a style according to the cell type, I'd recommend you to use a DataTemplateSelector

A good example can be found in Christian Mosers' DataGrid tutorial:

http://www.wpftutorial.net/DataGrid.html#rowDetails

Have fun :)

Share:
175,415
CPM
Author by

CPM

Updated on July 23, 2021

Comments

  • CPM
    CPM almost 3 years

    I have got a WPF datagrid and I want diffrent cell colours according to values. I have got below code on my xaml

    Style TargetType="DataGridCell"
    

    but instead of selecting a cell only is selecting all row? What am I missing?

  • CPM
    CPM about 13 years
    Hi Damascus, Thanks for your reply, I Have TargetType of DatagridCell,You are right because its affecticting my enire row. I want only one cell to be affected depending on value of that cell. How can I do that?
  • Damascus
    Damascus about 13 years
    Actually it depends on your case. Did you see the example on the link I gave you? There is a quite clear example on how to choose the style according to the object type (in the example: boy or girl). Is your problem similar?
  • CPM
    CPM about 13 years
    no its not similar beacuse I want hust a single cell to be selected and not entire row as its is sown in example.
  • CPM
    CPM about 13 years
    hi H.B,Thank you its more or less what I want , cell its changing but want to change depending on a value, have to look at value converter and connect with this code example that u sent me, but I think Its the right direction, have u got any example of what I am saying? Thank u
  • CPM
    CPM about 13 years
    I can have now a lightgreen color depending on a value of cell but want to do that with value converter, I dont want to define value="john" want to be triggered and setup color according to conditions that I ll set up programmatically(cs. file c#), Hope I am making my self clear.Thank u very much for helping me
  • H.B.
    H.B. about 13 years
    There are tons of question on SO that illustrate how to use a value converter, you really could have done some searching. Edited my answer to show the equivalent value converter.
  • CPM
    CPM about 13 years
    I have done that I acctually have one claa implemented already but it wasnt working properly thats why asked u, ** namespace GridCellColor { public class MyValueConverter : IValueConverter { #region IValueConverter Members object IValueConverter.Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture){ if (Convert.ToInt32(value) > 0) return 10; else if (Convert.ToInt32(value) == 0) return 0; else return -1; } but wasnt working properly that Why I asked u if u have any examples anyway Thanks for your reply
  • H.B.
    H.B. about 13 years
    That class converts your input integer to another integer, how is that supposed to colour the background? You need a converter which returns a Brush.
  • CPM
    CPM about 13 years
    Because I had then on xaml ** <Style.Triggers> <DataTrigger Binding="{Binding diffQuantity, Converter={StaticResource converter}}" Value="1"> <Setter Property="Background" Value="Green"/> </DataTrigger> <DataTrigger Binding="{Binding diffQuantity, Converter={StaticResource converter}}" Value="0"> <Setter Property="Background" Value="Black"/> </DataTrigger> <DataTrigger Binding="{Binding diffQuantity, Converter={StaticResource converter}}" Value="-1"> <Setter Property="Foreground" Value="Red"/> </DataTrigger></Style.Triggers> **
  • CPM
    CPM about 13 years
    but I Think Your way of doing is better return colors instead of integer.Thank u
  • H.B.
    H.B. about 13 years
    Of course you can do it like that if you compare the output value afterwards and then assign a background accordingly, i just bound directly to the background which means my converter needs to return a Brush.
  • Vivek Parikh
    Vivek Parikh about 11 years
    i want it to be done at run time . i am binding datagrid with datatable on window load.so how can it be done?
  • Vivek Parikh
    Vivek Parikh about 11 years
    i want it to be done at run time . i am binding datagrid with datatable on window load.so how can it be done?
  • H.B.
    H.B. about 11 years
    @VivekParikh: What exactly do you want to do at run-time? Usually you would do everything the same way, usually you just change the ItemsSource of your DataGrid (or the property it is bound to).
  • Vivek Parikh
    Vivek Parikh about 11 years
    @H.B. I have datatable and i dont want to use service reference.so how to apply above code?
  • H.B.
    H.B. about 11 years
    @VivekParikh: I still have no idea what you are talking about, what service reference?
  • Vivek Parikh
    Vivek Parikh about 11 years
    @H.B. forget it.sorry. i just want to change particular cell color of datagrid from code behind. i am binding datatable to grid on window_loaded event.please help me.there are no such column define on xaml page.
  • H.B.
    H.B. about 11 years
    @VivekParikh: If you use the autogenerated columns you can use the assiociated event to add a style like the ones i use in your code.
  • Vivek Parikh
    Vivek Parikh about 11 years
    @H.B. my updated Question is here please see this you will understand my problem. stackoverflow.com/questions/16189717/…
  • Ms. Nobody
    Ms. Nobody almost 11 years
    @H.B. How can I check each cell value and add a background to it during that autogeneratingcolumns event? I can't find the right method:|
  • MickyD
    MickyD over 8 years
  • Moumit
    Moumit over 6 years
    Brushes.Red not helping but "#" + color.Name is. Idea is yours surely. Thanks
  • Rodri
    Rodri over 6 years
    This is not painting the entire datagrid cell only the textbox within cell.
  • H.B.
    H.B. over 6 years
    @user1624552: Well, yes, but the cell itself is probably only accessible in the control template and the TextBox should fill most if not the entire cell.