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

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;
        }
    }
}

关键修改点

  1. 添加OnDetaching方法:当Behavior从DataGrid解绑时(如DataGrid被销毁、DataTemplate元素复用),清理所有事件监听,避免旧绑定残留导致逻辑混乱。
  2. 完善绑定更新逻辑:无论新值是否为空,都先移除旧的事件监听,确保每次绑定更新都建立正确的关联。
  3. 增加Sender校验:在SelectionChanged事件中校验sender是否为当前Behavior关联的DataGrid,防止跨实例触发错误逻辑。
  4. 空值处理:当SelectedItems绑定值为null时,同步清空DataGrid的选中项。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 06:14:58