Reputation: 2912
I have a ListView
bound to a collection, and I want the ListView
to automatically update when an item is added to the collection. I managed to get it working using an ObservableCollection
, but I'd rather to use INotifyPropertyChanged
instead. Maybe you can give me a hint what I am doing wrong?
First, here is the (relevant part of) XAML:
<StackPanel DataContext="{Binding Family}"> <!-- DataContext is of type Family -->
<TextBlock Text="{Binding LastName}"/>
<ListView ItemsSource="{Binding Members}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding FirstName}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
Here are the relevant classes:
public class Family : INotifyPropertyChanged
{
public string LastName { get; private set; }
private readonly IList<Member> _members;
public IEnumerable<Member> Members { get => _members; }
public Family(string lastName, IEnumerable<Member> members)
{
LastName = lastName;
_members = members.ToList();
}
public void AddMember(string name)
{
var member = new Member { FirstName = name };
_members.Add(member);
OnPropertyChanged(nameof(Members));
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
public class Member
{
public string FirstName { get; set; }
}
If I use this code and call AddMember
somewhere, it will not update the ListView GUI. I don't see why not, because AddMember
calls OnPropertyChanged(nameof(Members))
, and Members
is what the ListView
is bound to. So it should get notified about the change.
So what am I doing wrong?
If I change IList<Member> _members
into ObservableCollection<Member> _members
and _members = members.ToList()
into _members = new ObservableCollection<Member>(members)
accordingly, it works as expected.
Upvotes: 0
Views: 239
Reputation: 22119
After adding an item to the _members
collection, the reference returned by Members
is still the same. The Equals
method of collections will usually compare references, not items. Consequently, the binding will not detect a change and does not reevaluate the property.
If you want to get this to work, you could do one of the following:
Assign null
temporarily, raise property changed, reassign the collection and raise property changed again, so the binding detects a changed reference (thanks to @Ash).
public void AddMember(string name)
{
var member = new Member { FirstName = name };
_members.Add(member);
var members = _members;
_members = null;
OnPropertyChanged(nameof(Members));
_members = members;
OnPropertyChanged(nameof(Members));
}
Naive approach, recreate the collection when you add a member, e.g:
public void AddMember(string name)
{
var member = new Member { FirstName = name };
_members = _members.ToList();
_members.Add(member);
OnPropertyChanged(nameof(Members));
}
This is costly due to lots of unnecessary allocations, don't do it.
As you can see, both approaches have their downsides, either firing additional property changed notifications or unnecessary allocations which will additionally cause the ListView
to remove and recreate all of its items each time. This is why there is an ObservableCollection<T>
type that implements INotifyCollectionChanged
, which allows notifying added and removed items specifically, as well as other operations.
Upvotes: 1
Reputation: 30512
First of all, it is good that you decided that a Family is not an ObservableCollection. After all, you can do a lot of things with ObservableCollections that can't be done in Families. For instance: what would Replace(Member) mean in the context of a family?
The problem is, that you forgot to implement INotifyCollectionChanged. With this interface you can notify others that you added an element (and moved, and deleted, etc.)
public class Family : INotifyPropertyChanged, INotifyCollectionChanged
{
...
Because you also have to notify if elements are moved / deleted / etc. This will cost some development effort if your family can do more than just Add.
Therefore it might be a good idea to change your
private readonly IList<Member> _members;
into an ObservableCollection<Member>
, and implement the interface via this ObservableCollection.
class Family : INotifyPropertyChanged, INotifyCollectionChanged
{
public string LastName { get; private set; }
private readonly ObservableCollection<Member> members;
public IEnumerable<Member> Members => this.members;
public event NotifyCollectionChangedEventHandler CollectionChanged
{
add => this.members.NotifyCollectionChangedEventHandler.CollectinChanged += value;
remove => this.members.NotifyCollectionChangedEventHandler.CollectinChanged -= value;
}
Now your Add / Remove / Replace / Move / etc methods will be one-liners; the appropriate event will be raised.
public void Add(Member member)
{
this.members.Add(member);
}
public void Remove(Member member)
{
this.members.Remove(member);
}
Not sure if you need methods to Move and Replace family Members, but even if you need to, they will also be one-liner calls to the corresponding ObservableCollection method
You have completely hidden that you are using an ObservableCollection<Member>
, so if in future you need to completely get rid of the ObservableCollection, your users won't have to change, as long as you promise to implement the interfaces.
Upvotes: 2