question

RogerSchlueter-7899 avatar image
0 Votes"
RogerSchlueter-7899 asked RogerSchlueter-7899 commented

How to Refresh a ListBox

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.






windows-wpf
· 1
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.


The documentation shows a sample CustomerName property that raises notifications (in VB): https://docs.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.

0 Votes 0 ·
RogerSchlueter-7899 avatar image
0 Votes"
RogerSchlueter-7899 answered RogerSchlueter-7899 commented

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.

· 2
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.


The fact that an explicit refresh is needed probably denotes an insufficient benefiting from the theoretical potentials of data binding and notifications.

0 Votes 0 ·

Until you demonstrate a ListBox updating when an item property changes via binding only, your comments remain only "theoretical".

0 Votes 0 ·
JackJJun-MSFT avatar image
0 Votes"
JackJJun-MSFT answered RogerSchlueter-7899 commented

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.



3.gif (20.3 KiB)
· 4
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Hi @JackJJun-MSFT

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.

0 Votes 0 ·

Hi @RogerSchlueter-7899 ,
>> 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.


0 Votes 0 ·

Yes, of course, you cannot remove the OnPropertyChanged code. That is not the line I suggested that you remove. Rather, remove line 26 then try your code.

0 Votes 0 ·

Please see my edit to the OP.

0 Votes 0 ·
PeterFleischer-3316 avatar image
0 Votes"
PeterFleischer-3316 answered RogerSchlueter-7899 commented

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



· 2
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

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.

0 Votes 0 ·

Please see my edit to the OP.

0 Votes 0 ·
PeterFleischer-3316 avatar image
0 Votes"
PeterFleischer-3316 answered PeterFleischer-3316 published

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
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

PeterFleischer-3316 avatar image
0 Votes"
PeterFleischer-3316 answered PeterFleischer-3316 edited

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.





· 1
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

That's clever, Peter. I'll give that a try on other aspects of the app.

0 Votes 0 ·