How to create and bind a column for each element in the property of the collection of elements used as a DataGrid ItemsSource data element

The source of my data grid elements is a set of objects such as:

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

Now I want to snap the grid so that my resulting grid looks like

- Quarter - Column1 - Column2 - Column3 .... Column X

All items in the data source will have the same MyColumns.

Is there a way to bind a collection to grid columns?

+1
source share
2 answers

Here is the solution. It is not the most beautiful or simple, but it is quite customizable. It is mainly based on the idea from a WPF article : Dictionary <int, List <string β†’> in a DataGrid , just turned into a more generalized version with a small number of expressions and reflections.

It will work whether the elements used as ItemsSource will have the same or different number of elements contained in the corresponding property of the collection.


Custom length

Here are the necessary components:

 using System.Reflection; using Expressions = System.Linq.Expressions; // See - https://stackoverflow.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; } } 

Here is the XAML:

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

And this is the code:

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

Set length

Here is the code that will create the specified number of columns (you can put it in a separate autonomous class, because it is completely autonomous or the same class with arbitrary methods (in this case, just remember to remove duplicate generation methods)). This is a bit simpler and directly suits your needs:

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

And this uses the code that uses it:

  public MainWindow() { InitializeComponent(); this.Items = GetOriginalItems(); this.DataContext = this; this.ReinitializeColumns(2); } private void ReinitializeColumns(Int32 columnsCount) { this.dataGrid.RegenerateColumns<Item, Int32>("MyColumns", columnsCount); } 
+1
source

With the understanding that Eugene gave me on dynamics and expandoObject, this is what I implemented to solve the current problem.

here is my solution -

We have on the collection coming from the model, and its structure was rather rigid. Therefore, to associate it with the user interface grid, we needed another object that displays the current list visually in a different way.

- use of Expando objects -

You can create an ExpandoObjects collection and map your dynamic properties to the properties of your collection.

 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) 

So now we have a nested object flattened out like a simple set that has dynamic columns / properties created based on the values ​​in the original collection.

Now we can just bind this collection of ExpandoObjects

+1
source

Source: https://habr.com/ru/post/1242363/


All Articles