I am sharing the code of an Attached Behavior that dynamically populates the columns of an Infragistics data grid; XamDataGrid Control. This post is based on XamDataGrid–Dynamically Create and Data Bind Columns with an Editor of your Choice.
So What’s New Here?
- The dynamic column creation implementation is packaged up in an extensible and unit testable pattern using WPF attached behavior
- I am not relying on the FieldLayoutInitialized event in the attached behavior, instead on PropertyChangedCallback (justification below)
- Replaces deprecated Infragistics API.
- Built with .NET 4.6, Prism 6.2, and Infragistics WPF v16.1 (trial version)
Deliverable
The complete implementation is packaged up in a downloadable demo app as shown in the screenshot below. For fun reasons different rows can have different number of columns.

View
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<!-- Data Grid Snippet --> <igDP:XamDataGrid Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" DataSource="{Binding OrderViewModels}" Margin="5"> <igDP:XamDataGrid.FieldLayoutSettings> <igDP:FieldLayoutSettings AutoGenerateFields="False"/> </igDP:XamDataGrid.FieldLayoutSettings> <i:Interaction.Behaviors> <!-- Custom Dependency Property --> <local:DynamicColumnBehavior Columns="{Binding Path=OrderViewModels}"/> </i:Interaction.Behaviors> </igDP:XamDataGrid> |
View Models
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
internal sealed class OrderViewModel : BindableBase { public int Id { get; set; } private string date; public string Date { get { return date; } set { base.SetProperty(ref date, value); } } private ObservableCollection<OrderItem> orderItems; public ObservableCollection<OrderItem> OrderItems { get { return orderItems; } set { base.SetProperty(ref orderItems, value); } } } public class OrderItem { public int Id { get; set; } public string Name { get; set; } } |
Behaviour
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
internal class DynamicColumnBehavior : Behavior<XamDataGrid> { public static readonly DependencyProperty ColumnsProperty = DependencyProperty.Register( "Columns", typeof(ObservableCollection<OrderViewModel>), typeof(DynamicColumnBehavior), new PropertyMetadata(OnPropertyValueChanged)); public ObservableCollection<OrderViewModel> Columns { get { return (ObservableCollection<OrderViewModel>)GetValue(ColumnsProperty); } set { SetValue(ColumnsProperty, value); } } private static void OnPropertyValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var behavior = d as DynamicColumnBehavior; var newValues = e.NewValue as IEnumerable<OrderViewModel>; if (behavior != null && newValues != null) { behavior.OnPropertyChanged(newValues); } } private void OnPropertyChanged(IEnumerable<OrderViewModel> extraItems) { var fieldLayout = AssociatedObject.FieldLayouts.FirstOrDefault(); if (fieldLayout == null) return; //What if we want to have a fixed column e.g OrderViewModel.Id plus the dynamic columns? var fields = fieldLayout.Fields; if (fields.Any()) { fields.Clear(); } int columnsCount = MaximumColumns(extraItems); for (int i = 0; i < columnsCount; i++) { var field = new Field { Name = $"Header{i + 1}", Settings = { AllowEdit = false/*, EditAsType = typeof(string)*/ }, AlternateBinding = new Binding("OrderItems[" + i + "].Name") //specifies data property to show }; fields.Add(field); } } private static int MaximumColumns(IEnumerable<OrderViewModel> items) { var onlyOrderItems = items.Select(a => a.OrderItems); if(onlyOrderItems.Any()) return onlyOrderItems.Max(a => a.Count); return 0; } } |
Providers
Gets called when the button is clicked.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
internal class MockViewModelProvider : IViewModelProvider { //Generate Mixed Columns Length public ObservableCollection<OrderViewModel> GenerateOrders(int maxItemsCount) { const int rowCount = 5; var result = new ObservableCollection<OrderViewModel>(); var random = new Random(); for (int i = 1; i <= rowCount; i++) { var orderItems = new List<OrderItem>(); var columnsCount = maxItemsCount - random.Next(0, 2);//decrease max columns count for (int j = 1; j <= columnsCount; j++) { orderItems.Add(CreateOrderItem(i, j)); } var vm = new OrderViewModel { Id = i + 1000, // e.g.10001 Date = DateTime.UtcNow.AddDays(i).ToShortDateString(), OrderItems = new ObservableCollection<OrderItem>(orderItems) }; result.Add(vm); } return result; } private static OrderItem CreateOrderItem(int row, int column) { return new OrderItem { Id = row * column, Name = $"Product{row}{column}" }; //e.g. Id=2, Name=Product12 } } |
What about FieldLayoutInitialized Event
Initially i tried to hook to the FieldLayoutInitialized event of an AssociateObject in the attached behavior class, however i hit some limitations:
- The initialisation of FieldLayout which fires the event took place earlier than populating the Columns custom dependency property change event. Hence, this.Columns value of in the event handler was null.
- Flexibility to regenerate columns based on ‘Max. Number of Columns’ user input. Whenever the property (OrderViewModels) that is bound to Columns dependency property is updated it calls the value changed call back which in turn will render the grid field layout. This is achieved by telling it so during DependencyProperty.Register; PropertyMetadata(PropertyChangedCallback).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
protected override void OnAttached() { base.OnAttached(); AssociatedObject.FieldLayoutInitialized += OnFieldLayoutInitialized; } protected override void OnDetaching() { AssociatedObject.FieldLayoutInitialized -= OnFieldLayoutInitialized; base.OnDetaching(); } private void OnFieldLayoutInitialized(object sender, FieldLayoutInitializedEventArgs e) { //It was always true; //Because the initialisation took place earlier than populating our custom dependency property (Columns) if (this.Columns == null) return; //... } |
Source Code