Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

INotifyPropertyChanged causes cross-thread error

Here is my scenarion:

I have a GridControl bound to a BindingList. At first what I was doing was creating a worker thread and access the BindingList directly, but this was throwing a "Cross-thread operation detected", so I followed the guide here:

http://www.devexpress.com/Support/Center/p/AK2981.aspx

By cloning the original BindingList into the worker thread and changing that one, I got the desired effect. However, I recently implemeneted the INotifyPropertyChanged into the object that is held into the BindingList, and I started getting the error again.

My guess is that the GridView is still listening to the INotifyPropertyChanged from the object.

How can I fix this?

My class:

public class Proxy : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string name)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(name));
        }
    }
like image 985
TheGateKeeper Avatar asked Apr 14 '12 20:04

TheGateKeeper


1 Answers

I took a similar approach to TheGateKeeper's eventual solution. However I was binding to many different objects. So I needed something a bit more generic. The solution was to create a wrapper that implemented also ICustomTypeDescriptor. In this way, I do not need to create wrapper properties for everything that can be displayed in the UI.

public class SynchronizedNotifyPropertyChanged<T> : INotifyPropertyChanged, ICustomTypeDescriptor
    where T : INotifyPropertyChanged
{
    private readonly T _source;
    private readonly ISynchronizeInvoke _syncObject;

    public SynchronizedNotifyPropertyChanged(T source, ISynchronizeInvoke syncObject)
    {
        _source = source;
        _syncObject = syncObject;

        _source.PropertyChanged += (sender, args) => OnPropertyChanged(args.PropertyName);
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged == null) return;

        var handler = PropertyChanged;
        _syncObject.BeginInvoke(handler, new object[] { this, new PropertyChangedEventArgs(propertyName) });
    }

    public T Source { get { return _source; }}

    #region ICustomTypeDescriptor
    public AttributeCollection GetAttributes()
    {
        return new AttributeCollection(null);
    }

    public string GetClassName()
    {
        return TypeDescriptor.GetClassName(typeof(T));
    }

    public string GetComponentName()
    {
        return TypeDescriptor.GetComponentName(typeof (T));
    }

    public TypeConverter GetConverter()
    {
        return TypeDescriptor.GetConverter(typeof (T));
    }

    public EventDescriptor GetDefaultEvent()
    {
        return TypeDescriptor.GetDefaultEvent(typeof (T));
    }

    public PropertyDescriptor GetDefaultProperty()
    {
        return TypeDescriptor.GetDefaultProperty(typeof(T));
    }

    public object GetEditor(Type editorBaseType)
    {
        return TypeDescriptor.GetEditor(typeof (T), editorBaseType);
    }

    public EventDescriptorCollection GetEvents()
    {
        return TypeDescriptor.GetEvents(typeof(T));
    }

    public EventDescriptorCollection GetEvents(Attribute[] attributes)
    {
        return TypeDescriptor.GetEvents(typeof (T), attributes);
    }

    public PropertyDescriptorCollection GetProperties()
    {
        return TypeDescriptor.GetProperties(typeof (T));
    }

    public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
    {
        return TypeDescriptor.GetProperties(typeof(T), attributes);
    }

    public object GetPropertyOwner(PropertyDescriptor pd)
    {
        return _source;
    }
    #endregion ICustomTypeDescriptor
}

Then in the Ui, I bind to this wrapper using something like:

    private void CreateBindings()
    {
        if (_model == null) return;

        var threadSafeModel = new SynchronizedNotifyPropertyChanged<MyViewModel>(_model, this);

        directiveLabel.DataBindings.Add("Text", threadSafeModel, "DirectiveText", false, DataSourceUpdateMode.OnPropertyChanged);
    }

MyViewModel has a "DirectiveText" property and implements INotifyPropertyChanged with no special consideration to threading or to the view classes.

like image 81
Cody Barnes Avatar answered Oct 20 '22 01:10

Cody Barnes