MVVM模式下捕获ItemsControl中DataGrid的SelectionChanged事件
针对你用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

