WPF DataGrid 根据多个列上的第二个绑定应用相同的样式

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

在 WPF DataGrid 中,我需要基于相同的复杂基类(具有子属性)显示多个列,并且能够根据子绑定自定义 DataGridCell 的显示(如背景颜色)不同于要显示的 DataGridCell 值的属性。这是一个要清楚的例子:

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

提前致谢。 Rgds, 帕斯卡

这里的复杂之处在于一行的数据上下文是您集合中的整个对象,您指定要绑定到哪个 属性 并作为文本查看。 该单元格不会 "know" 您希望它对 属性 作为对象感兴趣并将其属性用于任何事情。 如果您希望样式为 re-usable,则必须以某种方式指示它查看 X 或 Y 上的 属性。

一种方法是,如果它具有 x 或 y 的数据上下文。 使样式应用于网格。

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

然后确保您的单元格中有网格:

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

由于 IsInrange 始终为红色或绿色,因此您可以只为 true(或 false)设置一个数据触发器,并将其设置为另一种状态的默认值,而不是橙色。

谢谢安迪, 它有效,这是向前迈出的一大步。但是有没有办法让它更通用,比如:

    <DataGrid ItemsSource="{Binding}" AutoGenerateColumns="False" Grid.ColumnSpan="2">
        <DataGrid.Resources>
            <DataTemplate x:Key="InOutDataTemplate">
                <Grid Style="{StaticResource InRangeStyle}">
                    <TextBlock Text="{Binding Value}"/>
                </Grid>
            </DataTemplate>
        </DataGrid.Resources>
        <DataGrid.Columns>
            <DataGridTemplateColumn Header="X" ClipboardContentBinding="{Binding X}" CellEditingTemplate="{StaticResource InOutDataTemplate}"/>
            <DataGridTemplateColumn Header="Y" ClipboardContentBinding="{Binding Y}" CellEditingTemplate="{StaticResource InOutDataTemplate}"/>
        </DataGrid.Columns>
    </DataGrid>

我确实使用了 ClipboardContentBinding,因为我没有为 DataGridTemplateColumn 找到任何其他 'binding stuff' 或 DataContext。

Rgds, 帕斯卡

用于在数据 class 具有多个相同类型的属性(如下面的 Item)的情况下自定义列的通用自动生成

public class Item
{
    public Enum Prop0 { get; set; } = Enum.CustomEnum1; 
    public Enum Prop1 { get; set; } = Enum.CustomEnum2;
}

我建议为数据创建外观 class,您打算将其实例绑定到数据网格。

外观 class 基本上会复制初始数据 class,但需要自定义日期单元格的相同类型的属性将具有不同的类型:

public class ItemFacade
{
    private readonly Item item;
    public ItemFacade(Item item) => this.item = item;

    public EnumContainer0 Prop0 
    {
        get => new EnumContainer0 { Enum = this.item.Prop0 };
        set => this.item.Prop0 = value.Enum;
    }
    public EnumContainer1 Prop1
    {
        get => new EnumContainer1 { Enum = this.item.Prop1 };
        set => this.item.Prop1 = value.Enum;
    }
}

这些单独的特定类型可以是所讨论的 属性 类型的子 class 类型(如果可能),或者是 class 类型的子 class 类型门面类型:

public class EnumContainer
{
    public Enum Enum { get; set; }
}
public class EnumContainer0 : EnumContainer
{ }
public class EnumContainer1 :EnumContainer
{ }  

在那种情况下,您甚至可以考虑隐式转换:)。 XAML 代码将如下所示:

Window x:Class="TestWpfDataGrid.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:TestWpfDataGrid"
        xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" 
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <DataGrid ItemsSource="{Binding Items}">
            <i:Interaction.Behaviors>
                <local:ColumnHeaderBehaviour/>
            </i:Interaction.Behaviors>
                <DataGrid.Resources>
                    <DataTemplate DataType="{x:Type local:EnumContainer0}">
                        <TextBlock Text="{Binding Prop0.Enum}"/>
                    </DataTemplate>
                    <DataTemplate DataType="{x:Type local:EnumContainer1}">
                        <TextBlock Text="{Binding Prop1.Enum}"/>
                    </DataTemplate>
                </DataGrid.Resources>
        </DataGrid>
    </Grid>
</Window>

请注意,您必须绑定到数据模板中的各个属性。原因是模板控件的数据上下文是包含实例。这就是我们需要这种解决方法的原因。 local:ColumnHeaderBehaviour 中的附加行为可以如下所示:

public class ColumnHeaderBehaviour : Behavior<DataGrid>
    {
        protected override void OnAttached()
        {
            AssociatedObject.AutoGeneratingColumn += OnGeneratingColumn;
        }

        protected override void OnDetaching()
        {
            AssociatedObject.AutoGeneratingColumn -= OnGeneratingColumn;
        }

        private static void OnGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs eventArgs)
        {
            if (eventArgs.PropertyDescriptor is PropertyDescriptor descriptor)
            {
                var control = (DataGrid)sender;
                var resourceDictionary = control.Resources;
                var dataTemplate = resourceDictionary.Values
                    .OfType<DataTemplate>()
                    .Where(el => (Type)el.DataType == descriptor.PropertyType)
                    .FirstOrDefault();

                if (dataTemplate != null)
                {
                    var column = new DataGridTemplateColumn()
                    {
                        CellTemplate = dataTemplate,
                    };
                    eventArgs.Column = column;
                }
                eventArgs.Column.Header = descriptor.DisplayName ?? descriptor.Name;
            }
            else
            {
                eventArgs.Cancel = true;
            }
        }
    } 

请注意,如果在相关数据网格中指定了数据模板,则上述行为代码有效。

这个外观 class 是完全可测试的。键入它并对其进行测试所花费的时间肯定比我在寻找解决方案时花费的时间少。

此致, 瓦西里