Skip to content

Commit

Permalink
Add ToObservableChangeSet for BindingLists (#288)
Browse files Browse the repository at this point in the history
ToObservableChangeSet for BindingLists
  • Loading branch information
trucksmart authored and RolandPheasant committed Oct 10, 2019
1 parent 9100e5c commit 9d2f7ef
Show file tree
Hide file tree
Showing 3 changed files with 253 additions and 0 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,20 @@ var myConnection = myObservableCollection.ToObservableChangeSet();
```
This method is only recommended for simple queries which act only on the UI thread as `ObservableCollection` is not thread safe.

### Create an Observable Change Set from an Binding List
```cs
var myBindingList = new BindingList<T>();
```
To create a cache based observable change set, call `.ToObservableChangeSet` and specify a key selector for the backing cache
```cs
var myConnection = myBindingList.ToObservableChangeSet(t => t.Key);
```
or to create a list based observable change set call `.ToObservableChangeSet` with no arguments
```cs
var myConnection = myBindingList.ToObservableChangeSet();
```
This method is only recommended for simple queries which act only on the UI thread as `ObservableCollection` is not thread safe.

### Using the ObservableChangeSet static class

There is also another way to create observable change sets, and that is to use the ```ObservableChangeSet``` static class. This class is a facsimile of the Rx.Net ```Observable``` static class and provides an almost identical API.
Expand Down
101 changes: 101 additions & 0 deletions src/DynamicData.Tests/Binding/BindingListToChangeSetFixture.cs
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));
}
}
}
}
138 changes: 138 additions & 0 deletions src/DynamicData/Binding/BindingListEx.cs
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);
}
}
}

0 comments on commit 9d2f7ef

Please sign in to comment.