📜 ⬆️ ⬇️

Range functionality with ObservableCollection

The ObservableCollection class does not allow adding, deleting, etc. collections of items.
To add this functionality, you can create a descendant of this class in which to implement the necessary functionality.


Purpose:
To avoid the multiple events of PropertyChanged and OnCollectionChanged during massive changes to the collection, and the syntax gain is almost negligible and does not matter.

In ObservableCollection there is a property inherited from Collection:
protected IList<T> Items { get; } 

with which it is necessary to work.
')
The completion template is:
1) Check for changeability:
 protected void CheckReentrancy(); 

2) Process the elements according to your logic:
 protected IList<T> Items { get; } 

3) Call the PropertyChanged event for the “Count” and “Item []” properties:
 OnPropertyChanged(new PropertyChangedEventArgs("Count")); OnPropertyChanged(new PropertyChangedEventArgs("Item[]")); 

4) Call the CollectionChanged event with the event parameters: Reset change type, do not send OldItems and NewItems parameters:
 OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); 


Disadvantages:
Due to item 4 in the CollectionChanged event handler, it will be impossible to work with OldItems and NewItems since they are empty. This is necessary due to the fact that some WPF controls do not work with collection changes not for one element, but for several. At the same time, if the type of change is Reset, then this means that a significant change in the collection has occurred, and this is normal for WPF controls. If you use a new class not as a data source for the WPF control, then in step 4, you can transfer other types of changes , as well as filled OldItems and NewItems values, and then calmly process them.

Example:
 using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; using System.Linq; namespace Common.Utils { public class ObservableRangeCollection<T> : ObservableCollection<T> { private const string CountString = "Count"; private const string IndexerName = "Item[]"; protected enum ProcessRangeAction { Add, Replace, Remove }; public ObservableRangeCollection() : base() { } public ObservableRangeCollection(IEnumerable<T> collection) : base(collection) { } public ObservableRangeCollection(List<T> list) : base(list) { } protected virtual void ProcessRange(IEnumerable<T> collection, ProcessRangeAction action) { if (collection == null) throw new ArgumentNullException("collection"); var items = collection as IList<T> ?? collection.ToList(); if (!items.Any()) return; this.CheckReentrancy(); if (action == ProcessRangeAction.Replace) this.Items.Clear(); foreach (var item in items) { if (action == ProcessRangeAction.Remove) this.Items.Remove(item); else this.Items.Add(item); } this.OnPropertyChanged(new PropertyChangedEventArgs(CountString)); this.OnPropertyChanged(new PropertyChangedEventArgs(IndexerName)); this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } public void AddRange(IEnumerable<T> collection) { this.ProcessRange(collection, ProcessRangeAction.Add); } public void ReplaceRange(IEnumerable<T> collection) { this.ProcessRange(collection, ProcessRangeAction.Replace); } public void RemoveRange(IEnumerable<T> collection) { this.ProcessRange(collection, ProcessRangeAction.Remove); } } } 


