Skip to content

Instantly share code, notes, and snippets.

@KirillAshikhmin
Created October 21, 2020 21:07
Show Gist options
  • Save KirillAshikhmin/fc31e174b668566cc9e60317379f1fdc to your computer and use it in GitHub Desktop.
Save KirillAshikhmin/fc31e174b668566cc9e60317379f1fdc to your computer and use it in GitHub Desktop.
Extended ObservableCollectionwith range actions and corrent notify calls. Use *WithNotify methods
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using JetBrains.Annotations;
// ReSharper disable PossibleMultipleEnumeration
// ReSharper disable UnusedMember.Global
// ReSharper disable LocalizableElement
namespace KirillAshikhmin.Helpers
{
public class SmartObservableCollection<T> : Collection<T>, INotifyCollectionChanged, INotifyPropertyChanged
{
readonly object _lockObject = new object();
readonly ConcurrentDictionary<string, object> _properties = new ConcurrentDictionary<string, object>();
/// <summary>
/// Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class.
/// </summary>
public SmartObservableCollection() { }
/// <summary>
/// Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class that contains elements copied from the specified collection.
/// </summary>
/// <param name="collection">collection: The collection from which the elements are copied.</param>
/// <exception cref="System.ArgumentNullException">The collection parameter cannot be null.</exception>
public SmartObservableCollection(IList<T> collection) : base(collection) { }
public SmartObservableCollection(IEnumerable<T> collection) : base(collection.ToList()) { }
protected bool CallPropertyChangeEvent { get; set; } = true;
public void AddWithNotify(T item)
{
lock (_lockObject)
{
var index = Count;
Add(item);
OnPropertyChanged(new PropertyChangedEventArgs("Count"));
OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new List<T> {item}, index));
}
}
public void InsertWithNotify(int index, T item)
{
lock (_lockObject)
{
Insert(index, item);
OnPropertyChanged(new PropertyChangedEventArgs("Count"));
OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new List<T> {item}, index));
}
}
public void RemoveWithNotify(T item)
{
lock (_lockObject)
{
var index = IndexOf(item);
if (index < 0) return;
Remove(item);
OnPropertyChanged(new PropertyChangedEventArgs("Count"));
OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
var args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new List<T> {item}, index);
OnCollectionChanged(args);
}
}
public void RemoveAtWithNotify(int at)
{
lock (_lockObject)
{
if (Count - 1 < at) return;
var item = this[at];
Remove(item);
OnPropertyChanged(new PropertyChangedEventArgs("Count"));
OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
var args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new List<T> {item}, at);
OnCollectionChanged(args);
}
}
public void ClearWithNotify()
{
lock (_lockObject)
{
Clear();
OnPropertyChanged(new PropertyChangedEventArgs("Count"));
OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
/// <summary>
/// Adds the elements of the specified collection to the end of the ObservableCollection(Of T).
/// </summary>
public void AddRange(IEnumerable<T> collection)
{
if (collection == null) throw new ArgumentNullException(nameof(collection));
lock (_lockObject)
{
var index = Count;
foreach (var i in collection) Add(i);
OnPropertyChanged(new PropertyChangedEventArgs("Count"));
OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, collection.ToList(), index));
}
}
/// <summary>
/// Adds the elements of the specified collection to the index of the ObservableCollection(Of T).
/// </summary>
public void InsertRange(int index, IEnumerable<T> collection)
{
if (collection == null) return;
lock (_lockObject)
{
var newItems = collection as T[] ?? collection.ToArray();
if (newItems.Length == 0) return;
var newIndex = index;
foreach (var i in newItems)
{
Insert(newIndex++, i);
}
OnPropertyChanged(new PropertyChangedEventArgs("Count"));
OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, newItems, index));
}
}
/// <summary>
/// Removes the first occurence of each item in the specified collection from ObservableCollection(Of T).
/// </summary>
public void RemoveRange(IEnumerable<T> collection)
{
lock (_lockObject)
{
var oldItems = collection as T[] ?? collection.ToArray();
if (oldItems.Length == 0) return;
var start = Items.IndexOf(oldItems.First());
foreach (var i in oldItems) Items.Remove(i);
OnPropertyChanged(new PropertyChangedEventArgs("Count"));
OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
var args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, oldItems, start);
OnCollectionChanged(args);
}
}
public void Notify()
{
OnPropertyChanged(new PropertyChangedEventArgs("Count"));
OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
var args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
OnCollectionChanged(args);
}
/// <summary>
/// Clears the current collection and replaces it with the specified item.
/// </summary>
public void Replace(T item, T newItem)
{
lock (_lockObject)
{
var index = Items.IndexOf(item);
Items.Remove(item);
Items.Insert(index, newItem);
OnPropertyChanged(new PropertyChangedEventArgs("Count"));
OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
var items = new List<T> {item};
var args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, items, items, index);
OnCollectionChanged(args);
}
}
/// <summary>
/// Clears the current collection and replaces it with the specified collection.
/// </summary>
public void ReplaceRange(IEnumerable<T> collection)
{
if (collection == null) throw new ArgumentNullException(nameof(collection));
lock (_lockObject)
{
Items.Clear();
foreach (var i in collection) Items.Add(i);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
public void RemoveAll(Func<T, bool> match)
{
lock (_lockObject)
{
var oldItems = this as T[] ?? this.ToArray();
if (oldItems.Length == 0) return;
var start = Items.IndexOf(oldItems.First());
foreach (var i in oldItems)
{
if (match(i))
{
Items.Remove(i);
OnPropertyChanged(new PropertyChangedEventArgs("Count"));
OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
var args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, oldItems, start);
OnCollectionChanged(args);
}
}
}
}
public event NotifyCollectionChangedEventHandler CollectionChanged;
public event PropertyChangedEventHandler PropertyChanged;
public event PropertyChangingEventHandler PropertyChanging;
[NotifyPropertyChangedInvocator]
protected void OnPropertyChanged([CallerMemberName] string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
protected void OnPropertyChanging([CallerMemberName] string propertyName = null) => PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(propertyName));
public void NotifyOfPropertyChanged(string propertyName) => OnPropertyChanged(propertyName);
protected void OnPropertyChanged(PropertyChangedEventArgs prop) => PropertyChanged?.Invoke(this, prop);
void OnCollectionChanged(NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs) => CollectionChanged?.Invoke(this, notifyCollectionChangedEventArgs);
internal TProp Get<TProp>(TProp defValue = default, [CallerMemberName] string name = null)
{
if (string.IsNullOrEmpty(name)) return defValue;
if (_properties.TryGetValue(name, out var value))
return (TProp) value;
_properties.AddOrUpdate(name, defValue, (s, o) => defValue);
return defValue;
}
internal bool Set(object value, [CallerMemberName] string name = null)
{
if (string.IsNullOrEmpty(name))
return false;
var isExists = _properties.TryGetValue(name, out var getValue);
if (isExists && Equals(value, getValue))
return false;
if (CallPropertyChangeEvent)
OnPropertyChanging(name);
_properties.AddOrUpdate(name, value, (s, o) => value);
if (CallPropertyChangeEvent)
OnPropertyChanged(name);
return true;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment