2008-09-11 8 views
31

Étudie la classe System.Collections.ObjectModel ObservableCollection<T>. Celui-ci est étrange parce queQuelle collection .Net pour ajouter plusieurs objets à la fois et recevoir des notifications?

  • il a une méthode Add qui prend un seul élément. Pas de AddRange ou équivalent.
  • les arguments d'événement de notification a une propriété NewItems, qui est un IList (d'objets .. non T)

Mon objectif est surtout d'ajouter un lot d'objets à une collection et l'auditeur reçoit aussi le lot dans le cadre de la notification. Est-ce qu'il me manque quelque chose avec ObservableCollection? Y at-il une autre classe qui répond à mes spécifications?

Mise à jour: Je ne veux pas rouler le mien aussi loin que possible. Je devrais construire ajouter/supprimer/changer etc. beaucoup de choses.


Associé Q:
https://stackoverflow.com/questions/670577/observablecollection-doesnt-support-addrange-method-so-i-get-notified-for-each

+4

Gishu, attention, si vous liez à un listview la plupart des mises en œuvre ici va exploser. –

Répondre

1

Hériter de la liste < T> et remplacer les méthodes Ajouter() et AddRange() pour déclencher un événement?

2

Si vous souhaitez hériter d'une sorte de collection, il vaut probablement mieux hériter de System.Collections.ObjectModel.Collection, car il fournit des méthodes virtuelles pour le remplacement. Vous devrez ombrer les méthodes de List si vous suivez cette route.

Je ne suis pas au courant de tout intégré dans des collections qui offrent cette fonctionnalité, bien que je serais heureux d'être corrigée :)

18

Il semble que l'interface INotifyCollectionChanged permet de mettre à jour lorsque plusieurs éléments ont été ajoutés, donc je Je ne sais pas pourquoi ObservableCollection<T> n'a pas de AddRange. Vous pouvez créer une méthode d'extension pour AddRange, mais cela entraînerait un événement pour chaque élément ajouté. Si ce n'est pas acceptable que vous devriez être en mesure d'hériter de ObservableCollection<T> comme suit:

public class MyObservableCollection<T> : ObservableCollection<T> 
{ 
    // matching constructors ... 

    bool isInAddRange = false; 

    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) 
    { 
     // intercept this when it gets called inside the AddRange method. 
     if (!isInAddRange) 
      base.OnCollectionChanged(e); 
    } 


    public void AddRange(IEnumerable<T> items) 
    { 
     isInAddRange = true; 
     foreach (T item in items) 
      Add(item); 
     isInAddRange = false; 

     var e = new NotifyCollectionChangedEventArgs(
      NotifyCollectionChangedAction.Add, 
      items.ToList()); 
     base.OnCollectionChanged(e); 
    } 
} 
+0

L'extrait de code a besoin de quelques corrections .. il n'y a pas de ToList() dans IEnumerable et AddRange devrait prendre une ICollection pour être cohérent ... Depuis que j'ai dû passer ce contretemps temporaire à mes grands projets de rencontrer ma cible hebdomadaire , en affichant mon exemple de code .. un peu plus court. – Gishu

+2

Gishu, la méthode ToList() est une méthode d'extension LINQ disponible sur IEnumerable. –

+0

Got it ... Vous devez définir les paramètres du projet pour utiliser .NET 3.5 et ajouter la référence d'assemblage LINQ et l'utilisation de la directive pour l'obtenir. – Gishu

-1

Méthode d'extension Man à la rescousse!

/// <summary> 
    /// Adds all given items to the collection 
    /// </summary> 
    /// <param name="collection">The collection.</param> 
    /// <param name="toAdd">Objects to add.</param> 
    public static void AddAll<T>(this IList<T> collection, params T[] toAdd) 
    { 
     foreach (var o in toAdd) 
      collection.Add(o); 
    } 
+0

Ceci est douloureusement lent ... par exemple, essayez d'ajouter 3000 éléments à un observable collecte (dire au revoir à l'interface utilisateur pendant 3 minutes) –

+1

Si vous êtes lié à l'interface utilisateur, oui, le plus probable. http://blogs.msdn.com/nathannesbit/archive/2009/04/20/addrange-and-observablecollection.aspx – Will

+0

Bon complément aux autres solutions mais probablement pas le meilleur en matière de performance. Tant que la création de vos propres collections est une option, je pense que les autres solutions sont probablement meilleures à long terme. – jpierson

4

Non seulement est System.Collections.ObjectModel.Collection<T> un bon pari, mais dans l'aide docs il y a an example de la façon de passer outre ses différentes méthodes protégées afin d'obtenir une notification. (Faites défiler jusqu'à l'exemple 2.)

+0

