Reputation: 2957
I'm doing MVVM where a DataGrid is bound to an ObservableCollection with a DeleteItemCommand hooked up to the DataGrid.InputBindings as follows:
<DataGrid.InputBindings>
<KeyBinding Key="Delete" Command="{Binding DeleteItemCommand}" />
</DataGrid.InputBindings>
The item and row are removed when the user hits the delete key but the grid looses focus. You have to click or tab to the grid so it regains focus before hitting Delete to remove another row (pretty freaking annoying). I tried setting the DataGrid.CanUserDeleteRows="False" but it doesn't make any difference.
I replaced the DataGrid with a ListView and the ListView retains focus.
Is this a bug with the DataGrid or am I doing something wrong? Peace and love, peace and love!
Upvotes: 4
Views: 2056
Reputation: 3583
I had to solve the issue in a different way than the jl.'s answer, because it could not do any processing before (e.g. checking access) deletion happen as a Command
could. While perhaps not as robust, it does exactly what you asked about. Keeping your original code unchanged, just hook the following SelectionChanged
, or even better use attached property.
Because deleting an item first produces SelectionChanged with index -1 it is easy enough to reliably guess when deletion happen and set a flag. After the first invocation with -1 another one with nearest neighbor index happens, at this point if the flag was set, it is safe to focus the current cell:
private int LastItemCount = 0;
private bool ShouldFocusOnSelection = false;
private void FocusOnDeleteDG_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (sender is DataGrid dg)
{
if (IsRemovalEvent(dg, e))
{
ShouldFocusOnSelection = true;
}
else if (ShouldFocusOnSelection)
{
dg.FocusCurrentCell();
ShouldFocusOnSelection = false;
}
LastItemCount = dg.Items.Count;
}
}
where IsRemovalEvent
checks if the selection event was produced by item removal:
private static bool IsRemovalEvent(DataGrid dg, SelectionChangedEventArgs e)
{
return e.RemovedItems.Count > 0
&& e.AddedItems.Count == 0
&& dg.SelectedIndex == -1
&& dg.Items.Count > 0
&& LastItemCount > dg.Items.Count;
}
and the FocusCurrentCell
/GetChildren
are helper methods you probably already have:
public static void FocusCurrentCell(this DataGrid dataGrid)
{
var rowIndex = dataGrid.SelectedIndex != -1 ? dataGrid.SelectedIndex : (dataGrid.Items.Count > 0 ? 0 : -1);
if (!(dataGrid.ItemContainerGenerator.ContainerFromIndex(rowIndex) is DataGridRow row))
{
return;
}
if (dataGrid.CurrentColumn?.DisplayIndex != null)
{
// traverse VisualTree using VisualTreeHelper.GetChild()
var cell = row.GetChildren<DataGridCell>()
.Skip(dataGrid.CurrentColumn.DisplayIndex).FirstOrDefault();
Keyboard.Focus(cell);
}
}
Upvotes: 0
Reputation: 4501
Did you check this answer yet?
How to bind delete action (in WPF Datagrid) to a command or property in view model
You probably need to:
make sure the key is actually bound to the specified command in your datacontext, like this:
<DataGrid.InputBindings>
<KeyBinding Key="Delete" Command="{Binding DataContext.DeleteEntry, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}}"/>
</DataGrid.InputBindings>
Previously I fail at item #2 and wrote Command="{Binding DeleteEntry}", while in fact I should bind to DataContext.DeleteEntry by RelativeSource.
Upvotes: 0
Reputation: 764
I solved this by using the built in functionality of WPF DataGrid. The grid handles removing items by default if the underlying collection is editable (if the collection is dedicated to this purpose that's no problem, otherwise an intermediate collection can be added...). I avoided any key bindings and just set up the grid like this:
<DataGrid ItemsSource="{Binding InvoiceItems}" IsReadOnly="False" CanUserDeleteRows="True" CanUserAddRows="False">
The ItemsSource collection is of type BidningCollection<>
In my ViewModel (my DataContext) I add a handler for CollectionChanged event:
InvoiceItems.CollectionChanged += InvoiceItemsCollectionChanged;
And implement it like this:
private void InvoiceItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action != NotifyCollectionChangedAction.Remove)
return;
foreach (var oldItem in e.OldItems)
{
//do any other processing necessary
}
}
That's because you will probably be having at least two ways of removing an item from you underlying collection (keyboard with Del key, some button) and maybe some other things to take care of when an item is deleted.
Upvotes: 4
Reputation: 41
I bumped on that some time ago. Somehow this event is never raised. Try this approach.
Long story short, event PreviewKeyDown
will get you where you want.
And in MVVM-friendly manner:
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewKeyDown">
<i:InvokeCommandAction Command="{Binding DeleteItemCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
Upvotes: 0