-
-
Notifications
You must be signed in to change notification settings - Fork 181
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add ToObservableChangeSet for BindingLists (#288)
ToObservableChangeSet for BindingLists
- Loading branch information
1 parent
9100e5c
commit 9d2f7ef
Showing
3 changed files
with
253 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
101 changes: 101 additions & 0 deletions
101
src/DynamicData.Tests/Binding/BindingListToChangeSetFixture.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
using DynamicData.Binding; | ||
using FluentAssertions; | ||
using System; | ||
using System.ComponentModel; | ||
using System.Linq; | ||
using Xunit; | ||
|
||
namespace DynamicData.Tests.Binding | ||
{ | ||
public class BindingListToChangeSetFixture : IDisposable | ||
{ | ||
private readonly TestBindingList<int> _collection; | ||
private readonly ChangeSetAggregator<int> _results; | ||
|
||
public BindingListToChangeSetFixture() | ||
{ | ||
_collection = new TestBindingList<int>(); | ||
_results = _collection.ToObservableChangeSet().AsAggregator(); | ||
} | ||
|
||
public void Dispose() | ||
{ | ||
_results.Dispose(); | ||
} | ||
|
||
[Fact] | ||
public void Add() | ||
{ | ||
_collection.Add(1); | ||
|
||
_results.Messages.Count.Should().Be(1); | ||
_results.Data.Count.Should().Be(1); | ||
_results.Data.Items.First().Should().Be(1); | ||
} | ||
|
||
[Fact] | ||
public void Remove() | ||
{ | ||
_collection.AddRange(Enumerable.Range(1, 10)); | ||
|
||
_collection.Remove(3); | ||
|
||
_results.Data.Count.Should().Be(9); | ||
_results.Data.Items.Contains(3).Should().BeFalse(); | ||
_results.Data.Items.ShouldAllBeEquivalentTo(_collection); | ||
} | ||
|
||
[Fact] | ||
public void Duplicates() | ||
{ | ||
_collection.Add(1); | ||
_collection.Add(1); | ||
|
||
_results.Data.Count.Should().Be(2); | ||
} | ||
|
||
[Fact] | ||
public void Replace() | ||
{ | ||
_collection.AddRange(Enumerable.Range(1, 10)); | ||
_collection[8] = 20; | ||
|
||
_results.Data.Items.ShouldBeEquivalentTo(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 20, 10 }); | ||
} | ||
|
||
[Fact] | ||
public void ResetFiresClearsAndAdds() | ||
{ | ||
_collection.AddRange(Enumerable.Range(1, 10)); | ||
|
||
_collection.Reset(); | ||
_results.Data.Items.ShouldAllBeEquivalentTo(_collection); | ||
|
||
var resetNotification = _results.Messages.Last(); | ||
resetNotification.Removes.Should().Be(10); | ||
resetNotification.Adds.Should().Be(10); | ||
} | ||
|
||
[Fact] | ||
public void RaiseListChangedEvents() | ||
{ | ||
_collection.RaiseListChangedEvents = true; | ||
_collection.Add(1); | ||
|
||
_results.Messages.Count.Should().Be(1); | ||
|
||
_collection.RaiseListChangedEvents = false; | ||
_collection.Add(1); | ||
|
||
_results.Messages.Count.Should().Be(1); | ||
} | ||
|
||
private class TestBindingList<T> : BindingList<T> | ||
{ | ||
public void Reset() | ||
{ | ||
OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1)); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
// Copyright (c) 2011-2019 Roland Pheasant. All rights reserved. | ||
// Roland Pheasant licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for full license information. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.ComponentModel; | ||
using System.Linq; | ||
using System.Reactive; | ||
using System.Reactive.Linq; | ||
|
||
namespace DynamicData.Binding | ||
{ | ||
/// <summary> | ||
/// Extensions to convert an binding list into a dynamic stream | ||
/// </summary> | ||
public static class BindingListEx | ||
{ | ||
/// <summary> | ||
/// Convert a binding list into an observable change set | ||
/// </summary> | ||
/// <typeparam name="T">The type of the object.</typeparam> | ||
/// <param name="source">The source.</param> | ||
/// <returns></returns> | ||
/// <exception cref="System.ArgumentNullException">source</exception> | ||
public static IObservable<IChangeSet<T>> ToObservableChangeSet<T>(this BindingList<T> source) | ||
{ | ||
if (source == null) | ||
{ | ||
throw new ArgumentNullException(nameof(source)); | ||
} | ||
|
||
return ToObservableChangeSet<BindingList<T>, T>(source); | ||
} | ||
|
||
/// <summary> | ||
/// Convert a binding list into an observable change set | ||
/// </summary> | ||
/// <typeparam name="TObject">The type of the object.</typeparam> | ||
/// <typeparam name="TKey">The type of the key.</typeparam> | ||
/// <param name="source">The source.</param> | ||
/// <param name="keySelector">The key selector.</param> | ||
/// <returns></returns> | ||
/// <exception cref="System.ArgumentNullException">source | ||
/// or | ||
/// keySelector</exception> | ||
public static IObservable<IChangeSet<TObject, TKey>> ToObservableChangeSet<TObject, TKey>(this BindingList<TObject> source, Func<TObject, TKey> keySelector) | ||
{ | ||
if (source == null) | ||
{ | ||
throw new ArgumentNullException(nameof(source)); | ||
} | ||
|
||
if (keySelector == null) | ||
{ | ||
throw new ArgumentNullException(nameof(keySelector)); | ||
} | ||
|
||
return ToObservableChangeSet<BindingList<TObject>, TObject>(source).AddKey(keySelector); | ||
} | ||
|
||
/// <summary> | ||
/// Convert a binding list into an observable change set | ||
/// </summary> | ||
/// <typeparam name="T">The type of the object.</typeparam> | ||
/// <typeparam name="TCollection"></typeparam> | ||
/// <param name="source">The source.</param> | ||
/// <returns></returns> | ||
/// <exception cref="System.ArgumentNullException">source</exception> | ||
public static IObservable<IChangeSet<T>> ToObservableChangeSet<TCollection, T>(this TCollection source) | ||
where TCollection : IBindingList, IEnumerable<T> | ||
{ | ||
if (source == null) | ||
{ | ||
throw new ArgumentNullException(nameof(source)); | ||
} | ||
|
||
return Observable.Create<IChangeSet<T>>(observer => | ||
{ | ||
var data = new ChangeAwareList<T>(source); | ||
|
||
if (data.Count > 0) | ||
{ | ||
observer.OnNext(data.CaptureChanges()); | ||
} | ||
|
||
return source.ObserveCollectionChanges() | ||
.Scan(data, (list, args) => | ||
{ | ||
var changes = args.EventArgs; | ||
|
||
switch (changes.ListChangedType) | ||
{ | ||
case ListChangedType.ItemAdded: | ||
{ | ||
list.Add((T)source[changes.NewIndex]); | ||
break; | ||
} | ||
|
||
case ListChangedType.ItemDeleted: | ||
{ | ||
list.RemoveAt(changes.NewIndex); | ||
break; | ||
} | ||
|
||
case ListChangedType.ItemChanged: | ||
{ | ||
list[changes.NewIndex] = (T)source[changes.NewIndex]; | ||
break; | ||
} | ||
|
||
case ListChangedType.Reset: | ||
{ | ||
list.Clear(); | ||
list.AddRange(source); | ||
break; | ||
} | ||
} | ||
|
||
return list; | ||
}) | ||
.Select(list => list.CaptureChanges()) | ||
.SubscribeSafe(observer); | ||
}); | ||
} | ||
|
||
/// <summary> | ||
/// Observes list changed args | ||
/// </summary> | ||
public static IObservable<EventPattern<ListChangedEventArgs>> ObserveCollectionChanges(this IBindingList source) | ||
{ | ||
return Observable | ||
.FromEventPattern<ListChangedEventHandler, ListChangedEventArgs>( | ||
h => source.ListChanged += h, | ||
h => source.ListChanged -= h); | ||
} | ||
} | ||
} |