DataGrid selection problem

the problem I am having is the following: when the first two rows in the DataGrid are selected and the first row is deleted, the selected row below becomes the first row and is canceled. If I sort any of the columns, the selection of that row is returned. Or, if I close the window, I get information that the actual selected row is selected (requesting the content of the main SelectedUsers binding property in the UserViewModel - done in the OnClosing method). Is there anyone who can help me or explain if I did something wrong or it might be a mistake. I have provided the complete source code below. Thanks for the 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); }
    }
}

      

+3


source to share


2 answers


I faced a similar issue implementing SelectedItemsList

. The main problem is that when the selection is resumed, the fallback list tries to remove items that are no longer in the selection. If, however, the item changes (or no longer exists), no comparison is made and the item remains in the list, causing this behavior.

You can keep the list in sync by adding an 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);
}

      

+1


source


As commented, somehow, when the first row is removed, the new property of the first rows IsSelected

stops syncing with the property IsSelected

. I don't know why this is happening, but it points to a workaround if you are mainly interested in keeping the style working: just use the rows property in the trigger



<DataTrigger Binding="{Binding IsSelected,RelativeSource={RelativeSource AncestorType={x:Type DataGridRow}}}" Value="True">
    <Setter Property="Background" Value="Red"/>
    <Setter Property="FontWeight" Value="Bold"/>
    <Setter Property="Foreground" Value="Black"/>
</DataTrigger>

      

+1


source







All Articles