Skip to content

Commit

Permalink
WAF ReadOnlyObservableList implements INotifyCollectionChanging
Browse files Browse the repository at this point in the history
  • Loading branch information
jbe2277 committed Sep 23, 2023
1 parent e397672 commit 514477a
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 14 deletions.
2 changes: 1 addition & 1 deletion src/System.Waf/System.Waf/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<Import Project="$([MSBuild]::GetPathOfFileAbove($(MSBuildThisFile), $(MSBuildThisFileDirectory)../))" />

<PropertyGroup>
<LangVersion>8.0</LangVersion>
<LangVersion>9.0</LangVersion>
<GenerateDocumentationFile>true</GenerateDocumentationFile>

<Authors>jbe2277</Authors>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
Expand All @@ -10,30 +11,50 @@ namespace Test.Waf.Foundation
public class ReadOnlyObservableListTest
{
[TestMethod]
public void ChangedEventsTest()
public void ObservableCollectionChangedEventsTest() => ChangedEventsTestCore(new ObservableCollection<string>(), false);

[TestMethod]
public void ObservableListChangedEventsTest() => ChangedEventsTestCore(new ObservableList<string>(), true);

private static void ChangedEventsTestCore(ObservableCollection<string> list, bool supportsCollectionChanging)
{
var list = new ObservableCollection<string>();
var readOnlyList = new ReadOnlyObservableList<string>(list);

int collectionChangingCalled = 0;
int collectionChangedCalled = 0;
void CollectionChangedHandler(object? sender, NotifyCollectionChangedEventArgs e) => collectionChangedCalled++;
int countPropertyChangedCalled = 0;
void PropertyChangedHandler(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(readOnlyList.Count)) countPropertyChangedCalled++;
}


if (supportsCollectionChanging) readOnlyList.CollectionChanging += CollectionChangingHandler;
else Assert.ThrowsException<NotSupportedException>(() => readOnlyList.CollectionChanging += CollectionChangingHandler);
readOnlyList.CollectionChanged += CollectionChangedHandler;
readOnlyList.PropertyChanged += PropertyChangedHandler;
list.Add("Hello");
if (supportsCollectionChanging) Assert.AreEqual(1, collectionChangingCalled);
Assert.AreEqual(1, collectionChangedCalled);
Assert.AreEqual(1, countPropertyChangedCalled);

readOnlyList.CollectionChanging -= CollectionChangingHandler;
readOnlyList.CollectionChanged -= CollectionChangedHandler;
readOnlyList.PropertyChanged -= PropertyChangedHandler;
list.Add("World");
if (supportsCollectionChanging) Assert.AreEqual(1, collectionChangingCalled);
Assert.AreEqual(1, collectionChangedCalled);
Assert.AreEqual(1, countPropertyChangedCalled);

void CollectionChangingHandler(object? sender, NotifyCollectionChangedEventArgs e) { Assert.AreSame(readOnlyList, sender); collectionChangingCalled++; }
void CollectionChangedHandler(object? sender, NotifyCollectionChangedEventArgs e) { Assert.AreSame(readOnlyList, sender); collectionChangedCalled++; }
void PropertyChangedHandler(object? sender, PropertyChangedEventArgs e)
{
Assert.AreSame(readOnlyList, sender);
if (e.PropertyName == nameof(readOnlyList.Count)) countPropertyChangedCalled++;
}
}

[TestMethod]
public void EmptyPropertyTest()
{
var readOnlyList = ReadOnlyObservableList<string>.Empty;
Assert.AreEqual(0, readOnlyList.Count);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,55 @@ namespace System.Waf.Foundation
/// This class implements the IReadOnlyObservableList interface and provides public CollectionChanged and PropertyChanged events.
/// </summary>
/// <typeparam name="T">The type of elements in the collection.</typeparam>
public class ReadOnlyObservableList<T> : ReadOnlyObservableCollection<T>, IReadOnlyObservableList<T>
public class ReadOnlyObservableList<T> : ReadOnlyObservableCollection<T>, INotifyCollectionChanging, IReadOnlyObservableList<T>
{
private readonly bool collectionChangingSupported;
[NonSerialized] private NotifyCollectionChangedEventHandler? collectionChanging;

/// <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) { }
public ReadOnlyObservableList(ObservableCollection<T> list) : base(list)
{
if (list is INotifyCollectionChanging x) { collectionChangingSupported = true; x.CollectionChanging += HandleCollectionChanging; }
}

/// <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."); }

/// <summary>Occurs when the collection changes.</summary>
/// <inheritdoc />
/// <exception cref="NotSupportedException">CollectionChanging event is not supported by the underlying list.</exception>
public event NotifyCollectionChangedEventHandler? CollectionChanging
{
add { CheckCollectionChanging(); collectionChanging += value; }
remove { collectionChanging -= value; }
}

/// <inheritdoc />
public new event NotifyCollectionChangedEventHandler? CollectionChanged
{
add => base.CollectionChanged += value;
remove => base.CollectionChanged -= value;
}

/// <summary>Occurs when a property value changes.</summary>
/// <inheritdoc />
public new event PropertyChangedEventHandler? PropertyChanged
{
add => base.PropertyChanged += value;
remove => base.PropertyChanged -= 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,5 @@
[assembly: SuppressMessage("Security", "CA2109:Review visible event handlers", Justification = "<Pending>", Scope = "member", Target = "~M:System.Waf.Foundation.SynchronizingCollectionCore`2.OriginalCollectionChanged(System.Object,System.Collections.Specialized.NotifyCollectionChangedEventArgs)")]
[assembly: SuppressMessage("Design", "CA1030:Use events where appropriate", Justification = "<Pending>", Scope = "member", Target = "~M:System.Waf.Foundation.Model.RaisePropertyChanged(System.String[])")]
[assembly: SuppressMessage("Maintainability", "CA1508:Avoid dead conditional code", Justification = "<Pending>", Scope = "member", Target = "~M:System.Waf.Foundation.Model.RaisePropertyChanged(System.String[])")]
[assembly: SuppressMessage("Usage", "CA2249:Consider using 'string.Contains' instead of 'string.IndexOf'", Justification = "<Pending>", Scope = "member", Target = "~M:System.Waf.Foundation.StringHelper.Contains(System.String,System.String,System.StringComparison)~System.Boolean")]
[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")]

0 comments on commit 514477a

Please sign in to comment.