简体   繁体   中英

Updating ListView's ItemsSource via INotifyPropertyChanged

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM