Search code examples
c#wpfdata-bindingwpfdatagrid

WPF DataGrid apply same style depending on second binding on multiple columns


In a WPF DataGrid I need to display multiple column based on the same complex base classe (which have sub properties) and be able to custom the display of the DataGridCell (like the background color) depending on a sub-binding properties different from the DataGridCell value to display. Here is an exemple to be clear :

<Window x:Class="Wpf_DataGrid_In_out_range.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:local="clr-namespace:Wpf_DataGrid_In_out_range"
    mc:Ignorable="d"
    Title="MainWindow" Height="200" Width="250">
<Window.Resources>
    <Style x:Key="InRangeStyle" TargetType="DataGridCell">
        <Setter Property="HorizontalAlignment" Value="Center"></Setter>
        <Setter Property="Background" Value="Orange"></Setter>
        <Style.Triggers>
            <DataTrigger Binding="{Binding Path=IsInRange}" Value="False"  >
                <Setter Property="Background" Value="Red"></Setter>
            </DataTrigger>
            <DataTrigger Binding="{Binding Path=IsInRange}" Value="True" >
                <Setter Property="Background" Value="Green"></Setter>
            </DataTrigger>
        </Style.Triggers>
    </Style>
</Window.Resources>
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="191*"/>
        <ColumnDefinition Width="326*"/>
    </Grid.ColumnDefinitions>
    <DataGrid ItemsSource="{Binding}" AutoGenerateColumns="False" Grid.ColumnSpan="2">
        <DataGrid.Columns>
            <DataGridTextColumn Binding="{Binding X}" CellStyle="{StaticResource InRangeStyle}" Header="X"></DataGridTextColumn>
            <DataGridTextColumn Binding="{Binding Y}" CellStyle="{StaticResource InRangeStyle}" Header="Y"></DataGridTextColumn>
        </DataGrid.Columns>
    </DataGrid>
</Grid>

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        List<MySample> samples = new List<MySample>();
        samples.Add(new MySample(5, 25));
        samples.Add(new MySample(25, 15));
        samples.Add(new MySample(0, 0));
        samples.Add(new MySample(15, 45));

        DataContext = samples;
    }
}

public class MySample
{
    public RangeValue X { get; set; }
    public RangeValue Y { get; set; }

    public MySample(int x,int y)
    {
        X = new RangeValue(x, 1, 10);
        Y = new RangeValue(y, 20, 40);
    }
}
public class RangeValue
{
    public int Value { get; set; }
    public int Min { get; set; }
    public int Max { get; set; }
    public bool IsInRange
    {
        get
        {
            if (Value <= Max && Value >= Min) return true;
            else return false;
        }
    }

    public RangeValue(int value, int min, int max)
    {
        Value = value;
        Min = min;
        Max = max;
    }

    public override string ToString()
    {
        return Value.ToString("F2");
    }
}

Thanks in advance. Rgds, Pascal.


Solution

  • The complication here is that the datacontext of a row is the entire object in your collection, you specify which property to bind to and see as the text. The cell doesn't "know" you intend it to be interested in that property as an object and use it's properties for anything. If you want the style to be re-usable then it has to somehow be directed to look at the property on X or Y.

    One way to do that would be if it's got a datacontext of x or y. Make the style apply to a grid.

    <Window.Resources>
        <Style x:Key="InRangeStyle" TargetType="Grid">
             <Setter Property="Background" Value="Orange"></Setter>
            <Style.Triggers>
                <DataTrigger Binding="{Binding IsInRange}" Value="False"  >
                    <Setter Property="Background" Value="Red"/>
                </DataTrigger>
                <DataTrigger Binding="{Binding IsInRange}" Value="True" >
                    <Setter Property="Background" Value="Green"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>
    

    Then make sure you have a grid in your cell:

    <DataGridTemplateColumn Header="X">
        <DataGridTemplateColumn.CellTemplate>
            <DataTemplate>
                <Grid DataContext="{Binding X}" Style="{StaticResource InRangeStyle}">
                    <TextBlock Text="{Binding Value}"/>
                </Grid>
            </DataTemplate>
        </DataGridTemplateColumn.CellTemplate>
    </DataGridTemplateColumn>
    

    Since IsInrange is always red or green then you could just have one datatrigger for true ( or false ) and give it the default for the other state rather than orange.