A Programmer's Dream

WPF Datagrid Items Refresh, part II

Posted by Stephen Wrighton on 19 Jul 2009


Back in January, I posted a short entry about refreshing the datagrid in WPF. Which has gotten a number of questions regarding it. Well, one from Mykhaylo Khodorev made me go look some more into it. Apparently, the use of a generic IEnumerable object was failing this little trick. First, some information I've learned since then.

If the collection you're using to assign to the ItemSource attribute of your datagrid is an ObservableCollection (or inherits from it) any changes to the collection are automatically propagated to the grid. Secondly, the process described works while using a Generic List collection.

But, just so that we're clear on exactly what's happening, I'm placing my code that I used here. So here's the XAML:


<Window x:Class="Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpftk="http://schemas.microsoft.com/wpf/2008/toolkit"
Title="Window1" Height="300" Width="300" Initialized="Window_Initialized" >
<DockPanel>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Top">
<TextBox Name="txtAddItem" Width="50" MaxWidth="50" />
<Button Name="btnAddItem" Content="Add Item" Click="btnAddItem_Click"
HorizontalAlignment="Right" />
<Button Name="btnRefresh" Content="Refresh" HorizontalAlignment="Right" Click="btnRefresh_Click" />
</StackPanel>
<Grid>
<Grid.ColumnDefinitions >
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<wpftk:DataGrid Name="xGridObservableCollection" Grid.Column="0" AutoGenerateColumns="False">
<wpftk:DataGrid.Columns>
<wpftk:DataGridTextColumn Header="Item" Binding="{Binding Item}" IsReadOnly="True" />
</wpftk:DataGrid.Columns>
</wpftk:DataGrid>

<wpftk:DataGrid Name="xGridList" Grid.Column="1" AutoGenerateColumns="False">
<wpftk:DataGrid.Columns>
<wpftk:DataGridTextColumn Header="Item" Binding="{Binding Item}" IsReadOnly="True" />
</wpftk:DataGrid.Columns>
</wpftk:DataGrid>
</Grid>
</DockPanel>
</Window>

And here's the VB.Net CodeBehind for that XAML:


Imports System.Collections.ObjectModel

Class Window1

Private Sub btnAddItem_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
Dim x As String = Me.txtAddItem.Text
Dim b As New ItemClass(x)
_itemList.Add(b)
_items.Add(b)
End Sub

Private _items As New Items
Private _itemList As New List(Of ItemClass)

Private Sub Window_Initialized(ByVal sender As System.Object, ByVal e As System.EventArgs)

Dim x As Integer = 0
Do While x < 5
Dim i As New ItemClass("Auto Gen Item # " & x)
i.Value = x
_itemList.Add(i)
_items.Add(i)

x += 1
Loop
Me.xGridList.ItemsSource = _itemList
Me.xGridObservableCollection.ItemsSource = _items
End Sub

Private Sub btnRefresh_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
Me.xGridList.Items.Refresh()
End Sub
End Class

Public Class ItemClass
Implements System.ComponentModel.INotifyPropertyChanged

Private _v As Integer
Public Property Value() As Integer
Get
Return _v
End Get
Set(ByVal value As Integer)
_v = value
OnPropertyChanged("Value")
End Set
End Property
Private _item As String
Public Property Item() As String
Get
Return _item
End Get
Set(ByVal value As String)
_item = value
OnPropertyChanged("Item")
End Set
End Property

Public Sub New(ByVal itemtext As String)
_item = itemtext
End Sub

Protected Overridable Sub OnPropertyChanged(ByVal propertyName As String)
Dim e As New System.ComponentModel.PropertyChangedEventArgs(propertyName)
RaiseEvent PropertyChanged(Me, e)
End Sub
Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
End Class

Public Class Items
Inherits ObservableCollection(Of ItemClass)
End Class

Now, please realize that this is not 100% perfect (or production worthy) code, but rather stuff I tossed together for the provenance of this point.

What I've done is built a WPF application which holds two grids, one whose ItemsSource is pointed towards an ObservableCollection while the other's ItemsSource looks at a List. This is to show that when new items are added to these collections, one will automatically update while the other requires an explicit call to the datagrid's Items collection in order for the UI to be updated.

Now if you go back and look at Mykhaylo's code in the comment posted, you'll notice that the values are being populated via an IEnumerable accessed via a LINQ query of a DataTable in a DataSet.

Additionally, when Mykhaylo adds new items to the datatable, it appears in the datatable but not in the datagrid.

There's a reason for this.

And that is because the datagrid and the datatable are not bound together.

Here is mykhaylo's code:
AccDataGrid.ItemsSource = from account in _myDataSet.Accounts select new Account(account);
Notice that what's happening is that Mykhaylo is basically using LINQ to generate a new List which only exists as an object in the Datagrid's ItemsSource property.

Therefore when new items are added to the _myDataSet.Accounts object, there's nothing anywhere telling the datagrid about those objects.

The easiest solution would be to bind the datagrid in this manner:
AccDatagrid.ItemsSource = _myDataSet.Accounts
Then when items are added to the Accounts datagrid, it becomes a simple matter of using the AccDataGrid.Items.Refresh() command to update the UI.

Tweet me @kidananubix if you like this post.

Tweet