Sunday, July 19, 2009

WPF Datagrid Items Refresh, part II


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.

2 comments:

SANDEEP SHARMA said...

Sir, plz. send this code in c#.
my level is at beginners.

Thanks in advance.
Happy Coding!!

Stephen Wrighton said...

You can easily go here and use this tool to do the conversion:

http://www.developerfusion.com/tools/convert/csharp-to-vb/

But please remember that this was not to be considered 'production' code. It's not perfect, I typed it by hand into the text editor for the blog post rather than VisualStudio, and there's little to no error checking/handling.

Blog Widget by LinkWithin