Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DataGrid selection issue

the problem I have is following: when first two rows in DataGrid are selected and the first row is deleted, the selected row below becomes the first row and gets deselected. If I sort any of the columns, the selection of that row comes back. Or, if I close the window I get information that deselected row is actually selected (querying content of underlying bound property SelectedUsers in UsersViewModel - done in OnClosing method). Is there anyone who can help or explain me if I did something wrong or this might be a bug. I provided complete source code below. Thanks for help.

MainWindow.xaml

<Window x:Class="DeleteFirstRowIssue.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:DeleteFirstRowIssue"
        Title="MainWindow" Height="350" Width="400">
    <Window.Resources>
        <Style x:Key="CustomDataGridCellStyle" TargetType="{x:Type DataGridCell}">
                <Style.Triggers>
                    <Trigger Property="IsSelected" Value="True">
                        <Setter Property="Background" Value="Red"/>
                        <Setter Property="FontWeight" Value="Bold"/>
                        <Setter Property="Foreground" Value="Black"/>
                    </Trigger>
                </Style.Triggers>
            </Style>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="25"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="30"/>
            <RowDefinition Height="30"/>
            <RowDefinition Height="30"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Label Content="Users:" Grid.Row="0" Grid.Column="0"/>
        <local:CustomDataGrid x:Name="UsersDataGrid" ItemsSource="{Binding UsersViewSource.View}" SelectionMode="Extended" AlternatingRowBackground="LightBlue" AlternationCount="2"
                              SelectionUnit="FullRow" IsReadOnly="True" SnapsToDevicePixels="True" AutoGenerateColumns="False" Grid.Row="1" Grid.Column="0" CanUserAddRows="False" CanUserDeleteRows="False" CanUserReorderColumns="False"
                              SelectedItemsList="{Binding SelectedUsers, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" IsSynchronizedWithCurrentItem="False" CellStyle="{StaticResource CustomDataGridCellStyle}">
            <local:CustomDataGrid.Columns>
                <DataGridTextColumn Header="Nickname:" Width="*" Binding="{Binding Nickname}"/>
                <DataGridTextColumn Header="Age:" Width="*" Binding="{Binding Age}"/>
            </local:CustomDataGrid.Columns>
        </local:CustomDataGrid>
        <Button Grid.Row="2" Grid.Column="0" Margin="5" MaxWidth="80" MinWidth="80" Content="Delete 1st row" Command="{Binding DeleteFirstUserCommand}"/>
        <Button Grid.Row="3" Grid.Column="0" Margin="5" MaxWidth="80" MinWidth="80" Content="Delete last row" Command="{Binding DeleteLastUserCommand}"/>
        <Button Grid.Row="4" Grid.Column="0" Margin="5" MaxWidth="80" MinWidth="80" Content="Initialize Grid" Command="{Binding InitializeListCommand}"/>
    </Grid>
</Window>

MainWindow.xaml.cs

using System;
using System.Collections;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;

