Nassif Bousaba
Nassif Bousaba

Reputation: 396

WPF Datagrid row color based on a value

I have the following code filling the resources of a data grid on run time.

DataTable dt = new DataTable();
dt.Columns.Add("Date");
dt.Columns.Add("User");
dt.Columns.Add("Type");
foreach (var x in query)
{
    var row = dt.NewRow();
    decimal total_REC = 0;
    decimal total_RET = 0;

    row["Date"] = x.CurrentDate.ToString();
    row["User"] = x.User;
    row["Type"] = x.TranscationType;
    dt.Rows.Add(row);
}

And in the end i refer my data grid to this datatable

dgv_Transations.DataContext = dt;

During the first function, i can figure out which rows need to be highlighted, from x.TransactionType. All the types which are equal to the value "A" will have their corresponding rows highlighted in yellow, as in the background would be yellow.

Is there any way to programatically set the background color of the whole row on runtime (from the c# code) to be as i want. I can use dt.rows.count as my index, but i can't figure out a way to set the datagrid row background color (always getting null reference exceptions).

In winforms i would go datagridview.rows and proceed, but in WPF i do not have the option. I tried the following but it keeps returning null.

if (dt.Rows.Count > 0)
{
    DataGridRow r = dgv_Transations.ItemContainerGenerator.ContainerFromIndex(dt.Rows.Count - 1) as DataGridRow;
    r.Background = Brushes.Red;
}

Upvotes: 0

Views: 8122

Answers (3)

mm8
mm8

Reputation: 169420

The common and recommended way to do this would be to use a data trigger in XAML as suggested by @Sach but if you still want to do this programmatically you can actually get a reference to the visual container using the ItemContainerGenerator.

You must wait until the containers have been created though. You could do this by calling the ContainerFromIndex method in a Loaded event handler:

public MainWindow()
{
    InitializeComponent();
    DataTable dt = new DataTable();
    dt.Columns.Add("Date");
    dt.Columns.Add("User");
    dt.Columns.Add("Type");
    for (int i = 1; i < 10; ++i)
    {
        var row = dt.NewRow();
        row["Date"] = 1;
        row["User"] = 2;
        row["Type"] = 3;
        dt.Rows.Add(row);
    }

    dgv_Transations.ItemsSource = dt.DefaultView;

    dgv_Transations.Loaded += (s, e) =>
    {
        if (dt.Rows.Count > 0)
        {
            DataGridRow r = dgv_Transations.ItemContainerGenerator.ContainerFromIndex(dt.Rows.Count - 1) as DataGridRow;
            r.Background = Brushes.Red;
        }
    };
}

The second issue is that the DataGrid uses UI virtualization. This means that only the containers for the data rows you actually see on the screen are generated. So if the last row is scrolled away it no longer has any DataGridRow container.

You could solve this by disabling the virtualization:

<DataGrid x:Name="dgv_Transations" VirtualizingPanel.IsVirtualizing="False" />

Of course this may affect the performance negatively.

The other option would be to scroll to the item and then generate the container programmatically:

dgv_Transations.Loaded += (s, e) => 
{
    if (dt.Rows.Count > 0)
    {
        var lastItem = dt.DefaultView[dt.Rows.Count - 1];
        DataGridRow r = dgv_Transations.ItemContainerGenerator.ContainerFromItem(lastItem) as DataGridRow;
        if (r == null)
        {
            dgv_Transations.ScrollIntoView(lastItem);
            r = dgv_Transations.ItemContainerGenerator.ContainerFromItem(lastItem) as DataGridRow;
        }
        r.Background = Brushes.Red;
    }
};

Upvotes: 0

Sach
Sach

Reputation: 10393

I'm modifying this answer from a solution I used slightly differently.

You could create an XAML style for the window where you have your DataGrid.

<Window.Resources>
   <Style TargetType="{x:Type DataGridRow}">
      <Style.Setters>
            <Setter Property="Background" Value="{Binding Path=Code}"></Setter>
      </Style.Setters>
   </Style>
</Window.Resources>

"Code" is the name of a class variable of type string which holds the color names ("Red", "Blue" etc). So in your case "Code" would be a column in your dt.

Then, you need to set the value of "Code" to yellow whenever you want the line to be highlighted.

If you're not very clear please refer to the original code sample I've used here:

Original Code Sample

Upvotes: 3

Rick Hodder
Rick Hodder

Reputation: 2252

You could create an IValueConverter that converts transaction type to a brush and put an entry in Window.Resources to register it.

public class TransactionTypeConverter: IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var trxnType = value as int;
            if (value == null || trxnType==null) return Brushes.Transparent;

            if(trxnType==1) return Brushes.Red;            

            return Brushes.Transparent;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {        
}               

Then there are two things you can try:

  1. You could bind the RowBackground of the DataGrid to the Type and bind Converter to TransactionTypeConverter:

    <DataGrid RowBackground="{Binding transactionType}" Converter="{StaticResource TransactionTypeConverter}">

  2. Do the same thing but in the cell template.

Upvotes: 0

Related Questions