Change DataGrid cell colour based on values
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>
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.
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 :)
CPM
Updated on July 23, 2021Comments
-
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 about 13 yearsHi 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 about 13 yearsActually 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 about 13 yearsno its not similar beacuse I want hust a single cell to be selected and not entire row as its is sown in example.
-
CPM about 13 yearshi 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 about 13 yearsI 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. about 13 yearsThere 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 about 13 yearsI 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. about 13 yearsThat 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 about 13 yearsBecause 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 about 13 yearsbut I Think Your way of doing is better return colors instead of integer.Thank u
-
H.B. about 13 yearsOf 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 about 11 yearsi 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 about 11 yearsi 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. 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 yourDataGrid
(or the property it is bound to). -
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. about 11 years@VivekParikh: I still have no idea what you are talking about, what service reference?
-
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. 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 about 11 years@H.B. my updated Question is here please see this you will understand my problem. stackoverflow.com/questions/16189717/…
-
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 over 8 years
-
Moumit over 6 years
Brushes.Red
not helping but"#" + color.Name
is. Idea is yours surely. Thanks -
Rodri over 6 yearsThis is not painting the entire datagrid cell only the textbox within cell.
-
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.