Sunday 23 September 2012

WPF MVVM DataGrid Column Collection Binding

In my efforts to “MVVM-ify” my WPF application as much as possible, I ran into the need to Data Bind the Columns Collection of a DataGrid.

After some searching on StackOverflow, I found a neat solution by David Osborn;

http://stackoverflow.com/questions/3065758/wpf-mvvm-datagrid-dynamic-columns

This solution was in C#, so I converted it to VB;

 Imports System.Collections.ObjectModel  
Imports System.Collections.Specialized
Namespace Controls
Public NotInheritable Class DataGridExtension
Private Sub New()
End Sub
Public Shared Function GetColumns(obj As DependencyObject) As ObservableCollection(Of DataGridColumn)
Return DirectCast(obj.GetValue(ColumnsProperty), ObservableCollection(Of DataGridColumn))
End Function
Public Shared Sub SetColumns(obj As DependencyObject, value As ObservableCollection(Of DataGridColumn))
obj.SetValue(ColumnsProperty, value)
End Sub
Public Shared ReadOnly ColumnsProperty As DependencyProperty = DependencyProperty.RegisterAttached("Columns", _
GetType(ObservableCollection(Of DataGridColumn)), _
GetType(DataGridExtension), _
New UIPropertyMetadata(New ObservableCollection(Of DataGridColumn)(), AddressOf OnDataGridColumnsPropertyChanged))
Private Shared Sub OnDataGridColumnsPropertyChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
If d.GetType = GetType(DataGrid) Then
Dim myGrid As DataGrid = TryCast(d, DataGrid)
Dim Columns As ObservableCollection(Of DataGridColumn) = DirectCast(e.NewValue, ObservableCollection(Of DataGridColumn))
If Columns IsNot Nothing Then
myGrid.Columns.Clear()
If Columns IsNot Nothing AndAlso Columns.Count > 0 Then
For Each dataGridColumn As DataGridColumn In Columns
myGrid.Columns.Add(dataGridColumn)
Next
End If
AddHandler Columns.CollectionChanged, Sub(sender As Object, args As NotifyCollectionChangedEventArgs)
If args.NewItems IsNot Nothing Then
For Each column As DataGridColumn In args.NewItems.Cast(Of DataGridColumn)()
myGrid.Columns.Add(column)
Next
End If
If args.OldItems IsNot Nothing Then
For Each column As DataGridColumn In args.OldItems.Cast(Of DataGridColumn)()
myGrid.Columns.Remove(column)
Next
End If
End Sub
End If
End If
End Sub
End Class
End Namespace

Then, in order to use the above, make sure you include the namespace in your XAML resources, for instance;


 xmlns:w="clr-namespace:MyApplication1.Controls"  


Then add the following to your DataGrid;


 w:DataGridExtension.Columns="{Binding Path=DataContext.DataGridColumns, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"  


Also, make sure you set AutoGenerateColumns to False.


Then go ahead and add you property in your View Model;

     ''' <summary>  
''' The DataGrid Columns
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Property DataGridColumns As ObservableCollection(Of DataGridColumn) Implements Interfaces.iMasterDetailsViewModel(Of T).DataGridColumns
Get
Return _DataGridColumns
End Get
Set(value As ObservableCollection(Of DataGridColumn))
If value.Equals(_DataGridColumns) = False Then
_DataGridColumns = value
OnPropertyChanged("DataGridColumns")
End If
End Set
End Property

You can then add a bunch of columns as follows;

 Dim cols As New ObservableCollection(Of DataGridColumn)  
Dim col As New DataGridTextColumn
cols.Add(New DataGridTextColumn With {.Binding = New System.Windows.Data.Binding("Customer_Code"), .Header = "Customer Code", .Width = 100})
cols.Add(New DataGridTextColumn With {.Binding = New System.Windows.Data.Binding("Name"), .Header = "Name", .Width = 250})
DataGridColumns = cols

Seems like quite a nice neat solution.


However, do bear in mind that, as Rakshit Bakshi points out in the answer;



“The above code will do the work in general case. However, it will fail when you hide the datagrid and make it visible again. Because the columns property will show that there are 0 columns when they are hidden and as the columns property is changed, the callback will be fired and it will try to add the columns again, but physically columns does exist in the datagrid so the code will fail with an exception saying can't add duplicate columns.”

No comments:

Post a Comment