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

MVVM模式下捕获ItemsControl中DataGrid的SelectionChanged事件

在MVVM模式下捕获多个DataGrid的选择变更事件

针对你用ItemsControl绑定ObservableCollection<ObservableCollection<TrayType>>生成多个DataGrid的场景,完全可以在ViewModel中实现选择变更的捕获,以下是两种符合MVVM规范的方案:


方案一:使用附加属性(Attached Property)

通过自定义附加属性,将DataGrid的SelectionChanged事件转换为ViewModel中的命令执行,无需在View的后台代码中写事件逻辑。

1. 定义附加属性类

创建一个静态类来封装附加属性,负责监听DataGrid的选择变更并触发绑定的命令:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

public static class DataGridSelectionBehavior
{
    public static readonly DependencyProperty SelectionChangedCommandProperty =
        DependencyProperty.RegisterAttached(
            "SelectionChangedCommand",
            typeof(ICommand),
            typeof(DataGridSelectionBehavior),
            new PropertyMetadata(null, OnSelectionChangedCommandChanged));

    public static void SetSelectionChangedCommand(DependencyObject obj, ICommand value)
    {
        obj.SetValue(SelectionChangedCommandProperty, value);
    }

    public static ICommand GetSelectionChangedCommand(DependencyObject obj)
    {
        return (ICommand)obj.GetValue(SelectionChangedCommandProperty);
    }

    private static void OnSelectionChangedCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is DataGrid dataGrid)
        {
            // 先移除旧事件绑定
            dataGrid.SelectionChanged -= DataGrid_SelectionChanged;
            
            // 绑定新命令对应的事件处理
            if (e.NewValue is ICommand command)
            {
                dataGrid.SelectionChanged += DataGrid_SelectionChanged;
            }
        }
    }

    private static void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (sender is DataGrid dataGrid)
        {
            var command = GetSelectionChangedCommand(dataGrid);
            if (command != null && command.CanExecute(dataGrid.SelectedItem))
            {
                // 将选中项作为参数传递给命令
                command.Execute(dataGrid.SelectedItem);
            }
        }
    }
}

2. 在View中绑定附加属性

修改ItemsControl的ItemTemplate,给DataGrid附加属性绑定ViewModel中的命令:

<ItemsControl ItemsSource="{Binding trayTypesDataGrids}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <DataGrid ItemsSource="{Binding}"
                      local:DataGridSelectionBehavior.SelectionChangedCommand="{Binding DataContext.TraySelectionChangedCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}">
                <!-- 你的DataGrid列定义 -->
            </DataGrid>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

这里通过RelativeSource获取父ItemsControl的DataContext(即你的ViewModel),确保命令绑定正确。

3. 在ViewModel中实现命令

定义一个命令来处理选中逻辑,这里用自定义的RelayCommand(如果没有现成的实现,可以参考下方的简单版本):

using System;
using System.Windows.Input;

public class YourViewModel
{
    public ICommand TraySelectionChangedCommand { get; }

    public YourViewModel()
    {
        TraySelectionChangedCommand = new RelayCommand<TrayType>(OnTraySelected);
    }

    private void OnTraySelected(TrayType selectedTray)
    {
        // 在这里处理选中逻辑,比如记录选中项、更新界面状态等
        if (selectedTray != null)
        {
            // 示例:打印选中项信息
            Console.WriteLine($"选中托盘:{selectedTray.Name}");
        }
    }
}

// 简单的RelayCommand实现
public class RelayCommand<T> : ICommand
{
    private readonly Action<T> _execute;
    private readonly Func<T, bool> _canExecute;

    public RelayCommand(Action<T> execute, Func<T, bool> canExecute = null)
    {
        _execute = execute ?? throw new ArgumentNullException(nameof(execute));
        _canExecute = canExecute;
    }

    public bool CanExecute(object parameter)
    {
        return _canExecute == null || _canExecute((T)parameter);
    }

    public void Execute(object parameter)
    {
        _execute((T)parameter);
    }

    public event EventHandler CanExecuteChanged
    {
        add => CommandManager.RequerySuggested += value;
        remove => CommandManager.RequerySuggested -= value;
    }
}

方案二:包装子集合为ViewModel

将每个ObservableCollection<TrayType>包装为一个子ViewModel,通过绑定SelectedItem属性来捕获选择变更,这种方式更适合需要独立管理每个DataGrid状态的场景。

1. 创建子ViewModel类

定义一个包含子集合和选中项的ViewModel,并通过属性变更通知或事件传递选中信息:

using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;

public class TrayGroupViewModel : INotifyPropertyChanged
{
    private ObservableCollection<TrayType> _trays;
    public ObservableCollection<TrayType> Trays
    {
        get => _trays;
        set
        {
            _trays = value;
            OnPropertyChanged();
        }
    }

    private TrayType _selectedTray;
    public TrayType SelectedTray
    {
        get => _selectedTray;
        set
        {
            if (_selectedTray != value)
            {
                _selectedTray = value;
                OnPropertyChanged();
                // 触发选中变更事件,通知父ViewModel
                SelectedTrayChanged?.Invoke(this, value);
            }
        }
    }

    // 选中变更事件
    public event EventHandler<TrayType> SelectedTrayChanged;

    public TrayGroupViewModel(ObservableCollection<TrayType> trays)
    {
        Trays = trays;
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

2. 修改父ViewModel的集合类型

将原有的ObservableCollection<ObservableCollection<TrayType>>转换为ObservableCollection<TrayGroupViewModel>,并订阅每个子ViewModel的选中事件:

public class YourViewModel
{
    public ObservableCollection<TrayGroupViewModel> TrayGroups { get; }

    // 假设原集合已初始化
    private ObservableCollection<ObservableCollection<TrayType>> _trayTypesDataGrids;

    public YourViewModel()
    {
        _trayTypesDataGrids = new ObservableCollection<ObservableCollection<TrayType>>();
        // 填充_trayTypesDataGrids数据...

        // 转换为子ViewModel集合
        TrayGroups = new ObservableCollection<TrayGroupViewModel>(
            _trayTypesDataGrids.Select(trays => new TrayGroupViewModel(trays)));

        // 订阅每个子ViewModel的选中变更事件
        foreach (var group in TrayGroups)
        {
            group.SelectedTrayChanged += OnTrayGroupSelectedTrayChanged;
        }
    }

    private void OnTrayGroupSelectedTrayChanged(object sender, TrayType selectedTray)
    {
        var sourceGroup = sender as TrayGroupViewModel;
        // 可以通过sourceGroup区分是哪个DataGrid的选中
        if (selectedTray != null && sourceGroup != null)
        {
            Console.WriteLine($"来自组{sourceGroup.Trays.GetHashCode()}的选中项:{selectedTray.Name}");
        }
    }
}

3. 在View中绑定子ViewModel属性

修改ItemsControl的绑定源为TrayGroups,并让DataGrid绑定子ViewModel的Trays和SelectedItem:

<ItemsControl ItemsSource="{Binding TrayGroups}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <DataGrid ItemsSource="{Binding Trays}"
                      SelectedItem="{Binding SelectedTray, Mode=TwoWay}">
                <!-- 你的DataGrid列定义 -->
            </DataGrid>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

方案对比

  • 附加属性方案:无需修改原有集合结构,实现快速,适合不需要区分DataGrid实例的简单场景;若需要区分来源,可将DataGrid或对应的集合作为CommandParameter传递。
  • 子ViewModel包装方案:结构更清晰,每个DataGrid的状态独立封装,便于复杂场景下的状态管理,缺点是需要额外定义子ViewModel并转换原集合。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 13:09:55