如何为用作 DataGrid 的 ItemsSource 的项目的集合属性中的每个项目生成和绑定一列

Posted

技术标签:

【中文标题】如何为用作 DataGrid 的 ItemsSource 的项目的集合属性中的每个项目生成和绑定一列【英文标题】:How to generate and bind a column for each item in the collection property of items used as DataGrid's ItemsSource 【发布时间】:2015-05-24 04:38:32 【问题描述】:

我的数据网格的项目源是一组对象,例如:

Public Property Quarter As Integer
Public Property MyColumns() As New List(Of MyColumn)

现在我想要一个网格绑定,使我的结果网格看起来像

-- 季度 -- Column1 -- Column2 -- Column3 .... ColumnX

数据源中的所有项目都将具有相同的 MyColumns。

有没有办法可以将集合绑定到网格列?

【问题讨论】:

***.com/questions/1983033/… 可能会给你一些想法 - 基本上将你的 property+collection 对象包装到 ExpandoObjects 与 property+(property for each item in collection) 并应用那里提出的解决方案。 感谢 Eugene 的回归,但老实说,我以前使用过 expandoObj,事实上从未听说过 :( .. 正在尝试,但现在变得越来越混乱...... 原来我的第一条评论有点误导(可以这样做,但太复杂了)。这里描述了最简单直接的解决方案 - ***.com/questions/24361013/…。它使用手动列生成和binding to indexers。 我已添加解决方案作为答案。第一部分处理任意长度的集合,第二部分完全处理您的情况。 我也得到了 ExpandoObject 的工作 :) .... 感谢您的提示学习了一个新功能 .. 它非常令人兴奋... 您可能想将其作为第二个答案发布对很多有用 【参考方案1】:

这里是解决方案。它不是最漂亮或最简单的,但它是可配置的。它主要基于 WPF: Dictionary<int, List<string>> in DataGrid 帖子中的想法,只是变成了一个更通用的版本,带有一些表达式和反射。

无论用作ItemsSource 的项目是否具有相同或不同数量的项目包含在相应的集合属性中,它都将起作用。


任意长度

以下是所需的组件:

using System.Reflection;
using Expressions = System.Linq.Expressions;

// See - https://***.com/questions/2132791/reflecting-over-all-properties-of-an-interface-including-inherited-ones
public static class ReflectionExtensions

    public static PropertyInfo GetInterfaceProperty(this Type type, String propName, Type returnType)
    
        if (propName == null)
            throw new ArgumentNullException("propName");
        if (returnType == null)
            throw new ArgumentNullException("propType");

        return type.GetInterfaces()
            .Select(parentInterface =>
                parentInterface.GetProperty(propName, returnType))
            .Where(prop =>
                prop != null)
            .Single();
    


