7

別の (非 GUI) スレッドによって変更されたオブジェクトに WinForms コントロールをバインドするための動的プロキシを作成したいと考えています。このようなプロキシは、PropertyChanged イベントをインターセプトし、適切な SynchronizationContext を使用してディスパッチします。

そうすれば、ヘルパー クラスを使用してジョブを実行でき、毎回手動で同期を実装する必要がありません ( if (control.InvokeRequired) etc.)。

LinFu、Castle、または同様のライブラリを使用してそれを行う方法はありますか?

[編集]

データ ソースは必ずしもリストではありません。任意のビジネス オブジェクトを指定できます。たとえば、次のようになります。

interface IConnection : INotifyPropertyChanged
{
    ConnectionStatus Status { get; }
}

ジョブを実行できるラッパーを作成できます。次のようになります。

public class ConnectionWrapper : IConnection
{
     private readonly SynchronizationContext _ctx;
     private readonly IConnection _actual;
     public ConnectionWrapper(IConnection actual)
     {
         _ctx = SynchronizationContext.Current;
         _actual= actual;
         _actual.PropertyChanged += 
            new PropertyChangedEventHandler(actual_PropertyChanged);
     }

     // we have to do 2 things:
     // 1. wrap each property manually
     // 2. handle the source event and fire it on the GUI thread

     private void PropertyChanged(object sender, PropertyChangedEvArgs e)
     {
         // we will send the same event args to the GUI thread
         _ctx.Send(delegate { this.PropertyChanged(sender, e); }, null);
     }

     public ConnectionStatus Status 
     { get { return _instance.Status; } }

     public event PropertyChangedEventHandler PropertyChanged;
}

(このコードにはいくつかのエラーがあるかもしれません、私はそれを作成しています)

私がやりたいことは、動的プロキシ(Reflection.Emit)を1つのライナーにすることです。

IConnection syncConnection
      = new SyncPropertyChangedProxy<IConnection>(actualConnection);

そして、既存の動的プロキシ実装を使用してこのようなことが可能かどうかを知りたかった.

より一般的な質問は次のとおりです。動的プロキシを作成するときにイベントをインターセプトする方法は? プロパティのインターセプト (オーバーライド) については、すべての実装で十分に説明されています。

[編集2]

プロキシが必要な理由 (と思います) は、スタック トレースが次のようになっているためです。

PropertyManager.OnCurrentChanged (System.EventArgs e) で
BindToObject.PropValueChanged (オブジェクトの送信者、EventArgs e) で
PropertyDescriptor.OnValueChanged (オブジェクト コンポーネント、EventArgs e) で
ReflectPropertyDescriptor.OnValueChanged (オブジェクト コンポーネント、EventArgs e) で
ReflectPropertyDescriptor.OnINotifyPropertyChanged (オブジェクト コンポーネント、
     PropertyChangedEventArgs e)    
MyObject.OnPropertyChanged で (文字列 propertyName)

がインスタンスを にBindToObject.PropValueChanged渡していないことがわかります。Reflector は、sender オブジェクトがどこにも参照されていないことを示しています。つまり、イベントがトリガーされると、コンポーネントはリフレクションを使用して元の (バインドされた) データ ソースのプロパティにアクセスします。senderPropertyManagerPropertyChanged

イベントのみを含むクラスでオブジェクトをラップした場合 ( Samが提案したように)、そのようなラッパー クラスには、Reflection を介してアクセスできるプロパティは含まれません。

4

3 に答える 3

5

INotifyPropertyChanged をラップし、SynchronizationContext.Current を介して PropertyChanged イベントを転送し、プロパティを転送するクラスを次に示します。

この解決策は機能するはずですが、しばらくすると、プロパティ名の代わりにラムダ式を使用するように改善される可能性があります。これにより、リフレクションを取り除き、プロパティへの型付きアクセスを提供できます。これが複雑になるのは、ラムダから式ツリーを取得してプロパティ名を取得し、それを OnSourcePropertyChanged メソッドで使用できるようにする必要があることです。ラムダ式ツリーからプロパティ名を取得することに関する投稿を見ましたが、今は見つかりませんでした。

このクラスを使用するには、バインディングを次のように変更します。

Bindings.Add("TargetProperty", new SyncBindingWrapper<PropertyType>(source, "SourceProperty"), "Value");

SyncBindingWrapper は次のとおりです。

using System.ComponentModel;
using System.Reflection;
using System.Threading;

public class SyncBindingWrapper<T> : INotifyPropertyChanged
{
    private readonly INotifyPropertyChanged _source;
    private readonly PropertyInfo _property;

    public event PropertyChangedEventHandler PropertyChanged;

    public T Value
    {
        get
        {
            return (T)_property.GetValue(_source, null);
        }
    }

    public SyncBindingWrapper(INotifyPropertyChanged source, string propertyName)
    {
        _source = source;
        _property = source.GetType().GetProperty(propertyName);
        source.PropertyChanged += OnSourcePropertyChanged;
    }

    private void OnSourcePropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName != _property.Name)
        {
            return;
        }
        PropertyChangedEventHandler propertyChanged = PropertyChanged;
        if (propertyChanged == null)
        {
            return;
        }

        SynchronizationContext.Current.Send(state => propertyChanged(this, e), null);
    }
}
于 2010-01-27T15:16:37.137 に答える
3

私は同じ問題に遭遇し、Samuel の解決策がうまくいかなかったので、同期コンテキストの初期化をコンストラクターに配置し"Value"、元のプロパティの代わりにプロパティ名を渡す必要があります。これは私のために働いた:

public class SyncBindingWrapper: INotifyPropertyChanged
{
    private readonly INotifyPropertyChanged _source;
    private readonly PropertyInfo _property;

    public event PropertyChangedEventHandler PropertyChanged;

    private readonly SynchronizationContext _context;

    public object Value
    {
        get
        {
            return _property.GetValue(_source, null);
        }
    }

    public SyncBindingWrapper(INotifyPropertyChanged source, string propertyName)
    {
        _context = SynchronizationContext.Current;
        _source = source;
        _property = source.GetType().GetProperty(propertyName);
        source.PropertyChanged += OnSourcePropertyChanged;
    }

    private void OnSourcePropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        var propertyChanged = PropertyChanged;
        if (propertyChanged != null && e.PropertyName == _property.Name)
        {
            _context.Send(state => propertyChanged(this, new PropertyChangedEventArgs("Value")), null);
        }
    }
}

使用法:

_textBox1.DataBindings.Add("Text", new SyncBindingWrapper(someObject, "SomeProperty"), "Value");
于 2013-02-18T11:18:16.257 に答える