2009-12-10 35 views
6

私は、datagridviewにバインドされているスレッドによるINotifyPropertyChanged

 BindingList<T> 

を持っています。私のクラスの1つのプロパティは計算に時間がかかるので、私はそのアクションをスレッド化しました。計算が終わると、OnPropertyChanged()イベントを発生させて、値が準備されていることをグリッドに通知します。

少なくとも、それは理論です。しかし、OnPropertyChangedメソッドはdifferendスレッドから呼び出されているので、グリッドのOnRowPrePaintメソッドでいくつかの例外が発生します。

誰かが私はどのようにOnPropertyChangedイベントをメインスレッドでexcecutedにするか教えていただけますか? MyClassクラスはWinformsアプリケーションで実行されていることを認識していないため、Form.Invokeを使用することはできません。

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について知らなかった。 これは、この作業をとても簡単にします。 –

1

の考察1:
はUIThreadMarshalクラスを見て、この記事では、その使用してください:
UI Thread Marshaling in the Model Layer
あなたはインスタンスに静的からクラスを変更して、オブジェクトにそれを注入することができます。したがって、あなたのオブジェクトはFormクラスについて知りません。 UIThreadMarshalクラスについてのみ認識します。

考察2:
あなたのプロパティから-1が返ってきたとは思いません。私にとっては悪いデザインのようだ。

考察3:
あなたのクラスでは、アンカースレッドを使用しないでください。たぶんそれはあなたのプロパティを呼び出す方法を決定する消費者クラスです:直接または別のスレッドで。この場合、IsSlowMemberInitializedのような追加のプロパティを用意する必要があるかもしれません。

+0

に1:ありがとうございます。この場合、BackgroundWorkerは私の問題を解決しましたが、近いうちに私はこれを必要とします。 2:そうです、特にSlowMemberは-1にすることができます。 3へ:DataGridViewは値を照会するため、値を更新し、INotifyPropertyChangedインターフェイスを使用して変更されたプロパティをDataGridviewに通知するよりも、最初に-1を取得します。 (私はタイマーを使用してIsSlowMemberInitialized = trueをチェックすることができますが、それは醜いものです とにかくthxがたくさんあります –

+0

DataGridViewを使用している場合、BindingSourceを使用する必要があります。 – nightcoder

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ですが、これは悪い設計を可能にするため、特定のシナリオでのみ使用する必要があると考えています。アクションが処理中に実際にコントロールを更新する必要があります。 –

6

人々は時々イベントハンドラがMultiCastDelegateであることを忘れて、など、私たちそれぞれの加入者に関するすべての情報を持っていますInvoke + Synchronizationパフォーマンスペナルティを不必要に課すことなく、この状況を適切に処理する必要があります。それは何

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); 
     } 
    } 
} 

は単純ですが、私はほとんどそれを行うための最善の方法を見つけようと当時私の脳を割れたことを覚えている:私は年齢のため、このようなコードを使用してきました。

まず、競合状態を回避するために、ローカルプロパティでイベントハンドラを「取得」します。

ハンドラがnullでない(少なくとも1つのサブスクライバが存在する)場合は、イベントargsを準備してから、このマルチキャストデリゲートの呼び出しリストを反復処理します。

呼び出しリストには、イベントのサブスクライバであるターゲットプロパティがあります。このサブスクライバがISynchronizeInvoke(すべてのUIコントロールが実装しています)を実装している場合、InvokeRequiredプロパティをチェックして、デリゲートとパラメータを渡すだけです。この方法で呼び出すと、呼び出しをUIスレッドに同期させます。

それ以外の場合は、イベントハンドラの委任を直接呼び出します。

+2

'System.InvalidCastException'を' System.EventHandler 'とタイプして' System.ComponentModel.PropertyChangedEventHandler '型のオブジェクトをキャストすることができませんでしたので、 'EventHandler'を' PropertyChangedEventHandler'にリネームしなければなりませんでした。 } ' イベントに内部的にサブスクライブするUIスレッドで作成されたBindingListがありますが、h.Targetがnullであるためsync変数は常にnullを返します。 –

+0

@RickShealerと同じ問題が発生しています。これが新しいバージョンの.Netで問題になっているのだろうと思っています。これは、クロススレッドのINotifyPropertyChangedの問題に対する非常に洗練されたソリューションのようです。 – Jacob

+0

@Jacob最新のフレームワークを対象にして、失敗したかどうかを確認します。プロジェクトのフレームワークのターゲットバージョンや関連すると思われるその他の情報を教えてください。 – Loudenvier

関連する問題