public static class CollectionPropertyDataGridBindingHelper

    public static void RemoveAutoGeneratedColumns(this DataGrid dataGrid, String propertyName)
    
        if (dataGrid == null)
            throw new ArgumentNullException("dataGrid");
        if (propertyName == null)
            throw new ArgumentNullException("propertyName");

        var autogeneratedColumns = dataGrid
            .Columns
            .OfType<DataGridBoundColumn>()
            .Where(col =>
                (col.Binding as Binding).Path.Path.Equals(propertyName));

        foreach (var autoColumn in autogeneratedColumns)
        
            dataGrid.Columns.Remove(autoColumn);
        
    


    public static void RegenerateColumns<TItem, TPropertyCollectionItem>(
        this DataGrid dataGrid,
        Expressions.Expression<Func<TItem, IEnumerable<TPropertyCollectionItem>>> propertyExpression, 
        IEnumerable<TItem> items)
    
        RegenerateColumns<TItem, TPropertyCollectionItem>(dataGrid,
            propertyExpression,
            items,
            (index) =>
                String.Format("Column - 0", index));
    


    public static void RegenerateColumns<TItem, TPropertyCollectionItem>(
        this DataGrid dataGrid, 
        Expressions.Expression<Func<TItem, IEnumerable<TPropertyCollectionItem>>> collectionPropertyExpression, 
        IEnumerable<TItem> items, 
        Func<Int32, String> formatHeader)
    
        if (dataGrid == null)
            throw new ArgumentNullException("dataGrid");
        if (collectionPropertyExpression == null)
            throw new ArgumentNullException("propertyExpression");
        if (items == null)
            throw new ArgumentNullException("items");
        if (formatHeader == null)
            throw new ArgumentNullException("formatHeader");

        var collectionPropInfo = GetCollectionPropertyInfoFor<TItem, TPropertyCollectionItem>(collectionPropertyExpression);
        var propertyName = collectionPropInfo.Name;
        var getCount = GetCountGetter<TItem, TPropertyCollectionItem>(
            collectionPropertyExpression.Compile(),
            collectionPropInfo);

        // Remove old autocolumns
        dataGrid.RemoveAutoGeneratedColumns(propertyName);

        Int32 columnsRequired = items.Select(item => getCount(item)).Max();

        // Create new columns
        GenerateColumns(dataGrid,
            formatHeader,
            propertyName,
            columnsRequired);
        

    private static void GenerateColumns(DataGrid dataGrid,
        Func<Int32, String> formatHeader,
        String propertyName,
        Int32 columnsRequired)
    
        for (int columnNumber = 0; columnNumber < columnsRequired; columnNumber++)
        
            DataGridTextColumn column = new DataGridTextColumn()
            
                Header = formatHeader(columnNumber),
                Binding = new Binding(String.Format("0[1]",
                    propertyName,
                    columnNumber))
            ;

            dataGrid.Columns.Add(column);
        
    


    private static Func<TItem, Int32> GetCountGetter<TItem, TPropertyCollectionItem>(
        Func<TItem, IEnumerable<TPropertyCollectionItem>> getCollection,
        PropertyInfo propInfo)
    
        if (getCollection == null)
            throw new ArgumentNullException("getCollection");
        if (propInfo == null)
            throw new ArgumentNullException("propInfo");

        var collectionType = propInfo.PropertyType;
        
        var countGetter = collectionType.GetInterfaceProperty("Count",
            typeof(Int32));

        if (countGetter != null)
        
            return (item) =>
                (Int32)countGetter.GetMethod.Invoke(getCollection(item), null);
        

        throw new NotImplementedException("Not implemented: For simple IEnumerables the use of Enumerable.Count() method shall be considered.");
    


    private static PropertyInfo GetCollectionPropertyInfoFor<TItem, TPropertyCollectionItem>(
        Expressions.Expression<Func<TItem, 
        IEnumerable<TPropertyCollectionItem>>> propertyExpression)
    
        if (propertyExpression == null)
            throw new ArgumentNullException("propertyExpression");
        
        var memberExp = propertyExpression.Body as Expressions.MemberExpression;
        if (memberExp == null)
            throw new ArgumentNullException("propertyExpression");

        var propInfo = memberExp.Member as PropertyInfo;
        if (propInfo == null)
            throw new ArgumentNullException("propertyExpression");

        if (!propInfo.DeclaringType.IsAssignableFrom(typeof(TItem)))
            throw new ArgumentException("propertyExpression");
        
        return propInfo;
    


这是 XAML:

    <DataGrid ItemsSource="Binding Items" AutoGenerateColumns="False" Name="dataGrid">
        <DataGrid.Columns>
            <DataGridTextColumn Header="Quarter" Binding="Binding Quarter"/>
        </DataGrid.Columns>
    </DataGrid>

这是代码隐藏:

using Expressions = System.Linq.Expressions

public class Item

    public Item(Int32 quarter, Int32 repeatColumns)
    
        this.Quarter = quarter;
        this.MyColumns = Enumerable
            .Range(1, repeatColumns)
            .ToList();
    

    public Int32 Quarter
    
        get; set;
    

    public IList<Int32> MyColumns
    
        get; set;
    



/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window

    public MainWindow()
    
        InitializeComponent();

        this.Items = GetOriginalItems();

        this.DataContext = this;

        this.ReinitializeColumns();
    

    private void ReinitializeColumns()
    
        Expressions.Expression<Func<Item, IEnumerable<Int32>>> exp = 
            obj => 
                obj.MyColumns;

        this.dataGrid.RegenerateColumns(exp,
            this.Items);
    

    public IEnumerable<Item> Items
    
        get;
        private set;
    

    public IEnumerable<Item> GetOriginalItems()
    
        return new Item[] 
        
            new Item(1, 3),
            new Item(2, 2),
            new Item(3, 5),
            new Item(4, 2),
        ;
    


