Ich habe hier etwas, das mich wirklich überrascht.
Ich habe eine ObservableCollection von T, die mit Elementen gefüllt ist. Ich habe auch einen Ereignishandler an das CollectionChanged-Ereignis angehängt.
Wenn Sie löschen die Sammlung es verursacht ein Collection Ereignis mit e.Action Set NotifyCollectionChangedAction.Reset. Ok, das ist normal. Aber was seltsam ist, ist, dass weder e.OldItems noch e.NewItems etwas enthalten. Ich würde erwarten, dass e.OldItems mit allen Elementen gefüllt wird, die aus der Sammlung entfernt wurden.
Hat das noch jemand gesehen? Und wenn ja, wie sind sie damit umgegangen?
Einige Hintergrundinformationen: Ich verwende das CollectionChanged-Ereignis, um ein anderes Ereignis anzuhängen und von diesem zu trennen. Wenn ich also keine Elemente in e.OldItems erhalte, kann ich mich nicht von diesem Ereignis trennen.
ERKLÄRUNG: Ich weiß, dass die Dokumentation nicht direkt besagt , dass sie sich so verhalten muss. Aber für jede andere Aktion benachrichtigt es mich darüber, was es getan hat. Ich gehe also davon aus, dass es mir sagen würde ... auch im Fall von Löschen / Zurücksetzen.
Unten finden Sie den Beispielcode, wenn Sie ihn selbst reproduzieren möchten. Zunächst einmal die xaml:
<Window
x:Class="ObservableCollection.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1"
Height="300"
Width="300"
>
<StackPanel>
<Button x:Name="addButton" Content="Add" Width="100" Height="25" Margin="10" Click="addButton_Click"/>
<Button x:Name="moveButton" Content="Move" Width="100" Height="25" Margin="10" Click="moveButton_Click"/>
<Button x:Name="removeButton" Content="Remove" Width="100" Height="25" Margin="10" Click="removeButton_Click"/>
<Button x:Name="replaceButton" Content="Replace" Width="100" Height="25" Margin="10" Click="replaceButton_Click"/>
<Button x:Name="resetButton" Content="Reset" Width="100" Height="25" Margin="10" Click="resetButton_Click"/>
</StackPanel>
</Window>
Als nächstes der Code dahinter:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
namespace ObservableCollection
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
_integerObservableCollection.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(_integerObservableCollection_CollectionChanged);
}
private void _integerObservableCollection_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case System.Collections.Specialized.NotifyCollectionChangedAction.Add:
break;
case System.Collections.Specialized.NotifyCollectionChangedAction.Move:
break;
case System.Collections.Specialized.NotifyCollectionChangedAction.Remove:
break;
case System.Collections.Specialized.NotifyCollectionChangedAction.Replace:
break;
case System.Collections.Specialized.NotifyCollectionChangedAction.Reset:
break;
default:
break;
}
}
private void addButton_Click(object sender, RoutedEventArgs e)
{
_integerObservableCollection.Add(25);
}
private void moveButton_Click(object sender, RoutedEventArgs e)
{
_integerObservableCollection.Move(0, 19);
}
private void removeButton_Click(object sender, RoutedEventArgs e)
{
_integerObservableCollection.RemoveAt(0);
}
private void replaceButton_Click(object sender, RoutedEventArgs e)
{
_integerObservableCollection[0] = 50;
}
private void resetButton_Click(object sender, RoutedEventArgs e)
{
_integerObservableCollection.Clear();
}
private ObservableCollection<int> _integerObservableCollection = new ObservableCollection<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
}
}
quelle
Antworten:
Es wird nicht behauptet, die alten Elemente einzuschließen, da Zurücksetzen nicht bedeutet, dass die Liste gelöscht wurde
Dies bedeutet, dass etwas Dramatisches passiert ist und die Kosten für das Ausarbeiten des Hinzufügens / Entfernens höchstwahrscheinlich die Kosten für das erneute Scannen der Liste von Grund auf übersteigen würden. Das sollten Sie also tun.
MSDN schlägt ein Beispiel für die Neusortierung der gesamten Sammlung als Kandidat für das Zurücksetzen vor.
Wiederholen. Zurücksetzen bedeutet nicht klar , es bedeutet, dass Ihre Annahmen über die Liste jetzt ungültig sind. Behandle es so, als wäre es eine völlig neue Liste . Klar ist zufällig eine Instanz davon, aber es könnte auch andere geben.
Einige Beispiele:
Ich hatte eine Liste wie diese mit vielen Elementen und sie wurde an eine WPF gebunden
ListView
, um sie auf dem Bildschirm anzuzeigen.Wenn Sie die Liste löschen und das
.Reset
Ereignis auslösen, ist die Leistung ziemlich augenblicklich. Wenn Sie jedoch stattdessen viele einzelne.Remove
Ereignisse auslösen, ist die Leistung schrecklich, da WPF die Elemente einzeln entfernt. Ich habe auch.Reset
in meinem eigenen Code angegeben, dass die Liste neu sortiert wurde, anstatt Tausende einzelnerMove
Vorgänge auszugeben . Wie bei Clear gibt es einen großen Leistungseinbruch, wenn viele einzelne Ereignisse ausgelöst werden.quelle
OldItems
beim Löschen einfach alle Elemente einfügen sollten (es wird nur eine Liste kopiert), aber vielleicht gab es ein Szenario, in dem dies zu teuer war. Auf jeden Fall, wenn Sie eine Sammlung wollen , die nicht Sie alle gelöschten Objekte benachrichtigen, wäre es nicht schwer zu tun.Reset
auf eine teure Operation hinweisen soll, ist es sehr wahrscheinlich, dass die gleiche Begründung für das Kopieren über die gesamte Liste giltOldItems
.Reset
bedeutet eigentlich „Der Inhalt der Sammlung wurde gelöscht .“ Siehe msdn.microsoft.com/en-us/library/…Wir hatten hier das gleiche Problem. Die Aktion Zurücksetzen in CollectionChanged enthält keine OldItems. Wir hatten eine Problemumgehung: Wir haben stattdessen die folgende Erweiterungsmethode verwendet:
Wir haben die Funktion Clear () nicht unterstützt und eine NotSupportedException im CollectionChanged-Ereignis für Reset-Aktionen ausgelöst. RemoveAll löst im CollectionChanged-Ereignis eine Remove-Aktion mit den richtigen OldItems aus.
quelle
Eine andere Option besteht darin, das Reset-Ereignis durch ein einzelnes Remove-Ereignis zu ersetzen, dessen gelöschte Elemente in der OldItems-Eigenschaft wie folgt enthalten sind:
Vorteile:
Keine Notwendigkeit, eine zusätzliche Veranstaltung zu abonnieren (wie in der akzeptierten Antwort erforderlich)
Generiert nicht für jedes entfernte Objekt ein Ereignis (einige andere vorgeschlagene Lösungen führen zu mehreren entfernten Ereignissen).
Der Abonnent muss bei jedem Ereignis nur NewItems & OldItems überprüfen, um nach Bedarf Ereignishandler hinzuzufügen / zu entfernen.
Nachteile:
Kein Reset-Ereignis
Kleiner (?) Overhead beim Erstellen einer Kopie der Liste.
???
EDIT 2012-02-23
Wenn Sie an WPF-Listen-basierte Steuerelemente gebunden sind, führt das Löschen einer ObservableCollectionNoReset-Auflistung mit mehreren Elementen leider zu einer Ausnahme "Bereichsaktionen werden nicht unterstützt". Um mit Steuerelementen mit dieser Einschränkung verwendet zu werden, habe ich die ObservableCollectionNoReset-Klasse in Folgendes geändert:
Dies ist nicht so effizient, wenn RangeActionsSupported false ist (Standardeinstellung), da pro Objekt in der Auflistung eine Benachrichtigung zum Entfernen entfernt wird
quelle
Range actions are not supported.
Ich weiß nicht, warum es dies tut, aber jetzt bleibt keine andere Wahl, als jedes Element einzeln zu entfernen ...foreach( NotifyCollectionChangedEventHandler handler in this.CollectionChanged )
If durchläuft.handler.Target is CollectionView
Dann können Sie den Handler mitAction.Reset
Args auslösen, andernfalls können Sie die vollständigen Args bereitstellen. Das Beste aus beiden Welten auf Handler-für-Handler-Basis :). Ein bisschenIch habe eine Lösung gefunden, mit der der Benutzer sowohl die Effizienz des Hinzufügens oder Entfernens vieler Elemente gleichzeitig nutzen kann, während nur ein Ereignis ausgelöst wird, als auch die Anforderungen von UIElements erfüllen kann, um die Action.Reset-Ereignisargumente abzurufen, während alle anderen Benutzer dies tun würden wie eine Liste von Elementen hinzugefügt und entfernt.
Bei dieser Lösung wird das CollectionChanged-Ereignis überschrieben. Wenn wir dieses Ereignis auslösen, können wir tatsächlich das Ziel jedes registrierten Handlers betrachten und dessen Typ bestimmen. Da nur ICollectionView-Klassen Argumente erfordern,
NotifyCollectionChangedAction.Reset
wenn sich mehr als ein Element ändert, können wir sie herausgreifen und allen anderen die richtigen Ereignisargumente geben, die die vollständige Liste der entfernten oder hinzugefügten Elemente enthalten. Unten ist die Implementierung.quelle
Okay, ich weiß, dass dies eine sehr alte Frage ist, aber ich habe eine gute Lösung für das Problem gefunden und dachte, ich würde sie teilen. Diese Lösung wurde von vielen der großartigen Antworten hier inspiriert, bietet jedoch die folgenden Vorteile:
Hier ist der Code:
Diese Erweiterungsmethode verwendet einfach eine,
Action
die aufgerufen wird, bevor die Sammlung gelöscht wird.quelle
Ok, obwohl ich mir immer noch wünsche, dass sich ObservableCollection so verhält, wie ich es mir gewünscht habe ... der folgende Code ist das, was ich letztendlich getan habe. Grundsätzlich habe ich eine neue Sammlung von T mit dem Namen TrulyObservableCollection erstellt und die ClearItems-Methode überschrieben, mit der ich dann ein Clearing-Ereignis ausgelöst habe.
In dem Code, der diese TrulyObservableCollection verwendet, verwende ich dieses Clearing-Ereignis, um die Elemente zu durchlaufen, die sich zu diesem Zeitpunkt noch in der Sammlung befinden, um das Trennen für das Ereignis durchzuführen, von dem ich mich trennen wollte.
Hoffe, dieser Ansatz hilft auch jemand anderem.
quelle
BrokenObservableCollection
, nichtTrulyObservableCollection
- Sie verstehen falsch, was die Rücksetzaktion bedeutet.ActuallyUsefulObservableCollection
. :)Ich habe dieses Problem auf eine etwas andere Art und Weise angegangen, da ich mich für ein Ereignis registrieren und alle Hinzufügungen und Entfernungen im Ereignishandler behandeln wollte. Ich habe damit begonnen, das von der Sammlung geänderte Ereignis zu überschreiben und Rücksetzaktionen auf Entfernungsaktionen mit einer Liste von Elementen umzuleiten. Dies alles ging schief, da ich die beobachtbare Sammlung als Elementquelle für eine Sammlungsansicht verwendete und "Bereichsaktionen nicht unterstützt" erhielt.
Ich habe endlich ein neues Ereignis namens CollectionChangedRange erstellt, das sich so verhält, wie ich es von der eingebauten Version erwartet hatte.
Ich kann mir nicht vorstellen, warum diese Einschränkung zulässig ist, und hoffe, dass dieser Beitrag zumindest andere davon abhält, in die Sackgasse zu geraten, die ich getan habe.
quelle
So funktioniert ObservableCollection. Sie können dies umgehen, indem Sie Ihre eigene Liste außerhalb der ObservableCollection belassen (Hinzufügen zur Liste, wenn Aktion Hinzufügen ist, Entfernen, wenn Aktion Entfernen ist usw.). Anschließend können Sie alle entfernten Elemente (oder hinzugefügten Elemente) abrufen ) Wenn die Aktion zurückgesetzt wird, vergleichen Sie Ihre Liste mit der ObservableCollection.
Eine andere Möglichkeit besteht darin, eine eigene Klasse zu erstellen, die IList und INotifyCollectionChanged implementiert. Anschließend können Sie Ereignisse innerhalb dieser Klasse anhängen und trennen (oder OldItems auf Clear setzen, wenn Sie möchten) - es ist wirklich nicht schwierig, aber es ist viel Tippen.
quelle
Für das Szenario des Anhängens und Trennens von Ereignishandlern an die Elemente der ObservableCollection gibt es auch eine "clientseitige" Lösung. Im Ereignisbehandlungscode können Sie mithilfe der Contains-Methode überprüfen, ob sich der Absender in der ObservableCollection befindet. Pro: Sie können mit jeder vorhandenen ObservableCollection arbeiten. Nachteile: Die Contains-Methode wird mit O (n) ausgeführt, wobei n die Anzahl der Elemente in der ObservableCollection ist. Dies ist also eine Lösung für kleine ObservableCollections.
Eine andere "clientseitige" Lösung besteht darin, einen Ereignishandler in der Mitte zu verwenden. Registrieren Sie einfach alle Ereignisse im Ereignishandler in der Mitte. Dieser Ereignishandler wiederum benachrichtigt den realen Ereignishandler über einen Rückruf oder ein Ereignis. Wenn eine Rücksetzaktion auftritt, entfernen Sie den Rückruf oder das Ereignis. Erstellen Sie einen neuen Ereignishandler in der Mitte und vergessen Sie den alten. Dieser Ansatz funktioniert auch für große ObservableCollections. Ich habe dies für das PropertyChanged-Ereignis verwendet (siehe Code unten).
quelle
Bei Betrachtung der NotifyCollectionChangedEventArgs scheint OldItems nur Elemente zu enthalten, die aufgrund der Aktion Ersetzen, Entfernen oder Verschieben geändert wurden. Es zeigt nicht an, dass es irgendetwas auf Clear enthalten wird. Ich vermute, dass Clear das Ereignis auslöst, die entfernten Elemente jedoch nicht registriert und den Code zum Entfernen überhaupt nicht aufruft.
quelle
Nun, ich habe beschlossen, mich selbst damit schmutzig zu machen.
Microsoft hat viel Arbeit investiert, um sicherzustellen, dass NotifyCollectionChangedEventArgs beim Aufrufen eines Resets keine Daten enthält. Ich gehe davon aus, dass dies eine Entscheidung über Leistung / Gedächtnis war. Wenn Sie eine Sammlung mit 100.000 Elementen zurücksetzen, gehe ich davon aus, dass sie nicht alle diese Elemente duplizieren wollten.
Da meine Sammlungen jedoch nie mehr als 100 Elemente enthalten, sehe ich kein Problem damit.
Wie auch immer, ich habe eine geerbte Klasse mit der folgenden Methode erstellt:
quelle
Die ObservableCollection- und die INotifyCollectionChanged-Schnittstelle sind klar für eine bestimmte Verwendung geschrieben: UI-Erstellung und ihre spezifischen Leistungsmerkmale.
Wenn Sie Benachrichtigungen über Sammlungsänderungen wünschen, sind Sie im Allgemeinen nur an Ereignissen zum Hinzufügen und Entfernen interessiert.
Ich benutze die folgende Schnittstelle:
Ich habe auch meine eigene Überladung von Collection geschrieben, wo:
Natürlich kann auch AddRange hinzugefügt werden.
quelle
Ich habe gerade einen Teil des Diagrammcodes in den Silverlight- und WPF-Toolkits durchgesehen und festgestellt, dass sie auch dieses Problem gelöst haben (auf ähnliche Weise) ... und ich dachte, ich würde fortfahren und ihre Lösung veröffentlichen.
Grundsätzlich haben sie auch eine abgeleitete ObservableCollection erstellt und ClearItems überschrieben, wobei bei jedem zu löschenden Element Remove aufgerufen wurde.
Hier ist der Code:
quelle
Dies ist ein heißes Thema ... weil Microsoft meiner Meinung nach seine Arbeit nicht richtig gemacht hat ... noch einmal. Versteht mich nicht falsch, ich mag Microsoft, aber sie sind nicht perfekt!
Ich habe die meisten vorherigen Kommentare gelesen. Ich stimme allen zu, die der Meinung sind, dass Microsoft Clear () nicht richtig programmiert hat.
Zumindest meiner Meinung nach braucht es ein Argument, um Objekte von einem Ereignis trennen zu können ... aber ich verstehe auch die Auswirkungen. Dann habe ich mir diese vorgeschlagene Lösung ausgedacht.
Ich hoffe, es wird alle glücklich machen, oder zumindest die meisten ...
Eric
quelle
Um es einfach zu halten, warum überschreiben Sie nicht die ClearItem-Methode und tun dort, was Sie wollen, dh trennen Sie die Elemente vom Ereignis.
Einfach, sauber und im Sammlungscode enthalten.
quelle
Ich hatte das gleiche Problem und dies war meine Lösung. Es scheint zu funktionieren. Hat jemand mögliche Probleme mit diesem Ansatz?
Hier sind einige andere nützliche Methoden in meiner Klasse:
quelle
Ich habe eine andere "einfache" Lösung gefunden, die von ObservableCollection abgeleitet ist, aber sie ist nicht sehr elegant, weil sie Reflection verwendet ... Wenn es Ihnen gefällt, ist hier meine Lösung:
Hier speichere ich die aktuellen Elemente in einem Array-Feld in der ClearItems-Methode, fange dann den Aufruf von OnCollectionChanged ab und überschreibe das private Feld e._oldItems (über Reflections), bevor ich base.OnCollectionChanged starte
quelle
Sie können die ClearItems-Methode überschreiben und das Ereignis mit Remove action und OldItems auslösen.
Teil der
System.Collections.ObjectModel.ObservableCollection<T>
Realisierung:quelle
http://msdn.microsoft.com/en-us/library/system.collections.specialized.notifycollectionchangedaction(VS.95).aspx
Bitte lesen Sie diese Dokumentation mit offenen Augen und eingeschaltetem Gehirn. Microsoft hat alles richtig gemacht. Sie müssen Ihre Sammlung erneut scannen, wenn eine Reset-Benachrichtigung für Sie ausgelöst wird. Sie erhalten eine Reset-Benachrichtigung, da das Auslösen von Hinzufügen / Entfernen für jedes Element (das aus der Sammlung entfernt und wieder hinzugefügt wird) zu teuer ist.
Orion Edwards hat vollkommen recht (Respekt, Mann). Bitte denken Sie beim Lesen der Dokumentation weiter nach.
quelle
Wenn Ihr
ObservableCollection
nicht klar wird, können Sie diesen Code unten versuchen. es kann Ihnen helfen:quelle