it-swarm.com.de

ObservableCollection, die auch Änderungen an den Elementen in der Auflistung überwacht

Gibt es eine Sammlung (BCL oder eine andere) mit folgenden Merkmalen:

Sendet ein Ereignis, wenn die Auflistung geändert wird UND sendet ein Ereignis, wenn eines der Elemente in der Auflistung ein Ereignis PropertyChanged sendet. Eine Art ObservableCollection<T>, wobei T: INotifyPropertyChanged und die Sammlung auch die Elemente auf Änderungen überwachen.

Ich könnte eine beobachtbare Sammlung selbst verpacken und das Ereignis abonnieren/abbestellen, wenn Elemente in der Sammlung hinzugefügt/entfernt werden, aber ich habe mich nur gefragt, ob dies bereits in einer vorhandenen Sammlung geschehen ist.

32
soren.enemaerke

Ich habe selbst eine schnelle Implementierung gemacht:

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

Zugegeben, es wäre verwirrend und irreführend, das PropertyChanged-Feuer in der Auflistung zu haben, wenn sich die tatsächlich geänderte Eigenschaft auf einem enthaltenen Element befindet, aber es würde meinem spezifischen Zweck entsprechen. Es könnte mit einem neuen Ereignis erweitert werden, das stattdessen in ContainerElementChanged ausgelöst wird

Gedanken?

BEARBEITEN: Beachten Sie, dass die BCL ObservableCollection die INotifyPropertyChanged-Schnittstelle nur durch eine explizite Implementierung verfügbar macht. Sie müssten also eine Besetzung bereitstellen, um das Ereignis wie folgt anzuhängen:

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

EDIT2: Handling von ClearItems hinzugefügt, danke Josh

EDIT3: Ein korrektes Abmelden für PropertyChanged wurde hinzugefügt, danke Mark

EDIT4: Wow, das ist wirklich "learn-as-you-go" :). KP stellte fest, dass das Ereignis mit der Auflistung als Absender und nicht mit dem Element ausgelöst wurde, wenn sich das enthaltene Element ändert. Er schlug vor, ein PropertyChanged-Ereignis für die mit new gekennzeichnete Klasse zu deklarieren. Dies hätte einige Probleme, die ich anhand des folgenden Beispiels veranschaulichen möchte:

  // 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

Sie können dem Beispiel entnehmen, dass das Überschreiben des Ereignisses den Nebeneffekt hat, dass Sie äußerst vorsichtig sein müssen, welchen Variablentyp Sie beim Abonnieren des Ereignisses verwenden, da dies vorschreibt, welche Ereignisse Sie empfangen.

43
soren.enemaerke

@ soren.enemaerke: Ich hätte diesen Kommentar zu Ihrem Antwortpost abgegeben, aber ich kann nicht (ich weiß nicht warum, vielleicht weil ich nicht viele Wiederholungspunkte habe). Wie auch immer, ich dachte nur, dass ich erwähnen würde, dass in deinem Code, den du gepostet hast, ich glaube nicht, dass das Abbestellen richtig funktionieren würde, weil es ein neues Lambda inline erstellt und dann versucht, den Ereignishandler dafür zu entfernen.

Ich würde die Zeilen zum Hinzufügen/Entfernen von Ereignishandlern folgendermaßen ändern:

element.PropertyChanged += ContainedElementChanged;

und

element.PropertyChanged -= ContainedElementChanged;

Ändern Sie anschließend die Signatur der ContainedElementChanged-Methode in:

private void ContainedElementChanged(object sender, PropertyChangedEventArgs e)

Dies würde erkennen, dass das Entfernen für den gleichen Handler wie das Hinzufügen ist und es dann korrekt entfernen. Hoffe das hilft jemandem :)

7
Mark Whitfeld

Wenn Sie etwas verwenden möchten, das in das Framework integriert ist, können Sie FreezableCollection verwenden. Dann wollen Sie das Geänderte Ereignis anhören.

Tritt ein, wenn das Freezable oder ein darin enthaltenes Objekt geändert wird.

