NotifyPropertyChanging Lists

Well BindingList and ObservableCollection work great to update data and notify when one of them has changed. However, when the property change notification is about to change, I think these options are not very good.

What I have to do right now to solve this problem (and I warn you that this is not gracefully AT ALL) is to implement INotifyPropertyChanging on a list-type object, and then bind it to an object that contains a list of PropertyChanging events or something like:

// this object will be the type of the BindingList
public class SomeObject : INotifyPropertyChanging, INotifyPropertyChanged
{
    private int _intProperty = 0;
    private string _strProperty = String.Empty;

    public int IntProperty
    {
        get { return this._intProperty; }
        set
        {
            if (this._intProperty != value)
            {
                NotifyPropertyChanging("IntProperty");
                this._intProperty = value;
                NotifyPropertyChanged("IntProperty");
            }
        }
    }

    public string StrProperty
    {
        get { return this._strProperty; }
        set
        {
            if (this._strProperty != value)
            {
                NotifyPropertyChanging("StrProperty");
                this._strProperty = value;
                NotifyPropertyChanged("StrProperty");
            }
        }
    }

    #region INotifyPropertyChanging Members

    public event PropertyChangingEventHandler PropertyChanging;

    #endregion

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    #endregion

    public void NotifyPropertyChanging(string propertyName)
    {
        if (this.PropertyChanging != null)
            PropertyChanging(this, new PropertyChangingEventArgs(propertyName));
    }

