如何在CommunityToolkit DataGrid中实现MVVM模式下的SelectedItems
在MVVM环境中为CommunityToolkit DataGrid实现SelectedItems双向绑定
我正在使用CommunityToolkit DataGrid,想要在MVVM模式下实现SelectedItems的双向绑定。我已经创建了一个自定义附加属性,但DataGrid的SelectedItems属性并未实现INotifyCollectionChanged接口,导致我无法通过该接口触发集合重置操作。以下是我编写的附加属性代码:
public class SelectedItemsAttachedProperty { public static IList GetSelectedItems(DependencyObject obj) { return (IList)obj.GetValue(SelectedItemsProperty); } public static void SetSelectedItems(DependencyObject obj, IList value) { obj.SetValue(SelectedItemsProperty, value); } public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.RegisterAttached("SelectedItems", typeof(IList), typeof(SelectedItemsAttachedProperty), new PropertyMetadata(new ObservableCollection<object>(), SelectedItemsChanged)); private static void SelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if(d is DataGrid) { DataGrid dg = d as DataGrid; dg.SelectionChanged += (sender, e) => { DataGrid it = sender as DataGrid; foreach (var item in e.AddedItems) { it.SelectedItems.Add(item); } foreach (var item in e.RemovedItems) { it.SelectedItems.Remove(item); } }; var oc = dg.SelectedItems as INotifyCollectionChanged; oc.CollectionChanged += (o, e) => { if (e.Action == NotifyCollectionChangedAction.Reset) { dg.SelectedItems.Clear(); } }; } } }
现有代码的核心问题
- SelectionChanged事件逻辑冗余:DataGrid本身会自动更新SelectedItems集合,手动添加/移除事件参数中的项属于重复操作。
- INotifyCollectionChanged转换失败:DataGrid的SelectedItems仅实现
IList,不支持INotifyCollectionChanged,导致后续集合变更监听完全失效。 - 全局默认集合冲突:附加属性默认值是一个全局共享的
ObservableCollection<object>,多个DataGrid使用时会出现选中项混乱。
修正后的双向绑定实现
要实现完整的双向同步(用户选择→ViewModel、ViewModel集合变更→DataGrid),同时支持集合重置操作,可使用以下实现:
public class DataGridSelectedItemsHelper { public static IList GetSelectedItems(DependencyObject obj) { return (IList)obj.GetValue(SelectedItemsProperty); } public static void SetSelectedItems(DependencyObject obj, IList value) { obj.SetValue(SelectedItemsProperty, value); } public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.RegisterAttached( "SelectedItems", typeof(IList), typeof(DataGridSelectedItemsHelper), new PropertyMetadata(null, OnSelectedItemsPropertyChanged)); private static void OnSelectedItemsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is not DataGrid dataGrid) return; // 清理旧监听 dataGrid.SelectionChanged -= OnDataGridSelectionChanged; if (e.OldValue is INotifyCollectionChanged oldCollection) { oldCollection.CollectionChanged -= OnViewModelCollectionChanged; } // 绑定新监听 dataGrid.SelectionChanged += OnDataGridSelectionChanged; if (e.NewValue is INotifyCollectionChanged newCollection) { newCollection.CollectionChanged += OnViewModelCollectionChanged; SyncViewModelToDataGrid(dataGrid, newCollection); } } // DataGrid选择变更同步到ViewModel private static void OnDataGridSelectionChanged(object sender, SelectionChangedEventArgs e) { if (sender is not DataGrid dataGrid) return; var viewModelCollection = GetSelectedItems(dataGrid); if (viewModelCollection == null) return; // 避免循环触发,临时移除ViewModel集合监听 if (viewModelCollection is INotifyCollectionChanged notifyCollection) { notifyCollection.CollectionChanged -= OnViewModelCollectionChanged; } try { foreach (var item in e.AddedItems) { if (!viewModelCollection.Contains(item)) viewModelCollection.Add(item); } foreach (var item in e.RemovedItems) { if (viewModelCollection.Contains(item)) viewModelCollection.Remove(item); } } finally { // 恢复监听 if (viewModelCollection is INotifyCollectionChanged notifyCollection) { notifyCollection.CollectionChanged += OnViewModelCollectionChanged; } } } // ViewModel集合变更同步到DataGrid private static void OnViewModelCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (sender is not IList viewModelCollection) return; // 查找绑定当前集合的DataGrid var dataGrid = Application.Current.Windows .OfType<Window>() .SelectMany(w => FindVisualChildren<DataGrid>(w)) .FirstOrDefault(dg => Equals(GetSelectedItems(dg), viewModelCollection)); if (dataGrid == null) return; // 避免循环触发,临时移除DataGrid选择监听 dataGrid.SelectionChanged -= OnDataGridSelectionChanged; try { switch (e.Action) { case NotifyCollectionChangedAction.Add: foreach (var item in e.NewItems) { if (!dataGrid.SelectedItems.Contains(item)) dataGrid.SelectedItems.Add(item); } break; case NotifyCollectionChangedAction.Remove: foreach (var item in e.OldItems) { if (dataGrid.SelectedItems.Contains(item)) dataGrid.SelectedItems.Remove(item); } break; case NotifyCollectionChangedAction.Reset: dataGrid.SelectedItems.Clear(); break; case NotifyCollectionChangedAction.Replace: foreach (var oldItem in e.OldItems) dataGrid.SelectedItems.Remove(oldItem); foreach (var newItem in e.NewItems) dataGrid.SelectedItems.Add(newItem); break; } } finally { // 恢复监听 dataGrid.SelectionChanged += OnDataGridSelectionChanged; } } // 初始化同步ViewModel集合到DataGrid private static void SyncViewModelToDataGrid(DataGrid dataGrid, INotifyCollectionChanged collection) { dataGrid.SelectionChanged -= OnDataGridSelectionChanged; try { dataGrid.SelectedItems.Clear(); foreach (var item in collection) dataGrid.SelectedItems.Add(item); } finally { dataGrid.SelectionChanged += OnDataGridSelectionChanged; } } // 视觉树遍历辅助方法 private static IEnumerable<T> FindVisualChildren<T>(DependencyObject parent) where T : DependencyObject { if (parent == null) yield break; for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++) { var child = VisualTreeHelper.GetChild(parent, i); if (child is T t) yield return t; foreach (var childOfChild in FindVisualChildren<T>(child)) yield return childOfChild; } } }
使用步骤
- ViewModel中声明集合:
public ObservableCollection<YourItemType> SelectedItems { get; } = new ObservableCollection<YourItemType>();
- XAML中绑定附加属性:
<controls:DataGrid ItemsSource="{Binding Items}" SelectionMode="Extended" local:DataGridSelectedItemsHelper.SelectedItems="{Binding SelectedItems, Mode=TwoWay}" />
核心优化点
- 完整双向同步:同时处理用户选择和ViewModel集合变更,避免循环触发事件。
- 支持Reset操作:ViewModel集合调用
Clear()时,DataGrid选中项会同步清空。 - 避免全局集合冲突:附加属性默认值设为
null,由ViewModel提供独立集合实例。 - 事件监听清理:属性变更时自动移除旧监听,防止内存泄漏。
内容的提问来源于stack exchange,提问作者Kraken
相关产品推荐
相关产品推荐