merci - lien utile ... fait une note mentale. – Gishu

6

Eh bien l'idée est la même que celle de fryguybob - un peu bizarre que ObservableCollection soit à moitié fait. Les args événement pour cette chose ne même pas utiliser Generics .. me faire utiliser un IList (c'est tellement .. :) hier suit ... Testée Snippet

using System.Collections.Generic; 
using System.Collections.ObjectModel; 
using System.Collections.Specialized; 

namespace MyNamespace 
{ 
    public class ObservableCollectionWithBatchUpdates<T> : ObservableCollection<T> 
    { 
     public void AddRange(ICollection<T> obNewItems) 
     { 
      IList<T> obAddedItems = new List<T>(); 
      foreach (T obItem in obNewItems) 
      { 
       Items.Add(obItem); 
       obAddedItems.Add(obItem); 
      } 
      NotifyCollectionChangedEventArgs obEvtArgs = new NotifyCollectionChangedEventArgs(
       NotifyCollectionChangedAction.Add, 
       obAddedItems as System.Collections.IList); 
      base.OnCollectionChanged(obEvtArgs); 
     } 

    } 
} 
+4

J'ai déjà essayé cette approche. Malheureusement, cela ne fonctionnera pas pour les liaisons WPF, car les notifications pour plusieurs éléments ne sont pas prises en charge. Voir [ce bug] (https://connect.microsoft.com/WPF/feedback/details/514922/range-actions-not-supported-in-collectionview) sur MS Connect –

4

Si vous utilisez l'une des implémentations ci-dessus qui envoient une commande d'ajout de plage et lier la observablecolletion à une liste, vous obtiendrez cette erreur désagréable.

 
NotSupportedException 
    at System.Windows.Data.ListCollectionView.ValidateCollectionChangedEventArgs(NotifyCollectionChangedEventArgs e) 
    at System.Windows.Data.ListCollectionView.ProcessCollectionChanged(NotifyCollectionChangedEventArgs args) 
    at System.Collections.Specialized.NotifyCollectionChangedEventHandler.Invoke(Object sender, NotifyCollectionChangedEventArgs e) 
    at System.Collections.ObjectModel.ObservableCollection`1.OnCollectionChanged(NotifyCollectionChangedEventArgs e) 

La mise en œuvre, je suis allé avec utilise l'événement Réinitialiser qui est plus uniformément mis en œuvre dans le cadre WPF:

public void AddRange(IEnumerable<T> collection) 
    { 
     foreach (var i in collection) Items.Add(i); 
     OnPropertyChanged("Count"); 
     OnPropertyChanged("Item[]"); 
     OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); 
    } 
+0

vous pourriez aller avec un ajout pour chaque élément, mais votre interface va s'arrêter –

+0

En fait, non, WPF UI rendra par lots, en tant que post-message. Cette fonction se termine avant que l'interface utilisateur ne soit vérifiée pour une actualisation. – cunningdave

+0

Quelqu'un a-t-il essayé cela avec XGED DataGrid WPF? – springy76

0

Pour rapidement vous ajouter pouvez utiliser:

((List<Person>)this.Items).AddRange(NewItems); 
3

Je l'ai vu ce genre de question plusieurs fois, et je me demande pourquoi même Microsoft fait la promotion ObservableCollection partout où ailleurs il y a une meilleure collection déjà thats disponible ..

BindingList<T>

Ce qui vous permet de désactiver les notifications et faites opéra en vrac et ensuite activer les notifications.

+1

Très bon point sur 'BindingList ' mais malheureusement il implémente 'IBindingList ' au lieu de 'IObservable ' ... Ce dernier est nécessaire pour les applications WPF utilisant mvvm. – RonnBlack

1

Une autre solution qui est similaire au modèle de CollectionView:

public class DeferableObservableCollection<T> : ObservableCollection<T> 
{ 
    private int deferLevel; 

    private class DeferHelper<T> : IDisposable 
    { 
     private DeferableObservableCollection<T> owningCollection; 
     public DeferHelper(DeferableObservableCollection<T> owningCollection) 
     { 
      this.owningCollection = owningCollection; 
     } 

     public void Dispose() 
     { 
      owningCollection.EndDefer(); 
     } 
    } 

    private void EndDefer() 
    { 
     if (--deferLevel <= 0) 
     { 
      deferLevel = 0; 
      OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); 
     } 
    } 

    public IDisposable DeferNotifications() 
    { 
     deferLevel++; 
     return new DeferHelper<T>(this); 
    } 

    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) 
    { 
     if (deferLevel == 0) // Not in a defer just send events as normally 
     { 
      base.OnCollectionChanged(e); 
     } // Else notify on EndDefer 
    } 
} 
Questions connexes