How to Refresh a ListBox

RogerSchlueter-7899 1,446 Reputation points
2021-04-05T01:49:49.077+00:00

I have:

 ocPieces = ObserveableCollection(Of Piece)

where a Piece is:

Public Class Piece
   Implements INotifyPropertyChanged

   Public Property PieceID As Integer
   Public Property Description As String
   <Numerous Other properties>
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.

Developer technologies Windows Presentation Foundation
{count} votes

Accepted answer
  1. RogerSchlueter-7899 1,446 Reputation points
    2021-04-07T15:47:17.503+00:00

    I solved the problem by changing the ItemsSource of the ListBox to a CollectionView and refreshing the view after a change of an item property. That does update both the view and the ListBox.


4 additional answers

Sort by: Most helpful
  1. Jack J Jun 25,296 Reputation points
    2021-04-05T07:06:40.83+00:00

    Hi @RogerSchlueter-7899 ,

    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:

     <Grid>  
            <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  
            Get  
                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:

    84413-3.gif

    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.


  2. Peter Fleischer (former MVP) 19,341 Reputation points
    2021-04-05T08:50:23.903+00:00

    Hi Roger,
    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
          Get
            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
    

  3. Peter Fleischer (former MVP) 19,341 Reputation points
    2021-04-06T08:21:05.417+00:00

    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)
        Next
      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
          Next
        End If
        If e.OldItems IsNot Nothing Then
          For Each itm In e.OldItems
            RemoveHandler CType(itm, INotifyPropertyChanged).PropertyChanged, AddressOf ItemPropertyChanged
          Next
        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
    
    0 comments No comments

  4. Peter Fleischer (former MVP) 19,341 Reputation points
    2021-04-07T05:51:30.813+00:00

    Hi,
    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
        Get
          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.


Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.