Share via

I can't count the selected DataGrid rows correctly to delete them, DataGrid.SelectedItems.Count doesn't give me the exact number of them. In WPF (by C#)

رضا جافری 1,296 Reputation points
2022-01-14T22:32:44.767+00:00

First and foremost, I apologize for my grammatical errors; my first language is Persian (Iran).
I want to remove one or more rows from DataGrid depending on the user's choice.
I add blank rows to the DataGrid with the following codes:

    public struct DefaultRow  
    {  
        public string NO { get; set; }  
        public string ID_Serial { get; set; }  
        public string ProductsOrServicesDescription { get; set; }  
        public string Quantity { get; set; }  
        public string UnitOfMeasurement { get; set; }  
        public string UnitPrice { get; set; }  
        public string Discount { get; set; }  
        public string TaxDutyVat { get; set; }  
        public string GuaranteeOrWarrantyPeriod { get; set; }  
        public string TotalAmount { get; set; }  
    }  
    public void AddBlankRow()  
    {  
        List<object> DefaultRow = new List<object>();  
        DefaultRow DR = new DefaultRow();  
        DR.NO = "";  
        DR.ID_Serial = "";  
        DR.ProductsOrServicesDescription = "";  
        DR.Quantity = "";  
        DR.UnitOfMeasurement = "";  
        DR.UnitPrice = "";  
        DR.Discount = "";  
        DR.TaxDutyVat = "";  
        DR.GuaranteeOrWarrantyPeriod = "";  
        DR.TotalAmount = "";  
        for (int i = 0; i < BillDataGrid.Items.Count; i++)  
        {  
            DefaultRow.Add(BillDataGrid.Items[i]);  
        }  
        DefaultRow.Add(DR);  
        BillDataGrid.ItemsSource = DefaultRow;  
    }  
    private void AddNewRow_Button_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)  
    {  
        AddBlankRow();  
    }  

When I try to fill the blank rows, DataGrid.SelectedItems.Count increases with each click, then when I want to select a number of rows again to delete, I get the following error:
System.ArgumentOutOfRangeException: 'Index was out of range. Must be non-negative and less than the size of the collection.'
These are my codes:
WPF:

                <DataGrid x:Name="BillDataGrid" HeadersVisibility="Column" CanUserAddRows="True" CanUserDeleteRows="True" EnableRowVirtualization="True" AutoGenerateColumns="False" HorizontalAlignment="Left" VerticalAlignment="Top" Height="190" Width="758" Margin="7,12,0,0">  
                    <DataGrid.Columns>  
                        <DataGridTextColumn x:Name="NO" Binding="{Binding NO}" Width="SizeToHeader"/>  
                        <DataGridTextColumn x:Name="ID_Serial" Binding="{Binding ID_Serial}" Width="SizeToHeader"/>  
                        <DataGridTextColumn x:Name="ProductsOrServicesDescription" Binding="{Binding ProductsOrServicesDescription}" Width="115"/>  
                        <DataGridTextColumn x:Name="Quantity" Binding="{Binding Quantity}" Width="SizeToHeader"/>  
                        <DataGridTextColumn x:Name="UnitOfMeasurement" Binding="{Binding UnitOfMeasurement}" Width="SizeToHeader"/>  
                        <DataGridTextColumn x:Name="UnitPrice" Binding="{Binding UnitPrice}" Width="SizeToHeader"/>  
                        <DataGridTextColumn x:Name="Discount" Binding="{Binding Discount}" Width="SizeToHeader"/>  
                        <DataGridTextColumn x:Name="TaxDutyVat" Binding="{Binding TaxDutyVat}" Width="SizeToHeader"/>  
                        <DataGridTextColumn x:Name="GuaranteeOrWarrantyPeriod" Binding="{Binding GuaranteeOrWarrantyPeriod}" Width="SizeToHeader"/>  
                        <DataGridTextColumn x:Name="TotalAmount" Binding="{Binding TotalAmount}" Width="SizeToHeader"/>  
                    </DataGrid.Columns>  
                </DataGrid>  

165301-datagrid.png

