Skip to content

Instantly share code, notes, and snippets.

@johnnyelwailer
Created April 24, 2012 08:33
Show Gist options
  • Save johnnyelwailer/2477916 to your computer and use it in GitHub Desktop.
Save johnnyelwailer/2477916 to your computer and use it in GitHub Desktop.
A version of ObservableCollectionView that inherits from ReactiveCollection
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using ReactiveUI;
public class ReactiveCollectionView<T> : ReactiveCollection<T>, IList<T>
{
readonly IReactiveCollection<T> source;
readonly Func<T, bool> filter;
readonly IComparer<T> order;
readonly Func<IObservedChange<T, object>, bool> updateFilter;
readonly ReplaySubject<int> viewCountChanged;
/// <summary>
/// Creates a read only view that tracks a collection providing filtering and sorting
/// </summary>
/// <param name="source">The source collection.</param>
/// <param name="filter">A filter to be applied to the source. Only items matching this filter will appear in this view.</param>
/// <param name="orders">A custom sort function to apply to the view.</param>
public ReactiveCollectionView(
IReactiveCollection<T> source = null,
Func<T, bool> filter = null,
params Func<T, IComparable>[] orders)
{
this.source = source ?? new ReactiveCollection<T>();
this.filter = filter ?? (_ => true);
this.updateFilter = this.updateFilter ?? (_ => true);
if (orders.Any())
{
this.order = new GenericComparer<T>(orders);
}
this.viewCountChanged = new ReplaySubject<int>();
this.fetchItems();
this.wireNotificationHandlers();
}
public void RefreshView()
{
this.fetchItems();
}
void fetchItems()
{
var items = this.source.Where(this.filter);
if (this.order != null)
{
items = items.OrderBy(_ => _, this.order);
}
this.ClearItems();
items.ToObservable(RxApp.DeferredScheduler).Subscribe(this.addItem);
}
void wireNotificationHandlers()
{
this.source.ObserveCollectionChanged().Where(x => x.Action == NotifyCollectionChangedAction.Reset)
.ObserveOn(RxApp.DeferredScheduler)
.Subscribe(_ => this.fetchItems());
this.source.ItemsAdded
.Where(this.filter)
.ObserveOn(RxApp.DeferredScheduler)
.Subscribe(this.addItem);
this.source.ItemsRemoved
.ObserveOn(RxApp.DeferredScheduler)
.Subscribe(this.removeItem);
this.source.ItemChanged
.Select(x => x.Sender)
.ObserveOn(RxApp.DeferredScheduler)
.Subscribe(x => {
if (this.filter(x)) {
this.updateItem(x);
} else {
this.removeItem(x);
}
});
}
public IObservable<int> ViewCountChanged
{
get { return this.viewCountChanged; }
}
void updateItem(T item)
{
this.removeItem(item);
this.addItem(item);
}
void addItem(T item)
{
this.InsertItem(this.getNewIndexFor(item), item);
this.viewCountChanged.OnNext(this.Count);
}
void removeItem(T item)
{
var index = this.IndexOf(item);
if (index >= 0)
{
this.RemoveItem(index);
this.viewCountChanged.OnNext(this.Count);
}
}
int getNewIndexFor(T item)
{
if (this.order == null)
{
return this.Count;
}
var match = this.Items.BinarySearch(item, this.order);
return match < 0 ? Math.Abs(match + 1) : match;
}
static bool hasItemsToRemove(NotifyCollectionChangedEventArgs notification)
{
return notification.Action == NotifyCollectionChangedAction.Remove
|| notification.Action == NotifyCollectionChangedAction.Replace;
}
static bool hasItemsToAdd(NotifyCollectionChangedEventArgs notification)
{
return notification.Action == NotifyCollectionChangedAction.Add
|| notification.Action == NotifyCollectionChangedAction.Replace;
}
bool ICollection<T>.IsReadOnly {
get { return true; }
}
T IList<T>.this[int index]
{
get { return this[index]; }
set { throw new InvalidOperationException(); }
}
void ICollection<T>.Add(T item)
{
throw new InvalidOperationException();
}
void ICollection<T>.Clear()
{
throw new InvalidOperationException();
}
bool ICollection<T>.Remove(T item)
{
throw new InvalidOperationException();
}
void IList<T>.Insert(int index, T item)
{
throw new InvalidOperationException();
}
void IList<T>.RemoveAt(int index)
{
throw new InvalidOperationException();
}
}
public class GenericComparer<T> : IComparer<T>
{
private readonly Func<T, IComparable>[] selectors;
public GenericComparer(params Func<T, IComparable>[] selectors)
{
this.selectors = selectors;
}
public int Compare(T x, T y)
{
return this.selectors.Select(s => s(x).CompareTo(y)).FirstOrDefault(i => i != 0);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment