I've got the simplest Datagrid imaginable, with a button bound to a command that adds a row to the ObservableCollection on which the grid is based. The code is really simple (see the MyData class below, it's just a bunch of lines).
The bug is this:
- Add a row
- Start editing the Name cell
- Press the Add button without exiting from the editing cell
Now the detection of the cell's end editing is broken.
That is, you can go and edit the other cells but setter won't be call for them.
You can go back to the initial editing row, press enter; this sort of fixup things because from now on the editing begin/end return working properly.
In the meantime the content of the edited cells (but the first one) is lost, because the setter has never been called. What you see is only graphical cache so just reorder the rows and puff! Data are gone.
Notice that for reproducing the problem, the Add button has to have the Focusable property set to false, because otherwise it works since clicking on the button makes the Datagrid loosing focus and so when the add-row happens, the cell is not in edit mode.
But being non-focusable is the typical state of ToolBar buttons, infact i first saw this bug with a ToolBar.
Workaround
A workaround is associating and event to the button in the code-behind that ensures that the datagrid is no longer in edit mode:
private void Button_Click(object sender, RoutedEventArgs e)
{
dg.CommitEdit();
}
But is required for all the buttons that potentially fires the addition of a row. I would like something more robust, and I would like to ensure that there is not a problem in adding rows this way (should be the mainstream, but I could miss something the same).
XAML
Just remove the Click="Button_Click"
if you don't wanna try the workaround.
<Window x:Class="WpfApp10_DataGridEditingBug.MainWindow"
...
Title="MainWindow" Height="140" Width="280">
<Window.DataContext>
<local:MyData></local:MyData>
</Window.DataContext>
<DockPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<Button Command="{Binding AddCommand}" Focusable="False" Click="Button_Click">Add</Button>
</StackPanel>
<DataGrid x:Name="dg" ItemsSource="{Binding People}" CanUserAddRows="False">
</DataGrid>
</DockPanel>
</Window>
DataContext
public class MyData
{
public ObservableCollection<Person> People { get; } = new ObservableCollection<Person>();
public RelayCommand AddCommand => addCommand ?? (addCommand = new RelayCommand() { ExecuteAction = AddCommandExecute });
private void AddCommandExecute(object obj)
{
var newPerson = new Person();
People.Add(newPerson);
}
private RelayCommand addCommand;
}
The Row Class
I just an host class for a couple of properties, Name and Surname. I didn't use automatic properties for debugging reasons, this way is easier to spot that it doesn't even call the set method of name property by putting a breakpoint there.
public class Person
{
public string Name
{
get => name;
set => name = value;
}
private string name;
public string Surname
{
get => surname;
set => surname = value;
}
private string surname;
}
And, for completeness, my smart RelayCommand implementation is this:
public class RelayCommand : ICommand
{
public event EventHandler CanExecuteChanged;
public Func<object, bool> CanExecuteFunc;
public Action<object> ExecuteAction;
public bool CanExecute(object parameter) => CanExecuteFunc?.Invoke(parameter) ?? true;
public void Execute(object parameter) => ExecuteAction?.Invoke(parameter);
}