У меня есть кое-что, что действительно застало меня врасплох.
У меня есть ObservableCollection of T, заполненный элементами. У меня также есть обработчик событий, прикрепленный к событию CollectionChanged.
Когда вы очистить коллекцию он вызывает событие CollectionChanged с e.Action набором для NotifyCollectionChangedAction.Reset. Хорошо, это нормально. Но что странно, ни в e.OldItems, ни в e.NewItems ничего нет. Я ожидал, что e.OldItems будет заполнен всеми элементами, которые были удалены из коллекции.
Кто-нибудь еще видел это? И если да, то как они это решили?
Немного предыстории: я использую событие CollectionChanged для присоединения и отсоединения от другого события, и поэтому, если я не получу никаких элементов в e.OldItems ... я не смогу отсоединиться от этого события.
УТОЧНЕНИЕ: Я знаю, что в документации прямо не говорится, что он должен вести себя подобным образом. Но для каждого другого действия он уведомляет меня о том, что он сделал. Итак, я предполагаю, что он скажет мне ... и в случае Clear / Reset.
Ниже приведен пример кода, если вы хотите воспроизвести его самостоятельно. Во-первых, 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>
Далее код:
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 };
}
}
источник
Ответы:
Он не претендует на включение старых элементов, потому что Reset не означает, что список был очищен.
Это означает, что произошла какая-то драматическая вещь, и стоимость работы над добавлением / удалением, скорее всего, превысит стоимость простого повторного сканирования списка с нуля ... так что вам следует поступить так.
MSDN предлагает пример повторной сортировки всей коллекции как кандидата на сброс.
Повторить. Сброс не означает очистить , это означает, что Ваши предположения о списке теперь неверны. Относитесь к нему как к совершенно новому списку . Clear является одним из примеров этого, но могут быть и другие.
Некоторые примеры:
у меня был такой список, в котором было много элементов, и он был привязан к WPF
ListView
для отображения на экране.Если вы очистите список и вызовете
.Reset
событие, производительность будет практически мгновенной, но если вместо этого вы создадите много отдельных.Remove
событий, производительность будет ужасной, поскольку WPF удаляет элементы один за другим. Я также использовал.Reset
в своем собственном коде, чтобы указать, что список был повторно отсортирован, вместо того, чтобы выполнять тысячи отдельныхMove
операций. Как и в случае с Clear, при возникновении множества отдельных событий производительность сильно падает.источник
OldItems
при очистке (это просто копирование списка), но, возможно, был какой-то сценарий, когда это было слишком дорого. Во всяком случае, если вы хотите коллекцию , которая делает уведомление о всех удаленных элементов, это не было бы трудно сделать.Reset
это указать на дорогостоящую операцию, очень вероятно, что те же рассуждения применимы к копированию всего списка вOldItems
.Reset
самом деле означает «Содержимое коллекции было очищено ». См. Msdn.microsoft.com/en-us/library/…У нас была такая же проблема. Действие Reset в CollectionChanged не включает OldItems. У нас был обходной путь: вместо этого мы использовали следующий метод расширения:
public static void RemoveAll(this IList list) { while (list.Count > 0) { list.RemoveAt(list.Count - 1); } }
В итоге мы перестали поддерживать функцию Clear () и выбросили NotSupportedException в событии CollectionChanged для действий Reset. RemoveAll вызовет действие Remove в событии CollectionChanged с соответствующими OldItems.
источник
Другой вариант - заменить событие Reset одним событием Remove, которое содержит все очищенные элементы в своем свойстве OldItems следующим образом:
public class ObservableCollectionNoReset<T> : ObservableCollection<T> { protected override void ClearItems() { List<T> removed = new List<T>(this); base.ClearItems(); base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed)); } protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { if (e.Action != NotifyCollectionChangedAction.Reset) base.OnCollectionChanged(e); } // Constructors omitted ... }
Преимущества:
Нет необходимости подписываться на дополнительное событие (в соответствии с принятым ответом)
Не генерирует событие для каждого удаленного объекта (некоторые другие предлагаемые решения приводят к нескольким удаленным событиям).
Подписчику нужно только проверить NewItems и OldItems для любого события, чтобы добавить / удалить обработчики событий по мере необходимости.
Недостатки:
Нет события сброса
Небольшие (?) Накладные расходы на создание копии списка.
???
РЕДАКТИРОВАТЬ 2012-02-23
К сожалению, при привязке к элементам управления на основе списка WPF очистка коллекции ObservableCollectionNoReset с несколькими элементами приведет к возникновению исключения «Действия диапазона не поддерживаются». Для использования с элементами управления с этим ограничением я изменил класс ObservableCollectionNoReset на:
public class ObservableCollectionNoReset<T> : ObservableCollection<T> { // Some CollectionChanged listeners don't support range actions. public Boolean RangeActionsSupported { get; set; } protected override void ClearItems() { if (RangeActionsSupported) { List<T> removed = new List<T>(this); base.ClearItems(); base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed)); } else { while (Count > 0 ) base.RemoveAt(Count - 1); } } protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { if (e.Action != NotifyCollectionChangedAction.Reset) base.OnCollectionChanged(e); } public ObservableCollectionNoReset(Boolean rangeActionsSupported = false) { RangeActionsSupported = rangeActionsSupported; } // Additional constructors omitted. }
Это не так эффективно, если RangeActionsSupported имеет значение false (по умолчанию), поскольку для каждого объекта в коллекции создается одно уведомление об удалении.
источник
Range actions are not supported.
я не знаю, почему он это делает, но теперь это не оставляет другого выбора, кроме как удалять каждый элемент по одному ...foreach( NotifyCollectionChangedEventHandler handler in this.CollectionChanged )
Ifhandler.Target is CollectionView
, тогда вы можете запустить обработчик с помощьюAction.Reset
аргументов, в противном случае вы можете предоставить полные аргументы. Лучшее из обоих миров в зависимости от обработчика :). Вроде как здесь: stackoverflow.com/a/3302917/529618Хорошо, я знаю, что это очень старый вопрос, но я нашел хорошее решение проблемы и подумал, что поделюсь. Это решение черпает вдохновение из многих замечательных ответов здесь, но имеет следующие преимущества:
Вот код:
public static void Clear<T>(this ObservableCollection<T> collection, Action<ObservableCollection<T>> unhookAction) { unhookAction.Invoke(collection); collection.Clear(); }
Этот метод расширения просто принимает объект,
Action
который будет вызываться перед очисткой коллекции.источник
Я нашел решение, которое позволяет пользователю как извлечь выгоду из эффективности добавления или удаления множества элементов за раз при запуске только одного события, так и удовлетворить потребности UIElements в получении аргументов события Action.Reset, в то время как все остальные пользователи будут как список добавленных и удаленных элементов.
Это решение включает в себя переопределение события CollectionChanged. Когда мы запускаем это событие, мы действительно можем посмотреть на цель каждого зарегистрированного обработчика и определить их тип. Поскольку только классы ICollectionView требуют
NotifyCollectionChangedAction.Reset
аргументов при изменении более одного элемента, мы можем выделить их и предоставить всем остальным правильные аргументы событий, которые содержат полный список элементов, удаленных или добавленных. Ниже представлена реализация.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 }
источник
Хорошо, хотя я все еще хочу, чтобы ObservableCollection вела себя так, как я хотел ... приведенный ниже код - это то, что я в итоге сделал. По сути, я создал новую коллекцию T под названием TrulyObservableCollection и переопределил метод ClearItems, который затем использовал для создания события очистки.
В коде, который использует эту TrulyObservableCollection, я использую это событие Clearing для циклического перебора элементов , которые все еще находятся в коллекции на тот момент, чтобы выполнить отсоединение от события, от которого я хотел отсоединиться.
Надеюсь, этот подход поможет и кому-то другому.
public class TrulyObservableCollection<T> : ObservableCollection<T> { public event EventHandler<EventArgs> Clearing; protected virtual void OnClearing(EventArgs e) { if (Clearing != null) Clearing(this, e); } protected override void ClearItems() { OnClearing(EventArgs.Empty); base.ClearItems(); } }
источник
BrokenObservableCollection
, а неTrulyObservableCollection
- вы неправильно понимаете, что означает действие сброса.ActuallyUsefulObservableCollection
. :)Я подошел к этому несколько иначе, так как хотел зарегистрироваться на одно событие и обрабатывать все добавления и удаления в обработчике событий. Я начал с переопределения события изменения коллекции и перенаправления действий сброса на действия удаления со списком элементов. Все пошло не так, поскольку я использовал наблюдаемую коллекцию в качестве источника элементов для представления коллекции и получил «Действия диапазона не поддерживаются».
Наконец, я создал новое событие под названием CollectionChangedRange, которое действует так, как я ожидал от встроенной версии.
Я не могу себе представить, почему это ограничение было разрешено, и надеюсь, что этот пост, по крайней мере, остановит других от того, чтобы зайти в тупик, который я сделал.
/// <summary> /// An observable collection with support for addrange and clear /// </summary> /// <typeparam name="T"></typeparam> [Serializable] [TypeConverter(typeof(ExpandableObjectConverter))] public class ObservableCollectionRange<T> : ObservableCollection<T> { private bool _addingRange; [field: NonSerialized] public event NotifyCollectionChangedEventHandler CollectionChangedRange; protected virtual void OnCollectionChangedRange(NotifyCollectionChangedEventArgs e) { if ((CollectionChangedRange == null) || _addingRange) return; using (BlockReentrancy()) { CollectionChangedRange(this, e); } } public void AddRange(IEnumerable<T> collection) { CheckReentrancy(); var newItems = new List<T>(); if ((collection == null) || (Items == null)) return; using (var enumerator = collection.GetEnumerator()) { while (enumerator.MoveNext()) { _addingRange = true; Add(enumerator.Current); _addingRange = false; newItems.Add(enumerator.Current); } } OnCollectionChangedRange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, newItems)); } protected override void ClearItems() { CheckReentrancy(); var oldItems = new List<T>(this); base.ClearItems(); OnCollectionChangedRange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, oldItems)); } protected override void InsertItem(int index, T item) { CheckReentrancy(); base.InsertItem(index, item); OnCollectionChangedRange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)); } protected override void MoveItem(int oldIndex, int newIndex) { CheckReentrancy(); var item = base[oldIndex]; base.MoveItem(oldIndex, newIndex); OnCollectionChangedRange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, item, newIndex, oldIndex)); } protected override void RemoveItem(int index) { CheckReentrancy(); var item = base[index]; base.RemoveItem(index); OnCollectionChangedRange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index)); } protected override void SetItem(int index, T item) { CheckReentrancy(); var oldItem = base[index]; base.SetItem(index, item); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, oldItem, item, index)); } } /// <summary> /// A read only observable collection with support for addrange and clear /// </summary> /// <typeparam name="T"></typeparam> [Serializable] [TypeConverter(typeof(ExpandableObjectConverter))] public class ReadOnlyObservableCollectionRange<T> : ReadOnlyObservableCollection<T> { [field: NonSerialized] public event NotifyCollectionChangedEventHandler CollectionChangedRange; public ReadOnlyObservableCollectionRange(ObservableCollectionRange<T> list) : base(list) { list.CollectionChangedRange += HandleCollectionChangedRange; } private void HandleCollectionChangedRange(object sender, NotifyCollectionChangedEventArgs e) { OnCollectionChangedRange(e); } protected virtual void OnCollectionChangedRange(NotifyCollectionChangedEventArgs args) { if (CollectionChangedRange != null) { CollectionChangedRange(this, args); } } }
источник
Вот как работает ObservableCollection, вы можете обойти это, сохранив свой собственный список вне ObservableCollection (добавление в список, когда действие - Добавить, удалить, когда действие - Удалить и т. Д.), Тогда вы можете получить все удаленные элементы (или добавленные элементы. ), когда действие равно Reset, сравнивая ваш список с ObservableCollection.
Другой вариант - создать свой собственный класс, реализующий IList и INotifyCollectionChanged, затем вы можете присоединять и отсоединять события из этого класса (или устанавливать OldItems на Clear, если хотите) - это действительно несложно, но требует много ввода.
источник
Для сценария присоединения и отсоединения обработчиков событий к элементам ObservableCollection также существует «клиентское» решение. В коде обработки событий вы можете проверить, находится ли отправитель в ObservableCollection, используя метод Contains. Pro: вы можете работать с любой существующей ObservableCollection. Минусы: метод Contains выполняется с O (n), где n - количество элементов в ObservableCollection. Итак, это решение для небольших ObservableCollections.
Другое «клиентское» решение - использовать обработчик событий посередине. Просто зарегистрируйте все события в обработчике событий посередине. Этот обработчик событий, в свою очередь, уведомляет реальный обработчик событий посредством обратного вызова или события. Если происходит действие Reset, удалите обратный вызов или событие, создайте новый обработчик событий посередине и забудьте о старом. Этот подход также работает для больших ObservableCollections. Я использовал это для события PropertyChanged (см. Код ниже).
/// <summary> /// Helper class that allows to "detach" all current Eventhandlers by setting /// DelegateHandler to null. /// </summary> public class PropertyChangedDelegator { /// <summary> /// Callback to the real event handling code. /// </summary> public PropertyChangedEventHandler DelegateHandler; /// <summary> /// Eventhandler that is registered by the elements. /// </summary> /// <param name="sender">the element that has been changed.</param> /// <param name="e">the event arguments</param> public void PropertyChangedHandler(Object sender, PropertyChangedEventArgs e) { if (DelegateHandler != null) { DelegateHandler(sender, e); } else { INotifyPropertyChanged s = sender as INotifyPropertyChanged; if (s != null) s.PropertyChanged -= PropertyChangedHandler; } } }
источник
Глядя на NotifyCollectionChangedEventArgs , кажется, что OldItems содержит только элементы, измененные в результате действия Replace, Remove или Move. Это не означает, что он будет содержать что-либо на Clear. Я подозреваю, что Clear запускает событие, но не регистрирует удаленные элементы и вообще не вызывает код удаления.
источник
Что ж, решил сам испачкаться.
Microsoft приложила ОЧЕНЬ много работы, чтобы всегда убедиться, что NotifyCollectionChangedEventArgs не имеет данных при вызове сброса. Я предполагаю, что это было решение производительности / памяти. Если вы сбрасываете коллекцию из 100 000 элементов, я предполагаю, что они не хотели дублировать все эти элементы.
Но поскольку в моих коллекциях никогда не бывает более 100 элементов, я не вижу в этом проблемы.
В любом случае я создал унаследованный класс следующим методом:
protected override void ClearItems() { CheckReentrancy(); List<TItem> oldItems = new List<TItem>(Items); Items.Clear(); OnPropertyChanged(new PropertyChangedEventArgs("Count")); OnPropertyChanged(new PropertyChangedEventArgs("Item[]")); NotifyCollectionChangedEventArgs e = new NotifyCollectionChangedEventArgs ( NotifyCollectionChangedAction.Reset ); FieldInfo field = e.GetType().GetField ( "_oldItems", BindingFlags.Instance | BindingFlags.NonPublic ); field.SetValue(e, oldItems); OnCollectionChanged(e); }
источник
Интерфейс ObservableCollection, а также интерфейс INotifyCollectionChanged явно написаны с учетом конкретного использования: создание пользовательского интерфейса и его конкретные характеристики производительности.
Если вам нужны уведомления об изменениях коллекции, вас обычно интересуют только события «Добавить и удалить».
Я использую следующий интерфейс:
using System; using System.Collections.Generic; /// <summary> /// Notifies listeners of the following situations: /// <list type="bullet"> /// <item>Elements have been added.</item> /// <item>Elements are about to be removed.</item> /// </list> /// </summary> /// <typeparam name="T">The type of elements in the collection.</typeparam> interface INotifyCollection<T> { /// <summary> /// Occurs when elements have been added. /// </summary> event EventHandler<NotifyCollectionEventArgs<T>> Added; /// <summary> /// Occurs when elements are about to be removed. /// </summary> event EventHandler<NotifyCollectionEventArgs<T>> Removing; } /// <summary> /// Provides data for the NotifyCollection event. /// </summary> /// <typeparam name="T">The type of elements in the collection.</typeparam> public class NotifyCollectionEventArgs<T> : EventArgs { /// <summary> /// Gets or sets the elements. /// </summary> /// <value>The elements.</value> public IEnumerable<T> Items { get; set; } }
Я также написал свою собственную перегрузку Collection, где:
Конечно, можно добавить и AddRange.
источник
Я просто просматривал код построения диаграмм в наборах инструментов Silverlight и WPF и заметил, что они также решили эту проблему (похожим образом) ... и я подумал, что опубликую их решение.
По сути, они также создали производную ObservableCollection и переопределили ClearItems, вызывая Remove для каждого очищаемого элемента.
Вот код:
/// <summary> /// An observable collection that cannot be reset. When clear is called /// items are removed individually, giving listeners the chance to detect /// each remove event and perform operations such as unhooking event /// handlers. /// </summary> /// <typeparam name="T">The type of item in the collection.</typeparam> public class NoResetObservableCollection<T> : ObservableCollection<T> { public NoResetObservableCollection() { } /// <summary> /// Clears all items in the collection by removing them individually. /// </summary> protected override void ClearItems() { IList<T> items = new List<T>(this); foreach (T item in items) { Remove(item); } } }
источник
Это горячая тема ... потому что, на мой взгляд, Microsoft не выполнила свою работу должным образом ... снова. Не поймите меня неправильно, мне нравится Microsoft, но они не идеальны!
Я прочитал большую часть предыдущих комментариев. Я согласен со всеми, кто думает, что Microsoft неправильно запрограммировала Clear ().
На мой взгляд, по крайней мере, ему нужен аргумент, чтобы можно было отделить объекты от события ... но я также понимаю его влияние. Тогда я придумал это предложенное решение.
Я надеюсь, что это сделает всех счастливыми, или, по крайней мере, почти всех ...
Эрик
using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Reflection; namespace WpfUtil.Collections { public static class ObservableCollectionExtension { public static void RemoveAllOneByOne<T>(this ObservableCollection<T> obsColl) { foreach (T item in obsColl) { while (obsColl.Count > 0) { obsColl.RemoveAt(0); } } } public static void RemoveAll<T>(this ObservableCollection<T> obsColl) { if (obsColl.Count > 0) { List<T> removedItems = new List<T>(obsColl); obsColl.Clear(); NotifyCollectionChangedEventArgs e = new NotifyCollectionChangedEventArgs ( NotifyCollectionChangedAction.Remove, removedItems ); var eventInfo = obsColl.GetType().GetField ( "CollectionChanged", BindingFlags.Instance | BindingFlags.NonPublic ); if (eventInfo != null) { var eventMember = eventInfo.GetValue(obsColl); // note: if eventMember is null // nobody registered to the event, you can't call it. if (eventMember != null) eventMember.GetType().GetMethod("Invoke"). Invoke(eventMember, new object[] { obsColl, e }); } } } } }
источник
Чтобы не усложнять, почему бы вам не переопределить метод ClearItem и не сделать там все, что вы хотите, т.е. отсоединить элементы от события.
public class PeopleAttributeList : ObservableCollection<PeopleAttributeDto>, { { protected override void ClearItems() { Do what ever you want base.ClearItems(); } rest of the code omitted }
Простой, чистый и содержащийся в коде коллекции.
источник
У меня была такая же проблема, и это было моим решением. Вроде работает. Кто-нибудь видит потенциальные проблемы с этим подходом?
// overriden so that we can call GetInvocationList public override event NotifyCollectionChangedEventHandler CollectionChanged; protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { NotifyCollectionChangedEventHandler collectionChanged = CollectionChanged; if (collectionChanged != null) { lock (collectionChanged) { foreach (NotifyCollectionChangedEventHandler handler in collectionChanged.GetInvocationList()) { try { handler(this, e); } catch (NotSupportedException ex) { // this will occur if this collection is used as an ItemsControl.ItemsSource if (ex.Message == "Range actions are not supported.") { handler(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } else { throw ex; } } } } } }
Вот еще несколько полезных методов в моем классе:
public void SetItems(IEnumerable<T> newItems) { Items.Clear(); foreach (T newItem in newItems) { Items.Add(newItem); } NotifyCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } public void AddRange(IEnumerable<T> newItems) { int index = Count; foreach (T item in newItems) { Items.Add(item); } NotifyCollectionChangedEventArgs e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new List<T>(newItems), index); NotifyCollectionChanged(e); } public void RemoveRange(int startingIndex, int count) { IList<T> oldItems = new List<T>(); for (int i = 0; i < count; i++) { oldItems.Add(Items[startingIndex]); Items.RemoveAt(startingIndex); } NotifyCollectionChangedEventArgs e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new List<T>(oldItems), startingIndex); NotifyCollectionChanged(e); } // this needs to be overridden to avoid raising a NotifyCollectionChangedEvent with NotifyCollectionChangedAction.Reset, which our other lists don't support new public void Clear() { RemoveRange(0, Count); } public void RemoveWhere(Func<T, bool> criterion) { List<T> removedItems = null; int startingIndex = default(int); int contiguousCount = default(int); for (int i = 0; i < Count; i++) { T item = Items[i]; if (criterion(item)) { if (removedItems == null) { removedItems = new List<T>(); startingIndex = i; contiguousCount = 0; } Items.RemoveAt(i); removedItems.Add(item); contiguousCount++; } else if (removedItems != null) { NotifyCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removedItems, startingIndex)); removedItems = null; i = startingIndex; } } if (removedItems != null) { NotifyCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removedItems, startingIndex)); } } private void NotifyCollectionChanged(NotifyCollectionChangedEventArgs e) { OnPropertyChanged(new PropertyChangedEventArgs("Count")); OnPropertyChanged(new PropertyChangedEventArgs("Item[]")); OnCollectionChanged(e); }
источник
Я нашел еще одно «простое» решение, основанное на ObservableCollection, но оно не очень элегантно, потому что использует Reflection ... Если вам это нравится, вот мое решение:
public class ObservableCollectionClearable<T> : ObservableCollection<T> { private T[] ClearingItems = null; protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { switch (e.Action) { case System.Collections.Specialized.NotifyCollectionChangedAction.Reset: if (this.ClearingItems != null) { ReplaceOldItems(e, this.ClearingItems); this.ClearingItems = null; } break; } base.OnCollectionChanged(e); } protected override void ClearItems() { this.ClearingItems = this.ToArray(); base.ClearItems(); } private static void ReplaceOldItems(System.Collections.Specialized.NotifyCollectionChangedEventArgs e, T[] olditems) { Type t = e.GetType(); System.Reflection.FieldInfo foldItems = t.GetField("_oldItems", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); if (foldItems != null) { foldItems.SetValue(e, olditems); } } }
Здесь я сохраняю текущие элементы в поле массива в методе ClearItems, затем перехватываю вызов OnCollectionChanged и перезаписываю приватное поле e._oldItems (через Reflections) перед запуском base.OnCollectionChanged
источник
Вы можете переопределить метод ClearItems и вызвать событие с помощью действия Remove и OldItems.
public class ObservableCollection<T> : System.Collections.ObjectModel.ObservableCollection<T> { protected override void ClearItems() { CheckReentrancy(); var items = Items.ToList(); base.ClearItems(); OnPropertyChanged(new PropertyChangedEventArgs("Count")); OnPropertyChanged(new PropertyChangedEventArgs("Item[]")); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, items, -1)); } }
Часть
System.Collections.ObjectModel.ObservableCollection<T>
реализации:public class ObservableCollection<T> : Collection<T>, INotifyCollectionChanged, INotifyPropertyChanged { protected override void ClearItems() { CheckReentrancy(); base.ClearItems(); OnPropertyChanged(CountString); OnPropertyChanged(IndexerName); OnCollectionReset(); } private void OnPropertyChanged(string propertyName) { OnPropertyChanged(new PropertyChangedEventArgs(propertyName)); } private void OnCollectionReset() { OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } private const string CountString = "Count"; private const string IndexerName = "Item[]"; }
источник
http://msdn.microsoft.com/en-us/library/system.collections.specialized.notifycollectionchangedaction(VS.95).aspx
Пожалуйста, прочтите эту документацию с открытыми глазами и включенным мозгом. Microsoft все сделала правильно. Вы должны повторно сканировать свою коллекцию, когда она выдает вам уведомление о сбросе. Вы получаете уведомление о сбросе, потому что добавление / удаление каждого элемента (удаляемого и добавляемого обратно в коллекцию) слишком дорого.
Орион Эдвардс полностью прав (респект, чувак). При чтении документации подумайте шире.
источник
Если вам
ObservableCollection
не ясно, вы можете попробовать следующий код. это может помочь вам:private TestEntities context; // This is your context context.Refresh(System.Data.Objects.RefreshMode.StoreWins, context.UserTables); // to refresh the object context
источник