...

вторник, 24 марта 2015 г.

Функциональность с Range в ObservableCollection

Класс ObservableCollection не позволяет добавлять, удалять и т.д. коллекции элементов.

Чтобы добавить такую функциональность можно создать потомок этого класса, в котором реализовать необходимый функционал.


В ObservableCollection есть унаследованное от Collection свойство:



protected IList<T> Items { get; }



с которым и необходимо работать.

Шаблон доработки такой:

1) Проверить на возможность изменения:



protected void CheckReentrancy();



2) Обработать элементы согласно вашей логике:

protected IList<T> 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<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);
}
}
}


Тесты:




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);
}
}
}


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Комментариев нет:

Отправить комментарий