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