DataGrid doesn't deselect hidden items properl

2019-07-30 15:29发布

I have an issue with WPF DataGrid, which drives me crazy. Let's consider this view model:

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
}

... this 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>

...and this code-behind:

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

Case 1.

  • Select Item 0. Item 0 is selected, check box is checked.
  • Scroll grid's content to hide Item 0. Let the Item 6 be on top of visible area.
  • Select Item 10. Item 10 is selected, check box is checked.
  • Scroll up to Item 0.
  • Select Item 0. Item 10 is selected, check box is not checked.

Debug output:

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

Screenshot:

Case 2.

  • Restart application.
  • Select several items on top of grid (e.g., three first items). Items are selected, check boxes are checked.
  • Scroll grid's content to hide Item 0. Let the Item 6 be on top of visible area.
  • Select Item 10. Item 10 is selected, check box is checked.
  • Scroll up to Item 0.
  • Item 0 and Item 1 are still checked. Item 2 isn't checked. All of three first items aren't selected.

Debug output:

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

Screenshot:

The problem is reproducing, when the selection mode is extended. When it is single, everything works fine.

Questions:
1. Am I missing something? 2. Anybody knows a workaround?

UPDATE.

I've added SelectionChanged event handler for the grid:

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

Debug output says, that SelectedItems collection is updated properly. E.g., for the first case output will be:

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.

But the bound data property IsSelected isn't updated!

2条回答
做自己的国王
2楼-- · 2019-07-30 16:08

At least, one workaround is found, and its related to the question's update.
Let's modify SelectionChanged event handler a little:

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

But it's definitely a bug in DataGrid, isn't it?

查看更多
姐就是有狂的资本
3楼-- · 2019-07-30 16:27

I tried it and I think you need to set the isSelected to false to both condition. It works for me. But thanks for the initial solution! It helped me a lot.

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;
    }
查看更多
登录 后发表回答