Search code examples
c#wpfxamldatagriddatatrigger

WPF DataGrid - How to setup correct DataTrigger binding to cell's data source (and not row's source)


Trying to setup the background of a cell dependend on a cell-object property in a WPF DataGrid I get an error, that the property is not found (but on the row-object):

System.Windows.Data Error: 40 : BindingExpression path error: 'IsOn' property not found on 'object' ''MyRow' (HashCode=48826322)'. BindingExpression:Path=IsOn; DataItem='MyRow' (HashCode=48826322); target element is 'DataGridCell' (Name=''); target property is 'NoTarget' (type 'Object')

I wonder, why the DataTrigger Binding is addressing the row object "MyRow", since the DataTrigger is defined for/inside a CellStyle.

XAML:

<DataGrid Name="tblTest" Grid.Column="2" IsReadOnly="True" AutoGenerateColumns="True">
    <DataGrid.CellStyle>
        <Style TargetType="{x:Type DataGridCell}">
            <Setter Property="Background" Value="PaleGreen" />
            <Style.Triggers>
                <DataTrigger Binding="{Binding IsOn}" Value="True">
                    <Setter Property="Background" Value="Red"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </DataGrid.CellStyle>
</DataGrid>

C#

class MyCell
{
    public MyCell( string v)
    {
        Value = v;
    }
    public string Value { get; set; }
    public bool IsOn { get => Value == "one";  }
    public override string ToString()
    {
        return Value;
    }
}

class MyRow
{
    public MyCell One { get; set;  }
    public MyCell Two { get; set;  }
}

void SetupTestTable()
{
    List<MyRow> data = new();
    data.Add(new MyRow
    {
        One = new MyCell("one"),
        Two = new MyCell("two")
    });
    tblTest.ItemsSource = data;
}

enter image description here

So how to bind against the cell object "MyCell" correctly?


Solution

  • DataGridCells have the same DataContext as DataGridRow - there are many obstacles to do differently in general-purpose manner. So single DataGrid.CellStyle won't work

    I will use AutoGeneratingColumn to create cell styles for each column. However they will be based on existing style which is stored in DataGrid.Resources.

    <DataGrid Name="tblTest" Grid.Column="2" IsReadOnly="True" 
              AutoGenerateColumns="True"
              AutoGeneratingColumn="tblTest_AutoGeneratingColumn">
        <DataGrid.Resources>
            <Style TargetType="{x:Type DataGridCell}" x:Key="ColoredCellStyle">
                <Setter Property="Background" Value="Cyan" />
                <Style.Triggers>
                    <DataTrigger Binding="{Binding Tag.IsOn, RelativeSource={RelativeSource Self}}" Value="True">
                        <Setter Property="Background" Value="Red"/>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </DataGrid.Resources>
    </DataGrid>
    

    I'm using binding to Tag instead of DataContext, because DataContext is MyRow object. In Tag there will be MyCell objects. It is achieved in event handler:

    private void tblTest_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
    {
        if (e.Column is DataGridTextColumn tc && tc.Binding is Binding binding)
        {
            // unique value for each column
            var property = binding.Path.Path;
    
            // DataGrid reference to get Resources
            var dg = (DataGrid)sender;
    
            // new cell style which inherits trigger from ColoredCellStyle and binds Tag to MyCell property
            var cellStyle = new Style
            {
                TargetType = typeof(DataGridCell),
                BasedOn = (Style)dg.Resources["ColoredCellStyle"],
                Setters =
                {
                    new Setter
                    { 
                        Property = DataGridCell.TagProperty, 
                        Value = new Binding(property)
                    }
                }
            };
    
            tc.CellStyle = cellStyle;
        };
    }
    

    ColoredCells