Public Property PieceID As Integer Public Property Description As String End Class

ocPieces is the Source of the items in a ListBox named lbxPieces. Standard stuff.

The user can add new pieces to ocPieces, which triggers the CollectionChanged event so that:

lbxPieces.Items.Refresh()

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?

Would rebinding the ListBox work? Again, 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?

EDIT EDIT EDIT
To me, the problem and solution are both obvious - I just don't know how to implement the solution.

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.

The documentation shows a sample CustomerName property that raises notifications (in VB): https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.inotifypropertychanged. I think that you should try it at least for Description property. I do not think that Refresh or other additions operations are needed when the listbox is correctly bound.

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.