我面临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();
}
}
}
您需要执行以下步骤:
MyItemsControl
,它们使用3个不同的数据源-ItemsAsList
,ItemsAsObservableCollection
和ItemsRecreatedList
。检查MainViewModel
,发现有3个来源:IList<ItemViewModel> ItemsAsList
ObservableCollection<ItemViewModel> ItemsAsObservableCollection
IList<ItemViewModel> ItemsRecreatedList
MainViewModel
方法。它应将项目添加到每个集合。第一个问题:为什么将项目添加到第一个集合中,但是即使调用RaisePropertyChanged也不会更新UI?MyItemsControl.xaml.cs
查找已注释的代码,取消注释并注释先前的代码。这将IList<ItemViewModel>
更改为IList<BaseViewModel>
。ObservableCollection
未更新。 第二个问题:为什么ObservableCollection不再触发getter?非常感谢您的帮助,也许我缺少一些东西!我对第二个问题非常感兴趣,不知道为什么它不起作用。希望你能帮助我!
这是使该问题易于重现的一项相当大的工作!
首先要记住的是,集合绑定依赖于两个接口:INotifyPropertyChanged
和INotifyCollectionChanged
,并且ObservableCollection<T>
实现这两个接口,而IList<T>
不实现这两个接口。
- 单击“添加新项目”。您应该看到,第2个和第3个集合已更新。检入名为AddNewItem的MainViewModel方法。它应将项目添加到每个集合。 第一个问题:为什么将项目添加到第一个集合中,但是即使调用RaisePropertyChanged也不会更新UI?
您将1项添加到3个支持集合的数据源中。这会发生什么:
IList
数据源不触发CollectionChanged
事件:没有通知绑定任何更改,没有UI更新发生ObservableCollection
数据源将自动按预期工作,将一个项目添加到UI列表。RaisePropertyChanged(nameof(ItemsRecreatedList));
手动通知绑定应使用新集合。用户界面已更新,但与第二种情况相比,它不仅是在UI列表中添加了一项,而且还重新填充了整个列表。
- 现在重建应用程序并再次运行。尝试再次单击“添加新项”,并注意ObservableCollection没有更新。 第二个问题:为什么ObservableCollection不再触发getter?
这里您使用了一个自定义的getter来获取依赖项属性,该属性有时会在集合上调用ToList()
方法。 ToList
创建基础ObservableCollection
内容的副本,该内容现在是a)与MainViewModel
类中的数据源分离的并且属于IList
类型,因此它不知道视图模型的后续更改集合,无法将其通知UI。