Hier ist eine kleine Auswahl. Die collection_Changed-Methode wird zweimal aufgerufen.

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)
    {
    }
}
3
Todd White

Ich würde ReactiveUIsReactiveCollection verwenden:

reactiveCollection.Changed.Subscribe(_ => ...);
1
Lukas Cenovsky

Rxx 2.0 enthält Operatoren die zusammen mit diesem Konvertierungsoperator für ObservableCollection<T> das Erreichen Ihres Ziels erleichtern.

ObservableCollection<MyClass> collection = ...;

var changes = collection.AsCollectionNotifications<MyClass>();
var itemChanges = changes.PropertyChanges();
var deepItemChanges = changes.PropertyChanges(
  item => item.ChildItems.AsCollectionNotifications<MyChildClass>());

Die folgenden Benachrichtigungsmuster mit geänderten Eigenschaften werden für MyClass und MyChildClass unterstützt:

  • INotifyPropertyChanged
  • [Eigenschaft] Geändertes Ereignismuster (Legacy, zur Verwendung durch das Komponentenmodell)
  • WPF-Abhängigkeitseigenschaften
0
Dave Sexton

Der einfachste Weg, dies zu tun, ist einfach zu tun

using System.ComponentModel;
public class Example
{
    BindingList<Foo> _collection;

    public Example()
    {
        _collection = new BindingList<Foo>();
        _collection.ListChanged += Collection_ListChanged;
    }

    void Collection_ListChanged(object sender, ListChangedEventArgs e)
    {
        MessageBox.Show(e.ListChangedType.ToString());
    }

}

Die BindingList - Klasse war in .NET Sence 2.0. Das Ereignis ListChanged wird jedes Mal ausgelöst, wenn ein Element in der Sammlung INotifyPropertyChanged auslöst.

0

Schauen Sie sich die C5 Generic Collection Library an . Alle Sammlungen enthalten Ereignisse, mit denen Sie Rückrufe anfügen können, wenn Elemente hinzugefügt, entfernt, eingefügt, gelöscht oder die Sammlung geändert wird.

Ich arbeite an einigen Erweiterungen dieser Bibliothek hier , die in naher Zukunft "Vorschau" -Ereignisse ermöglichen sollen, mit denen Sie ein Hinzufügen oder Ändern abbrechen können.

0
Marcus Griep

@ soren.enemaerke Machte dies zu einer Antwort, um den richtigen Code zu posten, da der Kommentarbereich Ihrer Antwort ihn unleserlich machen würde. Das einzige Problem, das ich bei der Lösung hatte, ist, dass das bestimmte Element, das das Ereignis PropertyChanged auslöst, verloren geht und Sie im Aufruf PropertyChanged keine Ahnung haben.

col.PropertyChanged += (s, e) => { Trace.WriteLine("Changed " + e.PropertyName)

Um dies zu beheben, habe ich eine neue Klasse PropertyChangedEventArgsEx erstellt und die Methode ContainedElementChanged in Ihrer Klasse geändert.

neue Klasse

public class PropertyChangedEventArgsEx : PropertyChangedEventArgs
{
    public object Sender { get; private set; }

    public PropertyChangedEventArgsEx(string propertyName, object sender) 
        : base(propertyName)
    {
        this.Sender = sender;
    }
}

Änderungen an Ihrer Klasse

 private void ContainedElementChanged(object sender, PropertyChangedEventArgs e)
    {
        var ex = new PropertyChangedEventArgsEx(e.PropertyName, sender);
        OnPropertyChanged(ex);
    }

Danach können Sie das Sender -Element in col.PropertyChanged += (s, e) abrufen, indem Sie e in PropertyChangedEventArgsEx umwandeln.

((INotifyPropertyChanged)col).PropertyChanged += (s, e) =>
        {
            var argsEx = (PropertyChangedEventArgsEx)e;
            Trace.WriteLine(argsEx.Sender.ToString());
        };

Auch hier sollten Sie beachten, dass die s die Auflistung von Elementen ist, nicht das eigentliche Element, das das Ereignis ausgelöst hat. Daher die neue Eigenschaft Sender in der Klasse PropertyChangedEventArgsEx.

0
Ovi