我想创建一个动态代理,用于将 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/