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

时间:2018-04-10 14:43:17

标签: c# wpf data-binding wpfdatagrid

在WPF DataGrid中,我需要根据相同的复杂基础classe(具有子属性)显示多个列,并能够自定义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, 帕斯卡。

3 个答案:

答案 0 :(得分:0)

这里的复杂之处在于,行的datacontext是集合中的整个对象,您可以指定要绑定到哪个属性并将其视为文本。 细胞不知道&#34;您打算将该属性作为对象感兴趣,并将其用于任何事物。 如果您希望样式可以重复使用,那么必须以某种方式指示查看X或Y上的属性。

这样做的一种方法是,如果它有一个x或y的datacontext。 使样式适用于网格。

<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),并为其他状态而不是橙色提供默认值。

答案 1 :(得分:0)

谢谢安迪, 它起作用,这是一个很好的进步。但有没有办法让它更通用,如:

    <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,因为我没有找到任何其他&#39;绑定的东西&#39;或DataConridTemplateColumn的DataContext。

RGDS, 帕斯卡。

答案 2 :(得分:0)

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

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

我建议为数据类制作一个立面,您打算将其实例绑定到数据网格。

facade类将基本上复制初始数据类,但相同类型的属性(需要自定义日期单元格)将具有单独的类型:

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

这些个别的特定类型可以是所讨论的属性类型的子类(如果可能),也可以是其外墙类型的子类:

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

请注意,如果在相关数据网格内指定了数据模板,则上述行为代码将起作用。

此外观类是完全可测试的。键入下来,对其进行测试肯定会比我在寻找解决方案时花费的时间少。

关于, 瓦西里