不幸的是,其他答案只能在某些情况下解决问题。例如,如果其中一个单元格在切换选项卡时出现验证错误,则其他解决方案将失败。
问题在于,当 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 还有助于从出现验证错误时出现的一些极端情况中恢复。