Saya memiliki sesuatu di sini yang benar-benar membuat saya lengah.
Saya memiliki ObservableCollection of T yang diisi dengan item. Saya juga memiliki penangan peristiwa yang dilampirkan ke acara CollectionChanged.
Ketika Anda Hapus koleksi itu menyebabkan sebuah acara CollectionChanged dengan e.Action set untuk NotifyCollectionChangedAction.Reset. Oke, itu normal. Tapi yang aneh adalah tidak ada e.OldItems atau e.NewItems yang memiliki apa pun di dalamnya. Saya berharap e.OldItems diisi dengan semua item yang dihapus dari koleksi.
Apakah ada orang lain yang melihat ini? Dan jika ya, bagaimana mereka bisa mengatasinya?
Beberapa latar belakang: Saya menggunakan acara CollectionChanged untuk melampirkan dan melepaskan dari acara lain dan dengan demikian jika saya tidak mendapatkan item apa pun di e.OldItems ... Saya tidak akan dapat melepaskan diri dari acara itu.
KLARIFIKASI: Saya tahu bahwa dokumentasi tidak secara langsung menyatakan bahwa ia harus berperilaku seperti ini. Tetapi untuk setiap tindakan lainnya, itu memberi tahu saya tentang apa yang telah dilakukannya. Jadi, asumsi saya adalah itu akan memberi tahu saya ... dalam kasus Clear / Reset juga.
Di bawah ini adalah contoh kode jika Anda ingin memperbanyaknya sendiri. Pertama dari 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>
Selanjutnya, kode di belakang:
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 };
}
}
sumber
Jawaban:
Itu tidak mengklaim menyertakan item lama, karena Reset tidak berarti bahwa daftar telah dihapus
Ini berarti bahwa beberapa hal dramatis telah terjadi, dan biaya untuk menambah / menghapus kemungkinan besar akan melebihi biaya hanya dengan memindai ulang daftar dari awal ... jadi itulah yang harus Anda lakukan.
MSDN menyarankan contoh dari seluruh koleksi yang diurutkan ulang sebagai kandidat untuk reset.
Untuk mengulangi. Reset tidak berarti jelas , itu berarti asumsi Anda tentang daftar tersebut sekarang tidak valid. Perlakukan seolah-olah itu adalah daftar yang sama sekali baru . Jelas kebetulan menjadi salah satu contoh dari ini, tetapi mungkin ada yang lain.
Beberapa contoh:
Saya memiliki daftar seperti ini dengan banyak item di dalamnya, dan telah ditempatkan di WPF
ListView
untuk ditampilkan di layar.Jika Anda menghapus daftar dan menaikkan
.Reset
acara, kinerjanya cukup instan, tetapi jika Anda malah meningkatkan banyak.Remove
acara individu , kinerjanya buruk, karena WPF menghapus item satu per satu. Saya juga menggunakan.Reset
kode saya sendiri untuk menunjukkan bahwa daftar telah diurutkan ulang, daripada mengeluarkan ribuanMove
operasi individu . Seperti halnya Clear, ada kinerja besar yang terpukul ketika meningkatkan banyak acara individu.sumber
OldItems
saat membersihkan, (ini hanya menyalin daftar), tetapi mungkin ada beberapa skenario di mana ini terlalu mahal. Bagaimanapun, jika Anda menginginkan koleksi yang tidak memberi tahu Anda tentang semua item yang dihapus, itu tidak akan sulit dilakukan.Reset
ingin menunjukkan operasi yang mahal, kemungkinan besar alasan yang sama berlaku untuk menyalin seluruh daftar keOldItems
.Reset
sebenarnya berarti "Konten koleksi telah dihapus ". Lihat msdn.microsoft.com/en-us/library/…Kami memiliki masalah yang sama di sini. Tindakan Reset di CollectionChanged tidak menyertakan OldItems. Kami memiliki solusi: kami menggunakan metode ekstensi berikut:
public static void RemoveAll(this IList list) { while (list.Count > 0) { list.RemoveAt(list.Count - 1); } }
Kami akhirnya tidak mendukung fungsi Clear (), dan melemparkan NotSupportedException di acara CollectionChanged untuk tindakan Reset. RemoveAll akan memicu tindakan Hapus di acara CollectionChanged, dengan OldItems yang sesuai.
sumber
Pilihan lainnya adalah mengganti event Reset dengan event Remove tunggal yang memiliki semua item yang telah dibersihkan dalam properti OldItems sebagai berikut:
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 ... }
Keuntungan:
Tidak perlu berlangganan acara tambahan (seperti yang diwajibkan oleh jawaban yang diterima)
Tidak menghasilkan peristiwa untuk setiap objek yang dihapus (beberapa solusi yang diusulkan menghasilkan beberapa peristiwa yang Dihapus).
Pelanggan hanya perlu memeriksa NewItems & OldItems pada acara apa pun untuk menambah / menghapus penangan acara sesuai kebutuhan.
Kekurangan:
Tidak ada acara Reset
Overhead kecil (?) Membuat salinan daftar.
???
EDIT 2012-02-23
Sayangnya, saat terikat ke kontrol berbasis daftar WPF, Menghapus kumpulan ObservableCollectionNoReset dengan beberapa elemen akan menghasilkan pengecualian "Tindakan rentang tidak didukung". Untuk digunakan dengan kontrol dengan batasan ini, saya mengubah kelas ObservableCollectionNoReset menjadi:
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. }
Ini tidak seefisien ketika RangeActionsSupported salah (default) karena satu pemberitahuan Hapus dihasilkan per objek dalam koleksi
sumber
Range actions are not supported.
Saya tidak tahu mengapa hal ini terjadi, tetapi sekarang tidak ada pilihan selain menghapus setiap item satu per satu ...foreach( NotifyCollectionChangedEventHandler handler in this.CollectionChanged )
Ifhandler.Target is CollectionView
, maka Anda dapat mematikan handler denganAction.Reset
args, jika tidak, Anda dapat memberikan args lengkap. Terbaik dari kedua dunia ini berdasarkan handler by handler :). Jenis seperti apa yang ada di sini: stackoverflow.com/a/3302917/529618Oke, saya tahu ini adalah pertanyaan yang sangat lama tetapi saya telah menemukan solusi yang baik untuk masalah ini dan saya pikir saya akan membagikannya. Solusi ini mengambil inspirasi dari banyak jawaban hebat di sini, tetapi memiliki keuntungan sebagai berikut:
Ini kodenya:
public static void Clear<T>(this ObservableCollection<T> collection, Action<ObservableCollection<T>> unhookAction) { unhookAction.Invoke(collection); collection.Clear(); }
Metode ekstensi ini hanya mengambil
Action
yang akan dipanggil sebelum koleksi dihapus.sumber
Saya telah menemukan solusi yang memungkinkan pengguna untuk memanfaatkan efisiensi menambahkan atau menghapus banyak item sekaligus sementara hanya mengaktifkan satu peristiwa - dan memenuhi kebutuhan UIElements untuk mendapatkan peristiwa Action.Reset sementara semua pengguna lain akan melakukannya seperti daftar elemen yang ditambahkan dan dihapus.
Solusi ini melibatkan pengesampingan acara CollectionChanged. Saat kita mengaktifkan peristiwa ini, kita sebenarnya dapat melihat target dari setiap penangan terdaftar dan menentukan jenisnya. Karena hanya kelas ICollectionView yang memerlukan
NotifyCollectionChangedAction.Reset
argumen ketika lebih dari satu item berubah, kita dapat memilihnya, dan memberikan argumen peristiwa yang tepat kepada semua orang yang berisi daftar lengkap item yang dihapus atau ditambahkan. Berikut implementasinya.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 }
sumber
Oke, meskipun saya masih berharap ObservableCollection berperilaku seperti yang saya inginkan ... kode di bawah ini adalah yang akhirnya saya lakukan. Pada dasarnya, saya membuat koleksi T baru yang disebut TrulyObservableCollection dan mengganti metode ClearItems yang kemudian saya gunakan untuk meningkatkan acara Kliring.
Dalam kode yang menggunakan TrulyObservableCollection ini, saya menggunakan acara Kliring ini untuk mengulang melalui item yang masih dalam koleksi pada saat itu untuk melakukan pelepasan pada acara yang ingin saya lepas.
Semoga pendekatan ini membantu orang lain juga.
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(); } }
sumber
BrokenObservableCollection
, bukanTrulyObservableCollection
- Anda salah paham tentang arti tindakan reset.ActuallyUsefulObservableCollection
. :)Saya menangani yang satu ini dengan cara yang sedikit berbeda karena saya ingin mendaftar ke satu acara dan menangani semua penambahan dan penghapusan di event handler. Saya mulai menimpa acara yang diubah koleksi dan mengarahkan tindakan penyetelan ulang ke tindakan penghapusan dengan daftar item. Ini semua salah karena saya menggunakan koleksi yang dapat diamati sebagai sumber item untuk tampilan koleksi dan mendapat "Tindakan rentang tidak didukung".
Saya akhirnya membuat acara baru bernama CollectionChangedRange yang bertindak dengan cara yang saya harapkan dari versi bawaan untuk bertindak.
Saya tidak dapat membayangkan mengapa batasan ini akan diizinkan dan berharap bahwa posting ini setidaknya menghentikan orang lain dari jalan buntu seperti yang saya lakukan.
/// <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); } } }
sumber
Ini adalah cara kerja ObservableCollection, Anda dapat mengatasinya dengan menyimpan daftar Anda sendiri di luar ObservableCollection (menambahkan ke daftar saat tindakan adalah Tambah, hapus saat tindakan Hapus, dll.) Maka Anda bisa mendapatkan semua item yang dihapus (atau menambahkan item ) saat tindakan Reset dengan membandingkan daftar Anda dengan ObservableCollection.
Pilihan lainnya adalah membuat kelas Anda sendiri yang mengimplementasikan IList dan INotifyCollectionChanged, lalu Anda dapat melampirkan dan melepaskan peristiwa dari dalam kelas itu (atau menyetel OldItems ke Hapus jika Anda mau) - ini benar-benar tidak sulit, tetapi banyak mengetik.
sumber
Untuk skenario memasang dan melepaskan penangan peristiwa ke elemen ObservableCollection, ada juga solusi "sisi klien". Dalam kode penanganan peristiwa, Anda dapat memeriksa apakah pengirim berada di ObservableCollection menggunakan metode Contains. Pro: Anda dapat bekerja dengan ObservableCollection yang ada. Kekurangan: metode Contains dijalankan dengan O (n) di mana n adalah jumlah elemen di ObservableCollection. Jadi ini adalah solusi untuk ObservableCollections kecil.
Solusi "sisi klien" lainnya adalah dengan menggunakan pengendali kejadian di tengah. Cukup daftarkan semua acara ke pengendali acara di tengah. Penangan peristiwa ini pada gilirannya memberi tahu pengendali peristiwa nyata melalui callback atau peristiwa. Jika tindakan Reset terjadi, hapus callback atau acara, buat penangan acara baru di tengah dan lupakan yang lama. Pendekatan ini juga berfungsi untuk ObservableCollections besar. Saya menggunakan ini untuk acara PropertyChanged (lihat kode di bawah).
/// <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; } } }
sumber
Melihat NotifyCollectionChangedEventArgs , tampaknya OldItems hanya berisi item yang diubah sebagai hasil dari tindakan Ganti, Hapus, atau Pindahkan. Itu tidak menunjukkan bahwa itu akan berisi apa pun di Clear. Saya menduga bahwa Clear mengaktifkan acara tersebut, tetapi tidak mendaftarkan item yang dihapus dan tidak memanggil kode Hapus sama sekali.
sumber
Nah, saya memutuskan untuk menjadi kotor dengan itu sendiri.
Microsoft berusaha keras untuk selalu memastikan NotifyCollectionChangedEventArgs tidak memiliki data apa pun saat memanggil reset. Saya berasumsi bahwa ini adalah keputusan kinerja / memori. Jika Anda menyetel ulang koleksi dengan 100.000 elemen, saya berasumsi bahwa mereka tidak ingin menduplikasi semua elemen tersebut.
Tetapi karena koleksi saya tidak pernah memiliki lebih dari 100 elemen, saya tidak melihat ada masalah dengan itu.
Pokoknya saya membuat kelas yang diwariskan dengan metode berikut:
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); }
sumber
Antarmuka ObservableCollection serta INotifyCollectionChanged ditulis dengan jelas dengan mempertimbangkan penggunaan khusus: pembuatan UI dan karakteristik kinerjanya yang spesifik.
Jika Anda menginginkan pemberitahuan tentang perubahan koleksi, biasanya Anda hanya tertarik pada Tambahkan dan Hapus acara.
Saya menggunakan antarmuka berikut:
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; } }
Saya juga menulis kelebihan Koleksi saya sendiri di mana:
Tentu saja, AddRange juga dapat ditambahkan.
sumber
Saya baru saja memeriksa beberapa kode charting di toolkit Silverlight dan WPF dan memperhatikan bahwa mereka juga memecahkan masalah ini (dengan cara yang serupa) ... dan saya pikir saya akan melanjutkan dan memposting solusi mereka.
Pada dasarnya, mereka juga membuat ObservableCollection turunan dan mengganti ClearItems, memanggil Hapus pada setiap item yang sedang dihapus.
Ini kodenya:
/// <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); } } }
sumber
Ini topik hangat ... karena menurut saya, Microsoft tidak melakukan tugasnya dengan benar ... lagi. Jangan salah paham, saya suka Microsoft, tapi mereka tidak sempurna!
Saya membaca sebagian besar komentar sebelumnya. Saya setuju dengan semua orang yang berpikir bahwa Microsoft tidak memprogram Clear () dengan benar.
Menurut saya, paling tidak perlu ada argumen yang memungkinkan untuk melepaskan objek dari suatu peristiwa ... tetapi saya juga memahami dampaknya. Kemudian, saya memikirkan solusi yang diusulkan ini.
Saya berharap ini akan membuat semua orang bahagia, atau setidaknya, hampir semua orang ...
Eric
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 }); } } } } }
sumber
Untuk membuatnya tetap sederhana mengapa Anda tidak mengganti metode ClearItem dan melakukan apa pun yang Anda inginkan di sana yaitu Lepaskan item dari acara tersebut.
public class PeopleAttributeList : ObservableCollection<PeopleAttributeDto>, { { protected override void ClearItems() { Do what ever you want base.ClearItems(); } rest of the code omitted }
Sederhana, bersih, dan berisi kode koleksi.
sumber
Saya memiliki masalah yang sama, dan inilah solusi saya. Sepertinya berhasil. Apakah ada yang melihat potensi masalah dengan pendekatan ini?
// 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; } } } } } }
Berikut adalah beberapa metode berguna lainnya di kelas saya:
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); }
sumber
Saya menemukan solusi "sederhana" lain yang berasal dari ObservableCollection, tetapi tidak terlalu elegan karena menggunakan Refleksi ... Jika Anda suka, inilah solusi saya:
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); } } }
Di sini saya menyimpan elemen saat ini dalam bidang array dalam metode ClearItems, kemudian saya mencegat panggilan OnCollectionChanged dan menimpa bidang pribadi e._oldItems (melalui Refleksi) sebelum meluncurkan base.OnCollectionChanged
sumber
Anda dapat mengganti metode ClearItems dan memunculkan acara dengan tindakan Hapus dan 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)); } }
Bagian dari
System.Collections.ObjectModel.ObservableCollection<T>
realisasi: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[]"; }
sumber
http://msdn.microsoft.com/en-us/library/system.collections.specialized.notifycollectionchangedaction(VS.95).aspx
Silakan baca dokumentasi ini dengan mata terbuka dan otak Anda aktif. Microsoft melakukan segalanya dengan benar. Anda harus memindai ulang koleksi Anda ketika muncul pemberitahuan Setel ulang untuk Anda. Anda mendapatkan pemberitahuan Setel ulang karena melempar Tambah / Hapus untuk setiap item (dihapus dari dan ditambahkan kembali ke koleksi) terlalu mahal.
Orion Edwards sepenuhnya benar (hormat, sobat). Harap berpikir lebih luas saat membaca dokumentasi.
sumber
Jika Anda
ObservableCollection
tidak jelas, maka Anda dapat mencoba kode di bawah ini. ini dapat membantu Anda:private TestEntities context; // This is your context context.Refresh(System.Data.Objects.RefreshMode.StoreWins, context.UserTables); // to refresh the object context
sumber