namespace DeleteFirstRowIssue
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = new UsersViewModel();
        }

        protected override void OnClosing(CancelEventArgs e)
        {
            UsersViewModel uvm = (UsersViewModel)DataContext;
            if (uvm.SelectedUsers.Count > 0)
            {
                StringBuilder sb = new StringBuilder(uvm.SelectedUsers.Count.ToString() + " selected user(s):\n");
                foreach (UserModel um in uvm.SelectedUsers)
                {
                    sb.Append(um.Nickname + "\n");
                }
                MessageBox.Show(sb.ToString());
            }
            base.OnClosing(e);
        }
    }

    public class UsersViewModel : INotifyPropertyChanged
    {
        private IList selectedUsers;
        public IList SelectedUsers
        {
            get { return selectedUsers; }
            set
            {
                selectedUsers = value;
                OnPropertyChanged("SelectedUsers");
            }
        }

        public CollectionViewSource UsersViewSource { get; private set; }
        public ObservableCollection<UserModel> Users { get; set; }
        public ICommand DeleteFirstUserCommand { get; }
        public ICommand DeleteLastUserCommand { get; }
        public ICommand InitializeListCommand { get; }
        public event PropertyChangedEventHandler PropertyChanged;
        public void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); }

        public UsersViewModel()
        {
            SelectedUsers = new ArrayList();
            Users = new ObservableCollection<UserModel>();
            UsersViewSource = new CollectionViewSource() { Source = Users };
            InitializeListCommand = new RelayCommand(p => Users.Count == 0, p => InitializeList());
            InitializeListCommand.Execute(null);
            DeleteFirstUserCommand = new RelayCommand(p => Users.Count > 0, p => DeleteFirstUser());
            DeleteLastUserCommand = new RelayCommand(p => Users.Count > 0, p => DeleteLastUser());
        }

        private void InitializeList()
        {
            Users.Add(new UserModel() { Nickname = "John", Age = 35 });
            Users.Add(new UserModel() { Nickname = "Jane", Age = 29 });
            Users.Add(new UserModel() { Nickname = "Mark", Age = 59 });
            Users.Add(new UserModel() { Nickname = "Susan", Age = 79 });
            Users.Add(new UserModel() { Nickname = "Joe", Age = 66 });
            Users.Add(new UserModel() { Nickname = "Nina", Age = 29 });
            Users.Add(new UserModel() { Nickname = "Selma", Age = 44 });
            Users.Add(new UserModel() { Nickname = "Katrin", Age = 24 });
            Users.Add(new UserModel() { Nickname = "Joel", Age = 32 });
        }

        private void DeleteFirstUser()
        {
            ListCollectionView lcw = (ListCollectionView)UsersViewSource.View;
            lcw.RemoveAt(0);
        }

        private void DeleteLastUser()
        {
            ListCollectionView lcw = (ListCollectionView)UsersViewSource.View;
            lcw.RemoveAt(lcw.Count - 1);
        }
    }

    public class UserModel
    {
        public string Nickname { get; set; }
        public int Age { get; set; }
    }

    public class CustomDataGrid : DataGrid
    {
        public static readonly DependencyProperty SelectedItemsListProperty = DependencyProperty.Register("SelectedItemsList", typeof(IList), typeof(CustomDataGrid), new PropertyMetadata(null));
        public IList SelectedItemsList
        {
            get { return (IList)GetValue(SelectedItemsListProperty); }
            set { SetValue(SelectedItemsListProperty, value); }
        }

        public CustomDataGrid() { SelectionChanged += CustomDataGrid_SelectionChanged; }
        void CustomDataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e) { SelectedItemsList = SelectedItems; }
    }

    public class RelayCommand : ICommand
    {
        private Predicate<object> canExecute;
        private Action<object> execute;

        public RelayCommand(Predicate<object> canExecute, Action<object> execute)
        {
            this.canExecute = canExecute;
            this.execute = execute;
        }

        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        public bool CanExecute(object parameter) { return canExecute(parameter); }
        public void Execute(object parameter) { execute(parameter); }
    }
}
like image 830
Cobek Avatar asked Mar 20 '26 06:03

Cobek


1 Answers

I encountered a similar issue implementing SelectedItemsList. The main gotcha is that when the selection gets renewed, the backing list tries to delete the items that are no longer in the selection. If, however, an item is changed (or no longer exists) the comparison fails and the item remains in the list, causing this behaviour.

You can keep the list in sync by adding a CollectionChanged EventHandler.

public UsersViewModel()
{
    ...

    Users.CollectionChanged += new NotifyCollectionChangedEventHandler(CollectionChanged);
}

private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    if (e.Action == NotifyCollectionChangedAction.Remove)
        foreach (UserModel item in e.OldItems) SelectedUsers.Remove(item);
}
like image 54
Funk Avatar answered Mar 22 '26 20:03

Funk