2009-12-10 125 views
6

INotifyPropertyChanged的我有一个有螺纹

BindingList<T> 

它绑定到一个DataGridView。我班的一个房产需要很长的时间来计算,所以我对这个行为进行了调整。计算后,我提出OnPropertyChanged()事件来通知网格该值已准备就绪。

至少,这是理论。但是由于OnPropertyChanged方法是从一个不同的线程调用的,所以我在网格的OnRowPrePaint方法中得到了一些有意识的异常。

任何人都可以告诉我如何在主线程中执行OnPropertyChanged事件吗?我不能使用Form.Invoke,因为类MyClass不知道它在Winforms应用程序中运行。

public class MyClass : INotifyPropertyChanged 
{ 
    public int FastMember {get;set;} 

    private int? slowMember; 
    public SlowMember 
    { 
     get 
     { 
      if (slowMember.HasValue) 
       return slowMember.Value; 
      else 
      { 
       Thread t = new Thread(getSlowMember); 
       t.Start(); 
       return -1; 
      } 

     } 
    } 

    private void getSlowMember() 
    { 
     Thread.Sleep(1000); 
     slowMember = 5; 
     OnPropertyChanged("SlowMember"); 
    } 

    public event PropertyChangedEventHandler PropertyChanged; 
    private void OnPropertyChanged(string propertyName) 
    { 
     PropertyChangingEventHandler eh = PropertyChanging; 
     if (eh != null) 
     { 
      eh(this, e); 
     } 
    } 

} 

回答

8

通过设计,控件只能通过它创建的线程来更新。这就是为什么你会收到异常。

考虑使用BackgroundWorker,并且只有通过订阅事件处理程序到RunWorkerCompleted才能在持久操作完成后更新成员。

+0

工程就像一个魅力。直到现在我还不知道BackgroundWorker。 这使得这个任务如此简单,thx很多。 – 2009-12-10 12:59:45

1

考虑1:
看看UIThreadMarshal类及其用法在这篇文章中:
UI Thread Marshaling in the Model Layer
您可以从静态类更改为实例,并将其注入到你的对象。所以你的对象不会知道Form类。它只会知道关于UIThreadMarshal类。

代价2:
我不认为从您的财产返回-1是个好主意。这对我来说看起来不太合适。

考虑3:
也许你的班级不应该使用antoher线程。也许是消费者类应该决定如何调用你的财产:直接或在一个单独的线程。在这种情况下,您可能需要提供其他属性,例如IsSlowMemberInitialized。

+0

致1:感谢您的链接。 BackgroundWorker在这种情况下解决了我的问题,但我敢肯定我的短裤在不久的将来我需要这个。 2:你说得对,尤其是因为SlowMember可以是-1。仅用于测试 至3:不可能,因为DataGridView查询值(并且首次获取-1,比我更新该值并使用INotifyPropertyChanged接口通知datagridview更改了属性,必须(确定我可以使用Timer并检查IsSlowMemberInitialized = true,但这很难看) 无论如何thx很多 – 2009-12-11 14:16:57

+0

如果你使用DataGridView,那么你可能需要使用BindingSource。在我给你的链接中,有一个支持BindingSource的实现,它支持来自不同线程的绑定,你可以使用该代码来更好地满足你的需求。 – nightcoder 2009-12-11 22:07:16

2

这是我前一段时间写的东西;它应该工作合理不错,但要注意很多更新的成本...

using System.ComponentModel; 
using System.Threading; 
public class ThreadedBindingList<T> : BindingList<T> { 
    SynchronizationContext ctx = SynchronizationContext.Current; 
    protected override void OnAddingNew(AddingNewEventArgs e) { 
     if (ctx == null) { BaseAddingNew(e); } 
     else { ctx.Send(delegate { BaseAddingNew(e); }, null); } 
    } 
    protected override void OnListChanged(ListChangedEventArgs e) { 
     if (ctx == null) { BaseListChanged(e); } 
     else { ctx.Send(delegate { BaseListChanged(e); }, null); } 
    } 
    void BaseListChanged(ListChangedEventArgs e) { base.OnListChanged(e); } 
    void BaseAddingNew(AddingNewEventArgs e) { base.OnAddingNew(e); } 
} 
+0

有趣的实现Marc,但是由于这允许糟糕的设计,我认为它应该只用于某些场景在处理动作时,实际需要控制权来更新。 – 2009-12-10 13:13:21

6

人们有时会忘记该事件处理程序是MultiCastDelegate并且,正因为如此,有所有关于每个用户的信息,我们需要适度地处理这种情况,而不会不必要地强加“调用+同步”性能损失。我一直在使用这样的代码适用年龄:

using System.ComponentModel; 
// ... 

public event PropertyChangedEventHandler PropertyChanged; 

protected virtual void OnPropertyChanged(string propertyName) 
{ 
    var handler = PropertyChanged; 
    if (handler != null) 
    { 
     var e = new PropertyChangedEventArgs(propertyName); 
     foreach (EventHandler h in handler.GetInvocationList()) 
     { 
      var synch = h.Target as ISynchronizeInvoke; 
      if (synch != null && synch.InvokeRequired) 
       synch.Invoke(h, new object[] { this, e }); 
      else 
       h(this, e); 
     } 
    } 
} 

它能做什么是简单的,但我记得我几乎打断了我的脑回,然后试图找到做到这一点的最好办法。

它首先“抓住”本地属性上的事件处理函数以避免任何竞争条件。

如果处理程序不为空(至少有一个订户确实存在),它将准备事件参数,然后遍历此多播委托的调用列表。

调用列表具有目标属性,它是事件的订阅者。如果这个订阅者实现了ISynchronizeInvoke(所有的UI控件实现它),我们然后检查它的InvokeRequired属性,我们只是调用它来传递委托和参数。以这种方式调用它会将调用同步到UI线程中。

否则,我们直接调用事件处理程序委托。

+2

我不得不将'EventHandler'重命名为'PropertyChangedEventHandler',因为我得到了一个'System.InvalidCastException',其详细信息为{{“无法强制类型为'System.ComponentModel.PropertyChangedEventHandler'的对象来键入'System.EventHandler'。” }' 我有一个BindingList是在订阅内部事件的UI线程中创建的,但同步变量总是返回null,因为h.Target为null。 – 2016-08-23 00:26:04

+0

我遇到了与@RickShealer相同的问题。注意到日期我想知道这是否是新版本.Net的问题?这对于交叉线程INotifyPropertyChanged问题来说似乎是一个非常优雅的解决方案,所以我希望我们能够使其发挥作用。 – Jacob 2016-12-20 17:40:48

+0

@Jacob我会为新的框架定位,看它是否失败。你告诉我你的项目的框架目标版本或任何其他信息,你可能认为是相关的? – Loudenvier 2016-12-20 18:35:29