42

I want to enable the user to highlight a row on the WPF DataGrid and press delete key to delete the row.

  • the functionality is already built into the UI of the grid, so to the user, the row disappears
  • I currently handle this on the SelectionChanged event (code below)
  • I loop through all the "e.RemovedItems" and delete them with LINQ

Problem is: even when you simply select a row and move off of it, selection change is fired and that row is in e.RemovedItems (which is odd, why would simply selecting something put it in a RemovedItems container?).

So I am looking for a DeleteKeyPressed event so I can simply handle it. What is that event called?

I am using the March 2009 toolkit.

XAML:

<Grid DockPanel.Dock="Bottom">
    <toolkit:DataGrid x:Name="TheDataGrid" 
                      SelectionChanged="TheDataGrid_SelectionChanged"
                      AutoGenerateColumns="True"
                      RowEditEnding="TheDataGrid_RowEditEnding"/>

code-behind:

private void TheDataGrid_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
    if (e.RemovedItems.Count > 0)
    {
        Message.Text = "The following were removed: ";
        foreach (object obj in e.RemovedItems)
        {
            Customer customer = obj as Customer;
            Message.Text += customer.ContactName + ",";
            _db.Order_Details.DeleteAllOnSubmit(
                customer.Orders.SelectMany(o => o.Order_Details));
            _db.Orders.DeleteAllOnSubmit(customer.Orders);
            _db.Customers.DeleteOnSubmit(customer);
        } 
    }

    try
    {
        _db.SubmitChanges();
    }
    catch (Exception ex)
    {
        Message.Text = ex.Message;
    }
}

ANSWER:

Thanks lnferis, that was exactly what I was looking for, here is my finished delete handling event for the datagrid, note the KeyDown event doesn't fire for some reason.

XAML:

<toolkit:DataGrid x:Name="TheDataGrid" 
                  KeyDown="TheDataGrid_KeyDown"
                  PreviewKeyDown="TheDataGrid_PreviewKeyDown"
                  AutoGenerateColumns="True"
                  RowEditEnding="TheDataGrid_RowEditEnding"/>

code-behind

private void TheDataGrid_PreviewKeyDown(object sender, KeyEventArgs e)
{
    if (e.Key == Key.Delete)
    {
        var grid = (DataGrid)sender;

        if (grid.SelectedItems.Count > 0)
        {
            string checkMessage = "The following will be removed: ";

            foreach (var row in grid.SelectedItems)
            {
                Customer customer = row as Customer;
                checkMessage += customer.ContactName + ",";
            }
            checkMessage = Regex.Replace(checkMessage, ",$", "");

            var result = MessageBox.Show(checkMessage, "Delete", MessageBoxButton.OKCancel);
            if (result == MessageBoxResult.OK)
            {
                foreach (var row in grid.SelectedItems)
                {
                    Customer customer = row as Customer;
                    _db.Order_Details.DeleteAllOnSubmit(
                        customer.Orders.SelectMany(o => o.Order_Details));
                    _db.Orders.DeleteAllOnSubmit(customer.Orders);
                    _db.Customers.DeleteOnSubmit(customer);
                }
                _db.SubmitChanges();
            }
            else
            {
                foreach (var row in grid.SelectedItems)
                {
                    Customer customer = row as Customer;
                    LoadData();
                    _db.Refresh(System.Data.Linq.RefreshMode.OverwriteCurrentValues, customer); //TODO: this doesn't refresh the datagrid like the other instance in this code
                }
            }
        }
    }
}

private void TheDataGrid_KeyDown(object sender, KeyEventArgs e)
{
    Console.WriteLine("never gets here for some reason");
}
Edward Tanguay
  • 189,012
  • 314
  • 712
  • 1,047

7 Answers7

32

The RemovedItems items reflects the items removed from the selection, and not from the grid.

Handle the PreviewKeyDown event, and use the SelectedItems property to delete the selected rows there:

private void PreviewKeyDownHandler(object sender, KeyEventArgs e) {
    var grid = (DataGrid)sender;
    if ( Key.Delete == e.Key ) {
        foreach (var row in grid.SelectedItems) {
            ... // perform linq stuff to delete here
        }
    }
}
Inferis
  • 4,582
  • 5
  • 37
  • 47
  • 6
    Is there a reason DataGrid doesn't just have a RowsDeleted event? WPF, by default, graphically removes the row when you press the delete key. So, it is already doing this logic and looping over the deleted rows. Why can't they just add in an event handler so that I don't have to duplicate this code and check keys. – Chad Feb 01 '11 at 23:13
  • 23
    This isn't very nice, if you're editing a cell and use the delete key to remove some characters in the cell, you'll end up deleting the whole row. – epalm May 12 '11 at 17:00
  • 2
    @epalm if you add the `foreach` code inside `if (grid.SelectedItems.Count > 0)` like Edward Tanguay did, then everything should work. But thanks for pointing it out! – Jo Smo Dec 01 '14 at 22:09
  • Solution to epalms problem below – andy Sep 02 '16 at 15:34
  • `grid.SelectedItems.Count` will also be greater than 0 when editing a cell thus leading to the erroneous deletion of the row. As @Kiran Chi pointed out below, you can check if the current selection is a DataGridCell with this code: `if(e.Device.Target.GetType().Name == "DataGridCell")`. As an alternative you can also check for `e.OriginalSource.GetType().Name`. – metatron Mar 14 '21 at 16:08
