【问题标题】:WPF datagrid 'newitemplaceholderposition' is not allowed during a transaction begun by 'addnew'在“addnew”开始的事务期间不允许 WPF 数据网格“newitemplaceholderposition”
【发布时间】:2011-04-15 15:29:19
【问题描述】:

我在 WPF 表单上有一个 tabControl。

在其中一个选项卡项中,我有一个用户控件,其中包含一个具有CanUserAddRows="True" 的 DataGrid。用户可以在列中输入数据,当他们按下 [enter] 时,会创建一个新行。

问题是当我在新行中键入数据然后更改选项卡时,我得到了这个异常:“WPF datagrid 'newitemplaceholderposition' is not allowed during a transaction started by 'Addnew'”

有什么建议可以避免吗?

我尝试将dg.CommitEdit() 放在usercontrol.Unloaded() 上。我没有得到异常,但我也没有得到新行。

【问题讨论】:

    标签: wpf datagrid


    【解决方案1】:

    我遇到了同样的问题...这里有一些描述我如何解决它的 sn-ps。请注意,在我的情况下,我想拒绝更改以避免错误。如果您想提交更改,这可能会引导您朝着正确的方向前进。

    1a) 使用数据网格上的 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) 然后当您需要拒绝更改时(在通知属性更改或任何您的情况之前),在捕获的数据行视图上使用 CancelEdit 方法。

     _drvAddingRow.CancelEdit();
    

    【讨论】:

    • 这不适用于“类型化”数据网格。当我从 InitializingNewItemEventArgs 捕获 e.NewItem 时,我得到了在绑定到网格的 ObservableCollection 中声明的类型的实体,而不是 DataRowView
    【解决方案2】:

    我刚刚遇到了同样的问题。找到了两种可能的解决方法:

    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】:

      不幸的是,其他答案只能在某些情况下解决问题。例如,如果其中一个单元格在切换选项卡时出现验证错误,则其他解决方案将失败。

      问题在于,当 IsEnabled 发生更改时,CanUserAddRows 会发生更改并触发 NewItemPlaceholderPosition 被重置。为了解决这个错误,我继承了 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 的设置器。通过在设置 NewItemPlaceholderPosition 之前调用 CancelRowItem 和 UpdateNewItemPlaceholder,我们立即取消事务(调用 CancelEdit 是不够的)。将 HasCellValidationError 设置为 false 还有助于从出现验证错误时出现的一些极端情况中恢复。

      【讨论】:

      • 这几乎可以完美运行。唯一的问题是它使 DataGrid 处于不可编辑状态。用户不能再对 DataGrid 进行任何类型的编辑。
      • 之后如何重新启用 DataGrid?
      【解决方案4】:

      我使用了福尔摩斯的答案,但没有为我正常工作。所以我改变了一点。

      这是我的解决方案:

      首先,因为我使用的是 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();
      

      最后效果很好:)

      PS: 首先,我尝试了 CancelEdit 方法而不是 CommitEdit。它起作用了,我去了另一个像弹出窗口一样打开的视图。当我完成要做什么并返回视图时,最后添加的行已经消失了。但它已提交给数据库。重新打开视图后,它就在那里。

      【讨论】:

        【解决方案5】:

        我遇到了这样的问题,但在我的情况下,网格被包裹在 AdornerDecorator 中,移除它,一切正常

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2013-08-19
          • 1970-01-01
          • 1970-01-01
          • 2021-07-15
          • 2013-04-12
          • 2013-10-20
          • 2011-01-04
          相关资源
          最近更新 更多