带有ObservableCollection的UWP DependencyProperty在使用基本抽象类存储库时不会更新UI

问题描述 投票:0回答:1

我面临ObservableCollection的某些奇怪行为,该行为与DependencyProperty一起使用。我在这里创建了最小的可重现方案:https://github.com/aosyatnik/UWP_ObservableCollection_Issue

有两个问题,我看不到,无法解释。

这里是我的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<ItemViewModel> ItemsAsList
    • ObservableCollection<ItemViewModel> ItemsAsObservableCollection
    • IList<ItemViewModel> ItemsRecreatedList
  3. 单击“添加新项目”。您应该看到,第2个和第3个集合已更新。签入名为“ AddNewItem”的MainViewModel方法。它应将项目添加到每个集合。第一个问题:为什么将项目添加到第一个集合中,但是即使调用RaisePropertyChanged也不会更新UI?
  4. 停止应用程序。
  5. 转到MyItemsControl.xaml.cs查找已注释的代码,取消注释并注释先前的代码。这将IList<ItemViewModel>更改为IList<BaseViewModel>
  6. 现在重建应用程序并再次运行。尝试再次单击“添加新项目”,然后注意ObservableCollection未更新。 第二个问题:为什么ObservableCollection不再触发getter?

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

非常感谢您的帮助,也许我缺少一些东西!我对第二个问题非常感兴趣,不知道为什么它不起作用。希望你能帮助我!

xaml uwp observablecollection dependency-properties xbind
1个回答
0
投票

这是使该问题易于重现的一项相当大的工作!

首先要记住的是,集合绑定依赖于两个接口:INotifyPropertyChangedINotifyCollectionChanged,并且ObservableCollection<T>实现这两个接口,而IList<T>不实现这两个接口。

  1. 单击“添加新项目”。您应该看到,第2个和第3个集合已更新。检入名为AddNewItem的MainViewModel方法。它应将项目添加到每个集合。 第一个问题:为什么将项目添加到第一个集合中,但是即使调用RaisePropertyChanged也不会更新UI?

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

  • 第一个,IList数据源不触发CollectionChanged事件:没有通知绑定任何更改,没有UI更新发生
  • 第二,ObservableCollection数据源将自动按预期工作,将一个项目添加到UI列表。
  • 第三个数据源实际上重新创建了数据源集合,并且您通过RaisePropertyChanged(nameof(ItemsRecreatedList));手动通知绑定应使用新集合。用户界面已更新,但与第二种情况相比,它不仅是在UI列表中添加了一项,而且还重新填充了整个列表。
  1. 现在重建应用程序并再次运行。尝试再次单击“添加新项”,并注意ObservableCollection没有更新。 第二个问题:为什么ObservableCollection不再触发getter?

这里您使用了一个自定义的getter来获取依赖项属性,该属性有时会在集合上调用ToList()方法。 ToList创建基础ObservableCollection内容的副本,该内容现在是a)与MainViewModel类中的数据源分离的并且属于IList类型,因此它不知道视图模型的后续更改集合,无法将其通知UI。

© www.soinside.com 2019 - 2024. All rights reserved.