J'ai quelque chose ici qui me prend vraiment au dépourvu.
J'ai une ObservableCollection de T qui est remplie d'éléments. J'ai également un gestionnaire d'événements attaché à l'événement CollectionChanged.
Lorsque vous effacez la collection, cela provoque un événement CollectionChanged avec e.Action défini sur NotifyCollectionChangedAction.Reset. Ok, c'est normal. Mais ce qui est étrange, c'est que ni e.OldItems ni e.NewItems ne contiennent quoi que ce soit. Je m'attendrais à ce que e.OldItems soit rempli de tous les éléments qui ont été supprimés de la collection.
Quelqu'un d'autre a-t-il vu cela? Et si oui, comment l'ont-ils contourné?
Un peu de contexte: j'utilise l'événement CollectionChanged pour attacher et détacher d'un autre événement et donc si je n'obtiens aucun élément dans e.OldItems ... je ne pourrai pas me détacher de cet événement.
CLARIFICATION: Je sais que la documentation n'indique pas carrément qu'elle doit se comporter de cette façon. Mais pour toutes les autres actions, il m'informe de ce qu'il a fait. Donc, je suppose que cela me dirait ... dans le cas de Clear / Reset également.
Vous trouverez ci-dessous l'exemple de code si vous souhaitez le reproduire vous-même. Tout d'abord le 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>
Ensuite, le code derrière:
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 };
}
}
la source
Réponses:
Il ne prétend pas inclure les anciens éléments, car Réinitialiser ne signifie pas que la liste a été effacée
Cela signifie que quelque chose de dramatique s'est produit et que le coût de l'élaboration des ajouts / suppressions dépasserait probablement le coût de la simple réanalyse de la liste à partir de zéro ... c'est donc ce que vous devez faire.
MSDN suggère un exemple de l'ensemble de la collection en cours de re-tri en tant que candidat pour la réinitialisation.
Recommencer. Réinitialiser ne veut pas dire clair , cela signifie que vos hypothèses sur la liste sont maintenant invalides. Traitez-le comme s'il s'agissait d'une liste entièrement nouvelle . Clear se trouve être un exemple de cela, mais il pourrait bien y en avoir d'autres.
Quelques exemples:
j'ai eu une liste comme celle-ci avec beaucoup d'éléments, et elle a été liée à un WPF
ListView
pour s'afficher à l'écran.Si vous effacez la liste et déclenchez l'
.Reset
événement, les performances sont à peu près instantanées, mais si vous augmentez à la place de nombreux.Remove
événements individuels , les performances sont terribles, car WPF supprime les éléments un par un. J'ai également utilisé.Reset
dans mon propre code pour indiquer que la liste a été triée, plutôt que d'émettre des milliers d'Move
opérations individuelles . Comme avec Clear, il y a un grand impact sur les performances lors du déclenchement de nombreux événements individuels.la source
OldItems
lors de la compensation (il s'agit simplement de copier une liste), mais peut-être qu'il y avait un scénario où cela coûtait trop cher. En tout cas, si vous voulez une collection qui ne vous informe de tous les éléments supprimés, il ne serait pas difficile à faire.Reset
pour indiquer une opération coûteuse, il est très probable que le même raisonnement s'applique à la copie de toute la liste versOldItems
.Reset
signifie en fait "Le contenu de la collection a été effacé ." Voir msdn.microsoft.com/en-us/library/…Nous avons eu le même problème ici. L'action Reset dans CollectionChanged n'inclut pas les OldItems. Nous avons eu une solution de contournement: nous avons utilisé à la place la méthode d'extension suivante:
Nous avons fini par ne pas prendre en charge la fonction Clear () et en lançant un événement NotSupportedException dans CollectionChanged pour les actions de réinitialisation. RemoveAll déclenchera une action Remove dans l'événement CollectionChanged, avec les OldItems appropriés.
la source
Une autre option consiste à remplacer l'événement Reset par un seul événement Remove qui a tous les éléments effacés dans sa propriété OldItems comme suit:
Avantages:
Pas besoin de souscrire à un événement supplémentaire (comme requis par la réponse acceptée)
Ne génère pas d'événement pour chaque objet supprimé (certaines autres solutions proposées entraînent plusieurs événements supprimés).
L'abonné doit uniquement vérifier NewItems & OldItems sur n'importe quel événement pour ajouter / supprimer des gestionnaires d'événements si nécessaire.
Désavantages:
Aucun événement de réinitialisation
Petit (?) Frais généraux créant une copie de la liste.
???
MODIFIER 2012-02-23
Malheureusement, lorsqu'il est lié à des contrôles basés sur une liste WPF, la suppression d'une collection ObservableCollectionNoReset avec plusieurs éléments entraînera une exception «Actions de plage non prises en charge». Pour être utilisé avec des contrôles avec cette limitation, j'ai changé la classe ObservableCollectionNoReset en:
Ce n'est pas aussi efficace lorsque RangeActionsSupported est false (la valeur par défaut) car une notification Remove est générée par objet dans la collection
la source
Range actions are not supported.
je ne sais pas pourquoi il fait cela, mais maintenant cela ne laisse aucune autre option que de supprimer chaque élément un à la fois ...foreach( NotifyCollectionChangedEventHandler handler in this.CollectionChanged )
Ifhandler.Target is CollectionView
, alors vous pouvez déclencher le gestionnaire avecAction.Reset
args, sinon, vous pouvez fournir les arguments complets. Le meilleur des deux mondes sur une base gestionnaire par gestionnaire :). Un peu comme ce qui est ici: stackoverflow.com/a/3302917/529618D'accord, je sais que c'est une question très ancienne, mais j'ai trouvé une bonne solution au problème et j'ai pensé partager. Cette solution s'inspire de nombreuses réponses intéressantes ici, mais présente les avantages suivants:
Voici le code:
Cette méthode d'extension prend simplement un
Action
qui sera appelé avant que la collection ne soit effacée.la source
J'ai trouvé une solution qui permet à l'utilisateur à la fois de capitaliser sur l'efficacité de l'ajout ou de la suppression de nombreux éléments à la fois tout en ne déclenchant qu'un seul événement - et de satisfaire les besoins des UIElements pour obtenir les arguments Action.Reset alors que tous les autres utilisateurs le feraient comme une liste d'éléments ajoutés et supprimés.
Cette solution implique de remplacer l'événement CollectionChanged. Lorsque nous allons déclencher cet événement, nous pouvons en fait regarder la cible de chaque gestionnaire enregistré et déterminer leur type. Étant donné que seules les classes ICollectionView nécessitent des
NotifyCollectionChangedAction.Reset
arguments lorsque plus d'un élément change, nous pouvons les distinguer et donner à tous les autres des arguments d'événement appropriés qui contiennent la liste complète des éléments supprimés ou ajoutés. Voici la mise en œuvre.la source
Ok, même si je souhaite toujours que ObservableCollection se comporte comme je le souhaitais ... le code ci-dessous est ce que j'ai fini par faire. Fondamentalement, j'ai créé une nouvelle collection de T appelée TrulyObservableCollection et remplacé la méthode ClearItems que j'ai ensuite utilisée pour déclencher un événement Clearing.
Dans le code qui utilise cette TrulyObservableCollection, j'utilise cet événement Clearing pour parcourir les éléments qui sont toujours dans la collection à ce stade pour effectuer le détachement sur l'événement dont je souhaitais me détacher.
J'espère que cette approche aide aussi quelqu'un d'autre.
la source
BrokenObservableCollection
, nonTrulyObservableCollection
- vous ne comprenez pas ce que signifie l'action de réinitialisation.ActuallyUsefulObservableCollection
. :)J'ai abordé celui-ci d'une manière légèrement différente car je voulais m'inscrire à un événement et gérer tous les ajouts et suppressions dans le gestionnaire d'événements. J'ai commencé à remplacer l'événement de modification de la collection et à rediriger les actions de réinitialisation vers des actions de suppression avec une liste d'éléments. Tout cela a mal tourné car j'utilisais la collection observable comme source d'éléments pour une vue de collection et obtenais "Actions de plage non prises en charge".
J'ai finalement créé un nouvel événement appelé CollectionChangedRange qui agit de la manière dont je m'attendais à ce que la version intégrée agisse.
Je ne peux pas imaginer pourquoi cette limitation serait autorisée et j'espère que ce message empêchera au moins les autres de descendre dans l'impasse que j'ai faite.
la source
C'est ainsi que fonctionne ObservableCollection, vous pouvez contourner ce problème en conservant votre propre liste en dehors de ObservableCollection (l'ajout à la liste lorsque l'action est Ajouter, supprimer lorsque l'action est Supprimer, etc.), vous pouvez alors obtenir tous les éléments supprimés (ou éléments ajoutés ) lorsque l'action est Reset en comparant votre liste à ObservableCollection.
Une autre option consiste à créer votre propre classe qui implémente IList et INotifyCollectionChanged, vous pouvez ensuite attacher et détacher des événements de cette classe (ou définir OldItems sur Clear si vous le souhaitez) - ce n'est vraiment pas difficile, mais c'est beaucoup de frappe.
la source
Pour le scénario d'attachement et de détachement de gestionnaires d'événements aux éléments de ObservableCollection, il existe également une solution «côté client». Dans le code de gestion des événements, vous pouvez vérifier si l'expéditeur est dans ObservableCollection à l'aide de la méthode Contains. Pro: vous pouvez travailler avec n'importe quelle ObservableCollection existante. Inconvénients: la méthode Contains s'exécute avec O (n) où n est le nombre d'éléments dans ObservableCollection. C'est donc une solution pour les petites ObservableCollections.
Une autre solution «côté client» consiste à utiliser un gestionnaire d'événements au milieu. Enregistrez simplement tous les événements dans le gestionnaire d'événements au milieu. Ce gestionnaire d'événements informe à son tour le gestionnaire d'événements réel via un rappel ou un événement. Si une action de réinitialisation se produit, supprimez le rappel ou l'événement, créez un nouveau gestionnaire d'événements au milieu et oubliez l'ancien. Cette approche fonctionne également pour les grandes ObservableCollections. J'ai utilisé ceci pour l'événement PropertyChanged (voir le code ci-dessous).
la source
En regardant le NotifyCollectionChangedEventArgs , il semble que OldItems contient uniquement les éléments modifiés à la suite de l'action Remplacer, Supprimer ou Déplacer. Cela n'indique pas qu'il contiendra quoi que ce soit sur Clear. Je soupçonne que Clear déclenche l'événement, mais n'enregistre pas les éléments supprimés et n'appelle pas du tout le code Remove.
la source
Eh bien, j'ai décidé de me salir moi-même.
Microsoft a mis BEAUCOUP de travail pour toujours s'assurer que NotifyCollectionChangedEventArgs ne dispose d'aucune donnée lors de l'appel d'une réinitialisation. Je suppose que c'était une décision de performance / mémoire. Si vous réinitialisez une collection avec 100 000 éléments, je suppose qu'ils ne voulaient pas dupliquer tous ces éléments.
Mais comme mes collections n'ont jamais plus de 100 éléments, je n'y vois pas de problème.
Quoi qu'il en soit, j'ai créé une classe héritée avec la méthode suivante:
la source
L'ObservableCollection ainsi que l'interface INotifyCollectionChanged sont clairement écrites avec une utilisation spécifique à l'esprit: la construction de l'interface utilisateur et ses caractéristiques de performance spécifiques.
Lorsque vous voulez des notifications de modifications de collection, vous n'êtes généralement intéressé que par les événements Ajouter et supprimer.
J'utilise l'interface suivante:
J'ai également écrit ma propre surcharge de Collection où:
Bien sûr, AddRange peut également être ajouté.
la source
Je passais juste en revue une partie du code de cartographie dans les boîtes à outils Silverlight et WPF et j'ai remarqué qu'ils résolvaient également ce problème (d'une manière similaire) ... et j'ai pensé que j'irais de l'avant et publierais leur solution.
Fondamentalement, ils ont également créé une ObservableCollection dérivée et remplacé ClearItems, appelant Remove sur chaque élément en cours d'effacement.
Voici le code:
la source
C'est un sujet brûlant ... car à mon avis, Microsoft n'a pas fait son travail correctement ... encore une fois. Ne vous méprenez pas, j'aime Microsoft, mais ils ne sont pas parfaits!
J'ai lu la plupart des commentaires précédents. Je suis d'accord avec tous ceux qui pensent que Microsoft n'a pas programmé Clear () correctement.
À mon avis, au moins, il faut un argument pour permettre de détacher des objets d'un événement ... mais je comprends aussi l'impact de celui-ci. Ensuite, j'ai imaginé cette solution proposée.
J'espère que cela rendra tout le monde heureux, ou du moins, presque tout le monde ...
Eric
la source
Pour faire simple, pourquoi ne pas remplacer la méthode ClearItem et faire ce que vous voulez, c'est-à-dire détacher les éléments de l'événement.
Simple, propre et contenu dans le code de la collection.
la source
J'ai eu le même problème, et c'était ma solution. Cela semble fonctionner. Quelqu'un voit-il des problèmes potentiels avec cette approche?
Voici quelques autres méthodes utiles dans ma classe:
la source
J'ai trouvé une autre solution "simple" dérivant d'ObservableCollection, mais elle n'est pas très élégante car elle utilise Reflection ... Si vous l'aimez voici ma solution:
Ici, je sauvegarde les éléments actuels dans un champ de tableau dans la méthode ClearItems, puis j'intercepte l'appel de OnCollectionChanged et j'écrase le champ privé e._oldItems (via Reflections) avant de lancer base.OnCollectionChanged
la source
Vous pouvez remplacer la méthode ClearItems et déclencher un événement avec l'action Remove et OldItems.
Partie de
System.Collections.ObjectModel.ObservableCollection<T>
réalisation:la source
http://msdn.microsoft.com/en-us/library/system.collections.specialized.notifycollectionchangedaction(VS.95).aspx
Veuillez lire cette documentation les yeux ouverts et le cerveau allumé. Microsoft a tout fait correctement. Vous devez réexaminer votre collection lorsqu'elle envoie une notification de réinitialisation à votre place. Vous recevez une notification de réinitialisation, car lancer Ajouter / Supprimer pour chaque élément (être supprimé et ajouté à la collection) est trop coûteux.
Orion Edwards a tout à fait raison (respect, mec). Veuillez réfléchir plus largement lorsque vous lisez la documentation.
la source
Si votre
ObservableCollection
n'est pas clair, vous pouvez essayer le code ci-dessous. cela peut vous aider:la source