简体   繁体   English

IEnumerable 更改事件

[英]IEnumerable changed event

I am programming an app in WinUI 3 using C#.我正在使用 C# 在 WinUI 3 中编写应用程序。

There are some controls that have an ItemsSource property.有些控件具有 ItemsSource 属性。
When I pass an IEnumerable into the ItemsSource and then change the IEnumerable, the control changes accordingly.当我将 IEnumerable 传递到 ItemsSource 然后更改 IEnumerable 时,控件会相应更改。

I now want to implement the same behavior in my custom UserControl.我现在想在我的自定义 UserControl 中实现相同的行为。
I have the following code, but it only works with IEnumerables that implement INotifyCollectionChanged (eg. ObservableCollection).我有以下代码,但它仅适用于实现 INotifyCollectionChanged(例如 ObservableCollection)的 IEnumerables。
WinUI supports IEnumerables that don't implement INotifyCollectionChanged. WinUI 支持未实现 INotifyCollectionChanged 的 IEnumerable。
How can I do the same?我怎样才能做同样的事情?

Here is my code:这是我的代码:

public sealed partial class MyUserControl : UserControl
{
    public static readonly DependencyProperty ItemsSourceProperty =
        DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(MyUserControl), new PropertyMetadata(null));
    public IEnumerable ItemsSource
    {
        get { return (IEnumerable)GetValue(ItemsSourceProperty); }
        set { SetValue(ItemsSourceProperty, value); }
    }
    private IEnumerable _oldItemsSource;

    public MyUserControl()
    {
        this.InitializeComponent();
        RegisterPropertyChangedCallback(ItemsSourceProperty, OnItemsSourceChanged);
    }

    private void OnItemsSourceChanged(DependencyObject sender, DependencyProperty prop)
    {
        if (prop == ItemsSourceProperty)
        {
            var newValue = (IEnumerable)sender.GetValue(ItemsSourceProperty);

            if (_oldItemsSource is INotifyCollectionChanged oldCollection)
            {
                oldCollection.CollectionChanged -= OnCollectionChanged;
            }
            if (newValue is INotifyCollectionChanged collection)
            {
                collection.CollectionChanged += OnCollectionChanged;
            }
            _oldItemsSource = ItemsSource;
        }
    }

    private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        // Update the control here
    }
}

