Skip to content

Instantly share code, notes, and snippets.

@jamie94bc
Last active January 21, 2016 13:54
Show Gist options
  • Save jamie94bc/6262479 to your computer and use it in GitHub Desktop.
Save jamie94bc/6262479 to your computer and use it in GitHub Desktop.
A early (largely untested), simple implementation of CollectionViewSource for use view models in portable class libraries.Does not yet support grouping
/// <summary>
/// A simple implementation of CollectionView / CollectionViewSource
/// for portable class libraries.
/// </summary>
/// <remarks>
/// Unfortunately due some Windows 8 oddness, we had to resort to using
/// an <see cref="ObservableCollection{T}"/> as the view. Probably something
/// to do with ObservableMap but ItemsControls and the inherited versions
/// don't subscribe to the <see cref="INotifyCollectionChanged.CollectionChanged"/>
/// event.
///
/// (Sept. 2013)
/// </remarks>
public class CollectionViewSource<T> : INotifyPropertyChanged, IEnumerable<T> {
private ObservableCollection<T> _source;
private Predicate<T> _filter;
private readonly ObservableCollection<SortDescription<T>> _sortDescriptions = new ObservableCollection<SortDescription<T>>();
private ObservableCollection<T> _view = new ObservableCollection<T>();
public ObservableCollection<T> Source {
get {
return _source;
}
set {
if (_source == value) return;
if (_source != null)
_source.CollectionChanged -= SourceOnCollectionChanged;
_source = value;
if (_source != null)
_source.CollectionChanged += SourceOnCollectionChanged;
OnSourceChanged();
}
}
public Predicate<T> Filter {
get { return this._filter; }
set {
if (_filter == value) return;
_filter = value;
OnFilterChanged();
}
}
public ICollection<SortDescription<T>> SortDescriptions {
get { return _sortDescriptions; }
}
public virtual ObservableCollection<T> View {
get { return _view; }
private set {
_view = value;
NotifyPropertyChanged();
}
}
public int Count {
get {
return _view != null ? _view.Count : 0;
}
}
private int _resetThreshold = 5;
/// <summary>
/// The number of items after which a single
/// <see cref="NotifyCollectionChangedAction.Reset"/> event
/// will be fired as opposed to multiple events.
/// </summary>
/// <remarks>
/// This is due to portable class library's insufficient support
/// for <see cref="NotifyCollectionChangedEventArgs"/>.
/// </remarks>
public virtual int ResetThreshold {
get { return _resetThreshold; }
set { _resetThreshold = value; }
}
#region Constructors
public CollectionViewSource(ObservableCollection<T> source) {
this.Init(source);
}
public CollectionViewSource(ObservableCollection<T> source, params SortDescription<T>[] sortDescriptions) {
if (source == null)
throw new ArgumentNullException("source");
if (sortDescriptions == null)
throw new ArgumentNullException("sortDescriptions");
this.SortDescriptions.AddRange(sortDescriptions);
this.Init(source);
}
public CollectionViewSource(ObservableCollection<T> source, Predicate<T> filter, params SortDescription<T>[] sortDescriptions) {
if (source == null)
throw new ArgumentNullException("source");
if (filter == null)
throw new ArgumentNullException("filter");
this._filter = filter;
this.SortDescriptions.AddRange(sortDescriptions);
this.Init(source);
}
private void Init(ObservableCollection<T> source) {
this.Source = source; // Source prop calls EnsureView()
this._sortDescriptions.CollectionChanged += SortDescriptionsOnCollectionChanged;
}
#endregion
/// <summary>
/// Resets the filter and sort descriptions to the given
/// values.
/// </summary>
/// <remarks>
/// Much more efficient than clearing/adding to the
/// sort descriptions collection by hand.
/// </remarks>
public void SetFilterAndSort(Predicate<T> filter, params SortDescription<T>[] sortDescriptions) {
this._sortDescriptions.CollectionChanged -= SortDescriptionsOnCollectionChanged;
this._sortDescriptions.Clear();
this._sortDescriptions.AddRange(sortDescriptions);
this.Filter = filter;
this._sortDescriptions.CollectionChanged += SortDescriptionsOnCollectionChanged;
}
#region OnChanged
private void SourceOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
var toAdd = new List<T>();
var toRemove = new List<T>();
switch (e.Action) {
case NotifyCollectionChangedAction.Add:
// ReSharper disable once LoopCanBeConvertedToQuery
foreach (var item in e.NewItems.Cast<T>().Where(MatchFilter)) {
toAdd.Add(item);
}
break;
case NotifyCollectionChangedAction.Replace:
toRemove.AddRange(e.OldItems.Cast<T>());
// ReSharper disable once LoopCanBeConvertedToQuery
foreach (var item in e.NewItems.Cast<T>().Where(MatchFilter)) {
toAdd.Add(item);
}
break;
case NotifyCollectionChangedAction.Remove:
// ReSharper disable once LoopCanBeConvertedToQuery
foreach (var item in e.OldItems.Cast<T>()) {
toRemove.Add(item);
}
break;
case NotifyCollectionChangedAction.Reset:
EnsureView();
break;
}
PerformAddAndRemove(toAdd, toRemove);
}
private void OnSourceChanged() {
EnsureView();
}
private void OnFilterChanged() {
EnsureView();
}
private void SortDescriptionsOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
this.ReSort();
}
#endregion
private void EnsureView() {
var toAdd = Source.Where(MatchFilter).Where(item => !this.View.Contains(item)).ToList();
var toRemove = this._view.Where(x => !Source.Contains(x) || !MatchFilter(x)).ToList();
PerformAddAndRemove(toAdd, toRemove);
}
private void PerformAddAndRemove(IList<T> toAdd, IList<T> toRemove) {
if ((toAdd.Count + toRemove.Count) > ResetThreshold) {
var newCollection = this._view.ToList();
foreach (var item in toAdd) {
AddSorted(item, newCollection);
}
foreach (var item in toRemove) {
newCollection.Remove(item);
}
this.View = newCollection.ToObservableCollection();
}
else {
foreach (var item in toAdd) {
AddSorted(item);
}
foreach (var item in toRemove) {
this.View.Remove(item);
}
}
// ReSharper disable once ExplicitCallerInfoArgument
NotifyPropertyChanged("Count");
}
public void NotifyItemsSourceChanged() {
this.EnsureView();
}
public void ReSort() {
// Unfortunately PCL's don't implement NotifyCollectionChangedEventArgs.Move so we have to reset the
// list whenever this changes :(
var sortedItems = this.Source.ToList();
sortedItems.Sort(SortComparison);
int notifyCount = 0;
var viewList = this._view.ToList();
foreach (var item in viewList) {
int sortedIndex = sortedItems.IndexOf(item);
if (sortedIndex != this._view.IndexOf(item)) {
this._view.Remove(item);
this._view.Insert(sortedIndex, item);
notifyCount++;
if (notifyCount > ResetThreshold) {
break;
}
}
}
if (notifyCount > ResetThreshold) {
this.View = sortedItems.ToObservableCollection();
}
}
#region Utils
private bool MatchFilter(T obj) {
return Filter == null || Filter(obj);
}
private Comparison<T> SortComparison {
get {
return (x, y) => {
// x > = 1, y > = -1
int move = 0;
foreach (var sd in SortDescriptions) {
var xPropVal = sd.Property(x);
var yPropVal = sd.Property(y);
if (xPropVal == null && yPropVal == null) {
move = 0;
}
else if (xPropVal == null) {
move = 1;
}
else if (yPropVal == null) {
move = -1;
}
else {
move = sd.Property(x).CompareTo(sd.Property(y));
}
if (sd.Order == SortOrder.Descending) {
move *= -1;
}
if (move != 0) return move;
}
return move;
};
}
}
private int AddSorted(T item, IList<T> collection = null) {
var sortedList = (collection ?? View).ToList();
sortedList.Add(item);
sortedList.Sort(SortComparison);
int idx = sortedList.IndexOf(item);
(collection ?? View).Insert(idx, item);
return idx;
}
#endregion
#region Implementation of INotifyPropertyChanged
protected virtual event PropertyChangedEventHandler PropertyChanged;
event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged {
add {
PropertyChanged += value;
}
remove {
PropertyChanged -= value;
}
}
protected virtual void NotifyPropertyChanged([CallerMemberName] string propertyName = null) {
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
#region Implementation of IEnumerable
public IEnumerator<T> GetEnumerator() {
return this.View.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() {
return GetEnumerator();
}
#endregion
}
public class SortDescription<T> {
public Func<T, IComparable> Property { get; private set; }
public SortOrder Order { get; private set; }
public SortDescription(Func<T, IComparable> property, SortOrder order = SortOrder.Ascending) {
if (property == null)
throw new ArgumentNullException("property");
this.Property = property;
this.Order = order;
}
}
public enum SortOrder {
Ascending,
Descending
}
@jamie94bc
Copy link
Author

@rb1234 there is definitely scope for improvement here however I have updated it with the latest version we are using.

(2) has been fixed and the View property is of type ObservableCollection<T> due to some oddness we encountered on Windows 8.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment