2009-05-26 8 views
1

Well BindingList et ObservableCollection fonctionnent très bien pour garder les données à jour et pour notifier quand l'un de ses objets a changé. Cependant, lorsque la notification d'une propriété est sur le point de changer, je pense que ces options ne sont pas très bonnes. Ce que je dois faire maintenant pour résoudre ceci (et je préviens que ce n'est pas élégant du tout), est d'implémenter INotifyPropertyChanging sur l'objet type de la liste et de l'attacher ensuite à l'objet qui contient l'événement PropertyChanging de liste, ou quelque chose comme ce qui suit:Listes NotifyPropertyChanging

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

Désolé, je sais que cela est beaucoup de code, ce qui me ramène à mon point principal IL eST bEAUCOUP dE CODE mettre en œuvre. Donc ma question est, est-ce que quelqu'un connaît une solution meilleure, plus courte, plus élégante?

Merci pour votre temps et vos suggestions.

Répondre

1

Vous pouvez créer une classe wrapper qui implémente ICustomTypeDescriptor. Ce wrapper implémentera également les interfaces nécessaires (comme INotifyPropertyChanging), interceptera les lectures/écritures sur l'objet sous-jacent, et vous pourrez appeler les méthodes NotifyPropertyChanging() et NotifyPropertyChanged() implémentées par un wrapper. Les consommateurs de données travailleront avec des objets emballés de la même manière qu'ils travaillent avec des objets originaux. Mais l'implémentation d'un tel wrapper ne sera pas facile si vous n'êtes pas un développeur expérimenté.

Voici la possibilité, encore non terminée mise en œuvre d'un tel emballage. Il prend déjà en charge INotifyPropertyChanged et il est facile de comprendre comment implémenter 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); 
    } 
} 

Encore une fois, ce n'est pas une version complète, mais il peut déjà être utilisé dans des scénarios simples. L'utilisation possible peut ressembler à ceci:

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; 

Donc, avec une simple ligne de code que vous avez une collection d'objets qui prennent en charge INotifyPropertyChanged, IEditableObject, interfaces IChangeTracking!

Bonne chance!

0

Ceci est un exemple classique de préoccupation transversale, qui pleure pour l'approche AOP. Aspect Oriented Programming est un paradigme qui étend la POO classique et vous permet de résoudre des problèmes comme "Je veux que tous les appels de méthode sur cet objet soient enregistrés".

Il y a plusieurs façons de le faire dans .NET, c'est une belle liste de la plupart d'entre eux:

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

L'une des approches énumérées est PostSharp, IL rewriter qui nous allons vous faire très AOP facilement. Voici un exemple de mise en œuvre INotifyPropertyChanged utilisant cet outil (un autre exemple vient avec PostSharp, je pense):

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