如何为用作 DataGrid 的 ItemsSource 的项目集合 属性 中的每个项目生成和绑定一列
How to generate and bind a column for each item in the collection property of items used as DataGrid's ItemsSource
我的数据网格的项目源是一组对象,例如:
Public Property Quarter As Integer
Public Property MyColumns() As New List(Of MyColumn)
现在我想以生成的网格看起来像
的方式进行网格绑定
-- Quarter -- Column1 -- Column2 -- Column3 .... ColumnX
数据源中的所有项目都将具有相同的 MyColumns。
有没有一种方法可以将集合绑定到网格列?
这是解决方案。它不是最漂亮或最简单的,但它是可配置的。它主要基于 post WPF: Dictionary<int, List<string>> in DataGrid 中的想法,只是变成了带有一些表达式和反射的更通用的版本。
无论用作 ItemsSource
的项目在相应集合 属性 中包含的项目数量相同还是不同,它都会起作用。
任意长度
这是必需的组件:
using System.Reflection;
using Expressions = System.Linq.Expressions;
// See -
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),
};
}
}
设置长度
这是将创建指定数量的列的代码(您可以将它放在一些独立的 class 中,因为它是完全独立的,或者与任意相同的 class -长度方法(在这种情况下不要忘记删除重复的生成方法))。它更简单一些,直接适合您的需求:
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);
}
根据 Eugene 给我的关于动力学和 expandoObject 的见解,这就是我为解决当前问题而实施的。
这是我的解决方案 --
我们有来自模型的集合,它的结构相当严格。
因此,要将其绑定到 UI 网格,我们需要另一个对象以视觉上不同的方式映射当前列表。
--使用 Expando 对象--
您可以创建一个 ExpandoObjects 集合并将其动态属性映射到您的集合的属性。
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)
现在我们将嵌套对象展平为一个简单的集合,该集合具有基于初始集合中的值生成的动态 columns/properties。
我们现在可以简单地绑定这个 ExpandoObject 集合
我的数据网格的项目源是一组对象,例如:
Public Property Quarter As Integer
Public Property MyColumns() As New List(Of MyColumn)
现在我想以生成的网格看起来像
的方式进行网格绑定-- Quarter -- Column1 -- Column2 -- Column3 .... ColumnX
数据源中的所有项目都将具有相同的 MyColumns。
有没有一种方法可以将集合绑定到网格列?
这是解决方案。它不是最漂亮或最简单的,但它是可配置的。它主要基于 post WPF: Dictionary<int, List<string>> in DataGrid 中的想法,只是变成了带有一些表达式和反射的更通用的版本。
无论用作 ItemsSource
的项目在相应集合 属性 中包含的项目数量相同还是不同,它都会起作用。
任意长度
这是必需的组件:
using System.Reflection;
using Expressions = System.Linq.Expressions;
// See -
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),
};
}
}
设置长度
这是将创建指定数量的列的代码(您可以将它放在一些独立的 class 中,因为它是完全独立的,或者与任意相同的 class -长度方法(在这种情况下不要忘记删除重复的生成方法))。它更简单一些,直接适合您的需求:
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);
}
根据 Eugene 给我的关于动力学和 expandoObject 的见解,这就是我为解决当前问题而实施的。
这是我的解决方案 --
我们有来自模型的集合,它的结构相当严格。 因此,要将其绑定到 UI 网格,我们需要另一个对象以视觉上不同的方式映射当前列表。
--使用 Expando 对象--
您可以创建一个 ExpandoObjects 集合并将其动态属性映射到您的集合的属性。
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)
现在我们将嵌套对象展平为一个简单的集合,该集合具有基于初始集合中的值生成的动态 columns/properties。
我们现在可以简单地绑定这个 ExpandoObject 集合