Skip to content

Instantly share code, notes, and snippets.

@DevJohnC
Created August 23, 2019 16:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save DevJohnC/33590107fb9eea3e7e39ae591eaebd85 to your computer and use it in GitHub Desktop.
Save DevJohnC/33590107fb9eea3e7e39ae591eaebd85 to your computer and use it in GitHub Desktop.
public class ObservableCollectionView<T> : IReadOnlyCollection<T>, INotifyCollectionChanged, IDisposable
{
private readonly ObservableCollection<T> _sourceCollection;
private List<T> _localCollection = new List<T>();
public int Count => _localCollection.Count;
public event NotifyCollectionChangedEventHandler CollectionChanged;
public bool IsOrdered => OrderComparer != null;
private IComparer<T> _orderComparer;
public IComparer<T> OrderComparer
{
get => _orderComparer;
set
{
_orderComparer = value;
Sort();
}
}
public bool IsFiltered => Filter != null;
private Func<T, bool> _filter;
public Func<T, bool> Filter
{
get => _filter;
set
{
_filter = value;
ApplyFilter();
}
}
public ObservableCollectionView(ObservableCollection<T> sourceCollection)
{
_sourceCollection = sourceCollection;
AttachToCollection();
}
private void AttachToCollection()
{
_sourceCollection.CollectionChanged += SourceCollectionChanged;
}
private void DetachFromCollection()
{
_sourceCollection.CollectionChanged += SourceCollectionChanged;
}
private void Sort()
{
var sortComparison = OrderComparer;
var unorderedList = _localCollection;
var orderedList = new List<T>();
var itemCount = unorderedList.Count;
for (var fromIndex = 0; fromIndex < Count; fromIndex++)
{
var item = unorderedList[fromIndex];
var toIndex = orderedList.BinarySearch(item, sortComparison);
if (toIndex < 0)
toIndex = ~toIndex;
orderedList.Insert(toIndex, item);
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Move,
item, toIndex, fromIndex
));
}
_localCollection = orderedList;
}
private void ApplyFilter()
{
if (IsFiltered)
{
RemoveFilteredItems();
}
AddNonFilteredItems();
}
private void AddNonFilteredItems()
{
foreach (var item in _sourceCollection)
{
if (PassesFilter(item))
{
var index = GetIndex(item);
if (index < 0)
AddItem(index, item);
}
}
}
private void RemoveFilteredItems()
{
for (var i = 0; i < _localCollection.Count; i++)
{
var item = _localCollection[i];
if (!PassesFilter(item))
{
RemoveItem(i, item);
i--;
}
}
}
private bool PassesFilter(T obj)
{
if (!IsFiltered)
return true;
return Filter(obj);
}
private bool TryAddItem(T obj)
{
if (!PassesFilter(obj))
return false;
if (!IsOrdered)
{
AddItem(_localCollection.Count, obj);
}
else
{
var index = _localCollection.BinarySearch(obj, OrderComparer);
AddItem(index, obj);
}
return true;
}
private void AddItem(int index, T obj)
{
if (index < 0 && IsOrdered)
index = ~index;
if (index < 0 && !IsOrdered)
index = _localCollection.Count;
_localCollection.Insert(index, obj);
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Add, obj, index
));
}
private int GetIndex(T obj)
{
if (!IsOrdered)
{
return _localCollection.IndexOf(obj);
}
return _localCollection.BinarySearch(obj, OrderComparer);
}
private bool TryRemoveItem(T obj)
{
var index = GetIndex(obj);
if (index < 0)
return false;
RemoveItem(index, obj);
return true;
}
private void RemoveItem(int index, T obj)
{
_localCollection.RemoveAt(index);
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Remove,
new[] { obj },
index
));
}
private void AddItems(IList newItems)
{
var itemCount = newItems.Count;
for (var i = 0; i < itemCount; i++)
{
var newObj = (T)newItems[i];
TryAddItem(newObj);
}
}
private void RemoveItems(IList oldItems)
{
var itemCount = oldItems.Count;
for (var i = 0; i < itemCount; i++)
{
var oldObj = (T)oldItems[i];
TryRemoveItem(oldObj);
}
}
private void SourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
AddItems(e.NewItems);
break;
case NotifyCollectionChangedAction.Remove:
RemoveItems(e.OldItems);
break;
case NotifyCollectionChangedAction.Replace:
break;
case NotifyCollectionChangedAction.Move:
break;
case NotifyCollectionChangedAction.Reset:
break;
}
}
public IEnumerator<T> GetEnumerator()
=> _localCollection.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
public void Dispose()
{
DetachFromCollection();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment