WPF MVVM模式下嵌套DataGrid的Interaction Behavior绑定异常问题
问题:子DataGrid的SelectionChanged事件绑定到主DataGrid的Behavior
我通过Behavior实现DataGrid的SelectedItems与ViewModel双向绑定,主DataGrid和行详情模板中的子DataGrid都应用了该Behavior,但子DataGrid的Behavior未生效,它的SelectionChanged事件始终关联到主DataGrid的Behavior实例。
视图代码
xmlns:i="http://schemas.microsoft.com/xaml/behaviors" <DataGrid Name="MainDataGrid" AutoGenerateColumns="False" HorizontalScrollBarVisibility="Disabled" Height="Auto" ItemsSource="{Binding ObCol_Model}" VerticalAlignment="Stretch" CanUserAddRows="false" BorderThickness="1" AlternatingRowBackground="#FFFFFFCC" HorizontalGridLinesBrush="#FFA0A0A0" VerticalGridLinesBrush="#FFA0A0A0" SelectionUnit="FullRow" HeadersVisibility="Column" GridLinesVisibility="Horizontal" ColumnHeaderHeight="25" IsReadOnly="True" CanUserResizeRows="False" RowHeight="22" VerticalContentAlignment="Center" BorderBrush="DarkGray" HorizontalAlignment="Left" RowDetailsVisibilityMode="VisibleWhenSelected"> <i:Interaction.Behaviors> <local:DataGridSelectedItemsBehavior SelectedItems="{Binding SelectedItems}" /> </i:Interaction.Behaviors> <DataGrid.Columns> <DataGridTextColumn Header="Code" Binding="{Binding Code}" Width="80"/> <DataGridTextColumn Header="Name" Binding="{Binding Name}" Width="*"/> </DataGrid.Columns> <DataGrid.RowDetailsTemplate> <DataTemplate> <DataGrid Name="SubDataGrid" Margin="10,0,0,0" AutoGenerateColumns="False" ItemsSource="{Binding ObCol_SubModel}" Height="Auto" Width="auto" VerticalAlignment="Stretch" CanUserAddRows="false" BorderThickness="1" AlternatingRowBackground="#FFFFFFCC" HorizontalGridLinesBrush="#FFA0A0A0" VerticalGridLinesBrush="#FFA0A0A0" SelectionUnit="FullRow" HeadersVisibility="Column" GridLinesVisibility="Horizontal" ColumnHeaderHeight="25" IsReadOnly="True" CanUserResizeRows="False" RowHeight="22" VerticalContentAlignment="Center" BorderBrush="DarkGray" HorizontalAlignment="Left"> <i:Interaction.Behaviors> <local:DataGridSelectedItemsBehavior SelectedItems="{Binding SubSelectedItems}" /> </i:Interaction.Behaviors> <DataGrid.Style> <Style TargetType="{x:Type DataGrid}"> <Setter Property="Visibility" Value="Visible"/> <Style.Triggers> <DataTrigger Binding="{Binding ObCol_SubModel}" Value="{x:Null}"> <Setter Property="Visibility" Value="Collapsed"/> </DataTrigger> </Style.Triggers> </Style> </DataGrid.Style> <DataGrid.Columns> <DataGridTextColumn Header="Detail" Binding="{Binding Detail}" Width="80"/> <DataGridTextColumn Header="Detail Name" Binding="{Binding Name}" Width="150"/> <DataGridTextColumn Header="Comment" Binding="{Binding Comment}" Width="*"/> </DataGrid.Columns> </DataGrid> </DataTemplate> </DataGrid.RowDetailsTemplate> </DataGrid>
Behavior代码(原代码)
public class DataGridSelectedItemsBehavior : Behavior<DataGrid> { protected override void OnAttached() { base.OnAttached(); if (SelectedItems != null) { AssociatedObject.SelectedItems.Clear(); foreach (var item in SelectedItems) { AssociatedObject.SelectedItems.Add(item); } } } public IList SelectedItems { get { return (IList)GetValue(SelectedItemsProperty); } set { SetValue(SelectedItemsProperty, value); } } public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.Register("SelectedItems", typeof(IList), typeof(DataGridSelectedItemsBehavior), new UIPropertyMetadata(null, SelectedItemsChanged)); private static void SelectedItemsChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) { var behavior = o as DataGridSelectedItemsBehavior; if (behavior == null) return; var oldValue = e.OldValue as INotifyCollectionChanged; var newValue = e.NewValue as INotifyCollectionChanged; if (oldValue != null) { oldValue.CollectionChanged -= behavior.SourceCollectionChanged; behavior.AssociatedObject.SelectionChanged -= behavior.DataGridSelectionChanged; } if (newValue != null) { behavior.AssociatedObject.SelectedItems.Clear(); foreach (var item in (IEnumerable)newValue) { behavior.AssociatedObject.SelectedItems.Add(item); } behavior.AssociatedObject.SelectionChanged += behavior.DataGridSelectionChanged; newValue.CollectionChanged += behavior.SourceCollectionChanged; } } private bool _isUpdatingTarget; private bool _isUpdatingSource; void SourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (_isUpdatingSource) return; try { _isUpdatingTarget = true; if (e.OldItems != null) { foreach (var item in e.OldItems) { AssociatedObject.SelectedItems.Remove(item); } } if (e.NewItems != null) { foreach (var item in e.NewItems) { AssociatedObject.SelectedItems.Add(item); } } if (e.Action == NotifyCollectionChangedAction.Reset) { AssociatedObject.SelectedItems.Clear(); } } finally { _isUpdatingTarget = false; } } private void DataGridSelectionChanged(object sender, SelectionChangedEventArgs e) { if (_isUpdatingTarget) return; var selectedItems = this.SelectedItems; if (selectedItems == null) return; try { _isUpdatingSource = true; foreach (var item in e.RemovedItems) { selectedItems.Remove(item); } foreach (var item in e.AddedItems) { selectedItems.Add(item); } } finally { _isUpdatingSource = false; } } }
ViewModel代码
public class ViewModel : ObservableObject { private readonly ObservableCollection<Model> myObCol_Model; private ObservableCollection<Model> mySelectedItems; public ViewModel() { mySelectedItems = new ObservableCollection<Model>(); myObCol_Model = new ObservableCollection<Model>() { new Model("100", "Hundred"), new Model("200", "Two Hundred"), new Model("300", "Three Hundred") }; var item = new Model("400", "Four Hundred"); item.AddSubModel(new SubModel("10", "Ten", "sub 10")); item.AddSubModel(new SubModel("20", "Twenty", "sub 20")); item.AddSubModel(new SubModel("30", "Thirty", "sub 30")); myObCol_Model.Add(item); } public ObservableCollection<Model> ObCol_Model { get { return myObCol_Model; } } public ObservableCollection<Model> SelectedItems { get { return mySelectedItems; } set { if (mySelectedItems == value) return; mySelectedItems = value; OnPropertyChanged(nameof(SelectedItems)); } } }
模型代码
public class Model : ObservableObject { private ObservableCollection<SubModel> myObCol_SubModel; private ObservableCollection<SubModel> mySubModelSelectedItems; public Model(string code, string name) { mySubModelSelectedItems = new ObservableCollection<SubModel>(); Code = code; Name = name; } public string Code { get; set; } public string Name { get; set; } public ObservableCollection<SubModel> ObCol_SubModel { get { return myObCol_SubModel; } } public void AddSubModel(SubModel subModel) { if (myObCol_SubModel == null) myObCol_SubModel = new ObservableCollection<SubModel>(); myObCol_SubModel.Add(subModel); } public ObservableCollection<SubModel> SubSelectedItems { get { return mySubModelSelectedItems; } set { if (mySubModelSelectedItems == value) return; mySubModelSelectedItems = value; OnPropertyChanged(nameof(SubSelectedItems)); } } } public class SubModel { public SubModel(string detail, string name, string comment) { Detail = detail; Name = name; Comment = comment; } public string Detail { get; set; } public string Name { get; set; } public string Comment { get; set; } }
解决方案
问题根源在于原Behavior未实现解绑时的事件清理逻辑,且绑定更新时的事件处理不完整,导致事件监听残留并关联到错误的DataGrid实例。以下是修改后的Behavior:
修改后的Behavior代码
public class DataGridSelectedItemsBehavior : Behavior<DataGrid> { protected override void OnAttached() { base.OnAttached(); if (SelectedItems != null) { AssociatedObject.SelectedItems.Clear(); foreach (var item in SelectedItems) { AssociatedObject.SelectedItems.Add(item); } } } // 新增解绑逻辑,清理所有事件监听 protected override void OnDetaching() { base.OnDetaching(); var selectedItems = SelectedItems as INotifyCollectionChanged; if (selectedItems != null) { selectedItems.CollectionChanged -= SourceCollectionChanged; } AssociatedObject.SelectionChanged -= DataGridSelectionChanged; } public IList SelectedItems { get { return (IList)GetValue(SelectedItemsProperty); } set { SetValue(SelectedItemsProperty, value); } } public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.Register("SelectedItems", typeof(IList), typeof(DataGridSelectedItemsBehavior), new UIPropertyMetadata(null, SelectedItemsChanged)); private static void SelectedItemsChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) { var behavior = o as DataGridSelectedItemsBehavior; if (behavior == null || behavior.AssociatedObject == null) return; var oldValue = e.OldValue as INotifyCollectionChanged; var newValue = e.NewValue as INotifyCollectionChanged; // 先移除所有旧的事件监听,避免残留 if (oldValue != null) { oldValue.CollectionChanged -= behavior.SourceCollectionChanged; } behavior.AssociatedObject.SelectionChanged -= behavior.DataGridSelectionChanged; // 处理新绑定值 if (newValue != null) { behavior.AssociatedObject.SelectedItems.Clear(); foreach (var item in (IEnumerable)newValue) { behavior.AssociatedObject.SelectedItems.Add(item); } behavior.AssociatedObject.SelectionChanged += behavior.DataGridSelectionChanged; newValue.CollectionChanged += behavior.SourceCollectionChanged; } else { behavior.AssociatedObject.SelectedItems.Clear(); } } private bool _isUpdatingTarget; private bool _isUpdatingSource; void SourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (_isUpdatingSource || AssociatedObject == null) return; try { _isUpdatingTarget = true; if (e.OldItems != null) { foreach (var item in e.OldItems) { AssociatedObject.SelectedItems.Remove(item); } } if (e.NewItems != null) { foreach (var item in e.NewItems) { AssociatedObject.SelectedItems.Add(item); } } if (e.Action == NotifyCollectionChangedAction.Reset) { AssociatedObject.SelectedItems.Clear(); } } finally { _isUpdatingTarget = false; } } private void DataGridSelectionChanged(object sender, SelectionChangedEventArgs e) { if (_isUpdatingTarget || AssociatedObject == null) return; // 校验sender,确保只处理当前关联DataGrid的事件 if (sender != AssociatedObject) return; var selectedItems = this.SelectedItems; if (selectedItems == null) return; try { _isUpdatingSource = true; foreach (var item in e.RemovedItems) { selectedItems.Remove(item); } foreach (var item in e.AddedItems) { selectedItems.Add(item); } } finally { _isUpdatingSource = false; } } }
关键修改点
- 添加
OnDetaching方法:当Behavior从DataGrid解绑时(如DataGrid被销毁、DataTemplate元素复用),清理所有事件监听,避免旧绑定残留导致逻辑混乱。 - 完善绑定更新逻辑:无论新值是否为空,都先移除旧的事件监听,确保每次绑定更新都建立正确的关联。
- 增加Sender校验:在SelectionChanged事件中校验sender是否为当前Behavior关联的DataGrid,防止跨实例触发错误逻辑。
- 空值处理:当SelectedItems绑定值为null时,同步清空DataGrid的选中项。
内容的提问来源于stack exchange,提问作者Yannick
相关产品推荐
相关产品推荐

