77

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?

Askolein
  • 3,250
  • 3
  • 28
  • 40
CPM
  • 855
  • 3
  • 12
  • 14
  • 2
    can you publish sample code for your scenario. That will help. BTW, one way is to bind the background property of your datagridcell with the value of that cell and use a converter (to convert from value to color). – publicgk Apr 05 '11 at 10:28

9 Answers9

173

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;
    }
}
H.B.
  • 166,899
  • 29
  • 327
  • 400
  • 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 Apr 05 '11 at 13:30
  • 1
    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 – CPM Apr 05 '11 at 14:34
  • 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. – H.B. Apr 05 '11 at 15:07
  • 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 – CPM Apr 05 '11 at 15:25
  • 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`. – H.B. Apr 05 '11 at 15:27
  • Because I had then on xaml ** ` `** – CPM Apr 06 '11 at 08:46
  • but I Think Your way of doing is better return colors instead of integer.Thank u – CPM Apr 06 '11 at 08:47
  • 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`. – H.B. Apr 06 '11 at 12:16
  • 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 Apr 22 '13 at 10:53
  • @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). – H.B. Apr 22 '13 at 16:04
  • @H.B. I have datatable and i dont want to use service reference.so how to apply above code? – Vivek Parikh Apr 23 '13 at 06:06
  • @VivekParikh: I still have no idea what you are talking about, what service reference? – H.B. Apr 23 '13 at 12:56
  • @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. – Vivek Parikh Apr 24 '13 at 06:32
  • @VivekParikh: If you use the autogenerated columns you can use [the assiociated event](http://msdn.microsoft.com/en-us/library/system.windows.controls.datagrid.autogeneratingcolumn.aspx) to add a style like the ones i use in your code. – H.B. Apr 24 '13 at 10:58
  • @H.B. my updated Question is here please see this you will understand my problem. http://stackoverflow.com/questions/16189717/how-to-change-single-cell-color-of-datagrid-in-wpf – Vivek Parikh Apr 24 '13 at 10:59
  • @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:| – Ms. Nobody May 20 '13 at 08:21
  • This is not painting the entire datagrid cell only the textbox within cell. – Willy Dec 18 '17 at 16:39
  • @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. – H.B. Dec 19 '17 at 18:48
26

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>
unwrittenrainbow
  • 473
  • 1
  • 7
  • 15
4

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>
rollsch
  • 2,518
  • 4
  • 39
  • 65
3

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;
}
rinatdobr
  • 543
  • 1
  • 6
  • 14
2
        // Example: Adding a converter to a column (C#)
        Style styleReading = new Style(typeof(TextBlock));
        Setter s = new Setter();
        s.Property = TextBlock.ForegroundProperty;
        Binding b = new Binding();
        b.RelativeSource = RelativeSource.Self;
        b.Path = new PropertyPath(TextBlock.TextProperty);
        b.Converter = new ReadingForegroundSetter();
        s.Value = b;
        styleReading.Setters.Add(s);
        col.ElementStyle = styleReading;
  • 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 Apr 22 '13 at 10:55
  • Consider improving your answer. _[Code-only answers may fall under 'Very Low Quality' ...and are candidates for deletion....We've always touted that we aren't a code factory. We are the people who teach others to fish. Code-only answers only feed a person for a day](http://meta.stackexchange.com/questions/148272/is-there-any-benefit-to-allowing-code-only-answers-while-blocking-code-only-ques)_ –  Nov 19 '15 at 07:25
2

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 :)

Damascus
  • 6,553
  • 5
  • 39
  • 53
  • 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? – CPM Apr 05 '11 at 09:45
  • 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? – Damascus Apr 05 '11 at 12:37
  • 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 Apr 05 '11 at 13:10
1

Based on the answer by 'Cassio Borghi'. With this method, there is no need to change the XAML at all.

        DataGridTextColumn colNameStatus2 = new DataGridTextColumn();
        colNameStatus2.Header = "Status";
        colNameStatus2.MinWidth = 100;
        colNameStatus2.Binding = new Binding("Status");
        grdComputer_Servives.Columns.Add(colNameStatus2);

        Style style = new Style(typeof(TextBlock));
        Trigger running = new Trigger() { Property = TextBlock.TextProperty, Value = "Running" };
        Trigger stopped = new Trigger() { Property = TextBlock.TextProperty, Value = "Stopped" };

        stopped.Setters.Add(new Setter() { Property = TextBlock.BackgroundProperty, Value = Brushes.Blue });
        running.Setters.Add(new Setter() { Property = TextBlock.BackgroundProperty, Value = Brushes.Green });

        style.Triggers.Add(running);
        style.Triggers.Add(stopped);

        colNameStatus2.ElementStyle = style;

        foreach (var Service in computerResult)
        {
            var RowName = Service;  
            grdComputer_Servives.Items.Add(RowName);
        }
1
 <DataGridTextColumn  Binding="{Binding Name}" Header="Name" Foreground="White">
     <DataGridTextColumn.CellStyle>
      <Style TargetType="{x:Type DataGridCell}">
         <Setter Property="Background" Value="{Binding color}" />
      </Style>
     </DataGridTextColumn.CellStyle>
 </DataGridTextColumn>

In xaml.cs file

SolidColorBrush color=new SolidColorBrush(Colors.Red);
Siddhi Kamat
  • 135
  • 2
  • 11
-1

To do this in the Code Behind (VB.NET)

Dim txtCol As New DataGridTextColumn

Dim style As New Style(GetType(TextBlock))
Dim tri As New Trigger With {.Property = TextBlock.TextProperty, .Value = "John"}
tri.Setters.Add(New Setter With {.Property = TextBlock.BackgroundProperty, .Value = Brushes.Green})
style.Triggers.Add(tri)

xtCol.ElementStyle = style
Conrad Frix
  • 51,984
  • 12
  • 96
  • 155