简体   繁体   中英

Structure of WPF DataGrid - change cells depending on value

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.

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 .

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

Hope this helps

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>

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>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM