Creating an INotifyPropertyChanged proxy to dispatch calls to UI thread
Here's a class that will wrap a INotifyPropertyChanged, forward the PropertyChanged event through SynchronizationContext.Current, and forward the property.
This solution should work, but with some time it could be improved to use a lambda expression instead of a property name. That would allow getting rid the reflection, provide typed access to the property. The complication with this is you need to also get the expression tree from the lambda to pull out the property name so you can use it in the OnSourcePropertyChanged method. I saw a post about pulling a property name from a lambda expression tree but I couldn't find it just now.
To use this class, you'd want to change your binding like this:
Bindings.Add("TargetProperty", new SyncBindingWrapper<PropertyType>(source, "SourceProperty"), "Value");
And here's 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);
}
}
I have come across the same problems and Samuel's solution didn't work for me, so I placed the synchronization context initialization in the constructor, and the "Value"
property name should be passed instead of the original property. This worked for me:
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);
}
}
}
Usage:
_textBox1.DataBindings.Add("Text", new SyncBindingWrapper(someObject, "SomeProperty"), "Value");