3
votes

I did bind an Object to a DataGridTextColumn and would like to reference one of its properties from within the corresponding CellStyle. I assumed that each cell of this column would contain an instance of MyObject. However I can't find a reference to the Object from within the DataGridCell (I used a trivial converter to set a break point and searched the DataGridCell-object for quite a while).

I am looking for the Property MyObject.IsEnabled and would like to reference that in the Path-Property noted with ??? in the code below. Any suggestions?

<DataGridTextColumn Binding="{Binding MyObject}">
    <DataGridTextColumn.CellStyle>
        <Style TargetType="DataGridCell">
            <Style.Triggers>
                <DataTrigger Path="???" Binding="{Binding RelativeSource={RelativeSource Self}, PresentationTraceSources.TraceLevel=High,Converter={StaticResource debugger}}"  Value="False">
                    <!-- some setters -->
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </DataGridTextColumn.CellStyle>
</DataGridTextColumn>

EDIT:

Since i want to apply this style to all Cells of my DataGrid later on it is essential to find the object which is bound to the cell via RelativeSource instead of adding a hardcoded binding to MyObject.

SOLUTION

Thanks to the input of antiocol i was able to find a solution for my case which possibly can be adapted to similar problems.

Since the problem is that we don't have access to the values of the Cell or a CellModel from within the CellStyle, we use an attached Property on the DataGridCell to store the whole CellModel in there. From there we can bind any accessible Property of the DataGridCell to any Property of our CellModel.

code for attached property:

public static class DataGridUtils
{
public static CellModel GetCellModel(DependencyObject obj)
{
    return (CellModel)obj.GetValue(CellModelProperty);
}
public static void SetCellModel(DependencyObject obj, CellModel value)
{
    obj.SetValue(CellModelProperty, value);
}
public static readonly DependencyProperty CellModelProperty =
    DependencyProperty.RegisterAttached("CellModel", typeof(CellModel), typeof(DataGridUtils), new UIPropertyMetadata(null));

We need to set this property on every cell in our DataGrid. I didn't find a good solution to do this in XAML, so for now I set it in the converter before I retrieve the information. (suggestions for improvement appreciated)

Converter:

public class CellToEnabledConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        var cell = values[0] as DataGridCell;
        DataGridTextColumn column = cell.Column as DataGridTextColumn;
        //you should probably check if column is null after casting.
        Binding b = column.Binding as Binding;

        //Any suggestions on how to create a Binding to the parent object properly? 
        //I needed this workaround since I bind `MyObject.Value` to the `DataGridTextColumn`, 
        //but need a reference to `MyObject` here.
        Binding b1 = new Binding(b.Path.Path.Split('.')[0]){ Source = cell.DataContext };

        cell.SetBinding(DataGridUtils.CellModelProperty, b1);
        CellModel c = DataGridUtils.GetCellModel(cell);

        return c.IsEnabled;
    }

Now we can define a global Style in XAML and apply it to the whole DataGrid instead of a single column.

<Window.Resources>
    <converter:CellToEnabledConverter x:Key="CellToEnabledConverter" />

    <Style x:Key="DataGridCellStyle" TargetType="DataGridCell">
        <Style.Triggers>
            <DataTrigger Value="False">
                <DataTrigger.Binding>
                    <!--This Converter works only on DataGridTextColumns with this minimal example!-->
                    <Binding Converter="{StaticResource CellToEnabledConverter}">
                        <Binding RelativeSource="{RelativeSource Self}" />
                    </Binding>
                </DataTrigger.Binding>
                <!--Setters-->
            </DataTrigger>
        </Style.Triggers>
    </Style>
</Window.Resources>

<DataGrid CellStyle="{StaticResource DataGridCellStyle}">
    <DataGridTextColumn Binding="{Binding MyObject.Value}"/>
</DataGrid>

Since i found several comments on the net stating that "styling a cell depending on its value just is not possible with the current DataGrid", i hope this workaround helps someone out.

3
What is ItemsSource for DataGrid?Dennis
An observable collection of RowModels. You can find my initial question with some details here : stackoverflow.com/questions/30887303/… . As far as i can tell, the context should not be relevant for this question though.H W
DataContext of the cell is entire RowModel. I'm afraid, you can't get CellModel in the style.Dennis
It seems like you are right. However i am really confused why this is not possible. After all i did bind the whole object to my GridCell using <DataGridTextColumn Binding="{Binding MyObject}">. Where is this object actually bound to? I went through most of the visual tree (including DataGrid, DataGridView, DataGridCellsPresenter, DataGridCell) and could not find a Property to which MyObject is bound. The GUI still shows the proper ToString() value of my object, so the reference has to be SOMEwhere?!H W
This is how DG built. You've just described binding expression, smth like: "When rendering content of this column, take this property of data context". It's not a "binding of object". So, data context is a RowModel. When DG have to render the row, it looks into binding expression for particular column, and resolves its value to render in cell. Resolving doesn't change data context of the cell.Dennis

