Merge pull request #137 from TorchAPI/observable-collections

Refactor observable collections and add read-only proxies for them
This commit is contained in:
Westin Miller
2017-10-12 02:23:48 -07:00
committed by GitHub
5 changed files with 167 additions and 62 deletions

View File

@@ -12,7 +12,7 @@ using System.Windows.Threading;
namespace Torch.Collections
{
[Serializable]
public class ObservableDictionary<TKey, TValue> : ViewModel, IDictionary<TKey, TValue>, INotifyCollectionChanged
public class ObservableDictionary<TKey, TValue> : ViewModel, IDictionary<TKey, TValue>
{
private IDictionary<TKey, TValue> _internalDict;
@@ -37,12 +37,6 @@ namespace Torch.Collections
};
}
/// <inheritdoc />
public event NotifyCollectionChangedEventHandler CollectionChanged;
/// <inheritdoc />
public event PropertyChangedEventHandler PropertyChanged;
/// <inheritdoc />
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
@@ -153,26 +147,5 @@ namespace Torch.Collections
/// <inheritdoc />
public ICollection<TValue> Values => _internalDict.Values;
private void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
NotifyCollectionChangedEventHandler collectionChanged = CollectionChanged;
if (collectionChanged != null)
foreach (NotifyCollectionChangedEventHandler nh in collectionChanged.GetInvocationList())
{
var dispObj = nh.Target as DispatcherObject;
var dispatcher = dispObj?.Dispatcher;
if (dispatcher != null && !dispatcher.CheckAccess())
{
dispatcher.BeginInvoke(
(Action)(() => nh.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))),
DispatcherPriority.DataBind);
continue;
}
nh.Invoke(this, e);
}
}
}
}

View File

