Reputation: 29792
While answering other question I've stepped into one thing I try to understand. I've a ListView, which ItemsSource is bound to a property of my page. The page implements INotifyPropertyChanged, the DataContext is set, evrything works, I can see my list rendered on the screen. But if I change something inside one of the elements and raise property changed on the whole collection, the change is not visible on the screen, the getter is called, so the binding works, but nothing changes.
Why is it so? Is there any internal ListView's optmization going on?
The XAML code:
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ListView ItemsSource="{Binding Elements}" Height="400">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" FontSize="16"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Content="Modify item" Click="Button_Click"/>
</StackPanel>
Code behind:
public class MyItem
{
public string Name { get; set; }
public MyItem(string name) { this.Name = name; }
}
public sealed partial class MainPage : Page, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaiseProperty(string name) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
private List<MyItem> elements = new List<MyItem> { new MyItem("Standard"), new MyItem("Standard"), new MyItem("Standard") };
public List<MyItem> Elements { get { Debug.WriteLine("Collection getter"); return elements; } }
public MainPage()
{
this.InitializeComponent();
this.DataContext = this;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
elements[1].Name = "Modified one";
RaiseProperty(nameof(Elements));
}
}
NOTE: I know how to make it work by implementing InotifyPropertyChanged in MyItem class. I also know that it's a bad pattern to reload whole collection when something changed inside one its item. I just want to ensure about some things.
Other case - it seems that binding makes a reference copy of source object, that's clear. But interesting is that when I bind to array of strings:
public string[] Elements { get; } = new string[] { "Standard", "Standard", "Standard" };
private void Button_Click(object sender, RoutedEventArgs e)
{
Elements[1] = "Modified one";
RaiseProperty(nameof(Elements));
}
Modification in XAML: <TextBlock Text="{Binding}" FontSize="16"/>
.
Then calling RaiseProperty(nameof(Elements));
updates the view, even without modifying the collection. So why binding works different way in this case? It seems that it simply returns array without checking for changes.
Upvotes: 3
Views: 4162
Reputation: 39006
Yes, the getter will be called because you are calling RaiseProperty(nameof(Elements))
. But nothing will get updated because the property Elements
is unchanged. It's still the exact same object. What's changed is its underlying object(elements[1]
)'s property Name
.
That's why in this case the InotifyPropertyChanged
needs to be implemented at the MyItem
level.
If you change your Elements
's type to ObservableCollection<MyItem>
. Then when you add/remove items to/from it, the UI will get updated. But if the underlying MyItem
's property is changed like what you did above, the UI will still remain the same.
Upvotes: 5