【问题标题】:DataGrid doesn't deselect hidden items properly when SelectionMode="Extended"当 SelectionMode="Extended" 时,DataGrid 没有正确取消选择隐藏项
【发布时间】:2025-12-13 12:30:01
【问题描述】:

我遇到了 WPF DataGrid 的问题,这让我抓狂。 让我们考虑一下这个视图模型:

public class ViewModel : INotifyPropertyChanged
{
    public int Id { get; set; }
    public string Name { get; set; }

    public bool IsSelected 
    {
        get { return isSelected; }
        set
        {
            System.Diagnostics.Debug.WriteLine("{0}'s IsSelected new value is: {1}", Name, value);
            if (isSelected != value)
            {
                isSelected = value;
                OnPropertyChanged("IsSelected");
            }
        }
    }
    private bool isSelected;

    // INPC implementation
}

...这个 XAML:

<Window x:Class="WpfApplication5.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <DataGrid ItemsSource="{Binding}" IsReadOnly="True" AutoGenerateColumns="False" 
                  SelectionMode="Extended" SelectionUnit="FullRow">
            <DataGrid.ItemContainerStyle>
                <Style TargetType="{x:Type DataGridRow}">
                    <Setter Property="IsSelected" Value="{Binding IsSelected}"/>
                </Style>
            </DataGrid.ItemContainerStyle>

            <DataGrid.Columns>
                <DataGridCheckBoxColumn Header="Is selected" Binding="{Binding IsSelected}"/>
                <DataGridTextColumn Header="Id" Binding="{Binding Id}"/>
                <DataGridTextColumn Header="Name" Width="*" Binding="{Binding Name}"/>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>

...还有这个代码隐藏:

public partial class MainWindow : Window
{
    private IList<ViewModel> GenerateViewModels()
    {
        var viewModels = new List<ViewModel>();

        for (var i = 0; i < 100; i++)
        {
            viewModels.Add(new ViewModel
            {
                Id = i,
                Name = string.Format("Item {0}", i)
            });
        }

        return viewModels;
    }

    public MainWindow()
    {
        InitializeComponent();
        DataContext = GenerateViewModels();
    }
}

案例 1。

  • 选择项目 0。项目 0 已选中,复选框已选中。
  • 滚动网格的内容以隐藏第 0 项。让第 6 项位于可见区域的顶部。
  • 选择项目 10。项目 10 被选中,复选框被选中。
  • 向上滚动到第 0 项。
  • 选择第 0 项。第 10 项已选中,复选框选中。

调试输出:

Item 0's IsSelected new value is: True
Item 0's IsSelected new value is: False
Item 10's IsSelected new value is: True
Item 10's IsSelected new value is: False

案例 2。

  • 重新启动应用程序。
  • 选择网格顶部的几个项目(例如,三个第一个项目)。项目已选中,复选框已选中。
  • 滚动网格的内容以隐藏第 0 项。让第 6 项位于可见区域的顶部。
  • 选择项目 10。项目 10 被选中,复选框被选中。
  • 向上滚动到第 0 项。
  • 第 0 项和第 1 项仍处于选中状态。第 2 项未选中。前三个项目未全部选中。

调试输出:

Item 0's IsSelected new value is: True
Item 1's IsSelected new value is: True
Item 2's IsSelected new value is: True
Item 2's IsSelected new value is: False
Item 10's IsSelected new value is: True

The problem is reproducing, when the selection mode is extended.单身时,一切正常。

问题:
1.我错过了什么吗? 2. 有人知道解决方法吗?

更新

我为网格添加了SelectionChanged 事件处理程序:

    private void MyGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (e.AddedItems != null)
        {
            foreach (var item in e.AddedItems.Cast<ViewModel>())
            {
                System.Diagnostics.Debug.WriteLine("** Item {0} is added to selection.", item.Id);
            }
        }
        if (e.RemovedItems != null)
        {
            foreach (var item in e.RemovedItems.Cast<ViewModel>())
            {
                System.Diagnostics.Debug.WriteLine("** Item {0} is removed from selection.", item.Id);
            }
        }

        e.Handled = true;
    }

调试输出显示,SelectedItems 集合已正确更新。例如,对于第一种情况,输出将是:

Item 0's IsSelected new value is: True
** Item 0 is added to selection.
Item 0's IsSelected new value is: False
Item 10's IsSelected new value is: True
** Item 10 is added to selection.
** Item 0 is removed from selection.
Item 10's IsSelected new value is: False
** Item 0 is added to selection.
** Item 10 is removed from selection.

但是绑定的数据属性IsSelected 没有更新!

【问题讨论】:

  • 没什么可补充的,除了同样的问题也让我发疯。这完全违反直觉。

标签: c# wpf wpfdatagrid


【解决方案1】:

至少找到了一种解决方法,并且与问题的更新有关。
让我们稍微修改一下SelectionChanged 事件处理程序:

    private void MyGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (e.AddedItems != null)
        {
            foreach (var item in e.AddedItems.Cast<ViewModel>())
            {
                System.Diagnostics.Debug.WriteLine("** Item {0} is added to selection.", item.Id);

                if (!item.IsSelected)
                {
                    // if bound data item still isn't selected, fix this
                    item.IsSelected = true;
                }
            }
        }
        if (e.RemovedItems != null)
        {
            foreach (var item in e.RemovedItems.Cast<ViewModel>())
            {
                System.Diagnostics.Debug.WriteLine("** Item {0} is removed from selection.", item.Id);

                if (item.IsSelected)
                {
                    // if bound data item still is selected, fix this
                    item.IsSelected = false;
                }
            }
        }

        e.Handled = true;
    }

但这绝对是DataGrid 中的一个错误,不是吗?

【讨论】:

    【解决方案2】:

    我试过了,我认为你需要将 isSelected 设置为 false 来满足这两个条件。这个对我有用。但感谢最初的解决方案!这对我帮助很大。

    private void MyGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (e.AddedItems != null)
            {
                foreach (var item in e.AddedItems.Cast<ViewModel>())
                {
                    System.Diagnostics.Debug.WriteLine("** Item {0} is added to selection.", item.Id);
    
                    if (!item.IsSelected)
                    {
                        // if bound data item still isn't selected, fix this
                        item.IsSelected = false;
                    }
                }
            }
            if (e.RemovedItems != null)
            {
                foreach (var item in e.RemovedItems.Cast<ViewModel>())
                {
                    System.Diagnostics.Debug.WriteLine("** Item {0} is removed from selection.", item.Id);
    
                    if (item.IsSelected)
                    {
                        // if bound data item still is selected, fix this
                        item.IsSelected = false;
                    }
                }
            }
    
            e.Handled = true;
        }
    

    【讨论】:

    • 不。检查AddedItems 时将IsSelected 设置为false 是错误的。这意味着,以可视方式选择的项目(即由DataGrid 突出显示)将具有IsSelected == false。这会导致数据不同步 - 数据项必须出现在 DataGrid.SelectedItems IsSelected == true
    • 我认为你是对的。我正在尝试解决相同的问题,但我的问题是所有项目最初都是 IsSelected = true。当我选择一行时,隐藏的项目不会被取消选择。你知道如何解决这个问题吗?
    • 嗯...看起来像虚拟化问题 - 因为尚未创建不可见项目的项目容器,DG 只是忽略它们。目前,我无法提出一些解决方案,稍后再看。
    • The only thing, that comes to mind, is to handle this as special case - that is, when selection was changed from several items to single item, find all items with IsSelected == true (except selected item ),并将 IsSelected 设置为 false。