设置长度

这里是创建指定数量列的代码(你可以把它放到某个独立的类中,因为它是完全独立的,或者放到具有任意长度方法的同一个类中(在这种情况下不要忘记删除重复的生成方法))。它有点简单,直接适合您的需求:

    public static void RegenerateColumns<TItem, TPropertyCollectionItem>(
       this DataGrid dataGrid,
       String propertyName,
       Int32 columnsRequired)
    
        dataGrid.RegenerateColumns<TItem, TPropertyCollectionItem>(propertyName,
            columnsRequired,
            index => String.Format("Column - 0",
                index));
    


    public static void RegenerateColumns<TItem, TPropertyCollectionItem>(
        this DataGrid dataGrid,
        String propertyName,
        Int32 columnsRequired,
        Func<Int32, String> formatHeader)
    
        if (dataGrid == null)
            throw new ArgumentNullException("dataGrid");
        if (propertyName == null)
            throw new ArgumentNullException("propertyName");
        if (columnsRequired < 0)
            throw new ArgumentOutOfRangeException("columnsRequired");
        if (formatHeader == null)
            throw new ArgumentNullException("formatHeader");

        // Remove old autocolumns
        dataGrid.RemoveAutoGeneratedColumns(propertyName);
        
        GenerateColumns(dataGrid,
            formatHeader,
            propertyName,
            columnsRequired);
    


    private static void GenerateColumns(DataGrid dataGrid,
        Func<Int32, String> formatHeader,
        String propertyName,
        Int32 columnsRequired)
    
        for (int columnNumber = 0; columnNumber < columnsRequired; columnNumber++)
        
            DataGridTextColumn column = new DataGridTextColumn()
            
                Header = formatHeader(columnNumber),
                Binding = new Binding(String.Format("0[1]",
                    propertyName,
                    columnNumber))
            ;

            dataGrid.Columns.Add(column);
        
    

使用它的是代码隐藏:

    public MainWindow()
    
        InitializeComponent();

        this.Items = GetOriginalItems();

        this.DataContext = this;

        this.ReinitializeColumns(2);
    

    private void ReinitializeColumns(Int32 columnsCount)
    
        this.dataGrid.RegenerateColumns<Item, Int32>("MyColumns",
            columnsCount);
    

【讨论】:

【参考方案2】:

根据 Eugene 对动力学和 expandoObject 的见解,这是我为解决当前问题而实施的。

这是我的解决方案--

我们有来自模型的收藏,它的结构相当僵化。 因此,要将其绑定到 UI Grid,我们需要另一个以视觉上不同的方式映射当前列表的对象。

--使用 Expando 对象--

您可以创建一个 ExpandoObject 集合并将其动态属性映射到您的集合的属性。

Dim pivotItems As Object = New ExpandoObject()
    ' set properties for object'
     pivotItems.Quarter = developmentQuarters.Key
     pivotItems.Name = developmentQuarters.Key.Name

     ' since expando obj is a dictionary for prop name and its value we can set property names dynamically like this'
     For Each developmentModel As DevelopmentModel In developmentModels
         Dim pivotAsDict As IDictionary(Of String, Object) = pivotItems
         pivotAsDict.Add(developmentModel.BusinessGroupName + " " + developmentModel.Currency.Code, developmentModel.DevelopmentPercentage)
      Next
      ReModelledItems.Add(pivotItems)

所以现在我们有一个嵌套对象被展平为一个简单的集合,它具有基于初始集合中的值生成的动态列/属性。

我们现在可以简单地绑定这个 ExpandoObjects 集合

【讨论】:

以上是关于如何为用作 DataGrid 的 ItemsSource 的项目的集合属性中的每个项目生成和绑定一列的主要内容,如果未能解决你的问题,请参考以下文章

如何为 WPF DataGrid 获取 IsSelectionActive?

如何为 Flex Datagrid 制作可重复使用的 labelFunction?

如何将 DataGridTemplateColumn.Visibility 绑定到 DataGrid.ItemsSource 之外的属性?

如何为图像添加渐变?

如何为电子(一个 nodejs 库)配置 Intellisense?

如何将工厂用于 DataGrid.CanUserAddRows = true