通过INotifyPropertyChanged更新ListView的ItemsSource

3
在回答其他问题时,我遇到了一件我试图理解的事情。我有一个ListView,它的ItemsSource绑定到我的页面的一个属性上。页面实现了INotifyPropertyChangedDataContext被设置,一切正常,我可以看到我的列表渲染在屏幕上。但是如果我在其中一个元素中更改了一些东西并在整个集合上引发了属性更改,则更改不会显示在屏幕上,获取器被调用,因此绑定起作用,但是没有任何更改。

为什么会这样?是否有任何内部的ListView优化正在进行?

XAML代码:

<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>

代码后台:

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));
    }
}

注意:我知道通过在MyItem类中实现InotifyPropertyChanged来使其工作。我也知道当集合中的一个项目发生变化时重新加载整个集合是一种不好的模式。我只是想确保一些事情。


其他情况 - 看起来绑定会创建源对象的引用副本,这很清楚。但有趣的是,当我绑定到字符串数组时:

public string[] Elements { get; } = new string[] { "Standard", "Standard", "Standard" };

private void Button_Click(object sender, RoutedEventArgs e)
{
    Elements[1] = "Modified one";
    RaiseProperty(nameof(Elements));
}

XAML中的修改: <TextBlock Text="{Binding}" FontSize="16"/>

然后调用RaiseProperty(nameof(Elements));会更新视图,即使没有修改集合。所以为什么在这种情况下绑定的工作方式不同呢?看起来它只是返回数组而没有检查变化。


2
我想知道这个问题出了什么问题,以至于它被踩了。有人能解释一下吗? - Romasz
1个回答

5
是的,因为你调用了RaiseProperty(nameof(Elements)),所以getter将被调用。但是由于属性Elements不变的,所以没有任何更新。它仍然是完全相同的对象。改变的是它的基础对象(elements[1])的属性Name
这就是为什么在这种情况下需要在MyItem级别实现InotifyPropertyChanged的原因。
如果您将Elements的类型更改为ObservableCollection<MyItem>,那么当您添加/删除项目时,UI将得到更新。但如果像上面所做的那样更改基础的MyItem属性,则UI仍将保持不变。

1
是的,这正是我想的。我刚刚在参考源上忍受了一下,似乎绑定会对源对象进行弱引用以供将来比较。看来当我玩List和Observable时被误导了。但还有一件小事——如果我将元素更改为数组public string[] Elements { get; } = new string[] { "Standard", "Standard", "Standard" };(更改模板中的绑定),然后修改Elements [1] ="Modified one";... - Romasz
我认为当你绑定到Stack(而不是Heap)上的值时,绑定不会检查更改。当你调用RaiseProperty(nameof(Elements));事件而没有更改元素时,你会发现listview被重新填充。 - Romasz
1
有趣!我从未尝试过。顺便说一下,List<string> 不起作用。 - Justin XL
2
但是 string[] 也不是引用类型吗? - Justin XL
是的,你说得对,我忘记了那个事实,这破坏了我整个绑定行为的概念。 - Romasz
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接