WPF DataGrid在IEditableObject上调用BeginEdit两次?

18
我有一个DataGrid,绑定了一组IEditableObject对象。现在当我在一个单元格中单击两次时,它会被打开以进行编辑。有趣的是:BeginEdit方法会被调用两次。有时是为同一个可编辑对象,但有时为集合中的两个不同对象(特别是当我使用PgDn滚动到DataGrid的末尾时),第一个调用正确,然后是从未聚焦过的某个其他项。EndEdit方法也被调用两次,但始终针对所选项目,而不是错误的项目。这是已知的问题吗?是否有解决办法只接收(正确的)一个通知?

听起来很有趣,所以我检查了我的DataGrid,它也绑定到一个ObservableCollection<IEditableObject>,但没有遇到这个问题...?我的表格每行都是一个对象,你是否合并了实现IEditableObject或类似接口的两个对象? - Aaron McIver
有趣。我的对象只实现了IEditableObject和INotifyPropertyChanged,所以没有合并。不过我的ObservableCollection被包装在一个自定义的ListCollectionView中。而且我将列绑定到了我的IEditableObject类中的属性,而不是直接绑定到IEditableObject的属性。至少现在我有理由检查我的源代码了,谢谢! - Sam
MSDN的示例总是包含一个标志,防止BeginEdit在除第一次以外的任何时候被调用时执行其操作。根据我的经验,它确实会被多次调用。 - peterG
4个回答

24

如果在调试器中查看堆栈跟踪,当调用BeginEdit时,第一次调用它的是集合视图,第二次则是BindingGroup

问题似乎是有两个东西都认为它们控制了IEditableObject的状态。当WPF提供默认的集合视图时,它会查找集合中的对象上是否有IEditableObject,并且会响应于相应的IEditableCollectionView方法的调用来调用BeginEdit和要么EndEditCancelEdit。但是,BindingGroup也将响应于对BeginEditCommitEditCancelEdit的调用而调用IEditableObject方法。

DataGrid同时使用了这两个功能:当你开始编辑并完成行编辑时,它会通知IEditableCollectionView BindingGroup,而这两件事情都认为接下来他们的职责是通知潜在源对象上的IEditableObject实现。

因此,这看起来很像一个DataGrid中的错误——它导致两个不同的对象调用BeginEdit(和相关的方法)。这是因为它利用可编辑的集合视图和绑定组——从外观上看,那些设计并不是为了在同一对象上同时使用而设计的,就像DataGrid使用它们一样。

你在工具包中看不到这个问题的原因是它似乎是一个稍旧版本的 - 对比一下工具包中的代码和Reflector显示的.NET 4.0,你会发现.NET 4.0 DataGrid 有一些额外的代码(一个新的方法EnsureItemBindingGroup,以及在MeasureOverrideOnRowValidationRulesChanged中的相关代码),可以确保绑定组始终存在,无论你是否请求。因此,如果WPF Toolkit更新了,它可能会增加类似的功能,除非这个问题得到修复。(我猜想,如果你使用当前版本-即我编写本文时的2023年3月 - 的WPF Toolkit,并且使用ItemBindingGroup属性显式地请求绑定组,那么你可能会看到完全相同的问题)。

这并没有解释你如何获得随机对象上的BeginEdit调用,就像你所描述的那样。我无法复制这个问题。但它确实解释了选定对象上的双重调用。最好的做法似乎是编写源对象,使其能够容忍双重调用。


4
我不确定在BeginEdit事件发生之前应该使用什么来中断它,但对于EndEdit,一个简单的isDirty标记就足够了。在实现IEditableObject的实体类中,添加以下内容:
    private bool _isDirty = false;

    #region IEditableObject Members

    public void BeginEdit()
    {
        // Bug Fix: Windows Controls call EndEdit twice; Once
        // from IEditableCollectionView, and once from BindingGroup.
        // This makes sure it only happens once after a BeginEdit.
        _isDirty = true;
    }

    public void CancelEdit() { }

    public void EndEdit()
    {
        if (ItemEndEdit != null && _isDirty)
        {
            _isDirty = false;
            ItemEndEdit(this);
        }
    }

    #endregion

在CancelEdit方法中,您应该将_isDirty设置为false。 - white.zaz

3

我使用.NET Framework 4数据网格时遇到了同样的问题。

将引用添加到最新版本的WPFToolkit。

添加

xmlns:dg="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit"

<DataGrid>替换为<dg:DataGrid>


酷,有趣。这是不同的版本吗?还是为什么它可以解决问题? - Sam

1

+1 @IanGriffiths 诊断了问题。至于解决方案(或变通方法),您可以计算“待处理”编辑的数量。这意味着类似于:

void BeginEdit()
{
    _numEdits++;
}

void CancelEdit()
{
    if(--_numEdits < 0)
        throw new Exception("WTF?"); 
}

void EndEdit()
{
    CancelEdit();
    if(_numEdits == 0)
        commitEdit();
}

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