WPF DataGrid的结构 - 根据值更改单元格

3
我已将一个对象与 DataGridTextColumn 绑定,并希望从相应的 CellStyle 中引用其属性之一。我认为这个列的每个单元格都将包含 MyObject 的一个实例。然而,我在 DataGridCell 中找不到对该对象的引用(我使用了一个简单的转换器设置断点并搜索了 DataGridCell 对象很长时间)。
我正在寻找 MyObject.IsEnabled 属性,并想在下面代码中标有 ??? 的 Path-Property 中引用它。 有什么建议吗?
<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>

编辑:

由于我希望将这种样式应用于后来的所有DataGrid单元格,因此通过RelativeSource找到绑定到单元格的对象而不是向MyObject添加硬编码绑定是至关重要的。

解决方案:

感谢antiocol的建议,我能够找到适用于我的情况的解决方案,该解决方案可能可用于类似的问题。

由于问题在于我们无法从CellStyle内部访问单元格或CellModel的值,因此我们在DataGridCell上使用一个附加属性将整个CellModel存储在其中。从那里,我们可以将DataGridCell的任何可访问属性绑定到我们CellModel的任何属性。

附加属性的代码:

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));

我们需要在DataGrid的每个单元格上设置这个属性。我没有找到一个好的XAML解决方案,所以现在我在检索信息之前在转换器中设置它。(欢迎提出改进建议) 转换器:
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;
    }

现在我们可以在XAML中定义全局的Style,并将其应用于整个DataGrid而不是单个列。
<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>

我发现网上有几条评论声称“使用当前的DataGrid无法根据其值设置单元格样式”,希望这个解决方法能对某些人有所帮助。


ItemsSourceDataGrid 中的什么? - Dennis
一个RowModels的可观察集合。你可以在这里找到我的初始问题和一些细节:http://stackoverflow.com/questions/30887303/bind-customobject-to-datagrid 。就我所知,上下文对于这个问题不应该有影响。 - H W
1
单元格的 DataContext 是整个 RowModel。恐怕您无法以这种方式获取 CellModel - Dennis
看起来你是对的。但我真的很困惑为什么这不可能。毕竟,我使用<DataGridTextColumn Binding="{Binding MyObject}">将整个对象绑定到了我的GridCell上。这个对象实际上绑定在哪里?我浏览了大部分的可视树(包括DataGrid、DataGridView、DataGridCellsPresenter、DataGridCell),但找不到MyObject绑定到的属性。GUI仍然显示我的对象的正确ToString()值,所以引用必须在某个地方! - H W
这就是 DG 构建的方式。你刚刚描述了绑定表达式,类似于:“渲染此列内容时,取数据上下文的此属性”。它不是“对象绑定”。因此,数据上下文是 RowModel。当 DG 必须呈现行时,它会查找特定列的绑定表达式,并解析其值以在单元格中呈现。解析不会更改单元格的数据上下文。 - Dennis
3个回答

1

我一直在尝试另一种解决方案来解决你的问题。

<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>

另一方面,我假设您的DataGrid的ItemsSource是Element对象(或类似的东西)的集合。
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;
    }
}

最终,转换器:

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();
    }
}

使用ConverterParameter来传递你想要绑定在DataTrigger中的属性名称是棘手的部分。
P.S. 你可以使用反射而不是hacky的CheckBox dummy元素,但这对我有用。
希望这能帮到你。

这个方法可行,比DataGridTemplateColumn更好,后者会产生其他问题,我还没有解决。我可能会尝试在我的DataGridCells上使用附加属性,并使用您在此处提供的代码将CellModel绑定到该属性上。非常感谢! - H W
我更新了问题,并提供了一个“最终”解决方案的最小示例。我还有两个问题没有解决,它们看起来不像干净的代码(在转换器中设置属性和手动操作BindingPath以查找正确的引用),但至少现在它可以工作了。如果您对这些问题有任何建议,我将非常感激 :) - H W
首先,因为您不需要传递多个值进行转换,所以不必使用MultiValueConverter。其次,为什么不创建一个附加属性来应用于DataGridTextColumn,甚至更好的是DataGridColumn,并在其中保存每组单元格的父属性?稍后,在转换器中,您可以从cell.Column读取此附加属性,并以正确的方式构建Binding。你觉得呢? - antiocol
多值转换器只是因为我之前传递了几个调试参数而留下的。我不太确定我是否正确理解了你的第二个建议,因为绑定到列属性已经是可能的了。我的问题是我无法为单元格属性指定单独的值,这个问题不会得到解决,对吗? - H W

0
我能想到的唯一方法是将每个 DataGridTextColumn 切换为 DataGridTemplateColumn,并在每个 CellTemplate 中添加一些 ContentControl,其 DataContext 绑定到该列中所需的属性。
这样,您可以为该 ContentControl 创建可重用的样式,因为 DataGridCell 对您没有任何帮助 :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>

“Triggers”也可以放在“DataTemplate”里面,这样你就可以轻松地引用其中的控件。顺便说一下。 - almulo
好的,所以如果我想要在我的单元格视图模型中包含数据绑定,但又想要具有完全相同的功能,则需要重新构建一个DataGridTextColumn。虽然我不太喜欢这种方法,但至少它是可行的解决方案。目前,我正在尝试使用DataGridCell.ColumnSortMemberPath,其中包含对MyObject的引用(作为字符串...)。如果我能够通过这种方式掌握MyObject,那么我就可以使用MultiConverter而不是重建模板。 - H W
使用“SortMemberPath”也太过狡猾了,所以我也没有其他想法了。 - H W
我没有想到SortMemberPath...这很粗糙,但可以使用多值转换器和反射来解决问题。 - almulo

-1

你首先必须修复DataTrigger的语法,类似于这样:

<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>

请注意DataTrigger的Binding属性。在该属性中,您必须编写您的Path,对于您的情况,是IsEnabled,因为每个DataGridCell中的DataContext将是MyObject对象的实例。您不需要使用RelativeSource,因为您的Binding指向MyObject,如前所述。
如果您想将此样式创建为资源,则可以执行以下操作:
<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>

很遗憾,正如Dennis在评论中所述,DataGridCell的DataContext指向DataGridRow。我希望在那里也能找到我的CellModel,但似乎这不是它的工作方式。 - H W
抱歉,你是对的。我刚刚编辑了我的答案,以修复绑定中的路径部分。正如Dennis所说,DataContext指向DataGridRow DataContext,因此诀窍在于指示IsEnabled属性的直接父级,在这种情况下是MyObject。 - antiocol
你的代码基本上就是我现在所使用的方法,即将对 MyObject 的引用硬编码到每个单独列的样式中。这种方法的问题在于,对于每一列来说, MyObject 都是不同的,这就是为什么我在最初的帖子中添加了 "EDIT" 来说明我实际上需要相对于 DataGridCell(或任何等效的解决方案)来查找 MyObject 的相对引用。这是否可行? - H W
我不确定我是否理解这个问题。MyObject 包含一个属性 IsEnabled。每个单元格应该只包含一个 MyObject 实例,但显然对于不同的单元格,这个实例是不同的。然而,我实际上找不到对 MyObject 的引用。 - H W
我猜它们是同一类型的不同属性,因此需要不同的绑定路径... - almulo
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接