    public void NotifyPropertyChanged(string propertyName)
    {
        if (this.PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

public class ObjectThatHoldsTheList : INotifyPropertyChanging, INotifyPropertyChanged
{
    public BindingList<SomeObject> BindingList { get; set; }

    public ObjectThatHoldsTheList()
    {
        this.BindingList = new BindingList<SomeObject>();
    }

    // this helps notifie Changing and Changed on Add
    private void AddItem(SomeObject someObject)
    {
        // this will tie the PropertyChanging and PropertyChanged events of SomeObject to this object
        // so it gets notifies because the BindingList does not notify PropertyCHANGING
        someObject.PropertyChanging += new PropertyChangingEventHandler(someObject_PropertyChanging);
        someObject.PropertyChanged += new PropertyChangedEventHandler(someObject_PropertyChanged);

        this.NotifyPropertyChanging("BindingList");
        this.BindingList.Add(someObject);
        this.NotifyPropertyChanged("BindingList");
    }

    // this helps notifies Changing and Changed on Delete
    private void DeleteItem(SomeObject someObject)
    {
        if (this.BindingList.IndexOf(someObject) > 0)
        {
            // this unlinks the handlers so the garbage collector can clear the objects
            someObject.PropertyChanging -= new PropertyChangingEventHandler(someObject_PropertyChanging);
            someObject.PropertyChanged -= new PropertyChangedEventHandler(someObject_PropertyChanged);
        }

        this.NotifyPropertyChanging("BindingList");
        this.BindingList.Remove(someObject);
        this.NotifyPropertyChanged("BindingList");
    }

    // this notifies an item in the list is about to change
    void someObject_PropertyChanging(object sender, PropertyChangingEventArgs e)
    {
        NotifyPropertyChanging("BindingList." + e.PropertyName);
    }

    // this notifies an item in the list has changed
    void someObject_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        NotifyPropertyChanged("BindingList." + e.PropertyName);
    }

    #region INotifyPropertyChanging Members

    public event PropertyChangingEventHandler PropertyChanging;

    #endregion

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    #endregion

    public void NotifyPropertyChanging(string propertyName)
    {
        if (this.PropertyChanging != null)
            PropertyChanging(this, new PropertyChangingEventArgs(propertyName));
    }

    public void NotifyPropertyChanged(string propertyName)
    {
        if (this.PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

      

Sorry, I know this is a lot of code that brings me back to my main point IT A LOT OF CODE to implement this. So my question is, does anyone know a better, shorter and more elegant solution?

Thanks for your time and suggestions.

+1


a source to share


2 answers


You can create a wrapper class that implements ICustomTypeDescriptor. This wrapper also implements the required interfaces (such as INotifyPropertyChanging), intercepts properties, reads / writes to the underlying object, and you can call the NotifyPropertyChanging () and NotifyPropertyChanged () methods implemented by the wrapper. Data users will work with wrapped objects in the same way they work with original objects.

But implementing such a wrapper will not be easy unless you are an experienced developer.

Here is a possible, but not yet complete implementation of such a wrapper. It already supports INotifyPropertyChanged and it is easy to see how to implement INotifyPropertyChanging.

public class Wrapper : ICustomTypeDescriptor, INotifyPropertyChanged, IEditableObject, IChangeTracking
{
    private bool _isChanged;

    public object DataSource { get; set; }

    public Wrapper(object dataSource)
    {
        if (dataSource == null)
            throw new ArgumentNullException("dataSource");
        DataSource = dataSource;
    }

    #region ICustomTypeDescriptor Members

    public AttributeCollection GetAttributes()
    {
        return new AttributeCollection(
                DataSource.GetType()
                                    .GetCustomAttributes(true)
                                    .OfType<Attribute>()
                                    .ToArray());
    }

    public string GetClassName()
    {
        return DataSource.GetType().Name;
    }

    public string GetComponentName()
    {
        return DataSource.ToString();
    }

    public TypeConverter GetConverter()
    {
        return new TypeConverter();
    }

    public EventDescriptor GetDefaultEvent()
    {
        return null;
    }

    public PropertyDescriptor GetDefaultProperty()
    {
        return null;
    }

    public object GetEditor(Type editorBaseType)
    {
        return Activator.CreateInstance(editorBaseType);
    }

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

    public EventDescriptorCollection GetEvents()
    {
        return TypeDescriptor.GetEvents(DataSource);
    }

    public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
    {
        return GetProperties();
    }

    private IEnumerable<PropertyDescriptor> _Properties;

    public IEnumerable<PropertyDescriptor> Properties
    {
        get
        {
            if (_Properties == null)
                _Properties = TypeDescriptor.GetProperties(DataSource)
                .Cast<PropertyDescriptor>()
                .Select(pd => new WrapperPropertyDescriptor(pd) as PropertyDescriptor)
                .ToList();
            return _Properties;
        }

    }

    public PropertyDescriptorCollection GetProperties()
    {
        return new PropertyDescriptorCollection(Properties.ToArray());
    }

    public object GetPropertyOwner(PropertyDescriptor pd)
    {
        return this;
    }

    #endregion ICustomTypeDescriptor

    #region ToString, Equals, GetHashCode
    public override string ToString()
    {
        return DataSource.ToString();
    }

    public override bool Equals(object obj)
    {
        var wrapper = obj as Wrapper;
        if (wrapper == null)
            return base.Equals(obj);
        else
            return DataSource.Equals(wrapper.DataSource);
    }

    public override int GetHashCode()
    {
        return DataSource.GetHashCode();
    }
    #endregion

    #region INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;

    public void OnPropertyChanged(string propertyName)
    {
        if (String.IsNullOrEmpty(propertyName))
            throw new ArgumentNullException("propertyName");

        _isChanged = true;

        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
    #endregion

    public IDictionary<string, object> MakeDump()
    {
        var result = new Dictionary<String, object>();
        foreach (var item in Properties)
            result[item.Name] = item.GetValue(this);

        return result;
    }

    #region IEditableObject Members

    private IDictionary<string, object> LastDump;

    public void BeginEdit()
    {
        LastDump = MakeDump();
    }

    public void CancelEdit()
    {
        if (LastDump != null)
        {
            foreach (var item in Properties)
                item.SetValue(this, LastDump[item.Name]);
            _isChanged = false;
        }
    }

    public void EndEdit()
    {
        AcceptChanges();
    }

    #endregion IEditableObject

    #region IChangeTracking
    public void AcceptChanges()
    {
        LastDump = null;
        _isChanged = false;
    }

    public bool IsChanged
    {
        get { return _isChanged;  }
    }
    #endregion IChangeTracking
}

public class WrapperPropertyDescriptor : PropertyDescriptor
{
    private Wrapper _wrapper;
    private readonly PropertyDescriptor SourceDescriptor;

    public WrapperPropertyDescriptor(PropertyDescriptor sourceDescriptor) :
        base(sourceDescriptor)
    {
        if (sourceDescriptor == null)
            throw new ArgumentNullException("sourceDescriptor");
        SourceDescriptor = sourceDescriptor;
    }


    public override Type ComponentType
    {
        get
        {
            return SourceDescriptor.ComponentType;
        }
    }

    public override bool IsReadOnly
    {
        get
        {
            return SourceDescriptor.IsReadOnly;
        }
    }

    public override Type PropertyType
    {
        get
        {
            return SourceDescriptor.PropertyType;
        }
    }

    public override object GetValue(object component)
    {
        var wrapper = component as Wrapper;
        if (wrapper == null)
            throw new ArgumentException("Unexpected component", "component");

        var value = SourceDescriptor.GetValue(wrapper.DataSource);
        if (value == null)
            return value;

        var type = value.GetType();

        // If value is user class or structure it should 
        // be wrapped before return.
        if (type.Assembly != typeof(String).Assembly)
        {
            if (typeof(IEnumerable).IsAssignableFrom(type))
                throw new NotImplementedException("Here we should construct and return wrapper for collection");

            if (_wrapper == null) 
                _wrapper = new Wrapper(value);
            else 
                _wrapper.DataSource = value; 

            return _wrapper;
        }

        return value;
    }

    public override void SetValue(object component, object value)
    {
        var wrapper = component as Wrapper;
        if (wrapper == null)
            throw new ArgumentException("Unexpected component", "component");

        var actualValue = value;

        var valueWrapper = value as Wrapper;
        if (valueWrapper != null)
            actualValue = valueWrapper.DataSource;

        // Make dump of data source previous values
        var dump = wrapper.MakeDump();

        SourceDescriptor.SetValue(wrapper.DataSource, actualValue);

        foreach (var item in wrapper.Properties)
        {
            var itemValue = item.GetValue(wrapper);
            if (!itemValue.Equals(dump[item.Name]))
                wrapper.OnPropertyChanged(item.Name);
        }
    }

    public override void ResetValue(object component)
    {
        var wrapper = component as Wrapper;
        if (wrapper == null)
            throw new ArgumentException("Unexpected component", "component");
        SourceDescriptor.ResetValue(wrapper.DataSource);
    }

    public override bool ShouldSerializeValue(object component)
    {
        var wrapper = component as Wrapper;
        if (wrapper == null)
            throw new ArgumentException("Unexpected component", "component");
        return SourceDescriptor.ShouldSerializeValue(wrapper.DataSource);
    }

    public override bool CanResetValue(object component)
    {
        var wrapper = component as Wrapper;
        if (wrapper == null)
            throw new ArgumentException("Unexpected component", "component");
        return SourceDescriptor.CanResetValue(wrapper.DataSource);
    }
}

      



Again, this is not a complete version, but it can already be used in simple scripts. Possible usage might look like this:

IList<Customer> customers = CustomerRepository.GetAllCustomers();  
IList<Wrapper> wrappedCustomers = customers.Select(c => new Wrapper(c)).ToList();
/* If you don't like LINQ in the line above you can use foreach to transform
list of Customer object to a list of Wrapper<Customer> objects */
comboBoxCustomers.DataSource = wrappedCustomers;
// or
dataGridViewCustomers.DataSource = wrappedCustomers;

      

So, with one simple line of code, you have a collection of objects that support the INotifyPropertyChanged, IEditableObject, IChangeTracking interfaces!

Good luck!

+1


a source


This is a classic example for the end-to-end care that cries out for the AOP approach. Aspect Oriented Programming is a paradigm that extends classic OOP to solve problems such as "I want all method calls on this object to be registered."

There are several ways to do this in .NET, this is a good list of most of them:

http://ayende.com/Blog/archive/2007/07/02/7-Approaches-for-AOP-in-.Net.aspx



One approach is PostSharp, an IL redirector that allows you to do AOP very easily. Here is an example of INotifyPropertyChanged implementation using this tool (in another example, for example PostSharp):

http://thetreeknowseverything.net/2009/01/21/auto-implement-inotifypropertychanged-with-aspects/

0


a source







All Articles