C#:

    private void DeleteSelectedRows_Button_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)  
    {  
        switch (BillDataGrid.SelectedItems.Count > 0)  
        {  
            case false:  
                break;  
            default:  
                List<object> DefaultRow = new List<object>();  
                for (int i = 0; i < BillDataGrid.Items.Count; i++)  
                {  
                    DefaultRow.Add(BillDataGrid.Items[i]);  
                }  
                for (int i = 0; i < BillDataGrid.SelectedItems.Count; i++)  
                {  
                    DefaultRow.RemoveAt(BillDataGrid.SelectedItems.IndexOf(BillDataGrid.SelectedItems[i]));  
                }  
                BillDataGrid.ItemsSource = DefaultRow;  
                break;  
        }  
    }  

Also, I tried to remove the selected rows from the DataGrid directly with the help of the following codes, but I got another error:

    private void DeleteSelectedRows_Button_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)  
    {  
        switch (BillDataGrid.SelectedItems.Count > 0)  
        {  
            case false:  
                break;  
            default:;  
                for (int i = 0; i < BillDataGrid.SelectedItems.Count; i++)  
                {  
                    BillDataGrid.Items.Remove(BillDataGrid.SelectedItems[i]);  
                }  
                break;  
        }  
    }  

System.InvalidOperationException: 'Operation is not valid while ItemsSource is in use. Access and modify elements with ItemsControl.ItemsSource instead.'

Thanks

Developer technologies | Windows Presentation Foundation
Developer technologies | XAML
Developer technologies | XAML

A language based on Extensible Markup Language (XML) that enables developers to specify a hierarchy of objects with a set of properties and logic.

Developer technologies | C#
Developer technologies | C#

An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.


Answer accepted by question author

Peter Fleischer (former MVP) 19,351 Reputation points
2022-01-15T16:51:41.51+00:00

Hi Reza,
you must iterate thru SelectedItems in descending order and remove form items collection (not from SelectedItems collection). I changed your demo. try it.

XAML:

<Window x:Class="WpfApp1.Window098"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp098"
        xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
        mc:Ignorable="d"
        Title="RezaJafery_220115" Height="450" Width="900">
  <Window.DataContext>
    <local:ViewModel/>
  </Window.DataContext>
  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition/>
      <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <DataGrid x:Name="BillDataGrid" 
              HeadersVisibility="Column" 
              CanUserAddRows="False" 
              CanUserDeleteRows="False" 
              EnableRowVirtualization="True" 
              AutoGenerateColumns="False" 
              HorizontalAlignment="Left" 
              VerticalAlignment="Top" 
              Margin="5">
      <i:Interaction.Behaviors>
        <local:DataGridBehavior/>
      </i:Interaction.Behaviors>
      <DataGrid.Columns>
        <DataGridTextColumn Header="NO" Binding="{Binding NO}" Width="SizeToHeader"/>
        <DataGridTextColumn Header="ID_Serial" Binding="{Binding ID_Serial}" Width="SizeToHeader"/>
        <DataGridTextColumn Header="ProductsOrServicesDescription" Binding="{Binding ProductsOrServicesDescription}" Width="115"/>
        <DataGridTextColumn Header="Quantity" Binding="{Binding Quantity}" Width="SizeToHeader"/>
        <DataGridTextColumn Header="UnitOfMeasurement" Binding="{Binding UnitOfMeasurement}" Width="SizeToHeader"/>
        <DataGridTextColumn Header="UnitPrice" Binding="{Binding UnitPrice}" Width="SizeToHeader"/>
        <DataGridTextColumn Header="Discount" Binding="{Binding Discount}" Width="SizeToHeader"/>
        <DataGridTextColumn Header="TaxDutyVat" Binding="{Binding TaxDutyVat}" Width="SizeToHeader"/>
        <DataGridTextColumn Header="GuaranteeOrWarrantyPeriod" Binding="{Binding GuaranteeOrWarrantyPeriod}" Width="SizeToHeader"/>
        <DataGridTextColumn Header="TotalAmount" Binding="{Binding TotalAmount}" Width="SizeToHeader"/>
      </DataGrid.Columns>
    </DataGrid>
    <StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Right">
      <Button Content="Add New Row" Command="{Binding}" CommandParameter="AddNewRow" Width="100" Margin="5"/>
      <Button Content="Delete selected rows" Command="{Binding}" CommandParameter="DeleteSelectedRows" Width="100" Margin="5"/>
    </StackPanel>
  </Grid>
</Window>

and code:

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interactivity;

namespace WpfApp098
{
  public class ViewModel : ICommand
  {
    public DataGrid BillDataGrid { get; set; }
    int NO;

    public void AddBlankRow()
    {
      List<object> DefaultRow = new List<object>();
      DefaultRow DR = new DefaultRow();
      DR.NO = (++NO).ToString(); //  "";
      DR.ID_Serial = "";
      DR.ProductsOrServicesDescription = "";
      DR.Quantity = "";
      DR.UnitOfMeasurement = "";
      DR.UnitPrice = "";
      DR.Discount = "";
      DR.TaxDutyVat = "";
      DR.GuaranteeOrWarrantyPeriod = "";
      DR.TotalAmount = "";
      for (int i = 0; i < BillDataGrid.Items.Count; i++)
      {
        DefaultRow.Add(BillDataGrid.Items[i]);
      }
      DefaultRow.Add(DR);
      BillDataGrid.ItemsSource = DefaultRow;
    }

    private void DeleteSelectedRows_Button_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
      switch (BillDataGrid.SelectedItems.Count > 0)
      {
        case false:
          break;
        default:
          List<object> DefaultRow = new List<object>();
          for (int i = 0; i < BillDataGrid.Items.Count; i++)
          {
            DefaultRow.Add(BillDataGrid.Items[i]);
          }
          for (int i = BillDataGrid.SelectedItems.Count - 1; i >= 0; i--)
          {
            DefaultRow.RemoveAt(BillDataGrid.Items.IndexOf(BillDataGrid.SelectedItems[i]));
          }
          BillDataGrid.ItemsSource = DefaultRow;
          break;
      }
    }

    //private void DeleteSelectedRows_Button_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    //{
    //  switch (BillDataGrid.SelectedItems.Count > 0)
    //  {
    //    case false:
    //      break;
    //    default:
    //      ;
    //      for (int i = 0; i < BillDataGrid.SelectedItems.Count; i++)
    //      {
    //        BillDataGrid.Items.Remove(BillDataGrid.SelectedItems[i]);
    //      }
    //      break;
    //  }
    //}

    public event EventHandler CanExecuteChanged;
    public bool CanExecute(object parameter) => true;
    public void Execute(object parameter)
    {
      switch (parameter.ToString())
      {
        case "AddNewRow": AddBlankRow(); break;
        case "DeleteSelectedRows": DeleteSelectedRows_Button_PreviewMouseLeftButtonDown(null, null); break;
        default: break;
      }
    }
  }

  public class DefaultRow
  {
    public string NO { get; set; }
    public string ID_Serial { get; set; }
    public string ProductsOrServicesDescription { get; set; }
    public string Quantity { get; set; }
    public string UnitOfMeasurement { get; set; }
    public string UnitPrice { get; set; }
    public string Discount { get; set; }
    public string TaxDutyVat { get; set; }
    public string GuaranteeOrWarrantyPeriod { get; set; }
    public string TotalAmount { get; set; }
  }

  public class DataGridBehavior : Behavior<DataGrid>
  {
    protected override void OnAttached() =>
      AssociatedObject.Loaded += (s, e) =>
      ((ViewModel)AssociatedObject.DataContext).BillDataGrid = AssociatedObject;
  }
}

A better solution is to use collection and ItemsSource. Try following demo:

XAML:

<Window x:Class="WpfApp1.Window099"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp099"
        xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
        mc:Ignorable="d"
        Title="RezaJafery_220115" Height="450" Width="900">
  <Window.DataContext>
    <local:ViewModel/>
  </Window.DataContext>
  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition/>
      <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <DataGrid ItemsSource="{Binding View}"
              HeadersVisibility="Column" 
              CanUserAddRows="False" 
              CanUserDeleteRows="False" 
              EnableRowVirtualization="True" 
              AutoGenerateColumns="False" 
              HorizontalAlignment="Left" 
              VerticalAlignment="Top" 
              Margin="5">
      <i:Interaction.Behaviors>
        <local:DataGridBehavior/>
      </i:Interaction.Behaviors>
      <DataGrid.Columns>
        <DataGridTextColumn Header="NO" Binding="{Binding NO}" Width="SizeToHeader"/>
        <DataGridTextColumn Header="ID_Serial" Binding="{Binding ID_Serial}" Width="SizeToHeader"/>
        <DataGridTextColumn Header="ProductsOrServicesDescription" Binding="{Binding ProductsOrServicesDescription}" Width="115"/>
        <DataGridTextColumn Header="Quantity" Binding="{Binding Quantity}" Width="SizeToHeader"/>
        <DataGridTextColumn Header="UnitOfMeasurement" Binding="{Binding UnitOfMeasurement}" Width="SizeToHeader"/>
        <DataGridTextColumn Header="UnitPrice" Binding="{Binding UnitPrice}" Width="SizeToHeader"/>
        <DataGridTextColumn Header="Discount" Binding="{Binding Discount}" Width="SizeToHeader"/>
        <DataGridTextColumn Header="TaxDutyVat" Binding="{Binding TaxDutyVat}" Width="SizeToHeader"/>
        <DataGridTextColumn Header="GuaranteeOrWarrantyPeriod" Binding="{Binding GuaranteeOrWarrantyPeriod}" Width="SizeToHeader"/>
        <DataGridTextColumn Header="TotalAmount" Binding="{Binding TotalAmount}" Width="SizeToHeader"/>
      </DataGrid.Columns>
    </DataGrid>
    <StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Right">
      <Button Content="Add New Row" Command="{Binding}" CommandParameter="AddNewRow" Width="100" Margin="5"/>
      <Button Content="Delete selected rows" Command="{Binding}" CommandParameter="DeleteSelectedRows" Width="100" Margin="5"/>
    </StackPanel>
  </Grid>
</Window>

And code:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Interactivity;

namespace WpfApp099
{
  public class ViewModel : ICommand
  {
    public DataGrid BillDataGrid { get; set; }
    int NO;

    public ViewModel() => cvs.Source = col;
    ObservableCollection<DefaultRow> col = new ObservableCollection<DefaultRow>();
    private CollectionViewSource cvs = new CollectionViewSource();
    public ICollectionView View { get => cvs.View; }

    public void AddBlankRow()
    {
      DefaultRow DR = new DefaultRow();
      DR.NO = (++NO).ToString(); //  "";
      DR.ID_Serial = "";
      DR.ProductsOrServicesDescription = "";
      DR.Quantity = "";
      DR.UnitOfMeasurement = "";
      DR.UnitPrice = "";
      DR.Discount = "";
      DR.TaxDutyVat = "";
      DR.GuaranteeOrWarrantyPeriod = "";
      DR.TotalAmount = "";
      col.Add(DR);
    }

    private void DeleteSelectedRows_Button_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
      switch (BillDataGrid.SelectedItems.Count > 0)
      {
        case false:
          break;
        default:
          List<DefaultRow> cache = new List<DefaultRow>();
          foreach (var item in BillDataGrid.SelectedItems) cache.Add((DefaultRow)item);
          foreach (DefaultRow item in cache) col.Remove(item);
          break;
      }
    }

    public event EventHandler CanExecuteChanged;
    public bool CanExecute(object parameter) => true;
    public void Execute(object parameter)
    {
      switch (parameter.ToString())
      {
        case "AddNewRow": AddBlankRow(); break;
        case "DeleteSelectedRows": DeleteSelectedRows_Button_PreviewMouseLeftButtonDown(null, null); break;
        default: break;
      }
    }
  }

  public class DefaultRow
  {
    public string NO { get; set; }
    public string ID_Serial { get; set; }
    public string ProductsOrServicesDescription { get; set; }
    public string Quantity { get; set; }
    public string UnitOfMeasurement { get; set; }
    public string UnitPrice { get; set; }
    public string Discount { get; set; }
    public string TaxDutyVat { get; set; }
    public string GuaranteeOrWarrantyPeriod { get; set; }
    public string TotalAmount { get; set; }
  }

  public class DataGridBehavior : Behavior<DataGrid>
  {
    protected override void OnAttached() =>
      AssociatedObject.Loaded += (s, e) =>
      ((ViewModel)AssociatedObject.DataContext).BillDataGrid = AssociatedObject;
  }
}

Was this answer helpful?

1 person found this answer helpful.

0 additional answers

Sort by: Most helpful

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.