11
votes

Observablecollection: appelant oncollectionchangted avec plusieurs nouveaux articles

S'il vous plaît noter que je suis en train d'utiliser l'action NotifyCollectionChangedAction.Add au lieu de .Reset. celui-ci fonctionne, mais il est pas très efficace avec de grandes collections.

i ObservableCollection sous-classé: p>

TestObservableCollection<Trade> testCollection = new TestObservableCollection<Trade>();
List<Trade> testTrades = new List<Trade>();

for (int i = 0; i < 200000; i++) 
    testTrades.Add(t);

testCollection.AddRange(testTrades); // no problems here.. 
_trades.AddRange(testTrades); // this one is bound to ListView .. BOOOM!!!


1 commentaires

Pourquoi removerange, addition d'incendie réinitialisée? Peut-être que quelqu'un ne pouvait pas comprendre la différence entre Supprimer, ajouter et réinitialiser les significations?


4 Réponses :


-1
votes

Je crois que vous devez la jeter à un ilist :

base.onCollectionchangned (New NotifyCollectionChangeDeventargs (notifyCollectionChangeChangeDaction.add, (IList) _Cacheditems));


5 commentaires

Merci, maintenant je suis de retour à "une collection Ajouter une manifestation fait référence à un élément qui n'appartient pas à la collection"


hmmm, que diriez-vous au lieu de items.add (élément) , base.add (élément) ?


George, essayé ajouter (article) et base.add (article), la plaint toujours ne fait pas partie de la collection ...


En fait, je voulais dire this.add (article) , désolé de ça ... j'ai déjà reçu cette erreur lors de la mise en œuvre d'un Observablecollection , mais je n'ai pas Accédez au code pour le moment je vais y arriver demain.


Isnt this.add identique que Ajouter? Quoi qu'il en soit, j'ai essayé juste d'être sûr, et toujours la même erreur .. Je pense que cela peut être un bug :(



9
votes

Vous pouvez implémenter Addrange () pour l'observablecollection comme celui-ci comme indiqué comme indiqué Ici : xxx

mise à jour: après la liaison à la liste de la liste, je voyais aussi une invalideOrationException également (même message que vous voyiez). Selon ce Article < / a> C'est parce que CollectionView ne prend pas en charge les actions de la plage. Heureusement, l'article fournit également une solution (bien que cela ressent un peu "hack-ish").

Mise à jour 2: Ajout d'une solution qui soulève l'événement collectif transféré dans la mise en œuvre ultérieure de l'encollectionChanged ().


10 commentaires

Merci, mais j'essaie de changer d'action .Reset. Tout cela est que je veux ajouter uniquement de nouveaux articles. Si ma collection atteint une grande taille, .Reset est très lente car je le filtrant aussi


Ah, j'ai raté que - a mis à jour mon code pour utiliser notifiercollectionChangeDaction.add au lieu de réinitialiser.


Ajout d'un lien et de code qui résout (évite) le problème de la collectionVoir les opérations de plage.


hmm ... j'ai branché votre collection en tant que et Addrange fonctionne avec une lancée !!! :) .. Mais pour une raison mystérieuse des articles ne sont pas ajoutés à partir de normal .ADD (FOO) .. Très probablement Le problème est de mon côté .. Enquête ..


Nope, désolé, ce n'était pas toi. Ma mise en œuvre n'a pas soulevé l'événement de collection remplaçable dans la méthode ultérieure de l'onCollectionChange. Corrigé cela, maintenant ajouter () fonctionne aussi.


Merci! Toute façon que vous puissiez élaborer pourquoi getinvocactlist doit être appelé dans ce scénario et pourquoi la collectionVoirie doit être rafraîchie. Est-ce que cela l'équivalent d'un événement de réinitialisation juste?


Yah .. On dirait qu'il n'y a pas d'avantage de la performance de le faire de cette façon. (Identique que simplement appeler .Reset)


