Home > Software design >  WPF datagrid - How to set cell background colour based on value
WPF datagrid - How to set cell background colour based on value

Time:10-31

I'd like to set datagrid cell background colour based on its value

What I understand is normally, I would need to create a class Then { get: set: } data property, bind data collection to datagrid itemsource, finally use datatrigger to set background color

However, in my scenario, data is dynamic instead of static from column 4 (starting from column 'Data001' in below example screenshot)

Which means,

  1. number of columns is dynamic;
  2. column header name is dynamic;

enter image description here

So I didn't end up creating data class, instead I put data into a dictionary first

And then from dictionary into datatable, convert datatable into dataview and finally bind to ItemsSource.

Something like below:

//viewModel
var dtCalcResult = new DataTable();
DtView = dtCalcResult.AsDataView();
 
//xaml
<DataGrid 
            x:Name="dtgdTest" 
            HorizontalAlignment="Stretch" 
            VerticalAlignment="Stretch"
            Margin="10" 
            ItemsSource="{Binding DtView}"
            RowHeight="16"
            FrozenColumnCount="3"
            ColumnHeaderHeight="24"
            EnableRowVirtualization="True"
            EnableColumnVirtualization="True"
            Loaded="dtgdTest_Load"
            >

Now my question is how to assign cell background colour by its value?

I have tried to work with the following way ->

Use GetCellContent() to get cell value, then set background colour base on value

Something like this:

                // GET ROW COUNT
                var rowCount = targetDataGrid.Items.Count;
                for (int i = 0; i < rowCount;   i)
                {
                    // GET ROW OBJECT
                    var row = targetDataGrid.ItemContainerGenerator.ContainerFromIndex(i) as DataGridRow;

                    if (row == null)
                    {
                        // USE ScrollIntoView() TO THAT ROW
                        targetDataGrid.UpdateLayout();
                        targetDataGrid.ScrollIntoView(targetDataGrid.Items[i]);
                        // TRY TO GET OBJECT AGAIN
                        row = targetDataGrid.ItemContainerGenerator.ContainerFromIndex(i) as DataGridRow;
                    }
 
                    // GET COLUMN COUNT
                    var columnCount = targetDataGrid.Columns.Count;
                    for (int j = 0; j < columnCount;   j)
                    {
                        // GET CELL OBJECT
                        var elem = targetDataGrid.Columns[j].GetCellContent(row);

                        if (elem == null)
                        {
                            // USE ScrollIntoView() TO THAT COLUMN
                            targetDataGrid.UpdateLayout();
                            targetDataGrid.ScrollIntoView(targetDataGrid.Columns[j]);
                            // TRY TO GET OBJECT AGAIN
                            elem = targetDataGrid.Columns[j].GetCellContent(row);
                        }
 
                        // SET BACKGROUND COLOUR
                        DataGridCell cell = elem.Parent as DataGridCell;
                        string colHeader = targetDataGrid.Columns[j].Header.ToString();
                        string cellData = _viewModel.DictColNameAndValues[colHeader][i];
                        //float actHour = _viewModel.DictValueLists_ActHour[colHeader][i];
 
                        if (8 < float.Parse(cellData))
                        {
                            cell.Background = Brushes.Green;
 
                        }
                        else
                        {
                            cell.Background = Brushes.White;
                        }
 

With above, the result is:

  1. Getting error, after ScrollIntoView(), still cannot get cell value, returns null;
  2. While cells without an error, background colour assignment is wrong; (like colour appears to incorrect cells)
  3. Once I scroll in view, the assigned colour will shift position;
  4. When I load ~1k columns, pagination becomes super slow and lag (I felt this one probably need to create a separate thread to ask)

Something like this: enter image description here

If datagrid is not the best way to do this, is there any other user control or method to archive my goal? (to be able to assign cell background colour when having dynamic columns)

I can give it a try if there's any as long as the package license is free to publish

Thanks!

CodePudding user response:

The following Xaml snippet was copied/pasted from production code. Where the key parts are the bound "model" PlanningComplete properties in the <Style.Triggers>:

            <DataGrid Grid.Row="2"
                      x:Name="dataGrid"
                      ItemsSource="{Binding CollectionView}"
                      PreviewKeyDown="DataGrid_PreviewKeyDown"
                      IsReadOnly="True">
                <DataGrid.Resources>
                    <Style TargetType="{x:Type DataGridRow}">
                        <EventSetter Event="MouseDoubleClick" Handler="DataGrid_MouseDoubleClick"/>
                        <Style.Triggers>
                            <DataTrigger Binding="{Binding Path=PlanningComplete}" Value="True">
                                <Setter Property="Background" Value="LightGreen"/>
                            </DataTrigger>
                            <DataTrigger Binding="{Binding Path=PlanningComplete}" Value="False">
                                <Setter Property="Background" Value="LightYellow"/>
                            </DataTrigger>
                        </Style.Triggers>
                    </Style>
                </DataGrid.Resources>
                <DataGrid.Columns>
                    <DataGridTextColumn Binding="{Binding EventName1}"
                                        Header="{Binding Source={x:Static rmc:AppLocalization.Event}}" 
                                        Width="200"/>
                    <DataGridTextColumn Binding="{Binding EventName2}"
                                        Width="80"/>
                    <DataGridTextColumn Binding="{Binding EventDate,Converter={StaticResource DateTimeToLongStringConverter}}"
                                        Header="{Binding Source={x:Static rmc:AppLocalization.Date}}" 
                                        Width="{Binding Source={x:Static rmc:AppConstants.DataGridColumnWidthDateTime}}"/>
                    <DataGridTextColumn Binding="{Binding LocationName1}"
                                        Header="{Binding Source={x:Static rmc:AppLocalization.EventLocation}}" 
                                        Width="190*" MinWidth="190"/>
                </DataGrid.Columns>
            </DataGrid>

CodePudding user response:

You could do it in xaml as Stefan offered (but if I am not mistaken his code is to colour one full row), but personally I prefer using converters, so you could reuse the code in all the project, or even better in all solution, putting converters in an external library :

XAML : (NameSpace and assembly at your choice)

<Window x:Class="V_CN.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:converters="clr-namespace:`NameSpace`;assembly=`YourAssembly`"
        mc:Ignorable="d"
        Title="YourTitle" >
    

<Window.Resources>
    <converters:ColoringAgeConverter x:Key="coloringAgeConverter" />
</Window.Resources>

Your column :

    <DataGridTextColumn.ElementStyle>
        <Style TargetType="{x:Type TextBlock}">
            <Setter Property="Background" Value="{Binding Path="Age" Converter="{StaticResource coloringAgeConverter}"}" />
        </Style>
    </DataGridTextColumn.ElementStyle>
</DataGridTextColumn>

Then the converter itself :

public class ColoringAgeConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)

    {
        int valInt = Int32.Parse(value.ToString());
        switch (valInt)
        {
            case 1: return "#F0FFE4B5";
            case 2: return "#4000FFFF";
            case 3: return "#407FFF00";
            case 4: return "#907FCC00";
            case 5: return "#C07FCC00";
            case 6: return "#FF0FFF00";
            default: return "#FFFFFFFF";
        }
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}
  • Related