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
相关产品推荐
相关产品推荐

