【问题标题】:Creating an INotifyPropertyChanged proxy to dispatch calls to UI thread创建 INotifyPropertyChanged 代理以将调用调度到 UI 线程
【发布时间】:2011-01-09 06:21:06
【问题描述】:

我想创建一个动态代理,用于将 WinForms 控件绑定到由不同(非 GUI)线程更改的对象。这样的代理会拦截 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),例如

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(字符串属性名称)

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

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

【问题讨论】:

标签: c# winforms multithreading inotifypropertychanged dynamic-proxy


【解决方案1】:

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

这个解决方案应该可以工作,但随着时间的推移,它可以改进为使用 lambda 表达式而不是属性名称。这将允许摆脱反射,提供对属性的类型访问。复杂之处在于您还需要从 lambda 中获取表达式树以提取属性名称,以便您可以在 OnSourcePropertyChanged 方法中使用它。我看到一篇关于从 lambda 表达式树中提取属性名称的帖子,但我现在找不到。

要使用此类,您需要像这样更改绑定:

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

【讨论】:

  • 谢谢,这基本上是我最后做的,忘记接受了。困扰我的是我一直认为我需要一个对象的单个包装器,而实际上我需要将每个 property 包装在不同的包装器中以使其工作。
  • 刚刚找到这个,正是我想要的。但是,对于遇到的任何其他人来说,有一件事是:SyncBindingWrapper 应该提供一种方法,可以将自己从源对象的 PropertyChanged 事件中移除,可能是通过实现 IDisposable。
  • 此代码无效。当从其他线程调用 SynchronizationContext.Current 时为 null。它应该在构造函数中初始化。
【解决方案2】:

我遇到了同样的问题,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");

【讨论】:

  • 是的,谢谢,IIRC 我也以同样的方式修复了该方法,但忘记更新了。在OnSourcePropertyChanged 中使用SynchronizationContext.Current 没有意义。
【解决方案3】:

不依赖 SynchronizeConext,您可以依赖 ISynchronizeInvoke

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-12-20
    • 2023-03-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-02-08
    相关资源
    最近更新 更多