I have a tabControl on a WPF form.
In one of the Tab Items I have a User Control that contains a DataGrid which has CanUserAddRows="True"
. The
I ran into the same problem...here are some snippets describing how I solved it. Note that in my case I wanted to reject the changes to avoid the error. If you want to commit the changes, this may lead you in the right direction.
1a) Use the InitializingNewItem event on the datagrid to capture the adding row.
private void mydatagrid_InitializingNewItem(object sender, InitializingNewItemEventArgs e)
{
_viewmodel.NewRowDefaults((DataRowView)e.NewItem);
}
1b) In this case, I'm calling a method in my view model to populate row defaults and save a reference to the row.
private DataRowView _drvAddingRow { get; set; }
public void NewRowDefaults(DataRowView drv)
{
_drvAddingRow = drv;
...
}
2) Then when you need to reject the change (before notifying property changes or whatever your case is), use the CancelEdit method on the captured datarowview.
_drvAddingRow.CancelEdit();
I have used holmes answer but didn't work for me properly. So I changed little bit.
Here is my solution:
First of all, because of I'm using MVVM, I added this codes to the datagrid:
<i:Interaction.Triggers>
<i:EventTrigger EventName="InitializingNewItem">
<ei:CallMethodAction TargetObject="{Binding}" MethodName="OnDataGridInitializingNewItem"/>
</i:EventTrigger>
</i:Interaction.Triggers>
Namespaces are these:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
Then, I added this code to the ViewModel and set the DataGrid:
private DataGrid _dg { get; set; }
public void OnDataGridInitializingNewItem(object sender, InitializingNewItemEventArgs e)
{
if (_dg == null)
_dg = (DataGrid)sender;
}
After all, when needed, I ran this code:
_dg.CommitEdit();
Finally it works very well :)
PS: First, I had tried CancelEdit method instead of CommitEdit. It worked and I went to another view that opened like pop-up. When I finished what to do and return to the view, last added row was gone. But it was committed to the db. After re-open the view, it was there.
Unfortunately, the other answers only solve the problem in some cases. For instance, if one of the cells has a validation error when switching tabs, the other solutions fail.
The problem is that when IsEnabled is changed, CanUserAddRows gets changed and that triggers NewItemPlaceholderPosition to be reset. To work around this bug, I inherited the DataGrid class and added some logic to the CoerceValueCallback of the CanUserAddRowsProperty.
namespace CustomControls
{
using System;
using System.ComponentModel;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using Utilities;
public class FixedDataGrid : DataGrid
{
static FixedDataGrid()
{
var originalPropertyChangedCallback = CanUserAddRowsProperty.GetMetadata(typeof(DataGrid)).PropertyChangedCallback;
var originalCoerceValueCallback = CanUserAddRowsProperty.GetMetadata(typeof(DataGrid)).CoerceValueCallback;
CanUserAddRowsProperty.OverrideMetadata(typeof(FixedDataGrid), new FrameworkPropertyMetadata(true,
originalPropertyChangedCallback,
(d, e) =>
{
var ths = ((FixedDataGrid) d);
// Fixes System.InvalidOperationException: 'NewItemPlaceholderPosition' is not allowed during a transaction begun by 'AddNew'.
if (ths.IsEnabled) return originalCoerceValueCallback(d, e);
if (!((IEditableCollectionViewAddNewItem) ths.Items).CanAddNewItem &&
!((IEditableCollectionViewAddNewItem) ths.Items).CanCancelEdit)
return originalCoerceValueCallback(d, e);
ths.CancelEdit();
ReflectionUtils.InvokeMethod(ths, "CancelRowItem");
ReflectionUtils.InvokeMethod(ths, "UpdateNewItemPlaceholder", false);
ReflectionUtils.SetProperty(ths, "HasCellValidationError", false);
CommandManager.InvalidateRequerySuggested();
return originalCoerceValueCallback(d, e);
}));
}
}
}
namespace Utilities
{
using System;
using System.Reflection;
public class ReflectionUtils
{
public static void InvokeMethod(object obj, string name, params object[] args)
{
InvokeMethod(obj, obj.GetType(), name, args);
}
public static void InvokeMethod(object obj, Type type, string name, params object[] args)
{
var method = type.GetMethod(name, BindingFlags.NonPublic | BindingFlags.Instance);
if (method == null)
{
if (type.BaseType == null)
throw new MissingMethodException($"Couldn't find method {name} in {type}");
InvokeMethod(obj, type.BaseType, name, args);
return;
}
method.Invoke(obj, args);
}
public static T InvokeMethod<T>(object obj, string name, params object[] args)
{
return InvokeMethod<T>(obj, obj.GetType(), name, args);
}
public static T InvokeMethod<T>(object obj, Type type, string name, params object[] args)
{
var method = type.GetMethod(name, BindingFlags.NonPublic | BindingFlags.Instance);
if (method == null)
{
if (type.BaseType == null)
throw new MissingMethodException($"Couldn't find method {name} in {type}");
return InvokeMethod<T>(obj, type.BaseType, name, args);
}
return (T) method.Invoke(obj, args);
}
public static T GetProperty<T>(object obj, string name)
{
return GetProperty<T>(obj, obj.GetType(), name);
}
public static T GetProperty<T>(object obj, Type type, string name)
{
var prop = type
.GetProperty(name, BindingFlags.NonPublic | BindingFlags.Instance);
if (prop == null)
{
if (type.BaseType == null)
throw new MissingMethodException($"Couldn't find property {name} in {type}");
return GetProperty<T>(obj, type.BaseType, name);
}
return (T) prop
.GetGetMethod(nonPublic: true).Invoke(obj, new object[] { });
}
public static void SetProperty<T>(object obj, string name, T val)
{
SetProperty(obj, obj.GetType(), name, val);
}
public static void SetProperty<T>(object obj, Type type, string name, T value)
{
var prop = type
.GetProperty(name, BindingFlags.NonPublic | BindingFlags.Instance);
if (prop == null)
{
if (type.BaseType == null)
throw new MissingMethodException($"Couldn't find property {name} in {type}");
SetProperty(obj, type.BaseType, name, value);
return;
}
prop.GetSetMethod(nonPublic: true).Invoke(obj, new object[] {value});
}
}
}
The way this code works is that when IsEnabled is updated, CanUserAddRows is changed and that triggers the setter of NewItemPlaceholderPosition. By calling CancelRowItem and UpdateNewItemPlaceholder before NewItemPlaceholderPosition is set, we cancel the transaction immediately (it is insufficient to call CancelEdit). Setting HasCellValidationError to false also helps recover from some corner cases that arise when you have validation errors.
I just ran into the same problem. Found two possible workarounds:
1/ Trigger the CommitEdit event of the DataGrid, then call CommitEdit. I'm not sure why this last step is needed, you may not have to call CommitEdit in your case.
DataGrid.CommitEditCommand.Execute(this.DataGridWorkItems, this.DataGridWorkItems);
yourDataGrid.CommitEdit(DataGridEditingUnit.Row, false);
2/ Simulate a stroke on the 'Return' key of the keyboard:
var keyEventArgs = new KeyEventArgs(InputManager.Current.PrimaryKeyboardDevice,PresentationSource.FromDependencyObject(yourDataGrid), System.Environment.ProcessorCount, Key.Return);
keyEventArgs.RoutedEvent = UIElement.KeyDownEvent;
yourDataGrid.RaiseEvent(keyEventArgs);
I settled for the last solution, since I had a few fishy side effects with the first one.
I had such a problem, but in my case the Grid was wrapped in an AdornerDecorator, removing it, everything worked