ObservableCollectionをクリアすると、e.OldItemsにアイテムがありません
-
03-07-2019 - |
質問
私はここに本当に不意を突かれる何かを持っています。
アイテムで満たされたTのObservableCollectionがあります。 CollectionChangedイベントにイベントハンドラーを追加しました。
コレクションをクリアすると、e.ActionがNotifyCollectionChangedAction.Resetに設定されたCollectionChangedイベントが発生します。 OK、それは正常です。しかし、奇妙なのは、e.OldItemsもe.NewItemsにも何も含まれていないことです。 e.OldItemsには、コレクションから削除されたすべてのアイテムが入力されると予想されます。
これを見た人はいますか?もしそうなら、彼らはどのようにそれを回避しましたか?
背景:CollectionChangedイベントを使用して別のイベントにアタッチおよびデタッチします。したがって、e.OldItemsにアイテムを取得できない場合は、そのイベントからデタッチできません。
明確化: ドキュメントは、このように振る舞わなければならないことを完全に述べていないことを知っています。しかし、他のすべてのアクションについては、それが何をしたかを私に通知しています。したがって、私の仮定は、それが私に伝えるだろうということです...クリア/リセットの場合にも。
自分で再現したい場合のサンプルコードは次のとおりです。まず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 };
}
}
解決 6
Ok、ObservableCollectionが希望どおりに動作することを今でも望んでいますが...次のコードは最終的に実行したものです。基本的に、TrulyObservableCollectionと呼ばれるTの新しいコレクションを作成し、ClearItemsメソッドをオーバーライドしてから、Clearingイベントを発生させるために使用しました。
このTrulyObservableCollectionを使用するコードでは、このClearingイベントを使用して、その時点でまだコレクションにあるアイテムをループ処理し、デタッチしたいイベントでデタッチします from。
このアプローチが他の誰かにも役立つことを願っています。
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();
}
}
他のヒント
リセットはリストがクリアされたことを意味しないため、古いアイテムを含めることを主張しません
これは、何か劇的なことが起こったことを意味し、追加/削除の作業にかかるコストは、リストをゼロから再スキャンするだけのコストを超える可能性が高いので、そうする必要があります。
MSDNは、コレクション全体がリセットの候補として再ソートされる例を示しています。
繰り返します。 リセットは明確という意味ではありません、リストに関する仮定は無効になりました。完全に新しいリストであるかのように処理します。クリアはたまたまこのインスタンスの1つですが、他のインスタンスも存在する可能性があります。
いくつかの例:
このようなリストには多くのアイテムが含まれており、画面に表示するためにWPF ListView
にデータバインドされています。
リストをクリアして.Reset
イベントを発生させると、パフォーマンスはほとんど瞬時になりますが、代わりに多数の個々の.Remove
イベントを発生させると、WPFがアイテムを1つずつ削除するため、パフォーマンスはひどくなります。
また、数千の個別のMove
操作を発行するのではなく、リストが再ソートされたことを示すために、独自のコードで<=>を使用しました。 Clearと同様に、多くの個々のイベントを発生させると、パフォーマンスが大幅に低下します。
ここでも同じ問題がありました。 CollectionChangedのResetアクションには、OldItemsは含まれません。回避策がありました:代わりに次の拡張メソッドを使用しました:
public static void RemoveAll(this IList list)
{
while (list.Count > 0)
{
list.RemoveAt(list.Count - 1);
}
}
Clear()関数をサポートせず、ResetアクションのCollectionChangedイベントでNotSupportedExceptionをスローしました。 RemoveAllは、適切なOldItemsを使用して、CollectionChangedイベントでRemoveアクションをトリガーします。
別のオプションは、次のように、OldItemsプロパティのすべてのクリアされたアイテムを含む単一のRemoveイベントでResetイベントを置き換えることです。
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
...
}
利点:
-
追加のイベントにサブスクライブする必要はありません(承認済みの回答で必要な場合)
-
削除されたオブジェクトごとにイベントを生成しません(いくつかの他の提案されたソリューションは複数のRemovedイベントになります)。
-
サブスクライバは、NewItems <!> amp;のみをチェックする必要があります。必要に応じて、イベントハンドラーを追加/削除するイベントのOldItems。
欠点:
-
リセットイベントなし
-
リストのコピーを作成する際の小さな(?)オーバーヘッド。
-
???
編集2012-02-23
残念ながら、WPFリストベースのコントロールにバインドされている場合、複数の要素を持つObservableCollectionNoResetコレクションをクリアすると、例外<!> quot;サポートされていない範囲アクション<!> quot;が発生します。 この制限を持つコントロールで使用するために、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(デフォルト)の場合、コレクション内のオブジェクトごとに1つの削除通知が生成されるため、効率的ではありません
ユーザーが一度に多くのアイテムを追加または削除する効率性を活用しながら、1つのイベントを発生させるだけでなく、UIElementのニーズを満たして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のメソッドをオーバーライドする必要はありません
- NotifyCollectionChangedの動作を改ざんしません(したがって、リセットを混乱させません)
- リフレクションを使用しません
コードは次のとおりです:
public static void Clear<T>(this ObservableCollection<T> collection, Action<ObservableCollection<T>> unhookAction)
{
unhookAction.Invoke(collection);
collection.Clear();
}
この拡張メソッドは、コレクションがクリアされる前に呼び出されるAction
を単に受け取ります。
1つのイベントに登録し、イベントハンドラーですべての追加と削除を処理したいので、少し異なる方法でこれに取り組みました。コレクション変更イベントをオーバーライドし、アイテムのリストを使用して、リセットアクションを削除アクションにリダイレクトし始めました。監視可能なコレクションをコレクションビューのアイテムソースとして使用し、<!> quot;範囲アクションはサポートされていません<!> quot;。
ついに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の外に独自のリストを保持することでこれを回避できます(アクションがAddの場合はリストに追加、アクションがRemoveの場合は削除など)。その後、削除されたすべてのアイテムを取得できます(または追加されたアイテム)リストをObservableCollectionと比較してアクションがリセットされたとき。
別のオプションは、IListとINotifyCollectionChangedを実装する独自のクラスを作成し、そのクラス内からイベントをアタッチおよびデタッチできます(または必要に応じてOldItemsをClearに設定します)-それは本当に難しいことではありませんが、たくさんあります入力します。
ObservableCollectionの要素にイベントハンドラーをアタッチおよびデタッチするシナリオでは、<!> quot; client-side <!> quot;もあります。溶液。イベント処理コードでは、Containsメソッドを使用して、送信者がObservableCollectionにあるかどうかを確認できます。プロ:既存のObservableCollectionで作業できます。短所:ContainsメソッドはO(n)で実行されます。nはObservableCollectionの要素数です。したがって、これは小さなObservableCollectionsのソリューションです。
別の<!> quot;クライアント側<!> quot;解決策は、途中でイベントハンドラを使用することです。すべてのイベントを中央のイベントハンドラーに登録するだけです。このイベントハンドラーは、コールバックまたはイベントを通じて実際のイベントハンドラーに通知します。リセットアクションが発生した場合、コールバックまたはイベントを削除し、途中で新しいイベントハンドラーを作成し、古いものを忘れてください。このアプローチは、大きな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には、置換、削除、または移動アクションの結果として変更されたアイテムのみが含まれているようです。 Clearに何かが含まれることを示すものではありません。 Clearはイベントを発生させますが、削除されたアイテムを登録せず、Removeコードをまったく呼び出しません。
まあ、私はそれを自分で汚すことにしました。
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インターフェイスは、UIの構築とその特定のパフォーマンス特性を念頭に置いて明確に記述されています。
コレクションの変更の通知が必要な場合、通常はイベントの追加と削除のみに関心があります。
次のインターフェイスを使用します:
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;
}
}
コレクションの独自のオーバーロードも記述しました。
- ClearItemsはRemoveingを発生させます
- InsertItemの発生が追加されました
- RemoveItemはRemoveingを発生させます
- SetItemは、削除および追加を発生させます
もちろん、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が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);
}
別の<!> quot; simple <!> quot;が見つかりました。 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の呼び出しをインターセプトし、base.OnCollectionChangedを起動する前にe._oldItemsプライベートフィールドを(リフレクションを介して)上書きします
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[]";
}
目を開けて脳をオンにして、このドキュメントをお読みください。 マイクロソフトはすべてを正しく行いました。リセット通知がスローされた場合、コレクションを再スキャンする必要があります。各アイテムの追加/削除(コレクションから削除され、コレクションに追加される)のスローが高すぎるため、リセット通知を受け取ります。
オリオン・エドワーズは完全に正しい(敬意、男性)。ドキュメントを読むときはもっと考えてください。
ObservableCollection
が明確になっていない場合は、以下のコードを試してください。役に立つかもしれません:
private TestEntities context; // This is your context
context.Refresh(System.Data.Objects.RefreshMode.StoreWins, context.UserTables); // to refresh the object context