【问题标题】:Dependency Property Cross-Contamination in DataGridDataGrid中的依赖属性交叉污染
【发布时间】:2016-05-04 19:42:23
【问题描述】:

我有一个绑定到集合的 WPF DataGrid。 DataGrid 的列之一是包含我的自定义控件的 DataGridTemplateColumn:

<DataGridTemplateColumn Header="$ Diff" Width="60*">
  <DataGridTemplateColumn.CellTemplate>
    <DataTemplate>
      <ui:BoardValueDifference Board1="{Binding Board1}" Board2="{Binding Board2}"/>
    </DataTemplate>
  </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

我的自定义控件有两个依赖属性,Board1 和 Board2,非常标准的东西。

public BoardValueDifference()
{
    InitializeComponent();
}

public static readonly DependencyProperty Board1Property = DependencyProperty.Register("Board1", typeof(PackageBoard),
        typeof(BoardValueDifference), new FrameworkPropertyMetadata(OnDataChanged));

public PackageBoard Board1
{
    get { return (PackageBoard)GetValue(Board1Property); }
    set { SetValue(Board1Property, value); }
}

protected static void OnDataChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
{
    BoardValueDifference bvd = d as BoardValueDifference;

    if ( bvd != null )
        bvd.CalculateDifference();
}

protected void CalculateDifference()
{
    if ( Board1 != null && Board2 != null) {
        // Calculate value difference between boards
    }
}

我的问题是 Board1 和 Board2 似乎是不确定的,我正在从其他行获取值。如果我在 OnDataChanged() 中设置断点,我会看到它被 Board1 的数据绑定击中,然后被 Board2 的数据绑定击中,但在 Board2 之间会有来自其他行的值,如果我向上和向下滚动我的 DataGrid,这尤其糟糕。

从另一个问题中,我找到了这个链接,Collection-Type Dependency Properties,它不是针对我的情况,但看起来很相似。他们建议在构造函数中使用 SetValue() 并将属性的默认值设置为新实例。我尝试将其设置为 null,但它似乎完全禁用了我的数据绑定。

SetValue(Board1Property, null);

我的猜测是,因为 DependencyProperties 是静态的,并且因为我的 DependencyProperty 是一个类,所以我的 BoardValueDifference 用户控件的实例之间存在某种共享。我该如何解决?我认为它可能类似于this question,但我对回复的理解不足以解决我的问题。

更新

在我的 UserControl 添加一些日志记录后,我想我明白了发生了什么:

  1. 我曾假设DataGrid 的每一行都会获得我的UserControl 的一个新实例,而Board1 和Board2 将从null 开始。根据我的日志,BoardValueDifference 被重新使用,只是更改了 DependencyProperties。
  2. 在我的代码中,当 Board1 和 Board2 不再为空时,我正在计算板之间的差异。我意识到我现在不能这样做,但是在我的 UserControl 中有剩余的数据感觉非常草率。我猜你得把它想成一个静态类?

底线,我可以解决这个问题,但如果有人有更好的解决方法或确切知道 DataGrid 中 UserControl 的生命周期应该如何工作,我将不胜感激。

【问题讨论】:

  • 为什么使用PropertyDescriptor.AddValueChanged而不是直接注册一个PropertyChangedCallback 将PropertyMetadata传递给DependencyProperty.Register
  • 没有具体原因,我在尝试解决此错误时曾在两种方法之间切换并没有返回。如果我没记错的话我在这里的方式can lead to a memory leak?
  • 使用 PropertyChangedCallback。这另一种方法很奇怪。
  • 为了让你开心而改变,但与根本问题无关,无论哪种方式都是同样的问题。
  • 还有一点,Board1 和 Board2 绑定上的UpdateSourceTrigger=PropertyChanged 没有意义。它只对双向绑定有效。

标签: c# wpf dependency-properties


【解决方案1】:

由于行虚拟化,您的用户控件被回收。如果您的 DataGrid 中没有太多行,您可以通过将 EnableRowVirtualization 属性设置为 false 来禁用此行为。

来自DataGrid.EnableRowVirtualization 页面上的备注部分:

为了提高性能,EnableRowVirtualization 属性设置为 默认情况下为真。当 EnableRowVirtualization 属性设置为 true,DataGrid 不会为每个实例实例化 DataGridRow 对象 绑定数据源中的数据项。相反,DataGrid 创建 仅在需要时使用 DataGridRow 对象,并尽可能多地重用它们 尽其所能。例如,DataGrid 创建一个 DataGridRow 对象 当前在视图中的每个数据项,并在它出现时回收该行 滚动到视野之外。

【讨论】:

  • 不幸的是我有超过 3,000 行并且需要虚拟化,你知道是否有办法让它在交换到新行时使 DependencyProperty 无效?我已经尝试将 FrameworkPropertyMetadata 上的默认值设置为 null,这似乎没有任何区别。
  • 您可以尝试在回收 UserControl 时是否触发 Unloaded 事件。另一种方法可能是只有一个属性已经拥有 Board1 和 Board2 之间的差异。您可以通过 MultiBinding 将其绑定到 Board1 和 Board2 源属性,其中包含计算差异的多值转换器。
猜你喜欢
  • 2019-12-22
  • 1970-01-01
  • 1970-01-01
  • 2015-11-29
  • 2019-07-11
  • 2013-07-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多