@SONICSOUL - Collectionview pour la liste de liste (dans ce cas) est une liste ListCollectionview, qui ne prend pas en charge l'ajout en vrac (ou la suppression) des éléments. Par conséquent, si vous tirez ces événements, la liste ListCollectionView ne sait pas comment les traiter. La collectionview.refresh () est de compenser cela. Si vous souhaitez une collection compatible avec tous les envois de collection, vous devez déclencher des événements individuels pour chaque article ajouté / supprimé.


Dans CollectionChanged Handler Collection Newitems Il n'existe qu'un seul élément, qui est de liste de types. Est-il possible d'utiliser une version de NotifyCollectionChangeDeventargs (notifyCollectionChangeDacDacti sur.Ajouter, liste) qui conduit à plus d'un article de la collection NewItems?


Quel est le but de la base.onCollectionChanged (e)? Est-ce que quelqu'un pourrait s'abonner sur la base.collectionChanged?



3
votes

Merci pour l'inspiration andyp. J'ai eu quelques problèmes avec votre implémentation, tels que l'utilisation de CollectionView au lieu d'IcollectionView dans le test et appelant manuellement "réinitialiser" sur les éléments. Les éléments qui hériter de CollectionView pourraient réellement traiter ces arguments de manière à d'autres moyens que d'appeler «cela.ReSet ()», il est donc préférable de toujours tirer leurs gestionnaires, juste avec l'action = réinitialiser arguments qu'ils ont besoin au lieu de l'amélioration de l'événement que Inclure la liste des éléments modifiés. Vous trouverez ci-dessous ma mise en œuvre (très similaire).

public class BaseObservableCollection<T> : ObservableCollection<T>
{
    //Flag used to prevent OnCollectionChanged from firing during a bulk operation like Add(IEnumerable<T>) and Clear()
    private bool _SuppressCollectionChanged = false;

    /// Overridden so that we may manually call registered handlers and differentiate between those that do and don't require Action.Reset args.
    public override event NotifyCollectionChangedEventHandler CollectionChanged;

    public BaseObservableCollection() : base(){}
    public BaseObservableCollection(IEnumerable<T> data) : base(data){}

    #region Event Handlers
    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        if( !_SuppressCollectionChanged )
        {
            base.OnCollectionChanged(e);
            if( CollectionChanged != null )
                CollectionChanged.Invoke(this, e);
        }
    }

    //CollectionViews raise an error when they are passed a NotifyCollectionChangedEventArgs that indicates more than
    //one element has been added or removed. They prefer to receive a "Action=Reset" notification, but this is not suitable
    //for applications in code, so we actually check the type we're notifying on and pass a customized event args.
    protected virtual void OnCollectionChangedMultiItem(NotifyCollectionChangedEventArgs e)
    {
        NotifyCollectionChangedEventHandler handlers = this.CollectionChanged;
        if( handlers != null )
            foreach( NotifyCollectionChangedEventHandler handler in handlers.GetInvocationList() )
                handler(this, !(handler.Target is ICollectionView) ? e : new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }
    #endregion

    #region Extended Collection Methods
    protected override void ClearItems()
    {
        if( this.Count == 0 ) return;

        List<T> removed = new List<T>(this);
        _SuppressCollectionChanged = true;
        base.ClearItems();
        _SuppressCollectionChanged = false;
        OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed));
    }

    public void Add(IEnumerable<T> toAdd)
    {
        if( this == toAdd )
            throw new Exception("Invalid operation. This would result in iterating over a collection as it is being modified.");

        _SuppressCollectionChanged = true;
        foreach( T item in toAdd )
            Add(item);
        _SuppressCollectionChanged = false;
        OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new List<T>(toAdd)));
    }

    public void Remove(IEnumerable<T> toRemove)
    {
        if( this == toRemove )
            throw new Exception("Invalid operation. This would result in iterating over a collection as it is being modified.");

        _SuppressCollectionChanged = true;
        foreach( T item in toRemove )
            Remove(item);
        _SuppressCollectionChanged = false;
        OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new List<T>(toRemove)));
    }
    #endregion
}


2 commentaires

Comment accompliriez-vous cela si la baseobservablecollection est défini dans un projet portable? Je crois que Icollectionview est spécifique à Windows et il n'est donc pas disponible.


