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.
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.
Developer technologies Windows Presentation Foundation
4 additional answers
Sort by: Most helpful
-
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:
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. -
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
-
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
-
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.