@@ -12,16 +12,10 @@ namespace Torch
/// <summary>
/// An observable version of <see cref="List{T}"/>.
/// </summary>
public class ObservableList<T> : IList<T>, INotifyCollectionChanged, INotifyPropertyChanged
public class ObservableList<T> : ViewModel, IList<T>
{
private List<T> _internalList = new List<T>();
/// <inheritdoc />
public event NotifyCollectionChangedEventHandler CollectionChanged;
/// <inheritdoc />
public event PropertyChangedEventHandler PropertyChanged;
/// <inheritdoc />
public void Clear()
{
@@ -146,31 +140,6 @@ namespace Torch
}
}
protected void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
var collectionChanged = CollectionChanged;
if (collectionChanged != null)
foreach (var del in collectionChanged.GetInvocationList())
{
var nh = (NotifyCollectionChangedEventHandler)del;
var dispObj = nh.Target as DispatcherObject;
var dispatcher = dispObj?.Dispatcher;
if (dispatcher != null && !dispatcher.CheckAccess())
{
dispatcher.BeginInvoke(() => nh.Invoke(this, e), DispatcherPriority.DataBind);
continue;
}
nh.Invoke(this, e);
}
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
/// <inheritdoc />
public IEnumerator<T> GetEnumerator()
{

View File

@@ -2,6 +2,9 @@
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Windows.Threading;
namespace Torch
{
@@ -27,6 +30,20 @@ namespace Torch
return source as IReadOnlyList<T> ?? new ReadOnlyCollection<T>(source);
}
/// <summary>
/// Returns a read-only wrapped <see cref="IList{T}"/> and proxies its <see cref="INotifyPropertyChanged"/> and <see cref="INotifyCollectionChanged"/> events.
/// </summary>
public static IReadOnlyList<T> AsReadOnlyObservable<T>(this IList<T> source)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
if (source is INotifyPropertyChanged && source is INotifyCollectionChanged)
return new ObservableReadOnlyList<T>(source);
throw new InvalidOperationException("The given list is not observable.");
}
/// <summary>
/// Returns a read-only wrapped <see cref="IDictionary{TKey, TValue}"/>
/// </summary>
@@ -37,6 +54,126 @@ namespace Torch
return source as IReadOnlyDictionary<TKey, TValue> ?? new ReadOnlyDictionary<TKey, TValue>(source);
}
/// <summary>
/// Returns a read-only wrapped <see cref="IDictionary{TKey,TValue}"/> and proxies its <see cref="INotifyPropertyChanged"/> and <see cref="INotifyCollectionChanged"/> events.
/// </summary>
public static IReadOnlyDictionary<TKey, TValue> AsReadOnlyObservable<TKey, TValue>(this IDictionary<TKey, TValue> source)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
if (source is INotifyPropertyChanged && source is INotifyCollectionChanged)
return new ObservableReadOnlyDictionary<TKey, TValue>(source);
throw new InvalidOperationException("The given dictionary is not observable.");
}
sealed class ObservableReadOnlyList<T> : ViewModel, IReadOnlyList<T>, IDisposable
{
private IList<T> _list;
public ObservableReadOnlyList(IList<T> list)
{
_list = list;
if (_list is INotifyPropertyChanged p)
p.PropertyChanged += OnPropertyChanged;
if (_list is INotifyCollectionChanged c)
c.CollectionChanged += OnCollectionChanged;
}
public void Dispose()
{
if (_list is INotifyPropertyChanged p)
p.PropertyChanged -= OnPropertyChanged;
if (_list is INotifyCollectionChanged c)
c.CollectionChanged -= OnCollectionChanged;
}
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
OnCollectionChanged(e);
}
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
OnPropertyChanged(e.PropertyName);
}
/// <inheritdoc />
public IEnumerator<T> GetEnumerator() => _list.GetEnumerator();
/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_list).GetEnumerator();
/// <inheritdoc />
public int Count => _list.Count;
/// <inheritdoc />
public T this[int index] => _list[index];
}
sealed class ObservableReadOnlyDictionary<TKey, TValue> : ViewModel, IReadOnlyDictionary<TKey, TValue>, IDisposable
{
private readonly IDictionary<TKey, TValue> _dictionary;
public ObservableReadOnlyDictionary(IDictionary<TKey, TValue> dictionary)
{
_dictionary = dictionary;
if (_dictionary is INotifyPropertyChanged p)
p.PropertyChanged += OnPropertyChanged;
if (_dictionary is INotifyCollectionChanged c)
c.CollectionChanged += OnCollectionChanged;
}
public void Dispose()
{
if (_dictionary is INotifyPropertyChanged p)
p.PropertyChanged -= OnPropertyChanged;
if (_dictionary is INotifyCollectionChanged c)
c.CollectionChanged -= OnCollectionChanged;
}
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
OnCollectionChanged(e);
}
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
OnPropertyChanged(e.PropertyName);
}
/// <inheritdoc />
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => _dictionary.GetEnumerator();
/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_dictionary).GetEnumerator();
/// <inheritdoc />
public int Count => _dictionary.Count;
/// <inheritdoc />
public bool ContainsKey(TKey key) => _dictionary.ContainsKey(key);
/// <inheritdoc />
public bool TryGetValue(TKey key, out TValue value) => _dictionary.TryGetValue(key, out value);
/// <inheritdoc />
public TValue this[TKey key] => _dictionary[key];
/// <inheritdoc />
public IEnumerable<TKey> Keys => _dictionary.Keys;
/// <inheritdoc />
public IEnumerable<TValue> Values => _dictionary.Values;
}
sealed class ReadOnlyCollectionAdapter<T> : IReadOnlyCollection<T>
{
private readonly ICollection<T> _source;

View File

@@ -34,7 +34,7 @@ namespace Torch.Managers
#pragma warning restore 649
/// <inheritdoc />
public IReadOnlyDictionary<Guid, ITorchPlugin> Plugins => _plugins.AsReadOnly();
public IReadOnlyDictionary<Guid, ITorchPlugin> Plugins => _plugins.AsReadOnlyObservable();
public event Action<IReadOnlyCollection<ITorchPlugin>> PluginsLoaded;

View File

@@ -7,21 +7,47 @@ using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Threading;
namespace Torch
{
/// <summary>
/// Provides a method to notify an observer of changes to an object's properties.
/// </summary>
public abstract class ViewModel : INotifyPropertyChanged
public abstract class ViewModel : INotifyPropertyChanged, INotifyCollectionChanged
{
/// <inheritdoc />
public event PropertyChangedEventHandler PropertyChanged;
/// <inheritdoc />
public event NotifyCollectionChangedEventHandler CollectionChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
NotifyCollectionChangedEventHandler collectionChanged = CollectionChanged;
if (collectionChanged != null)
foreach (NotifyCollectionChangedEventHandler nh in collectionChanged.GetInvocationList())
{
var dispObj = nh.Target as DispatcherObject;
var dispatcher = dispObj?.Dispatcher;
if (dispatcher != null && !dispatcher.CheckAccess())
{
dispatcher.BeginInvoke(
(Action)(() => nh.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))),
DispatcherPriority.DataBind);
continue;
}
nh.Invoke(this, e);
}
}
protected virtual void SetValue<T>(ref T backingField, T value, [CallerMemberName] string propName = "")
{
if (backingField.Equals(value))