3 Answers

1
votes

I have been trying another solution for your problem.

<Style x:Key="DataGridCellStyle" TargetType="{x:Type DataGridCell}">
    <Style.Triggers>
        <DataTrigger Binding="{Binding Path=., RelativeSource={RelativeSource Self},  Converter={StaticResource DataGridCellToBooleanConverter}, ConverterParameter=IsEnabled}" Value="True">
            <Setter Property="Background" Value="Yellow"/>
        </DataTrigger>
    </Style.Triggers>
</Style>

<DataGrid CellStyle="{StaticResource DataGridCellStyle}">
   <DataGrid.Columns>
        <DataGridTextColumn Header="MyObject1" Binding="{Binding MyObject1}" />
        <DataGridTextColumn Header="MyObject2" Binding="{Binding MyObject2}" />
   </DataGrid.Columns>
</DataGrid>

In the other hand, I assume the ItemsSource of your DataGrid is a collection of Element objects (or something similar, of course)

public class Element
{
    public string Name { get; set; }
    public MyObject MyObject1 { get; set; }
    public MyObject MyObject2 { get; set; }
}
public class MyObject
{
    public string Name { get; set; }
    public bool IsEnabled { get; set; }
    public override string ToString()
    {
        return Name;
    }
}

Finally, the converter:

public class DataGridCellToBooleanConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        string propertyToAppend = parameter as string;
        var cell = value as DataGridCell;
        var column = cell.Column as DataGridTextColumn;
        Binding b = column.Binding as Binding;
        Binding b1 = new Binding(string.Join(".", b.Path.Path, propertyToAppend)) { Source = cell.DataContext };
        CheckBox dummy = new CheckBox();
        dummy.SetBinding(CheckBox.IsCheckedProperty, b1);
        return dummy.IsChecked;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

The tricky part is using the ConverterParameter in order to pass the name of the property you want to bind in the DataTrigger.

P.S. You can use reflection instead of the hacky CheckBox dummy element, but this works for me.

Hope this helps

0
votes

The only thing I can think of is switching each DataGridTextColumn to a DataGridTemplateColumn and inside each CellTemplate add some ContentControl whose DataContext is bound to the property that goes in that column.

That way you can create a reusable Style for that ContentControl, since the DataGridCell hasn't been of any help for you :P

<Style x:Key="MyObjectStyle" TargetType="{x:Type ContentControl}">
    <Setter Property="ContentTemplate">
        <Setter.Value>
            <DataTemplate>
                <!-- your TextBlock and stuff -->
            </DataTemplate>
        </Setter.Value>
    </Setter>
        <Style.Triggers>
            <DataTrigger Binding="{Binding IsEnabled, PresentationTraceSources.TraceLevel=High, Converter={StaticResource debugger}}"  Value="False">
                <!-- some setters -->
            </DataTrigger>
        </Style.Triggers>
</Style>

<DataGridTemplateColumn>
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <ContentControl Content="{Binding MyObject}"
                            Style="{StaticResource MyObjectStyle}" />
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
-1
votes

You have to fix the sintax of your DataTrigger first, something like this:

<DataGridTextColumn Binding="{Binding MyObject}">
    <DataGridTextColumn.CellStyle>
        <Style TargetType="DataGridCell">
            <Style.Triggers>
                <DataTrigger Binding="{Binding MyObject.IsEnabled, PresentationTraceSources.TraceLevel=High, Converter={StaticResource debugger}}"  Value="False">
                    <!-- some setters -->
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </DataGridTextColumn.CellStyle>
</DataGridTextColumn>

Pay attention to the Binding property of the DataTrigger. Is in that property where you have to write your Path, in your case IsEnabled due to the fact that the DataContext in each DataGridCell will be an instance of MyObject object. You don't need to use the RelativeSource because your Binding is pointing to MyObject, as said before.

If you want to create this style as a resource you can do this:

<Style x:Key="cellStyle" TargetType="DataGridCell">
    <Style.Triggers>
        <DataTrigger Binding="{Binding Path=MyObject.IsEnabled, PresentationTraceSources.TraceLevel=High, Converter={StaticResource debugger}}"  Value="False">
            <!-- some setters -->
        </DataTrigger>
    </Style.Triggers>
</Style>