1

我面临着一些奇怪的行为ObservableCollection,与DependencyProperty. 我在这里创建了最小的可重现场景:https ://github.com/aosyatnik/UWP_ObservableCollection_Issue 。

有 2 个问题,我看到但无法解释。

这是我的MainViewModel

using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace UWP_ObservableCollection
{
    public class MainViewModel : BaseViewModel
    {
        public IList<ItemViewModel> ItemsAsList { get; private set; }
        public ObservableCollection<ItemViewModel> ItemsAsObservableCollection { get; private set; }
        public IList<ItemViewModel> ItemsRecreatedList { get; private set; }

        public MainViewModel()
        {
            ItemsAsList = new List<ItemViewModel>();
            ItemsAsObservableCollection = new ObservableCollection<ItemViewModel>();
            ItemsRecreatedList = new List<ItemViewModel>();
        }

        public void AddNewItem()
        {
            var newItem = new ItemViewModel();

            // First try: add to list and raise property change - doesn't work.
            ItemsAsList.Add(newItem);
            RaisePropertyChanged(nameof(ItemsAsList));

            // Second try: with ObservableCollection - doesn't work?
            ItemsAsObservableCollection.Add(newItem);

            // Third try: recreate the whole collection - works
            ItemsRecreatedList.Add(newItem);
            ItemsRecreatedList = new List<ItemViewModel>(ItemsRecreatedList);
            RaisePropertyChanged(nameof(ItemsRecreatedList));
        }
    }
}

还有ItemViewModel.cs

namespace UWP_ObservableCollection
{
    public class ItemViewModel : BaseViewModel
    {
        private static int Counter;
        public string Text { get; private set; }

        public ItemViewModel()
        {
            Counter++;
            Text = $"{Counter}";
        }
    }
}

这里是MainPage.xaml

<Page
    x:Class="UWP_ObservableCollection.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:UWP_ObservableCollection"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
    Loaded="Page_Loaded">

    <StackPanel>
        <StackPanel Orientation="Vertical">
            <TextBlock>Items as List</TextBlock>
            <local:MyItemsControl ItemsSource="{Binding ItemsAsList}"/>
        </StackPanel>

        <StackPanel Orientation="Vertical">
            <TextBlock>Items as ObservableCollection</TextBlock>
            <local:MyItemsControl ItemsSource="{Binding ItemsAsObservableCollection}"/>
        </StackPanel>

        <StackPanel Orientation="Vertical">
            <TextBlock>Items recreated list</TextBlock>
            <local:MyItemsControl ItemsSource="{Binding ItemsRecreatedList}"/>
        </StackPanel>

        <Button Click="Button_Click">Add new item</Button>
    </StackPanel>
</Page>

MainPage.xaml.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;

// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409

namespace UWP_ObservableCollection
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainPage : Page
    {
        public MainViewModel MainViewModel
        {
            get => DataContext as MainViewModel;
        }

        public MainPage()
        {
            this.InitializeComponent();
        }

        private void Page_Loaded(object sender, RoutedEventArgs e)
        {
            DataContext = new MainViewModel();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            MainViewModel.AddNewItem();
        }
    }
}

MyItemsControl.xaml

<UserControl
    x:Class="UWP_ObservableCollection.MyItemsControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:UWP_ObservableCollection"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300"
    d:DesignWidth="400">

    <Grid>
        <ItemsControl ItemsSource="{x:Bind ItemsSource, Mode=OneWay}">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Text}" />
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </Grid>
</UserControl>

MyItemsControl.xaml.cs

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;

// The User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236

namespace UWP_ObservableCollection
{
    public sealed partial class MyItemsControl : UserControl
    {
        // This works fine.
        public static readonly DependencyProperty ItemsSourceProperty =
            DependencyProperty.Register(
                "ItemsSource",
                typeof(IList<ItemViewModel>),
                typeof(MyItemsControl),
                new PropertyMetadata(null, ItemsSourcePropertyChanged)
            );

        public IList<ItemViewModel> ItemsSource
        {
            get { return (IList<ItemViewModel>)GetValue(ItemsSourceProperty); }
            set { SetValue(ItemsSourceProperty, value); }
        }

