WPF数据网格“newitemplaceholderposition”不允许在由“addnew”开始的事务期间使用。

7
我有一个 WPF 表单上的 tabControl。
其中一个选项卡中,我有一个包含 DataGrid 的用户控件,该 DataGrid 具有 CanUserAddRows = "True"。用户可以在列中输入数据,并在按 [Enter] 键时创建新行。
问题在于,当我在新行中输入数据并切换选项卡时,会出现以下异常:"WPF datagrid 'newitemplaceholderposition' is not allowed during a transaction begun by 'Addnew' "
有什么建议可以避免这种情况吗?
我尝试在 usercontrol.Unloaded() 上放置 dg.CommitEdit()。我没有遇到异常,但也没有得到新行。
5个回答

3
我遇到了同样的问题。找到了两种可能的解决方法:
1/ 触发DataGrid的CommitEdit事件,然后调用CommitEdit。我不确定为什么需要这最后一步,你的情况下可能不需要调用CommitEdit。
        DataGrid.CommitEditCommand.Execute(this.DataGridWorkItems, this.DataGridWorkItems);

        yourDataGrid.CommitEdit(DataGridEditingUnit.Row, false);

2/ 模拟键盘上“返回”键的按下:

        var keyEventArgs = new KeyEventArgs(InputManager.Current.PrimaryKeyboardDevice,PresentationSource.FromDependencyObject(yourDataGrid), System.Environment.ProcessorCount, Key.Return);
        keyEventArgs.RoutedEvent = UIElement.KeyDownEvent;
        yourDataGrid.RaiseEvent(keyEventArgs);

我选择了最后一种解决方案,因为第一种方案有一些可疑的副作用。


3
我遇到了同样的问题...以下是一些描述我如何解决它的片段。请注意,在我的情况下,我想要拒绝更改以避免错误。如果你想提交更改,这可能会引导你朝着正确的方向前进。
1a) 在 datagrid 上使用 InitializingNewItem 事件来捕获添加行。
private void mydatagrid_InitializingNewItem(object sender, InitializingNewItemEventArgs e)
    {
        _viewmodel.NewRowDefaults((DataRowView)e.NewItem);
    }

1b) 在这种情况下,我调用我的视图模型中的一个方法来填充行默认值并保存对该行的引用。

    private DataRowView _drvAddingRow { get; set; }
    public void NewRowDefaults(DataRowView drv)
    {
        _drvAddingRow = drv;
        ...
    }

2) 如果需要在通知属性更改或其他情况之前拒绝更改,请使用捕获的DataRowView上的CancelEdit方法。

 _drvAddingRow.CancelEdit();

1
这对于“类型化”数据网格不起作用。当我从InitializingNewItemEventArgs捕获e.NewItem时,我得到的是绑定到网格的ObservableCollection中声明的实体,而不是DataRowView - Robert Harvey

2

很不幸,其他答案只在某些情况下解决了问题。例如,如果切换标签时其中一个单元格存在验证错误,则其他解决方案将失败。

问题在于当IsEnabled更改时,CanUserAddRows也会更改,这会触发NewItemPlaceholderPosition被重置。为了解决这个bug,我继承了DataGrid类并在CanUserAddRowsProperty的CoerceValueCallback中添加了一些逻辑。

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

这段代码的工作方式是,当IsEnabled被更新时,CanUserAddRows会改变并触发NewItemPlaceholderPosition的setter。通过在设置NewItemPlaceholderPosition之前调用CancelRowItem和UpdateNewItemPlaceholder,我们立即取消了事务(仅调用CancelEdit不足以实现)。将HasCellValidationError设置为false也有助于解决某些验证错误引起的一些边缘情况。


这个几乎完美地工作。唯一的问题是它会使DataGrid处于不可编辑状态。用户不能再对DataGrid进行任何编辑。 - Robert Harvey
之后如何重新启用DataGrid? - Robert Harvey

0

我曾经使用过Holmes的答案,但是对我来说并没有完全奏效。所以我做了一些修改。

这是我的解决方案:

首先,因为我正在使用MVVM,所以我将以下代码添加到数据网格中:

<i:Interaction.Triggers>
    <i:EventTrigger EventName="InitializingNewItem">
        <ei:CallMethodAction TargetObject="{Binding}" MethodName="OnDataGridInitializingNewItem"/>
    </i:EventTrigger>
</i:Interaction.Triggers>

命名空间是这些:

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"

然后,我将这段代码添加到ViewModel中并设置了DataGrid:

private DataGrid _dg { get; set; }

public void OnDataGridInitializingNewItem(object sender, InitializingNewItemEventArgs e)
{
    if (_dg == null)
        _dg = (DataGrid)sender;
}

毕竟,当需要时,我运行了这段代码:

_dg.CommitEdit();

终于它运行得非常好 :)

附注: 首先,我尝试了CancelEdit方法而不是CommitEdit。它起作用了,然后我进入了另一个弹出式视图。当我完成操作并返回到视图时,最后添加的行消失了。但它已经提交到数据库中了。重新打开视图后,它就在那里。


0

我曾遇到类似的问题,但是在我的情况下,网格被包含在一个 AdornerDecorator 中,移除它后,一切都正常了。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接