updates the ListBox. However, when the user changes, for example, the Description of one of the pieces, this does not trigger the CollectionChanged event so that refreshing the ListBox does not update its items.
Do I need to raise the PropertyChanged event of ocPieces? If so, how do I do that?
If those are not the right approach, how can I refresh the ListBox after an item property has changed?
The problem is that changing the properties of an item in an ObservableCollection does not change that collection. Thus a ListBox.Items.Refresh does not work because it doesn't know the collection has changed. Well then. the obvious solution is to raise the CollectionChanged event when the user makes a change to a property. That's where I need help, not with the answers provided so far.
Hi @Roger Schlueter ,
Based on my test, there is no need to use collectionchanged event if we only want to add item to the listbox. Because we have bound it before.
However, we need to use PropertyChanged event to refresh the listbox after we changed an item property.
Here is a code example you can refer to.
XAML.cs:
<ListBox Name="lbxPieces" Width="200" Height="200">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Name="stackPanel2" Orientation="Horizontal">
<TextBlock Text="{Binding PieceID,Mode=TwoWay}" Margin="5" />
<TextBlock Text="{Binding Description,Mode=TwoWay}" Margin="5"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Name="btntest" Content="test" Width="100" Height="50" HorizontalAlignment="Center" VerticalAlignment="Bottom" Click="btntest_Click"></Button>
</Grid>
VB.NET Code:
Class MainWindow
Dim ocPieces As ObservableCollection(Of Piece) = New ObservableCollection(Of Piece)
Private Sub Window_Loaded(sender As Object, e As RoutedEventArgs)
Dim piece1 As Piece = New Piece
piece1.PieceID = 1001
piece1.Description = "d1"
Dim piece2 As Piece = New Piece
piece2.PieceID = 1002
piece2.Description = "d2"
Dim piece3 As Piece = New Piece
piece3.PieceID = 1003
piece3.Description = "d3"
ocPieces.Add(piece1)
ocPieces.Add(piece2)
ocPieces.Add(piece3)
Me.lbxPieces.ItemsSource = ocPieces
End Sub
Private Sub btntest_Click(sender As Object, e As RoutedEventArgs)
Dim piece4 As Piece = New Piece
piece4.PieceID = 1004
piece4.Description = "d4"
ocPieces.Add(piece4) // add item to change collection
ocPieces.Item(0).Description = "test-description" // change property
End Sub
End Class
Public Class Piece
Implements INotifyPropertyChanged
Private _description As String
Public Property PieceID As Integer
Public Property Description() As String
Return _description
End Get
Set(ByVal value As String)
_description = value
OnPropertyChanged("Description")
End Set
End Property
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Protected Friend Overridable Sub OnPropertyChanged(ByVal propertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
End Class
Result:
As the above picture showed, we can refresh the listbox successfully when we add a new item or change property.
If the response is helpful, please click "Accept Answer" and upvote it.
Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.
Hi @Jack J Jun
Thanks for taking the time to reply. Your code and my existing code are identical except for minor, inconsequential differences so this did not help much.
Except that your btntest_Click event handler has a subtle difference that makes it invalid for property changed events. Take this line from your event handler:
ocPieces.Item(0).Description = "test-description" // change property
and put it in different click event handler and you will see that the ListBox is NOT updated. The reason your code works is that the property changed event is included as part of the collection changed event so the collection change event automatically handled the property changed event.
Hi @Roger Schlueter ,
>> The reason your code works is that the property changed event is included as part of the collection changed event so the collection change event automatically handled the property changed event.
Based on my further test ,the fact is not that. If I delete the code OnPropertyChanged("Description")
in the set method, the code will not work. Therefore. we have to use OnPropertyChanged event if we want to update the property in the listbox. If you still have some misunderstanding about it, you can refer to the Microsoft doc How to implement collections.
if you use MVVM pattern you can try following demo based on Jacks demo:
XAML:
<Window x:Class="Window087"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1.WpfApp087"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<StackPanel>
<Button Content="test" Command="{Binding}" Width="100" Height="30" Margin="5"/>
<ListBox ItemsSource="{Binding Pieces}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding PieceID}" Margin="5" />
<TextBlock Text="{Binding Description}" Margin="5"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Window>
And classes:
Imports System.Collections.ObjectModel
Imports System.ComponentModel
Namespace WpfApp087
Public Class ViewModel
Implements ICommand
Public Sub New()
Pieces.Add(New Piece With {.PieceID = 1001, .Description = "d1"}) ' add item to collection '
Pieces.Add(New Piece With {.PieceID = 1002, .Description = "d2"}) ' add item to collection '
Pieces.Add(New Piece With {.PieceID = 1003, .Description = "d3"}) ' add item to collection '
End Sub
Public Property Pieces As New ObservableCollection(Of Piece)
Public Sub Execute(parameter As Object) Implements ICommand.Execute
Pieces.Add(New Piece With {.PieceID = 1004, .Description = "d4"}) ' add item to collection '
Pieces.Item(0).Description = "test-description" ' change Property '
End Sub
Public Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged
Public Function CanExecute(parameter As Object) As Boolean Implements ICommand.CanExecute
Return True
End Function
End Class
Public Class Piece
Implements INotifyPropertyChanged
Public Property PieceID As Integer
Private _description As String
Public Property Description() As String
Return _description
End Get
Set(ByVal value As String)
_description = value
OnPropertyChanged("Description")
End Set
End Property
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Protected Friend Overridable Sub OnPropertyChanged(ByVal propertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
End Class
End Namespace
Hi anonymous user-3316
Thanks for taking the time to reply. Your code and my existing code are identical except for minor, inconsequential differences so this did not help much.
See my comment above to Jack. Your solution suffers from the same problem. It works ONLY because the property changed event is buried inside the collection changed event. Pull it out of there and place it somewhere else and your code does NOT update the ListBox.
Hi Roger,
if you want to try changes in items of collection use TrulyObservableCollection instead of Observablecollection:
TrulyObservableCollection:
Imports System.Collections.ObjectModel
Imports System.Collections.Specialized
Imports System.ComponentModel
''' <summary>
''' Implements the "ItemPropertyChanged" Event for a ObservableCollection
''' </summary>
''' <typeparam name="T"></typeparam>
''' <seealso cref="System.Collections.ObjectModel.ObservableCollection(Of T)" />
Public NotInheritable Class TrulyObservableCollection(Of T As INotifyPropertyChanged)
Inherits ObservableCollection(Of T)
Implements ICollectionItemPropertyChanged(Of T)
''' <summary>
''' Initializes a new instance of the <see cref="TrulyObservableCollection(Of T)"/> class.
''' </summary>
Public Sub New()
AddHandler CollectionChanged, AddressOf FullObservableCollectionCollectionChanged
End Sub
''' <summary>
''' Initializes a new instance of the <see cref="TrulyObservableCollection(Of T)"/> class.
''' </summary>
''' <param name="pItems">The p items.</param>
Public Sub New(pItems As IEnumerable(Of T))
MyClass.New
For Each itm In pItems
Me.Add(itm)
End Sub
Public Event ItemChanged As EventHandler(Of ItemChangedEventArgs(Of T)) Implements ICollectionItemPropertyChanged(Of T).ItemChanged
''' <summary>
''' Fulls the observable collection collection changed.
''' </summary>
''' <param name="sender">The sender.</param>
''' <param name="e">The <see cref="NotifyCollectionChangedEventArgs"/> instance containing the event data.</param>
Private Sub FullObservableCollectionCollectionChanged(sender As Object, e As NotifyCollectionChangedEventArgs)
If e.NewItems IsNot Nothing Then
For Each itm In e.NewItems
AddHandler CType(itm, INotifyPropertyChanged).PropertyChanged, AddressOf ItemPropertyChanged
End If
If e.OldItems IsNot Nothing Then
For Each itm In e.OldItems
RemoveHandler CType(itm, INotifyPropertyChanged).PropertyChanged, AddressOf ItemPropertyChanged
End If
End Sub
''' <summary>
''' Items the property changed.
''' </summary>
''' <param name="sender">The sender.</param>
''' <param name="e">The <see cref="PropertyChangedEventArgs"/> instance containing the event data.</param>
Private Sub ItemPropertyChanged(sender As Object, e As PropertyChangedEventArgs)
Dim args As New CollectionItemPropertyChangedEventArgs(NotifyCollectionChangedAction.Replace, sender, sender, IndexOf(CType(sender, T)), e.PropertyName)
OnCollectionChanged(args)
End Sub
End Class
Friend Interface ICollectionItemPropertyChanged(Of T)
Event ItemChanged As EventHandler(Of ItemChangedEventArgs(Of T))
End Interface
Public Class ItemChangedEventArgs(Of T)
Public ReadOnly Property ChangedItem As T
Public ReadOnly Property PropertyName As String
Public Sub New(item As T, propertyName As String)
Me.ChangedItem = item
Me.PropertyName = propertyName
End Sub
End Class
Public Class CollectionItemPropertyChangedEventArgs
Inherits NotifyCollectionChangedEventArgs
Public Sub New(action As NotifyCollectionChangedAction, newItem As Object, oldItem As Object, index As Integer, itemPropertyName As String)
MyBase.New(action, newItem, oldItem, index)
If itemPropertyName Is Nothing Then Throw New ArgumentNullException(NameOf(itemPropertyName))
PropertyName = itemPropertyName
End Sub
''' <summary>
''' Gets the name of the collection item's property that changed.
''' </summary>
''' <returns>The name of the collection item's property that changed.</returns>
Public Overridable ReadOnly Property PropertyName As String
End Class
'Using this Event
'Public Class Demo
' Private WithEvents c As New TrulyObservableCollection(Of Demo2)
' Private Sub c_CollectionChanged(sender As Object, e As NotifyCollectionChangedEventArgs) Handles c.CollectionChanged
' Dim e1 = TryCast(e, CollectionItemPropertyChangedEventArgs)
' If e1 Is Nothing Then Exit Sub
' If e1.PropertyName = "SomeProperty" Then
' ' deal with item property change
' End If
' End Sub
'End Class
'Public Class Demo2
' Implements INotifyPropertyChanged
' Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
'End Class
try following demo. To update ListBox use refresh of CollectionView.
<Window x:Class="Window087"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1.WpfApp087"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<StackPanel>
<Button Content="test" Command="{Binding}" Width="100" Height="30" Margin="5"/>
<ListBox ItemsSource="{Binding Pieces}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding PieceID}" Margin="5" />
<TextBlock Text="{Binding Description}" Margin="5"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Window>
And classes:
Imports System.Collections.ObjectModel
Imports System.ComponentModel
Namespace WpfApp087
Public Class ViewModel
Implements ICommand
Public Sub New()
col.Add(New Piece With {.PieceID = 1001, .Description = "d1"}) ' add item to collection
col.Add(New Piece With {.PieceID = 1002, .Description = "d2"}) ' add item to collection
col.Add(New Piece With {.PieceID = 1003, .Description = "d3"}) ' add item to collection
cvs.Source = col
End Sub
Private col As New ObservableCollection(Of Piece)
Private cvs As New CollectionViewSource
Public ReadOnly Property Pieces As ICollectionView
Return cvs.View
End Get
End Property
Public Sub Execute(parameter As Object) Implements ICommand.Execute
col.Add(New Piece With {.PieceID = 1004, .Description = "d4"}) ' add item to collection
col.Item(0).Description = "test-description" ' change Property
cvs.View.Refresh()
End Sub
Public Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged
Public Function CanExecute(parameter As Object) As Boolean Implements ICommand.CanExecute
Return True
End Function
End Class
Public Class Piece
Public Property PieceID As Integer
Public Property Description() As String
End Class
End Namespace
Update:
Refresh with CollectionView.Refresh.