Created
April 24, 2012 08:33
-
-
Save johnnyelwailer/2477916 to your computer and use it in GitHub Desktop.
A version of ObservableCollectionView that inherits from ReactiveCollection
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
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