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

如何在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;
        }
    }
}

使用步骤

  1. ViewModel中声明集合:
public ObservableCollection<YourItemType> SelectedItems { get; } = new ObservableCollection<YourItemType>();
  1. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 22:47:03