WinUI 3 allows me to use a List (doesn't implement INotifyCollectionChanged) as an ItemsSource. WinUI 3 允许我使用列表(不实现 INotifyCollectionChanged)作为 ItemsSource。
Changes made to the List affect the control.对列表所做的更改会影响控件。
This is the code inside a test page:这是测试页中的代码:

public TestPage()
{
    this.InitializeComponent();

    var list = new List<string> { "Item1", "Item2", "Item3" };

    var bar = new BreadcrumbBar(); ;
    bar.ItemsSource = list;

    this.Content = bar;

    list.Add("Item4");
    // The BreadcrumbBar now has 4 elements.
}

When I pass an IEnumerable into the ItemsSource and then change the IEnumerable, the control changes accordingly.当我将 IEnumerable 传递到 ItemsSource 然后更改 IEnumerable 时,控件会相应更改。

No, it doesn't unless the IEnumerable is an INotifyCollectionChanged .不,除非IEnumerableINotifyCollectionChanged ,否则不会。

Try to call list.Add("Item4") in an event handler after the control has been initially rendered if you don't believe me.如果您不相信我的话,请在最初呈现控件尝试在事件处理程序中调用list.Add("Item4")

For example, this code will not add "Item4" to the BreadcrumbBar control:例如,此代码不会将“Item4”添加到BreadcrumbBar控件:

public sealed partial class TestPage : Page
{
    private readonly List<string> list = new List<string> { "Item1", "Item2", "Item3" };

    public TestPage()
    {
        this.InitializeComponent();
        breadcrumbBar.ItemsSource = list;
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        list.Add("Item4");
    }
}

Changing the type of list to ObservableCollection<string> will make it work as expected.list类型更改为ObservableCollection<string>将使其按预期工作。

So your custom control is no worse than any built-in control in that sense.因此,从这个意义上说,您的自定义控件并不比任何内置控件差。 The source collection must notify the view somehow.源集合必须以某种方式通知视图。

WinUI 3 allows me to use a List (doesn't implement INotifyCollectionChanged) as an ItemsSource. WinUI 3 允许我使用列表(不实现 INotifyCollectionChanged)作为 ItemsSource。 Changes made to the List affect the control.对列表所做的更改会影响控件。

It most certainly doesn't react to changes of "dumb" collections. Your example only works because the items haven't been constructed yet, and they won't be until the control is loaded (your code is all in the constructor).它肯定不会对“哑”collections 的变化做出反应。您的示例之所以有效,是因为尚未构建项目,并且在加载控件之前不会构建(您的代码全部在构造函数中)。

This example will show you a comparison between a plain List and an ObservableCollection .此示例将向您展示普通ListObservableCollection之间的比较。

Let's say we have a UserControl like this one.假设我们有一个像这样的UserControl

TestUserControl.xaml TestUserControl.xaml

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

    <Grid>
        <ListView ItemsSource="{x:Bind ItemsSource, Mode=OneWay}" />
    </Grid>

</UserControl>

TestUserControl.xaml.cs TestUserControl.xaml.cs

using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Data;
using System.Collections;
using Windows.Foundation.Collections;

namespace ItemsSourceTest;

public sealed partial class TestUserControl : UserControl
{
    public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register(
        nameof(ItemsSource),
        typeof(IEnumerable),
        typeof(TestUserControl),
        new PropertyMetadata(default, (d, e) => (d as TestUserControl)?.UpdateItemsSource()));

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

    // You don't need CollectionViewSource or CollectionView
    // in order to populate the ListView. This is just to show 
    // you how you can get events when the collection changes.
    public CollectionViewSource? CollectionViewSource { get; set; }

    public ICollectionView? CollectionView { get; set; }

    public IEnumerable ItemsSource
    {
        get => (IEnumerable)GetValue(ItemsSourceProperty);
        set => SetValue(ItemsSourceProperty, value);
    }

    private void UpdateItemsSource()
    {
        CollectionViewSource = new CollectionViewSource()
        {
            Source = ItemsSource,
        };

        if (CollectionView is not null)
        {
            CollectionView.VectorChanged -= CollectionView_VectorChanged;
        }

        CollectionView = CollectionViewSource.View;
        CollectionView.VectorChanged += CollectionView_VectorChanged;
    }

    private void CollectionView_VectorChanged(IObservableVector<object> sender, IVectorChangedEventArgs @event)
    {
        // Your can do your work for collection changes here...
    }
}

And a MainPage a ListView and a TestUserControl both bound to a plain List named NonObservableItems and another set of a ListView and a TestUserControl both bound to an ObservableCollection named ObservableItems .一个MainPage一个ListView和一个TestUserControl都绑定到一个名为NonObservableItems的普通List ,另一组ListView和一个TestUserControl都绑定到一个名为ObservableCollectionObservableCollection

When you add items to the collections, you'll see that only the controls bound to the ObservableItems will be populated.当您将项目添加到 collections 时,您会看到只有绑定到ObservableItems的控件才会被填充。

MainPage.xaml主页.xaml

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

    <Grid RowDefinitions="Auto,*">
        <Button
            Grid.Row="0"
            Click="Button_Click"
            Content="Add item" />
        <Grid
            Grid.Row="1"
            ColumnDefinitions="*,*"
            RowDefinitions="Auto,*,*">
            <TextBlock
                Grid.Row="0"
                Grid.Column="0"
                Text="Non-Observable Items" />
            <ListView
                Grid.Row="1"
                Grid.Column="0"
                ItemsSource="{x:Bind NonObservableItems, Mode=OneWay}" />
            <local:TestUserControl
                Grid.Row="2"
                Grid.Column="0"
                ItemsSource="{x:Bind NonObservableItems, Mode=OneWay}" />

            <TextBlock
                Grid.Row="0"
                Grid.Column="1"
                Text="Observable Items" />
            <ListView
                Grid.Row="1"
                Grid.Column="1"
                ItemsSource="{x:Bind ObservableItems, Mode=OneWay}" />
            <local:TestUserControl
                Grid.Row="2"
                Grid.Column="1"
                ItemsSource="{x:Bind ObservableItems, Mode=OneWay}" />
        </Grid>

    </Grid>
</Page>

MainPage.xaml.cs MainPage.xaml.cs

using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace ItemsSourceTest;

public sealed partial class MainPage : Page
{
    public MainPage()
    {
        this.InitializeComponent();
        AddItem();
    }

    public List<string> NonObservableItems { get; set; } = new();

    public ObservableCollection<string> ObservableItems { get; set; } = new();

    private int Counter { get; set; } = 0;

    private void AddItem()
    {
        NonObservableItems.Add(Counter.ToString());
        ObservableItems.Add(Counter.ToString());
        Counter++;
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        AddItem();
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM