创建一个INotifyPropertyChanged代理来调度对UI线程的调用

Gro*_*roo 7 c# multithreading inotifypropertychanged dynamic-proxy winforms

我想创建一个动态代理,用于将WinForms控件绑定到由不同(非GUI)线程更改的对象.这样的代理将拦截PropertyChanged事件并使用适当的SynchronizationContext调度它.

这样我就可以使用辅助类来完成这项工作,而不必每次都手动实现同步(if (control.InvokeRequired) etc.).

有没有办法使用LinFu,Castle或类似的库?

[编辑]

数据源不一定是列表.它可以是任何业务对象,例如:

interface IConnection : INotifyPropertyChanged
{
    ConnectionStatus Status { get; }
}
Run Code Online (Sandbox Code Playgroud)

我可以创建一个可以完成工作的包装器,它看起来像这样:

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;
}
Run Code Online (Sandbox Code Playgroud)

(此代码中可能存在一些错误,我正在编写)

我想做的是为此设置一个动态代理(Reflection.Emit)一个班轮,例如

IConnection syncConnection
      = new SyncPropertyChangedProxy<IConnection>(actualConnection);
Run Code Online (Sandbox Code Playgroud)

我想知道使用现有的动态代理实现是否可以做到这一点.

更一般的问题是:如何在创建动态代理时拦截事件?在所有实现中都很好地解释了拦截(覆盖)属性.

[EDIT2]

原因(我认为)我需要一个代理是堆栈跟踪如下所示:

at PropertyManager.OnCurrentChanged(System.EventArgs e)
at BindToObject.PropValueChanged(object sender, EventArgs e)
at PropertyDescriptor.OnValueChanged(object component, EventArgs e) 
at ReflectPropertyDescriptor.OnValueChanged(object component, EventArgs e)
at ReflectPropertyDescriptor.OnINotifyPropertyChanged(object component,
     PropertyChangedEventArgs e)    
at MyObject.OnPropertyChanged(string propertyName)

您可以看到BindToObject.PropValueChanged没有将sender实例传递给PropertyManager,并且Reflector显示发件人对象未在任何地方引用.换句话说,当PropertyChanged事件被触发时,组件将使用反射来访问原始(绑定)数据源的属性.

如果我将对象包装在仅包含事件的类中(如Sam所建议的那样),则此类包装类将不包含可通过Reflection访问的任何属性.

Sam*_*eff 5

这是一个包装INotifyPropertyChanged的类,通过SynchronizationContext.Current转发PropertyChanged事件,并转发该属性.

这个解决方案应该可行,但有一段时间可以改进使用lambda表达式而不是属性名称.这将允许摆脱反射,提供对属性的类型访问.与此相关的复杂性是您还需要从lambda获取表达式树以提取属性名称,以便您可以在OnSourcePropertyChanged方法中使用它.我看到一篇关于从lambda表达式树中提取属性名称的帖子,但我刚才找不到它.

要使用此类,您需要更改绑定,如下所示:

Bindings.Add("TargetProperty", new SyncBindingWrapper<PropertyType>(source, "SourceProperty"), "Value");
Run Code Online (Sandbox Code Playgroud)

这是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);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 此代码无效.从其他线程调用时,SynchronizationContext.Current为null.它应该在构造函数中初始化. (2认同)