Reputation: 583
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.
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
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