2012-04-14 16 views
5

ここに私のシナリオがあります:INotifyPropertyChangedによってスレッド間エラーが発生する

私はBindingListにバインドされたGridControlを持っています。最初に私が何をやっていた時にワーカースレッドを作成し、直接するBindingListにアクセスするが、これは、「検出されたクロススレッド操作」を投げていたので、私はここで、ガイドに従った:

http://www.devexpress.com/Support/Center/p/AK2981.aspx

元をクローニングすることにより、 BindingListをワーカースレッドに追加し、それを変更すると、私は望みの効果を得ました。しかし、私は最近、BindingListに保持されているオブジェクトにINotifyPropertyChangedを実装しました。私は再びエラーを取得し始めました。

私の推測では、GridViewはオブジェクトからINotifyPropertyChangedを引き続きリッスンしています。

どうすればこの問題を解決できますか?

私のクラス:

public class Proxy : INotifyPropertyChanged 
{ 
    public event PropertyChangedEventHandler PropertyChanged; 

    protected void OnPropertyChanged(string name) 
    { 
     PropertyChangedEventHandler handler = PropertyChanged; 
     if (handler != null) 
     { 
      handler(this, new PropertyChangedEventArgs(name)); 
     } 
    } 

答えて

10

あなたは(そのようなワーカースレッドからなど)UIスレッドの外からUIを操作している場合、あなたは、UIスレッドに再加入する必要があります。これを行うには、UIコントロールでInvokeを呼び出します。 InvokeRequiredを使用して、これが必要かどうかをテストできます。

一般的に使用されるパターンはこれです:

public void ChangeText(string text) 
{ 
    if(this.InvokeRequired) 
    { 
     this.Invoke(new Action(() => ChangeText(text))); 
    } 
    else 
    { 
     label.Text = text; 
    } 
} 

あなたのケースではUIがINotifyPropertyChangedの結果として操作されているので、あなたはあなたが常に(UIスレッド上で、あなたのエンティティを変更するいずれかのことを確認する必要があります上記の手法を使用して)、またはgeneric asynchronous INotifyPropertyChanged helperを使用してください。これはバインドされているアイテムのラッパーです。上記のテクニックを使用して、UIスレッド上でChangePropertyイベントの発生を確実にします。

ここには、Entityクラスのプロキシの非常に粗い例があります。これにより、プロパティ変更イベントがUIスレッドに再結合され、エンティティ自体が変更されないようになります。明らかに、DynamicObjectを使用してより一般的にこれを実装したいと思うでしょう。

public class NotificationHelper : INotifyPropertyChanged 
{ 
    public event PropertyChangedEventHandler PropertyChanged; 

    private readonly ISynchronizeInvoke invokeDelegate; 
    private readonly Entity entity; 

    public NotificationHelper(ISynchronizeInvoke invokeDelegate, Entity entity) 
    { 
     this.invokeDelegate = invokeDelegate; 
     this.entity = entity; 

     entity.PropertyChanged += OnPropertyChanged; 
    } 

    public string Name 
    { 
     get { return entity.Name; } 
    } 

    private void OnPropertyChanged(object sender, PropertyChangedEventArgs e) 
    { 
     if (PropertyChanged != null) 
     { 
      if (invokeDelegate.InvokeRequired) 
      { 
       invokeDelegate.Invoke(new PropertyChangedEventHandler(OnPropertyChanged), 
            new[] { sender, e }); 
       return; 
      } 
      PropertyChanged(this, e); 
     } 
    } 
} 
+0

hmm ....だから私はこれをINotifyPropertyChangedイベントに配置する必要がありますか?私はクラスコードで質問を更新しました。 – TheGateKeeper

+0

UIスレッドのバインドされたオブジェクトのみを変更するか、バインディング時にヘルパークラスにラップするかのどちらかを明確にするように更新しました。 – TheCodeKing

+0

これを使用しませんでしたが、多くの詳細を提供するので答えとしてマークしました。 – TheGateKeeper

1

誰かが同じ問題に遭遇した場合は...私は数時間後にそれを修正することができました。ここに私がしたことがあります:

基本的に問題は、INotifyPropertyChangedを実装しているオブジェクトがワーカースレッドに居住していて、UIスレッドにアクセスするときに問題が発生することでした。

私がしたことは、INotifyPropertyChangedオブジェクトに更新する必要のあるオブジェクトへの参照を渡してから、そのオブジェクトに対してinvokeを使用することでした。ここで

はそれがどのように見えるかです:スレッドから

public event PropertyChangedEventHandler PropertyChanged; 

    protected void OnPropertyChanged(string name) 
    { 
     PropertyChangedEventHandler handler = PropertyChanged; 
     if (handler != null) 
     { 
      //If the Proxy object is living in a non-UI thread, use invoke 
      if (c != null) 
      { 
       c.BeginInvoke(new Action(() => handler(this, new PropertyChangedEventArgs(name)))); 
      } 
      //Otherwise update directly 
      else 
      { 
       handler(this, new PropertyChangedEventArgs(name)); 
      } 

     } 
    } 

    //Use this to reference the object on the UI thread when there is need to 
    public Control C 
    { 
     set { c = value; } 
    } 

、私がしたすべてはだった:

    prox.c = this; 
        //Logic here 
        prox.c = null; 

・ホープこれは誰かに役立ちます!

+2

これは、ビジネスオブジェクトがUIへの参照を持つという意味で、少し汚れています。ラッパークラスを使う方がいいです、他の答えのリンクを見てください。 – TheCodeKing

+0

ありがとう、しかし、私はあなたがまだ一方向または他の方法でオブジェクトへの参照を渡す必要があると思う。 'this.Invoke(new Action(()=> ChangeText(text)));例えば、' this'が実際のコントロールであることが必要です。 – TheGateKeeper

+0

私は非常にスレッドの新しいですので、これはすべて私にとって非常に新しいです、私はあなたがリンクしたコードを理解することはできません。私は今のところ私の道に固執すると思う。 – TheGateKeeper

2

私はTheGateKeeperの最終的な解決策に似たアプローチを採用しました。しかし、私は多くの異なるオブジェクトにバインドしていました。だから私はもう少し一般的なものが必要でした。解決策は、ICustomTypeDescriptorも実装したラッパーを作成することでした。このようにして、UIに表示できるすべてのラッパープロパティを作成する必要はありません。

public class SynchronizedNotifyPropertyChanged<T> : INotifyPropertyChanged, ICustomTypeDescriptor 
    where T : INotifyPropertyChanged 
{ 
    private readonly T _source; 
    private readonly ISynchronizeInvoke _syncObject; 

    public SynchronizedNotifyPropertyChanged(T source, ISynchronizeInvoke syncObject) 
    { 
     _source = source; 
     _syncObject = syncObject; 

     _source.PropertyChanged += (sender, args) => OnPropertyChanged(args.PropertyName); 
    } 

    public event PropertyChangedEventHandler PropertyChanged; 
    protected virtual void OnPropertyChanged(string propertyName) 
    { 
     if (PropertyChanged == null) return; 

     var handler = PropertyChanged; 
     _syncObject.BeginInvoke(handler, new object[] { this, new PropertyChangedEventArgs(propertyName) }); 
    } 

    public T Source { get { return _source; }} 

    #region ICustomTypeDescriptor 
    public AttributeCollection GetAttributes() 
    { 
     return new AttributeCollection(null); 
    } 

    public string GetClassName() 
    { 
     return TypeDescriptor.GetClassName(typeof(T)); 
    } 

    public string GetComponentName() 
    { 
     return TypeDescriptor.GetComponentName(typeof (T)); 
    } 

    public TypeConverter GetConverter() 
    { 
     return TypeDescriptor.GetConverter(typeof (T)); 
    } 

    public EventDescriptor GetDefaultEvent() 
    { 
     return TypeDescriptor.GetDefaultEvent(typeof (T)); 
    } 

    public PropertyDescriptor GetDefaultProperty() 
    { 
     return TypeDescriptor.GetDefaultProperty(typeof(T)); 
    } 

    public object GetEditor(Type editorBaseType) 
    { 
     return TypeDescriptor.GetEditor(typeof (T), editorBaseType); 
    } 

    public EventDescriptorCollection GetEvents() 
    { 
     return TypeDescriptor.GetEvents(typeof(T)); 
    } 

    public EventDescriptorCollection GetEvents(Attribute[] attributes) 
    { 
     return TypeDescriptor.GetEvents(typeof (T), attributes); 
    } 

    public PropertyDescriptorCollection GetProperties() 
    { 
     return TypeDescriptor.GetProperties(typeof (T)); 
    } 

    public PropertyDescriptorCollection GetProperties(Attribute[] attributes) 
    { 
     return TypeDescriptor.GetProperties(typeof(T), attributes); 
    } 

    public object GetPropertyOwner(PropertyDescriptor pd) 
    { 
     return _source; 
    } 
    #endregion ICustomTypeDescriptor 
} 

が続いUIで、私のようなものを使用して、このラッパーにバインド:

private void CreateBindings() 
    { 
     if (_model == null) return; 

     var threadSafeModel = new SynchronizedNotifyPropertyChanged<MyViewModel>(_model, this); 

     directiveLabel.DataBindings.Add("Text", threadSafeModel, "DirectiveText", false, DataSourceUpdateMode.OnPropertyChanged); 
    } 

MyViewModelは「DirectiveText」プロパティを持っており、スレッディングするために特別な考慮してか、ビュークラスにINotifyPropertyChangedのを実装します。

0

サブクラスBindingListですので、必要な場合はInvokeを確認できました。この方法では、ビジネスオブジェクトにはUIへの参照がありません。

public class InvokingBindingList<T> : BindingList<T> 
{ 
    public InvokingBindingList(IList<T> list, Control control = null) : base(list) 
    { 
    this.Control = control; 
    } 

    public InvokingBindingList(Control control = null) 
    { 
    this.Control = control; 
    } 

    public Control Control { get; set; } 

    protected override void OnListChanged(ListChangedEventArgs e) 
    { 
    if (Control?.InvokeRequired == true) 
     Control.Invoke(new Action(() => base.OnListChanged(e))); 
    else 
     base.OnListChanged(e); 
    } 
} 
関連する問題