Tests:

 using System; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; using Common.Utils; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Tests.Common { [TestClass] public class ObservableRangeCollectionTests { [TestMethod] public void AddRangeTest() { var eventCollectionChangedCount = 0; var eventPropertyChangedCount = 0; var orc = new ObservableRangeCollection<int>(new List<int> {0, 1, 2, 3}); orc.CollectionChanged += (sender, e) => { Assert.AreEqual(NotifyCollectionChangedAction.Reset, e.Action); eventCollectionChangedCount++; }; ((INotifyPropertyChanged) orc).PropertyChanged += (sender, e) => { CollectionAssert.Contains(new[] { "Count", "Item[]" }, e.PropertyName); eventPropertyChangedCount++; }; orc.AddRange(new List<int> { 4, 5, 6, 7 }); Assert.AreEqual(8, orc.Count); CollectionAssert.AreEqual(new List<int> { 0, 1, 2, 3, 4, 5, 6, 7 }, orc); Assert.AreEqual(1, eventCollectionChangedCount); Assert.AreEqual(2, eventPropertyChangedCount); } [TestMethod] public void ReplaceRangeTest() { var eventCollectionChangedCount = 0; var eventPropertyChangedCount = 0; var orc = new ObservableRangeCollection<int>(new List<int> { 0, 1, 2, 3 }); orc.CollectionChanged += (sender, e) => { Assert.AreEqual(NotifyCollectionChangedAction.Reset, e.Action); eventCollectionChangedCount++; }; ((INotifyPropertyChanged)orc).PropertyChanged += (sender, e) => { CollectionAssert.Contains(new[] { "Count", "Item[]" }, e.PropertyName); eventPropertyChangedCount++; }; orc.ReplaceRange(new List<int> { 4, 5, 6 }); Assert.AreEqual(3, orc.Count); CollectionAssert.AreEqual(new List<int> { 4, 5, 6 }, orc); Assert.AreEqual(1, eventCollectionChangedCount); Assert.AreEqual(2, eventPropertyChangedCount); } [TestMethod] public void RemoveRangeTest() { var eventCollectionChangedCount = 0; var eventPropertyChangedCount = 0; var orc = new ObservableRangeCollection<int>(new List<int> { 0, 1, 2, 3 }); orc.CollectionChanged += (sender, e) => { Assert.AreEqual(NotifyCollectionChangedAction.Reset, e.Action); eventCollectionChangedCount++; }; ((INotifyPropertyChanged)orc).PropertyChanged += (sender, e) => { CollectionAssert.Contains(new[] { "Count", "Item[]" }, e.PropertyName); eventPropertyChangedCount++; }; orc.RemoveRange(new List<int> { 1, 3, 6 }); Assert.AreEqual(2, orc.Count); CollectionAssert.AreEqual(new List<int> { 0, 2 }, orc); Assert.AreEqual(1, eventCollectionChangedCount); Assert.AreEqual(2, eventPropertyChangedCount); } private enum RangeAction { Add, Replace, Remove } private void EmptyRangeTest(RangeAction action) { var eventCollectionChangedCount = 0; var eventPropertyChangedCount = 0; var orc = new ObservableRangeCollection<int>(new List<int> { 0, 1, 2, 3 }); orc.CollectionChanged += (sender, e) => { eventCollectionChangedCount++; }; ((INotifyPropertyChanged)orc).PropertyChanged += (sender, e) => { eventPropertyChangedCount++; }; switch (action) { case RangeAction.Replace: orc.ReplaceRange(new List<int>()); break; case RangeAction.Remove: orc.RemoveRange(new List<int>()); break; default: orc.AddRange(new List<int>()); break; } Assert.AreEqual(4, orc.Count); CollectionAssert.AreEqual(new List<int> { 0, 1, 2, 3 }, orc); Assert.AreEqual(0, eventCollectionChangedCount); Assert.AreEqual(0, eventPropertyChangedCount); } [TestMethod] public void AddEmptyRangeTest() { this.EmptyRangeTest(RangeAction.Add); } [TestMethod] public void ReplaceEmptyRangeTest() { this.EmptyRangeTest(RangeAction.Replace); } [TestMethod] public void RemoveEmptyRangeTest() { this.EmptyRangeTest(RangeAction.Remove); } [TestMethod] [ExpectedException(typeof(ArgumentNullException))] public void AddNullRangeTest() { new ObservableRangeCollection<int>().AddRange(null); } [TestMethod] [ExpectedException(typeof(ArgumentNullException))] public void ReplaceNullRangeTest() { new ObservableRangeCollection<int>().ReplaceRange(null); } [TestMethod] [ExpectedException(typeof(ArgumentNullException))] public void RemoveNullRangeTest() { new ObservableRangeCollection<int>().RemoveRange(null); } } } 

Source: https://habr.com/ru/post/253857/


All Articles