You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WinUI 3自定义UserControl如何支持非INotifyCollectionChanged的IEnumerable变更?

WinUI 3 自定义UserControl实现原生级ItemsSource同步更新

我在开发WinUI 3应用时发现:部分原生控件的ItemsSource传入普通IEnumerable(比如List)后,修改集合内容控件会自动同步更新,但我自己写的自定义UserControl目前仅支持实现INotifyCollectionChanged接口的集合(比如ObservableCollection)。想知道如何修改代码,让自定义控件具备和原生控件一样的能力。

我的自定义UserControl代码

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
    }
}

原生控件测试代码

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");
    // 此时BreadcrumbBar会显示4个元素
}

修改方案

WinUI原生控件之所以能支持普通IEnumerable的更新,核心是在UI布局周期中自动检查集合变化,结合对INotifyCollectionChanged集合的事件监听实现双向同步。我们可以通过以下步骤修改自定义控件:

1. 优化依赖属性注册

将PropertyMetadata替换为FrameworkPropertyMetadata,指定布局影响标记,确保ItemsSource引用变化时触发控件重排:

public static readonly DependencyProperty ItemsSourceProperty =
    DependencyProperty.Register(
        nameof(ItemsSource),
        typeof(IEnumerable),
        typeof(MyUserControl),
        new FrameworkPropertyMetadata(
            null,
            FrameworkPropertyMetadataOptions.AffectsMeasure,
            OnItemsSourceChanged));

2. 添加集合变化检查逻辑

新增字段记录集合状态,在UI布局更新时自动检查普通集合的内容变化:

private int _lastItemCount = -1;
private IEnumerable _currentItemsSource;

public MyUserControl()
{
    this.InitializeComponent();
    this.LayoutUpdated += MyUserControl_LayoutUpdated;
}

private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var control = (MyUserControl)d;
    var oldValue = e.OldValue as IEnumerable;
    var newValue = e.NewValue as IEnumerable;

    // 移除旧集合的事件监听
    if (oldValue is INotifyCollectionChanged oldCollection)
    {
        oldCollection.CollectionChanged -= control.OnCollectionChanged;
    }

    // 添加新集合的事件监听
    if (newValue is INotifyCollectionChanged newCollection)
    {
        newCollection.CollectionChanged += control.OnCollectionChanged;
    }

    // 更新当前集合快照
    control._currentItemsSource = newValue;
    control._lastItemCount = newValue?.Cast<object>().Count() ?? -1;
    // 立即更新控件内容
    control.UpdateControlContent();
}

3. 布局更新时检查集合

在LayoutUpdated事件中对比集合元素数量,判断是否需要更新控件:

private void MyUserControl_LayoutUpdated(object sender, object e)
{
    // 已实现INotifyCollectionChanged的集合通过事件处理,无需重复检查
    if (_currentItemsSource == null || _currentItemsSource is INotifyCollectionChanged)
    {
        return;
    }

    int currentCount = _currentItemsSource.Cast<object>().Count();
    if (currentCount != _lastItemCount)
    {
        _lastItemCount = currentCount;
        UpdateControlContent();
    }
}

4. 统一控件更新逻辑

将原有的更新逻辑提取为单独方法,确保事件触发和布局检查都能调用:

private void UpdateControlContent()
{
    // 这里实现控件内容更新的核心逻辑
    // 示例:清空容器并重新生成子元素
    // myItemsContainer.Children.Clear();
    // if (ItemsSource != null)
    // {
    //     foreach (var item in ItemsSource)
    //     {
    //         // 创建对应UI元素并添加到容器
    //     }
    // }
}

private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    UpdateControlContent();
    // 若需要更精细的更新(比如仅添加/移除单个元素),可根据e.Action分支处理
}

注意事项

  • 对于大型集合,布局周期中枚举集合会影响性能,此时建议优先使用ObservableCollection;
  • 若需要手动触发更新,可额外提供Refresh()方法直接调用UpdateControlContent();
  • 原生控件的“自动更新”在部分场景下是渲染前的集合读取,后续修改仍需通过上述逻辑触发更新。

内容的提问来源于stack exchange,提问作者RD4

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 11:40:25