【问题标题】:UWP DependencyProperty with ObservableCollection doesn't update UI when working with base abstract class使用基本抽象类时,带有 ObservableCollection 的 UWP DependencyProperty 不会更新 UI
【发布时间】:2020-04-21 06:50:14
【问题描述】:

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

你可以再次在repo中找到所有这些!

感谢您的帮助,也许我遗漏了什么!我对第二个问题很感兴趣,不知道为什么它不起作用。希望你能帮助我!

【问题讨论】:

    标签: xaml uwp observablecollection dependency-properties xbind


    【解决方案1】:

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

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

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

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

    • 1st,IList 数据源不会触发CollectionChanged 事件:绑定不会收到任何更改通知,也不会发生 UI 更新。对RaisePropertyChanged(nameof(ItemsAsList)); 的调用什么也不做,因为数据源对象 (ItemsAsList) 保持不变,只是列表内容发生了变化。如果IList 将实现INotifyCollectionChanged(它不会),这将起作用。
    • 2nd,ObservableCollection 数据源自动按预期工作:将新项目添加到集合时,会通知绑定并将项目添加到 UI 列表中。
    • 第三个数据源实际上重新创建了数据源集合,您通过RaisePropertyChanged(nameof(ItemsRecreatedList)); 手动通知绑定应该使用新的数据源集合。 UI 已更新,但与第 2 种情况相比,它不仅向 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); }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-12-17
      • 1970-01-01
      • 2015-03-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-03-16
      相关资源
      最近更新 更多