列出INotifyPropertyChanging

时间:2009-05-26 21:27:08

标签: c# collections

BindingList和ObservableCollection很好地保持数据更新,并在其中一个对象发生更改时通知。但是,当通知房产即将改变时,我认为这些选择并不是很好。

我现在需要做的就是解决这个问题(我警告这不是优雅的AT ALL),就是在列表的类型对象上实现INotifyPropertyChanging,然后将其绑定到包含列表PropertyChanging事件的对象,或者其他东西如下:

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

很抱歉,我知道这是很多代码,这让我回到了我的主要观点,实现这一点需要很多代码。所以我的问题是,有没有人知道一个更好,更短,更优雅的解决方案?

感谢您的时间和建议。

2 个答案:

答案 0 :(得分:1)

您可以创建一个实现ICustomTypeDescriptor的包装类。这个包装器还将实现必要的接口(例如INotifyPropertyChanging),拦截属性读取/写入底层对象,并且您将能够调用由包装器实现的NotifyPropertyChanging()和NotifyPropertyChanged()方法。数据使用者将使用与原始对象相同的包装对象。

但如果您不是一位经验丰富的开发人员,那么实现这样的包装并不容易。

这是一个可能的,尚未完成这种包装器的实现。它已经支持INotifyPropertyChanged,并且很容易理解如何实现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's 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);
    }
}

同样,这不是一个完整的版本,但它已经可以在简单的场景中使用。可能的用法如下所示:

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;

因此,只需一行简单的代码就可以获得支持INotifyPropertyChanged,IEditableObject,IChangeTracking接口的对象集合!

祝你好运!

答案 1 :(得分:0)

这是跨领域关注的典型例子,它呼吁采用AOP方法。 Aspect Oriented Programming是一个扩展经典OOP的范例,可以解决“我希望记录此对象上的所有方法调用”等问题。

在.NET中有几种方法可以做到这一点,这是大多数方法的一个很好的列表:

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

列出的方法之一是PostSharp,IL重写器,它让你很容易做AOP。下面是使用此工具实现INotifyPropertyChanged的示例(我认为另一个示例是PostSharp):

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

相关问题