Функциональность с Range в ObservableCollection24.03.2015 16:05
Класс ObservableCollection не позволяет добавлять, удалять и т.д. коллекции элементов.Чтобы добавить такую функциональность можно создать потомок этого класса, в котором реализовать необходимый функционал.В ObservableCollection есть унаследованное от Collection свойство:
protected IList Items { get; }
с которым и необходимо работать.Шаблон доработки такой:1) Проверить на возможность изменения:
protected void CheckReentrancy ();
2) Обработать элементы согласно вашей логике:
protected IList Items { get; }
3) Вызвать событие PropertyChanged для свойств «Count» и «Item[]»:
OnPropertyChanged (new PropertyChangedEventArgs («Count»));
OnPropertyChanged (new PropertyChangedEventArgs («Item[]»));
4) Вызвать событие CollectionChanged с параметрами события: тип изменения Reset, параметры OldItems и NewItems не передавать:
OnCollectionChanged (new NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction.Reset));
Недостатки: Из-за п.4 в обработчике события CollectionChanged невозможно будет работать с OldItems и NewItems так как они пустые. Это необходимо из-за того, что некоторые контролы WPF не работают с изменениями коллекции не по одному элементу, а по несколько. При этом, если тип изменения Reset, то это означает что произошло существенно изменение коллекции, и для контролов WPF это нормально. Если же вы используете новый класс не в качестве источника данных для контрола WPF, то можно в п.4 передавать и другие типы изменений, а также заполненные значения OldItems и NewItems и затем спокойно их обрабатывать.
Пример:
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: ObservableCollection
{
private const string CountString = «Count»;
private const string IndexerName = «Item[]»;
protected enum ProcessRangeAction
{
Add,
Replace,
Remove
};
public ObservableRangeCollection () : base ()
{
}
public ObservableRangeCollection (IEnumerable collection) : base (collection)
{
}
public ObservableRangeCollection (List list) : base (list)
{
}
protected virtual void ProcessRange (IEnumerable collection, ProcessRangeAction action)
{
if (collection == null) throw new ArgumentNullException («collection»);
var items = collection as IList? 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 collection)
{
this.ProcessRange (collection, ProcessRangeAction.Add);
}
public void ReplaceRange (IEnumerable collection)
{
this.ProcessRange (collection, ProcessRangeAction.Replace);
}
public void RemoveRange (IEnumerable collection)
{
this.ProcessRange (collection, ProcessRangeAction.Remove);
}
}
}
Тесты:
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(new List {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 { 4, 5, 6, 7 });
Assert.AreEqual (8, orc.Count);
CollectionAssert.AreEqual (new List { 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(new List { 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 { 4, 5, 6 });
Assert.AreEqual (3, orc.Count);
CollectionAssert.AreEqual (new List { 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(new List { 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 { 1, 3, 6 });
Assert.AreEqual (2, orc.Count);
CollectionAssert.AreEqual (new List { 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(new List { 0, 1, 2, 3 });
orc.CollectionChanged += (sender, e) =>
{
eventCollectionChangedCount++;
};
((INotifyPropertyChanged)orc).PropertyChanged += (sender, e) =>
{
eventPropertyChangedCount++;
};
switch (action)
{
case RangeAction.Replace: orc.ReplaceRange (new List());
break;
case RangeAction.Remove: orc.RemoveRange (new List());
break;
default: orc.AddRange (new List());
break;
}
Assert.AreEqual (4, orc.Count);
CollectionAssert.AreEqual (new List { 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().AddRange (null);
}
[TestMethod]
[ExpectedException (typeof (ArgumentNullException))]
public void ReplaceNullRangeTest ()
{
new ObservableRangeCollection().ReplaceRange (null);
}
[TestMethod]
[ExpectedException (typeof (ArgumentNullException))]
public void RemoveNullRangeTest ()
{
new ObservableRangeCollection().RemoveRange (null);
}
}
}
© Habrahabr.ru