Azzarrel
Azzarrel

Reputation: 583

How to get a new template row in a DataGrid when deleting the newest item without changing the focus?

Preface: I am having a real problem to find a fitting title to accurately describe my problem, so feel free to edit, if you find a better title.

Problem

I had a chat with a colleague about how to get the empty template row back, that's usually shown in a DataGrid when setting the CanUserAddRows Property to True.

Apparently when you add a new item using this template row, but then delete it before it ever lost focus, no new template row will be created.

Example

<Window x:Class="WpfApp1.MainWindow"
    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:WpfApp1"
    mc:Ignorable="d"
    Title="MainWindow" Height="450" Width="800">
<Grid>
    <DataGrid CanUserAddRows="True" ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}" AutoGenerateColumns="False">
        <DataGrid.Columns>
            <DataGridTextColumn Width="50" Binding="{Binding Name}"/>
            <DataGridTextColumn Width="50" Binding="{Binding Desc}"/>
            <DataGridTemplateColumn>
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <Button Width="30" Command="{Binding DataContext.DeleteCommand, RelativeSource={RelativeSource AncestorType=Window}}" CommandParameter="{Binding}"></Button>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
        </DataGrid.Columns>
    </DataGrid>
</Grid>
</Window>

I have created a DataGrid with its ItemsSource bound to a ObservableCollection of Items using the MVVM pattern. I've set the CanUserAddRows Property to True, which means there will be an empty row as the last row in my DataGrid which i can use to add new items. I also want to be able to delete any item from the list by clicking on a button within the same row, which executes this Command in my viewmodel:

    public void ExecuteDelete(object obj)
    {
        if (obj is Item item)
        {
            Items.Remove(item);
        }
    }

The button works as intended for all items already in the list, but if I click in the template row to create a new item and then delete this row using the button in the rightmost column, the row disappears without creating a new template row.

Only if the row has lost focus before pressing the button, there will be a new template row.

Screenshots

I've added a few screenshots for clarification. From top to bottm you can see (1) the empty template row, (2) I click in the template row, creating a new item, adding some text and then deleting it without ever de-selecting it, (3) there is no more template row. If i click on another row beforehand (4), editing will be completed and I'll get a new template row, allowing me to delete my 'abc' row without any issues.

Empty row

create new item in empty row

no template row

new template row

Question

How do I avoid permanently deleting the template row in my DataGrid like shown above? The solution should be in MVVM.

An acceptable solution should either prohibit the user to delete new rows before he finished editing them or get the template row to reappear by whatever means necessary, which would be the favourable outcome.

Simply switching the SelectedItem to some other item before deleting didn't help.

Upvotes: 0

Views: 52

Answers (1)

mm8
mm8

Reputation: 169370

It's the control in the view that is responsible for creating/removing the blank row. The view model doesn't care or know about the blank row at all.

If you want to handle this in the view model, you should set the CanUserAddRows property to false in the view and add and remove the items yourself according to your own custom logic.

Here is a brief example that should give you the idea:

public ViewModel()
{
    DeleteCommand = new DelegateCommand(ExecuteDelete);
    AddNewItemCommand = new DelegateCommand(_ => Items.Add(new Item()));

    // start with a blank item
    Items.Add(new Item());
    Items.CollectionChanged += async (s, e) =>
    {
        //...and make sure there always is at least one item in the collection
        if (e.Action == NotifyCollectionChangedAction.Remove
            && Items.Count == 0)
        {
            await Task.Delay(1); // or use the dispatcher...
            Items.Add(new Item());
        }
    };
}

public ObservableCollection<Item> Items { get; } =
    new ObservableCollection<Item>();

public DelegateCommand DeleteCommand { get; }
public DelegateCommand AddNewItemCommand { get; }

XAML:

<DataGrid.InputBindings>
    <KeyBinding Key="Return" Command="{Binding AddNewItemCommand}" />
</DataGrid.InputBindings>

The other option would be to trying to fix this in the view or control or create a custom control that behaves like you want. Apparently the DataGrid's built-in functionality doesn't fit your requirements.

Upvotes: 1

Related Questions