Search code examples
c#.netwpfdatagridcommand

Delete selected items in WPF DataGrid (multiselect)


I have a WPF application with a DataGrid, menus and buttons. When rows in the DataGrid are selected, buttons and menu items are activated, to enable deleting data from a database.

Partial XAML for this main window:

<Button ToolTip="Delete Record" Command="{Binding DeleteCommand}" Name="button_delete" IsEnabled="False"/>
<MenuItem>
    <MenuItem Header="Delete" IsEnabled="False" Name="menuItem_delete" Command="{Binding DeleteCommand}"/>
</MenuItem>

<DataGrid Name="BooksDataGrid" ItemsSource="{Binding BooksList}" SelectionChanged="dataGrid_selectionChanged">
    <DataGrid.Columns>
         <DataGridTextColumn Header="Title" Binding="{Binding title_long}"/>
         <DataGridTextColumn Header="ISBN" Binding="{Binding isbn}"/>
    </DataGrid.Columns>
</DataGrid>

The DeleteCommand is to be defined within the class that is the DataContext for the main window above. Partial code for this class is as follows:

sealed class BookViewModel
{
    public ObservableCollection<IBook> Books { get; private set; }

    // load data command code

    // delete record command code
    // ...
    public void deleteAction(IEnumerable<string> isbnList)
    {
        // delete data from database
        // this already works
    }
}

There is already a command implemented to load data from the database. That was implemented in a very similar fashion to the answer to the following question: How to bind WPF button to a command in ViewModelBase?

What is to be achieved:

  1. When items in the DataGrid are selected, the UI elements for the delete command are activated if one or more items are selected. This is already achieved with the following event handler, in the codebehind for the main window:
private void dataGrid_selectionChanged(object sender, SelectionChangedEventArgs args)
{
    // this works

    // if nothing is selected, disable delete button and menu item
    if (BooksDataGrid.SelectedItems.Count == 0)
    {
         button_deleteBook.IsEnabled = false;
         menuItem_deleteBook.IsEnabled = false;
    }
    else
    {
        // delete command can now be executed, as shown in the binding in XAML
        button_deleteBook.IsEnabled = true;
        menuItem_deleteBook.IsEnabled = true;
    }
}
  1. The delete command to be implemented. What is not clear so far, is how to pass parameters to a command implemented in the ViewModel (DataContext for the View). I am new to WPF and trying to understand how commands work. Specifically, this command should take a parameter of IEnumerable<string>, or perhaps a collection of string. I have already completed and tested the deleteAction method. The string objects are to be the values in the "ISBN" column of the selected rows of the DataGrid.

Solution

  • You've wandered into one of the tricky bits of wpf / mvvm in that what you would ideally want to use cannot be bound. Or at least not straight out the box.

    If you wanted just a single item select and delete then you could just bind selecteditem to a property in your window viewmodel. The command could use the IBook object that gives to do a delete.

    Since you want multiple selection and deletion that's a complication because you can't bind the entire list of selecteditems. This is not a bindable dependency property.

    There are a number of ways round that.

    You could sub class the datagrid and extend.

    Or

    You could use a behavior. What these allow you to do is encapsulate a chunk of event orientated code and add an attached dependency property to store the data. This itself is then bindable. I recommend you read up on behaviors generally and google a bit to take a look at examples. Binding selecteditems is a fairly common requirement and you should get a number of hits. Here's one though.

    Select multiple items from a DataGrid in an MVVM WPF project

    You end up with a List of observablecollection if IBook you can work with in your viewmodel.

    I recommend observablecollection and you can subscribe to the collectionchanged event in the viewmodel so you can check the count. Use 0 to return false for CanExecute of your command and 1+ true.

    https://learn.microsoft.com/en-us/dotnet/api/system.windows.input.icommand.canexecute?view=netcore-3.1#System_Windows_Input_ICommand_CanExecute_System_Object_

    Your iBook doesn't sound like it's going to be a viewmodel. It should be. Pretty much anything you're binding which isn't marked explicitly as OneTime should be a viewmodel that implements inotifypropertychanged. This is because there's a long existing bug which can give memory leaks otherwise. Don't worry about whether your viewmodel is going to leak or not. Just always use a viewmodel and build a base viewmodel implements inpc so you can easily inherit everything from that.