22

XAML

<DataGrid ItemsSource="{Binding}" CommandManager.PreviewCanExecute="Grid_PreviewCanExecute" />

Code behind

private void Grid_PreviewCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
  DataGrid grid = (DataGrid)sender;
  if (e.Command == DataGrid.DeleteCommand)
  {
    if (MessageBox.Show(String.Format("Would you like to delete {0}", (grid.SelectedItem as Person).FirstName), "Confirm Delete", MessageBoxButton.OKCancel) != MessageBoxResult.OK)
      e.Handled = true;
  }
}
flux
  • 1,518
  • 1
  • 17
  • 31
  • e.Handled in my situation conflicted with removing row from UI and from bound observable collection . I commented it out and then it got fine – Ivan P. Jul 14 '23 at 11:10
17

What are you binding your DataGrid to? Ideally, you should react to CollectionChanged events on the collection you are binding to. That way, your logic (deletion of removed items) will be separated from your UI.

You can build an Observable collection containing your objects and bind it to ItemsSource just for that purpose if the original collection does not have the necessary events.

It might not suit your specific setup, but that's how I usually do it.

Denis Troller
  • 7,411
  • 1
  • 23
  • 36
  • I totally agree with this approach. An ObservableCollection allows your ViewModel to handle any changes to the underlying databound source. You don't have to rely upon code-behind... nothing grossly wrong with getting it done via code-behind... just cleaner using the ViewModel. – Mike L Aug 26 '10 at 04:10
  • Beware this solution indroduces focus-related issues (namely loss of focus), apparently the default 'delete' logic is quite different from the one for underlying collection changed. – wondra Jan 15 '20 at 15:03
4

Please follow the below code. I have succeeded with the below code.

Please let me know if changes are required.

 private void grdEmployee_PreviewKeyDown(object sender, KeyEventArgs e)
    {

        if (e.Device.Target.GetType().Name == "DataGridCell")
        {
            if (e.Key == Key.Delete)
            {
                MessageBoxResult res = MessageBox.Show("Are you sure want to delete?", "Confirmation!", MessageBoxButton.YesNo,MessageBoxImage.Question);
                e.Handled = (res == MessageBoxResult.No);
            }
        }
    }
demongolem
  • 9,474
  • 36
  • 90
  • 105
Kiran Ch
  • 41
  • 1
  • This will also work (and sounds a tad more logical to me): `if (e.OriginalSource.GetType().Name == "DataGridCell")` – metatron Mar 14 '21 at 16:10
2

A little late to the party, but to get Inferis answer working:

Dim isEditing = False
AddHandler dg.BeginningEdit, Sub() isEditing = True
AddHandler dg.RowEditEnding, Sub() isEditing = False
AddHandler dg.PreviewKeyDown, Sub(obj, ev) 
  If e.Key = Key.Delete AndAlso Not isEditing Then ...

This fixes epalms comment: "if you're editing a cell and use the delete key to remove some characters in the cell, you'll end up deleting the whole row"

andy
  • 531
  • 5
  • 16
1

The cleanest solution is to use PreviewCanExecute like answered by flux, this is a completed solution to make it a bit more clear for anybody that overlooked his answer like I did:

private void Grid_PreviewCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
    if (e.Command == DataGrid.DeleteCommand)
    {
        if (MessageBox.Show($"Delete something from something else?", "Confirm removal of something", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
        {
            // Do what ever needs to be done when someone deletes the row
        }
        else
        {
            e.Handled = true;   
            // Handled means.. no worries, I took care of it.. and it will not delete the row
        }
    }
}

No need to hook on to CommandManager.Executed after this.

Bart Vanseer
  • 348
  • 3
  • 6
-1

You want to handle the KeyUp or KeyDown event and check the pressed Key for Delete.

private void OnKeyDown(object sender, KeyEventArgs e) {
  if ( Key.Delete == e.Key ) {
    // Delete pressed
  }
}
JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454