        // Uncomment this code to see the issue.
        /*
        public static readonly DependencyProperty ItemsSourceProperty =
           DependencyProperty.Register(
               "ItemsSource",
               typeof(IList<BaseViewModel>),
               typeof(MyItemsControl),
               new PropertyMetadata(null, ItemsSourcePropertyChanged)
           );

        public IList<BaseViewModel> ItemsSource
        {
            get
            {
                var values = GetValue(ItemsSourceProperty) as IEnumerable<BaseViewModel>;
                if (values is null)
                {
                    return null;
                }
                return values.ToList();
            }
            set { SetValue(ItemsSourceProperty, value); }
        }
        */

        private static void ItemsSourcePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Debug.WriteLine("Items changed");
        }

        public MyItemsControl()
        {
            this.InitializeComponent();
        }
    }
}

您需要执行以下步骤:

  1. 构建并运行应用程序。
  2. 请参阅有 3 个MyItemsControl使用 3 个不同的数据源-ItemsAsList和。检查并发现有 3 个来源: ItemsAsObservableCollectionItemsRecreatedListMainViewModel
    • IList<ItemViewModel> ItemsAsList
    • ObservableCollection<ItemViewModel> ItemsAsObservableCollection
    • IList<ItemViewModel> ItemsRecreatedList
  3. 单击“添加新项目”。您应该看到,第二个和第三个集合已更新。签入MainViewModel名为`AddNewItem 的方法。它应该将项目添加到每个集合中。 第一个问题:为什么item被添加到第一个collection中,但是调用了RaisePropertyChanged后UI没有更新?
  4. 停止应用程序。
  5. MyItemsControl.xaml.cs查找注释代码,取消注释并注释以前的​​代码。这IList<ItemViewModel>变为IList<BaseViewModel>
  6. 现在重建应用程序并再次运行它。尝试再次单击“添加新项目”并注意,ObservableCollection未更新。第二个问题:为什么 ObservableCollection 不再触发 getter 了?

同样,您可以在repo中找到所有这些!

感谢您的帮助,也许我错过了一些东西!我对第二个问题很感兴趣,不知道为什么它不起作用。希望,你会帮助我!

4

1 回答 1

3

Ань,这是一项让您的问题易于重现的工作。好工作!

首先要记住的是,集合绑定依赖于 2 个接口:INotifyPropertyChanged并且实现了这两个接口,而INotifyCollectionChanged两者都没有实现。 的职责是通知事件订阅者有关在实现它的集合中添加、替换、移动或删除的项目。ObservableCollection<T>IList<T>
INotifyCollectionChanged

  1. 单击“添加新项目”。您应该看到,第二个和第三个集合已更新。签入名为“AddNewItem”的 MainViewModel 方法。它应该将项目添加到每个集合中。第一个问题:为什么item被添加到第一个collection中,但是调用了RaisePropertyChanged后UI没有更新?

您将 1 个项目添加到 3 个集合支持的数据源。这里会发生什么:

  • 第一,IList数据源不触发CollectionChanged事件:绑定不通知任何更改,不发生 UI 更新。调用RaisePropertyChanged(nameof(ItemsAsList));什么都不做,因为数据源对象 ( ItemsAsList) 保持不变,只是列表内容发生了变化。如果IList会实施INotifyCollectionChanged(它不会)这将起作用。
  • 2、ObservableCollection数据源自动按预期工作:当向集合中添加新项目时,会通知绑定并将项目添加到 UI 列表中。
  • 第三个数据源实际上重新创建了数据源集合,并且您通过手动通知绑定RaisePropertyChanged(nameof(ItemsRecreatedList));应该使用新的数据源集合。UI 已更新,但与第二种情况相比,它不仅向 UI 列表添加了一项,而且在 UI 树中重新填充了整个列表。
  1. 现在重建应用程序并再次运行它。尝试再次单击“添加新项目”并注意 ObservableCollection 未更新。第二个问题:为什么 ObservableCollection 不再触发 getter 了?

在这里,您为依赖属性使用自定义的 getter,它在某些时候调用ToList()集合上的方法并返回它。ToList创建底层ObservableCollection内容的副本,该副本现在与类中的数据源分离MainViewModel并且属于IList类型,因此它 a) 不知道视图模型集合中的后续更改 b) 无法通知 UI。

public IList<BaseViewModel> ItemsSource
{
    get
    {
        var values = GetValue(ItemsSourceProperty) as IEnumerable<BaseViewModel>;
        if (values is null)
        {
            return null;
        }
        return values.ToList();
    }
    set { SetValue(ItemsSourceProperty, value); }
}
于 2020-01-02T17:21:18.200 回答