WPF datagrid 'newitemplaceholderposition' is not allowed during a transaction begun by 'addnew'

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 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();

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.

Tags:

Wpf

Datagrid