2011-11-12 48 views
4

现状线程和WPF的绑定

我让我的应用程序下面的见异思迁的行为:在约20处决一,这势必给DataTable一个WPFToolkit的DataGrid不会使所有行,在预期的整个4行之间缺少1到3之间的任何东西。

内部工作

  • DataGrid绑定到DataTableD1,它是一个自定义的类的属性,C1
  • 当用户激活视图时,我们必须从后端检索数据,这可能需要时间。为此,我们创建一个线程(实际上,我们使用BackgroundWorker来表示,但似乎与使用其中一个没有什么区别),它运行一个方法,即打开连接并请求数据的方法,即M1。该线程用于避免出现无响应的应用程序。
  • M1首先检索数据并将其存储在DTO中。之后,他要求C1清除它的表格。 C1这样做(通过调用D1.Clear())并提出NotifyPropertyChanged()(来自线程)。
  • M1将新后端的DataTable传递给C1,它将行逐行插入D1。插完行后,C1增加NotifyPropertyChanged()。线程退出。

所以,换句话说,我清理表格,通知WPF,插入数据,通知WPF并退出。

在我看来,只要最后一次Notify从UI正确使用,就应该总是显示所有行。

除了DataTable之外,还有大量属性(主要是字符串和整数)被更新并通知。我们有而不是在任何其他情况下观察到此行为,只能使用DataTable

我知道这很深入到WPF机制的绑定,但我希望任何人都可以在这里阐明。 任何欢迎使用WPF绑定或使用WPF多线程的信息。

+0

当M1要求C1对D1进行更改时,是否调度到UI线程? –

+0

@KentBoogaart:不!这些更改是在线程本身中进行的。但是,更改完成后,C1会引发NotifyPropertyChanged。我相信这应该足以保证使用正确的内容刷新UI。这是真的吗? –

回答

3

DataTable预设日期WPF因此不会执行INotifyCollectionChanged这是WPF如何监视收集更改。您有两种选择:

  1. 用新的DataTable替换现有的DataTable(设置行后)。然后启动属性更改通知。
  2. 从DataTable更改为ObservableCollection。每当您更改项目列表时,该集合都会触发更改通知。 (注意:如果你改变了项目之一的内容已经在列表中它不会闪光)

INotifyPropertyChanged的通知时,该属性已经改变,而不是在内部状态(无论是属性或集合)已改变。当您触发Property Changed事件时,如果该属性是与上次绑定数据不同的对象,则仅重新绑定控件。当你只在一个对象图中改变几个图层的一个属性时,这使它不会刷新整个屏幕。

+0

大卫,我只是通知* *后,我已经加载行。在添加期间,我不需要通知屏幕,因为应用程序仅从用户刺激中检索数据。无论如何,你说如果WPF认为数据不同,那么它只会更新屏幕。他是怎么做到的? –

+0

