Списки NotifyPropertyChanging - PullRequest
       9

Списки NotifyPropertyChanging

1 голос
/ 27 мая 2009

Ну, BindingList и ObservableCollection прекрасно работают для обновления данных и уведомления, когда один из их объектов изменился. Однако при уведомлении об изменении свойства я думаю, что эти параметры не очень хороши.

То, что я должен сделать прямо сейчас, чтобы решить эту проблему (и я предупреждаю, что это не элегантно ВСЕ), это реализовать 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 ]

1 голос
/ 27 мая 2009

Вы можете создать класс-оболочку, который реализует 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!

Удачи!

0 голосов
/ 27 мая 2009

Это классический пример сквозного подхода, который требует подхода АОП. Аспектно-ориентированное программирование - это парадигма, которая расширяет классическую ООП и позволяет решать такие проблемы, как «Я хочу, чтобы все вызовы методов для этого объекта регистрировались».

Есть несколько способов сделать это в .NET, это хороший список большинства из них:

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

Одним из перечисленных подходов является PostSharp, IL переписчик, который позволяет очень легко выполнять АОП. Вот пример реализации INotifyPropertyChanged с использованием этого инструмента (я думаю, что другой пример поставляется с PostSharp):

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...