32 votes

ObservableCollection qui surveille également les changements sur les éléments de la collection

Existe-t-il une collection (BCL ou autre) qui présente les caractéristiques suivantes :

Envoie un événement si la collection est modifiée ET envoie un événement si l'un des éléments de la collection envoie un PropertyChanged événement. Une sorte de ObservableCollection<T>T: INotifyPropertyChanged et la collection surveille également les éléments pour détecter les changements.

J'ai pu créer moi-même une collection observable et faire l'événement subscribe/unsubscribe lorsque des éléments de la collection sont ajoutés/supprimés, mais je me demandais si des collections existantes le faisaient déjà.

43voto

soren.enemaerke Points 2083

J'ai moi-même procédé à une mise en œuvre rapide :

public class ObservableCollectionEx<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        Unsubscribe(e.OldItems);
        Subscribe(e.NewItems);
        base.OnCollectionChanged(e);
    }

    protected override void ClearItems()
    {
        foreach(T element in this)
            element.PropertyChanged -= ContainedElementChanged;

        base.ClearItems();
    }

    private void Subscribe(IList iList)
    {
        if (iList != null)
        {
            foreach (T element in iList)
                element.PropertyChanged += ContainedElementChanged;
        }
    }

    private void Unsubscribe(IList iList)
    {
        if (iList != null)
        {
            foreach (T element in iList)
                element.PropertyChanged -= ContainedElementChanged;
        }
    }

    private void ContainedElementChanged(object sender, PropertyChangedEventArgs e)
    {
        OnPropertyChanged(e);
    }
}

Il est vrai qu'il serait un peu déroutant et trompeur d'avoir le PropertyChanged déclenché sur la collection alors que la propriété qui a réellement changé se trouve sur un élément contenu, mais cela répondrait à mon objectif spécifique. Il pourrait être étendu avec un nouvel événement qui est déclenché à la place de ContainerElementChanged

Réflexions ?

EDIT : Il convient de noter que la BCL ObservableCollection n'expose l'interface INotifyPropertyChanged que par le biais d'une implémentation explicite, de sorte que vous devriez fournir un cast afin de vous attacher à l'événement de cette manière :

ObservableCollectionEx<Element> collection = new ObservableCollectionEx<Element>();
((INotifyPropertyChanged)collection).PropertyChanged += (x,y) => ReactToChange();

EDIT2 : Ajout de la gestion de ClearItems, merci Josh

EDIT3 : Ajout d'un désabonnement correct pour PropertyChanged, merci Mark

EDIT4 : Wow, c'est vraiment de l'apprentissage au fur et à mesure :). KP a noté que l'événement était déclenché avec la collection comme expéditeur et non avec l'élément lorsque l'élément a contenu change. Il a suggéré de déclarer un événement PropertyChanged sur la classe marquée par le symbole nouveau . Cela poserait quelques problèmes que j'essaierai d'illustrer avec l'exemple ci-dessous :

  // work on original instance
  ObservableCollection<TestObject> col = new ObservableCollectionEx<TestObject>();
  ((INotifyPropertyChanged)col).PropertyChanged += (s, e) => { Trace.WriteLine("Changed " + e.PropertyName); };

  var test = new TestObject();
  col.Add(test); // no event raised
  test.Info = "NewValue"; //Info property changed raised

  // working on explicit instance
  ObservableCollectionEx<TestObject> col = new ObservableCollectionEx<TestObject>();
  col.PropertyChanged += (s, e) => { Trace.WriteLine("Changed " + e.PropertyName); };

  var test = new TestObject();
  col.Add(test); // Count and Item [] property changed raised
  test.Info = "NewValue"; //no event raised

Vous pouvez voir dans l'exemple que le fait de "surcharger" l'événement a pour effet secondaire de vous obliger à faire extrêmement attention au type de variable que vous utilisez lorsque vous vous abonnez à l'événement, car cela dicte les événements que vous recevez.

0 votes

C'est la réponse, car rien de mieux (à mon avis) n'a fait surface.

4 votes

Il y a cependant un problème avec cette classe. Si vous appelez Clear(), l'événement OnCollectionChanged recevra une notification Reset et vous n'aurez pas accès aux éléments qui ont été effacés de la collection. Ce problème peut être résolu en surchargeant ClearItems et en désabonnant les gestionnaires avant d'appeler base.ClearItems().

1 votes

Voir le commentaire de Mark Whitfeld pour une façon plus propre (et plus correcte) de gérer l'abonnement et le désabonnement : stackoverflow.com/questions/269073/

7voto

Mark Whitfeld Points 623

@soren.enemaerke : J'aurais bien fait ce commentaire sur votre post de réponse, mais je ne peux pas (je ne sais pas pourquoi, peut-être parce que je n'ai pas beaucoup de points de rep). Quoi qu'il en soit, je pensais juste mentionner que dans le code que vous avez posté, je ne pense pas que le Unsubscribe fonctionnerait correctement parce qu'il crée un nouveau lambda en ligne et essaie ensuite de supprimer le gestionnaire d'événement pour lui.

Je changerais les lignes de gestion des événements d'ajout/suppression en quelque chose comme :

element.PropertyChanged += ContainedElementChanged;

et

element.PropertyChanged -= ContainedElementChanged;

Puis modifiez la signature de la méthode ContainedElementChanged en :

private void ContainedElementChanged(object sender, PropertyChangedEventArgs e)

Cela permettrait de reconnaître que la suppression concerne le même gestionnaire que l'ajout et de le supprimer correctement. J'espère que cela aidera quelqu'un :)

3voto

Todd White Points 4257

Si vous voulez utiliser quelque chose d'intégré dans le cadre, vous pouvez utiliser Collection congelable . Ensuite, vous voudrez écouter le Événement modifié .

Se produit lorsque la fonction Freezab qu'il contient est modifié.

En voici un petit échantillon. La méthode collection_Changed sera appelée deux fois.

public partial class Window1 : Window
{
    public Window1()
    {
        InitializeComponent();

        FreezableCollection<SolidColorBrush> collection = new FreezableCollection<SolidColorBrush>();
        collection.Changed += collection_Changed;
        SolidColorBrush brush = new SolidColorBrush(Colors.Red);
        collection.Add(brush);
        brush.Color = Colors.Blue;
    }

    private void collection_Changed(object sender, EventArgs e)
    {
    }
}

7 votes

Il convient également de noter que les FreezableCollections ne peuvent contenir que des éléments héritant de DependencyObject.

1voto

Lukas Cenovsky Points 2425

J'utiliserais L'interface de ReactiveUI ReactiveCollection :

reactiveCollection.Changed.Subscribe(_ => ...);

0voto

Marcus Griep Points 3010

Consultez le site Bibliothèque de la collection générique C5 . Toutes ses collections contiennent des événements que vous pouvez utiliser pour attacher des rappels lorsque des éléments sont ajoutés, supprimés, insérés, effacés ou lorsque la collection change.

Je travaille sur des extensions de cette bibliothèque. aquí qui, dans un avenir proche, devrait permettre d'organiser des événements de "prévisualisation" qui pourraient vous permettre d'annuler un ajout ou une modification.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X