@BrunoBrant,WPF通过保持对已绑定数据的引用并将其与新引用进行比较来了解数据是否不同。你可以阅读更多关于它[这里](http://www.lhotka.net/weblog/CommentView,guid,06f305de-ec32-4e20-b042-171b58f305ae.aspx)和[这里](http://kentb.blogspot .COM/2007/03 /提防-的datacontext和-equals.html)。还有一个选项,我忘了提及。在触发第一个PropertyChanged通知之前将实际属性设置为null,然后在触发最后一个通知之前将其设置回数据表。 – David

+0

但我不改变DataContext,我只是更新对象的一个​​属性。 Equal()是否仍然用于确定更改?我的DataContext不是DataTable,而是一个属性是DataTable的对象。 –

2

是否将新数据加载到相同已经绑定到DataGrid的DataTable实例? (a)每当你从后台代码改变DataTable时,它会从错误的线程中发出通知,这是一个不允许的;和(b)在最后触发PropertyChanged时,DataGrid可能足够聪明,可以注意到引用并没有真正改变,所以它不需要做任何事情。 (我不知道DataGrid是否试图变得聪明,但它不会不合理 - 尤其是考虑到WPF constructs views on top of collections的方式 - 它可能有助于解释您所看到的症状。)

尝试在每次需要刷新时创建一个新的DataTable实例,然后在完成后台线程中的实例填充后,将新的(完全填充的)引用分配到您的通知属性中并触发PropertyChanged (当然,确保从UI线程完成赋值+ PropertyChanged)。

+0

Joe,为什么我需要从UI Thread执行此操作? PropertyChanged,AFAIC已经使用一个调度器来做到这一点... –

+0

@BrunoBrant,它看起来像PropertyChanged通知确实得到了整个线程的封送,但我没有看到说它有保证的文档,并且我看到了MVVM框架自己执行跨线程通知。所以我不确定有多少信任它。如果你看到奇怪的行为,我倾向于从消除所有未知的事物开始。 –

+0

感谢您的提示。我现在正在编组它,它似乎能够解决问题......但到目前为止,我只用Invoke而不是BeginInvoke进行了测试,这使我怀疑,也许,通过使用调用,我最小化了发生的错误... –

1
  1. 不是直接绑定DataTable,而是直接绑定表的DataView。该表的视图版本DataView具有ListChanged和DataRowView具有PropertyChanged
  2. WPF确实支持更新直到行级。如果你改变一行值,它肯定会立即传播。
  3. PropertyChanged不是线程安全的。您不能在其他线程上触发PropertyChanged进行任何更改。它必须在调度员上完成,所以变更通过调度员。例如, 例如,应该使用Dispatcher.Invoke(new Action(model => model.Data = newData), Model)或类似的代替Model.Data = newData
+0

感谢您对Asti的反馈。我在这篇文章(http://blog.lab49.com/archives/1166)中看到ClrBindingWorker已经整理了线程之间的操作......这有意义吗?我通过Invoke对它进行编组,并且它有帮助(现在,我有一个内存泄漏,似乎是由我最近的变化引发的 - 引入了调用 - 但我还没有确认它)。 –

+0

尽管使用调度程序不太可能发生泄漏。 – Asti

+0

阿斯蒂,我确切的想法。我正在重新测试应用程序而不进行任何更改,以确保泄漏不是由调度程序调用引入的,并且已经存在于应用程序中。虽然这很难测试,因为在泄漏出现之前需要连续工作大约18个小时(大约8000个连续呼叫)。 –

2

基于Asti的第三点,我经常遇到一个跨线程PropertyChanged场景,并且有一个基本视图模型。视图模型基于PRISM NotificationObject,但当然,如果您不想使用PRISM,则可以直接实现INotifyPropertyChanged界面。如果您曾经使用过Silverlight,那么也适用于Silverlight。

namespace WPF.ViewModel 
{ 
    using System.Windows; 
    using System.Windows.Threading; 

    using Microsoft.Practices.Prism.ViewModel; 

    /// <summary>The async notification object.</summary> 
    public abstract class AsyncNotificationObject : NotificationObject 
    { 
     #region Constructors and Destructors 

     /// <summary>Initializes a new instance of the <see cref="AsyncNotificationObject"/> class.</summary> 
     protected AsyncNotificationObject() 
     { 
      Dispatcher = Application.Current.Dispatcher; 
     } 

     #endregion 

     #region Properties 

     /// <summary>Gets or sets Dispatcher.</summary> 
     protected Dispatcher Dispatcher { get; set; } 

     #endregion 

     #region Methods 

     /// <summary>The raise property changed.</summary> 
     /// <param name="propertyName">The property name.</param> 
     protected override void RaisePropertyChanged(string propertyName) 
     { 
      if (Dispatcher.CheckAccess()) base.RaisePropertyChanged(propertyName); 
      else Dispatcher.BeginInvoke(() => base.RaisePropertyChanged(propertyName)); 
     } 

     #endregion 
    } 
}