c# - 创建一个 INotifyPropertyChanged 代理以将调用分派(dispatch)到 UI 线程

标签 c# winforms multithreading inotifypropertychanged dynamic-proxy

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

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

有没有办法使用 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]

(我认为)我需要代理的原因是堆栈跟踪看起来像这样:

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显示没有引用sender对象任何地方。换句话说,当 PropertyChanged 事件被触发时,组件将使用反射来访问原始(绑定(bind))数据源的属性。

如果我将我的对象包装在一个只包含事件的类中(如 Sam 所建议的),这样的包装类将不包含任何可以通过反射访问的属性。

最佳答案

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

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

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

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

关于c# - 创建一个 INotifyPropertyChanged 代理以将调用分派(dispatch)到 UI 线程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2135227/

相关文章:

c# - 如何创建列表列表并向其添加项目然后读回这些项目?

multithreading - 通过线程下载图像错误

.net - 如何在无法访问 System.Windows.Forms.PaintEventArgs 命名空间的情况下在 Winform 上绘图?

c# - 如何从窗口获取屏幕截图?

c# - Winforms:具有数千个用户控件的可滚动 FlowLayoutPanel - 如何防止内存泄漏并以正确的方式处理对象?

c# - 使用 Dynamic 而不是反射来按名称调用方法

c# - 读取另一个应用程序的 Web.Config 以获取 ConnectionString

multithreading - Perl:如何编辑shared_clone的结构?

c# - ASP.NET Web API 异步任务,发送邮件

c# - 在哪里存储 C# 应用程序的 key