@ user2481095, mettez la vérification de l'ICOLLECTIONView (et éventuellement l'action pour cela (si vous utilisez l'ICOLLECTIONView pour que quelque chose, comme l'actualisation de l'appel d'appel () ci-dessus)) dans une méthode virtuelle distincte. Dans votre projet de base portable, la méthode virtuelle ne ferait pas référence à ICollectionview et ne revenait simplement au comportement par défaut. Ensuite, dans une bibliothèque Windows, vous remplacez la classe et la méthode de contrôle pour effectuer la vérification réelle de l'ICollectionview.



2
votes

Après de nombreuses itérations, nous avons fini par cette version de observablerangecollection code> et readonlyobservablerangecollection code> qui est basé sur le code de la réponse acceptée et que nous n'avions pas eu à Modifier au cours des 6 derniers mois:

public class ObservableRangeCollection<T> : ObservableCollection<T>
{
    private bool suppressNotification;

    public ObservableRangeCollection() { }

    public ObservableRangeCollection(IEnumerable<T> items)
        : base(items)
    {
    }

    public override event NotifyCollectionChangedEventHandler CollectionChanged;

    protected virtual void OnCollectionChangedMultiItem(
        NotifyCollectionChangedEventArgs e)
    {
        var handlers = CollectionChanged;
        if (handlers == null) return;

        foreach (NotifyCollectionChangedEventHandler handler in handlers.GetInvocationList())
        {
            if (handler.Target is ReadOnlyObservableCollection<T>
                && !(handler.Target is ReadOnlyObservableRangeCollection<T>))
            {
                throw new NotSupportedException(
                    "ObservableRangeCollection is wrapped in ReadOnlyObservableCollection which might be bound to ItemsControl " +
                    "which is internally using ListCollectionView which does not support range actions.\n" +
                    "Instead of ReadOnlyObservableCollection, use ReadOnlyObservableRangeCollection");
            }
            var collectionView = handler.Target as ICollectionView;
            if (collectionView != null)
            {
                collectionView.Refresh();
            }
            else
            {
                handler(this, e);
            }
        }
    }

    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        if (suppressNotification) return;

        base.OnCollectionChanged(e);
        if (CollectionChanged != null)
        {
            CollectionChanged.Invoke(this, e);
        }
    }

    public void AddRange(IEnumerable<T> items)
    {
        if (items == null) return;

        suppressNotification = true;

        var itemList = items.ToList();

        foreach (var item in itemList)
        {
            Add(item);
        }
        suppressNotification = false;

        if (itemList.Any())
        {
            OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, itemList));
        }
    }

    public void AddRange(params T[] items)
    {
        AddRange((IEnumerable<T>)items);
    }

    public void ReplaceWithRange(IEnumerable<T> items)
    {
        Items.Clear();
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        AddRange(items);
    }

    public void RemoveRange(IEnumerable<T> items)
    {
        suppressNotification = true;

        var removableItems = items.Where(x => Items.Contains(x)).ToList();

        foreach (var item in removableItems)
        {
            Remove(item);
        }

        suppressNotification = false;

        if (removableItems.Any())
        {
            OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removableItems));
        }
    }
}

public class ReadOnlyObservableRangeCollection<T> : ReadOnlyObservableCollection<T>
{
    public ReadOnlyObservableRangeCollection(ObservableCollection<T> list)
        : base(list)
    {            
    }

    protected override event NotifyCollectionChangedEventHandler CollectionChanged;

    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        var handlers = CollectionChanged;
        if (handlers == null) return;

        foreach (NotifyCollectionChangedEventHandler handler in handlers.GetInvocationList())
        {
            var collectionView = handler.Target as ICollectionView;
            if (collectionView != null)
            {
                collectionView.Refresh();
            }
            else
            {
                handler(this, e);
            }
        }
    }
}


2 commentaires

Pourquoi la notification dans remplacer par la plage est après Ajouter ?


Vous souhaitez notifier une seule fois après que tous les articles aient été ajoutés.