Skip to content

Commit

Permalink
WAF ReadOnlyObservableList implements INotifyCollectionItemChanged
Browse files Browse the repository at this point in the history
  • Loading branch information
jbe2277 committed Sep 24, 2023
1 parent fc14aee commit 1ef3beb
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -112,37 +112,43 @@ static void AssertEqualEventArgs(NotifyCollectionChangedEventArgs expected, Noti
[TestMethod]
public void CollectionItemChangedTest()
{
var collectionItemChangedList = new List<(object? item, string? propertyName)>();
var list = new ObservableList<CollectionEventsTestModel>(new[] { new CollectionEventsTestModel() });
var item1 = list[0];
CollectionItemChangedTestCore(list, list);
}

internal static void CollectionItemChangedTestCore(ObservableCollection<CollectionEventsTestModel> source, object observable)
{
var collectionItemChangedList = new List<(object? item, string? propertyName)>();

var item1 = source[0];
item1.Name = "Empty";

list.CollectionItemChanged += CollectionItemChangedHandler;
((INotifyCollectionItemChanged)observable).CollectionItemChanged += CollectionItemChangedHandler;

item1.Name = "First";
Assert.AreEqual((item1, nameof(CollectionEventsTestModel.Name)), collectionItemChangedList.Last());
Assert.AreEqual(1, collectionItemChangedList.Count);

var item2 = new CollectionEventsTestModel();
list.Add(item2);
source.Add(item2);
item2.Name = "Second";
Assert.AreEqual((item2, nameof(CollectionEventsTestModel.Name)), collectionItemChangedList.Last());
Assert.AreEqual(2, collectionItemChangedList.Count);

var item2b = new CollectionEventsTestModel();
list[1] = item2b;
source[1] = item2b;
item2b.Name = "Second B";
Assert.AreEqual((item2b, nameof(CollectionEventsTestModel.Name)), collectionItemChangedList.Last());
Assert.AreEqual(3, collectionItemChangedList.Count);

item2.Name = "Removed 2";
Assert.AreEqual(3, collectionItemChangedList.Count);

list.Remove(item2b);
source.Remove(item2b);
item2b.Name = "Removed 2B";
Assert.AreEqual(3, collectionItemChangedList.Count);

list.Clear();
source.Clear();
item1.Name = "Cleared 1";
Assert.AreEqual(3, collectionItemChangedList.Count);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@ public void CollectionEventsTest()
ObservableListTest.CollectionEventsTestCore(list, readOnlyList);
}

[TestMethod]
public void CollectionItemChangedTest()
{
var list = new ObservableList<CollectionEventsTestModel>(new[] { new CollectionEventsTestModel() });
var readOnlyList = new ReadOnlyObservableList<CollectionEventsTestModel>(list);
ObservableListTest.CollectionItemChangedTestCore(list, readOnlyList);
}

[TestMethod]
public void EmptyPropertyTest()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,37 +6,39 @@ namespace System.Waf.Foundation
{
/// <summary>
/// Represents a read-only <see cref="ObservableCollection{T}"/>.
/// This class implements the IReadOnlyObservableList interface and provides public CollectionChanged and PropertyChanged events.
/// This class implements the IReadOnlyObservableList interface and provides public CollectionChanging, CollectionChanged, CollectionItemChanged and PropertyChanged events.
/// </summary>
/// <typeparam name="T">The type of elements in the collection.</typeparam>
public class ReadOnlyObservableList<T> : ReadOnlyObservableCollection<T>, INotifyCollectionChanging, IReadOnlyObservableList<T>
public class ReadOnlyObservableList<T> : ReadOnlyObservableCollection<T>, INotifyCollectionChanging, INotifyCollectionItemChanged, IReadOnlyObservableList<T>
{
private readonly bool collectionChangingSupported;
private readonly bool collectionItemChangedSupported;
[NonSerialized] private NotifyCollectionChangedEventHandler? collectionChanging;
[NonSerialized] private PropertyChangedEventHandler? collectionItemChanged;

/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyObservableCollection{T}"/>
/// class that serves as a wrapper around the specified <see cref="ObservableCollection{T}"/>.
/// </summary>
/// <summary>Initializes a new instance of the <see cref="ReadOnlyObservableCollection{T}"/> class that serves as a wrapper around the specified <see cref="ObservableCollection{T}"/>.</summary>
/// <param name="list">The <see cref="ObservableCollection{T}"/> with which to create this instance of the
/// <see cref="ReadOnlyObservableCollection{T}"/> class.</param>
/// <exception cref="ArgumentNullException">list is null.</exception>
public ReadOnlyObservableList(ObservableCollection<T> list) : base(list)
{
if (list is INotifyCollectionChanging x) { collectionChangingSupported = true; x.CollectionChanging += HandleCollectionChanging; }
if (list is INotifyCollectionItemChanged y) { collectionItemChangedSupported = true; y.CollectionItemChanged += HandleCollectionItemChanged; }
}

/// <summary>Gets an empty <see cref="ReadOnlyObservableList{T}"/>.</summary>
/// <remarks>The returned instance is immutable and will always be empty.</remarks>
public static ReadOnlyObservableList<T> Empty { get; } = new(new ObservableList<T>());

private void CheckCollectionChanging() { if (!collectionChangingSupported) throw new NotSupportedException("CollectionChanging event is not supported by the underlying list."); }

/// <inheritdoc />
/// <exception cref="NotSupportedException">CollectionChanging event is not supported by the underlying list.</exception>
public event NotifyCollectionChangedEventHandler? CollectionChanging
{
add { CheckCollectionChanging(); collectionChanging += value; }
add
{
if (!collectionChangingSupported) throw new NotSupportedException("CollectionChanging event is not supported by the underlying list.");
collectionChanging += value;
}
remove { collectionChanging -= value; }
}

Expand All @@ -54,10 +56,29 @@ public event NotifyCollectionChangedEventHandler? CollectionChanging
remove => base.PropertyChanged -= value;
}

/// <inheritdoc />
/// <exception cref="NotSupportedException">CollectionItemChanged event is not supported by the underlying list.</exception>
public event PropertyChangedEventHandler? CollectionItemChanged
{
add
{
if (!collectionItemChangedSupported) throw new NotSupportedException("CollectionItemChanged event is not supported by the underlying list.");
collectionItemChanged += value;
}
remove { collectionItemChanged -= value; }
}

/// <summary>Raises the CollectionChanged event with the provided arguments.</summary>
/// <param name="e">Arguments of the event being raised.</param>
protected virtual void OnCollectionChanging(NotifyCollectionChangedEventArgs e) => collectionChanging?.Invoke(this, e);

private void HandleCollectionChanging(object? sender, NotifyCollectionChangedEventArgs e) => OnCollectionChanging(e);
/// <summary>Raise the CollectionItemChanged event with the provided arguments.</summary>
/// <param name="item">The collection item that raised the PropertyChanged event.</param>
/// <param name="e">The PropertyChanged event argument.</param>
protected virtual void OnCollectionItemChanged(object? item, PropertyChangedEventArgs e) => collectionItemChanged?.Invoke(item, e);

private void HandleCollectionChanging(object? sender, NotifyCollectionChangedEventArgs e) => OnCollectionChanging(e);

private void HandleCollectionItemChanged(object? sender, PropertyChangedEventArgs e) => OnCollectionItemChanged(sender, e);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,4 @@
[assembly: SuppressMessage("Globalization", "CA1307:Specify StringComparison for clarity", Justification = "<Pending>", Scope = "member", Target = "~M:System.Waf.Foundation.ValidatableModel.ValidationResultComparer.GetHashCode(System.ComponentModel.DataAnnotations.ValidationResult)~System.Int32")]
[assembly: SuppressMessage("Design", "CA1000:Do not declare static members on generic types", Justification = "<Pending>", Scope = "member", Target = "~P:System.Waf.Foundation.ReadOnlyObservableList`1.Empty")]
[assembly: SuppressMessage("Security", "CA2109:Review visible event handlers", Justification = "<Pending>", Scope = "member", Target = "~M:System.Waf.Foundation.ObservableList`1.OnCollectionItemChanged(System.Object,System.ComponentModel.PropertyChangedEventArgs)")]
[assembly: SuppressMessage("Security", "CA2109:Review visible event handlers", Justification = "<Pending>", Scope = "member", Target = "~M:System.Waf.Foundation.ReadOnlyObservableList`1.OnCollectionItemChanged(System.Object,System.ComponentModel.PropertyChangedEventArgs)")]

0 comments on commit 1ef3beb

Please sign in to comment.