7 changed files with 464 additions and 1 deletions
@ -0,0 +1,237 @@ |
|||||
|
// -----------------------------------------------------------------------
|
||||
|
// <copyright file="PerspexDictionary.cs" company="Steven Kirk">
|
||||
|
// Copyright 2015 MIT Licence. See licence.md for more information.
|
||||
|
// </copyright>
|
||||
|
// -----------------------------------------------------------------------
|
||||
|
|
||||
|
namespace Perspex.Collections |
||||
|
{ |
||||
|
using System; |
||||
|
using System.Collections; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Collections.Specialized; |
||||
|
using System.ComponentModel; |
||||
|
using System.Linq; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// A notifying dictionary.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TKey">The type of the dictionary key.</typeparam>
|
||||
|
/// <typeparam name="TValue">The type of the dictionary value.</typeparam>
|
||||
|
public class PerspexDictionary<TKey, TValue> : IDictionary<TKey, TValue>, |
||||
|
INotifyCollectionChanged, |
||||
|
INotifyPropertyChanged |
||||
|
{ |
||||
|
private Dictionary<TKey, TValue> inner; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="PerspexDictionary{TKey, TValue}"/> class.
|
||||
|
/// </summary>
|
||||
|
public PerspexDictionary() |
||||
|
{ |
||||
|
this.inner = new Dictionary<TKey, TValue>(); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Occurs when the collection changes.
|
||||
|
/// </summary>
|
||||
|
public event NotifyCollectionChangedEventHandler CollectionChanged; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Raised when a property on the collection changes.
|
||||
|
/// </summary>
|
||||
|
public event PropertyChangedEventHandler PropertyChanged; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public int Count |
||||
|
{ |
||||
|
get { return this.inner.Count; } |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public bool IsReadOnly |
||||
|
{ |
||||
|
get { return false; } |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public ICollection<TKey> Keys |
||||
|
{ |
||||
|
get { return this.inner.Keys; } |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public ICollection<TValue> Values |
||||
|
{ |
||||
|
get { return this.inner.Values; } |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the named resource.
|
||||
|
/// </summary>
|
||||
|
/// <param name="key">The resource key.</param>
|
||||
|
/// <returns>The resource, or null if not found.</returns>
|
||||
|
public TValue this[TKey key] |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
return this.inner[key]; |
||||
|
} |
||||
|
|
||||
|
set |
||||
|
{ |
||||
|
TValue old; |
||||
|
bool replace = this.inner.TryGetValue(key, out old); |
||||
|
this.inner[key] = value; |
||||
|
|
||||
|
if (replace) |
||||
|
{ |
||||
|
if (this.PropertyChanged != null) |
||||
|
{ |
||||
|
this.PropertyChanged(this, new PropertyChangedEventArgs($"Item[{key}]")); |
||||
|
} |
||||
|
|
||||
|
if (this.CollectionChanged != null) |
||||
|
{ |
||||
|
var e = new NotifyCollectionChangedEventArgs( |
||||
|
NotifyCollectionChangedAction.Replace, |
||||
|
new KeyValuePair<TKey, TValue>(key, value), |
||||
|
new KeyValuePair<TKey, TValue>(key, old)); |
||||
|
this.CollectionChanged(this, e); |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
this.NotifyAdd(key, value); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public void Add(TKey key, TValue value) |
||||
|
{ |
||||
|
this.inner.Add(key, value); |
||||
|
this.NotifyAdd(key, value); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public void Clear() |
||||
|
{ |
||||
|
var old = this.inner; |
||||
|
|
||||
|
this.inner = new Dictionary<TKey, TValue>(); |
||||
|
|
||||
|
if (this.PropertyChanged != null) |
||||
|
{ |
||||
|
this.PropertyChanged(this, new PropertyChangedEventArgs("Count")); |
||||
|
this.PropertyChanged(this, new PropertyChangedEventArgs($"Item[]")); |
||||
|
} |
||||
|
|
||||
|
if (this.CollectionChanged != null) |
||||
|
{ |
||||
|
var e = new NotifyCollectionChangedEventArgs( |
||||
|
NotifyCollectionChangedAction.Remove, |
||||
|
old.ToList(), |
||||
|
-1); |
||||
|
this.CollectionChanged(this, e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public bool ContainsKey(TKey key) |
||||
|
{ |
||||
|
return this.inner.ContainsKey(key); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) |
||||
|
{ |
||||
|
((IDictionary<TKey, TValue>)this.inner).CopyTo(array, arrayIndex); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() |
||||
|
{ |
||||
|
return this.inner.GetEnumerator(); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public bool Remove(TKey key) |
||||
|
{ |
||||
|
TValue value; |
||||
|
|
||||
|
if (this.inner.TryGetValue(key, out value)) |
||||
|
{ |
||||
|
if (this.PropertyChanged != null) |
||||
|
{ |
||||
|
this.PropertyChanged(this, new PropertyChangedEventArgs("Count")); |
||||
|
this.PropertyChanged(this, new PropertyChangedEventArgs($"Item[{key}]")); |
||||
|
} |
||||
|
|
||||
|
if (this.CollectionChanged != null) |
||||
|
{ |
||||
|
var e = new NotifyCollectionChangedEventArgs( |
||||
|
NotifyCollectionChangedAction.Remove, |
||||
|
new[] { new KeyValuePair<TKey, TValue>(key, value) }, |
||||
|
-1); |
||||
|
this.CollectionChanged(this, e); |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public bool TryGetValue(TKey key, out TValue value) |
||||
|
{ |
||||
|
return this.inner.TryGetValue(key, out value); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
IEnumerator IEnumerable.GetEnumerator() |
||||
|
{ |
||||
|
return this.inner.GetEnumerator(); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item) |
||||
|
{ |
||||
|
this.Add(item.Key, item.Value); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item) |
||||
|
{ |
||||
|
return this.inner.Contains(item); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item) |
||||
|
{ |
||||
|
return this.Remove(item.Key); |
||||
|
} |
||||
|
|
||||
|
private void NotifyAdd(TKey key, TValue value) |
||||
|
{ |
||||
|
if (this.PropertyChanged != null) |
||||
|
{ |
||||
|
this.PropertyChanged(this, new PropertyChangedEventArgs("Count")); |
||||
|
this.PropertyChanged(this, new PropertyChangedEventArgs($"Item[{key}]")); |
||||
|
} |
||||
|
|
||||
|
if (this.CollectionChanged != null) |
||||
|
{ |
||||
|
var val = new KeyValuePair<TKey, TValue>(key, value); |
||||
|
var e = new NotifyCollectionChangedEventArgs( |
||||
|
NotifyCollectionChangedAction.Add, |
||||
|
new[] { new KeyValuePair<TKey, TValue>(key, value) }, |
||||
|
-1); |
||||
|
this.CollectionChanged(this, e); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,36 @@ |
|||||
|
// -----------------------------------------------------------------------
|
||||
|
// <copyright file="CollectionChangedTracker.cs" company="Steven Kirk">
|
||||
|
// Copyright 2015 MIT Licence. See licence.md for more information.
|
||||
|
// </copyright>
|
||||
|
// -----------------------------------------------------------------------
|
||||
|
|
||||
|
namespace Perspex.Base.UnitTests.Collections |
||||
|
{ |
||||
|
using System; |
||||
|
using System.Collections.Specialized; |
||||
|
|
||||
|
internal class CollectionChangedTracker |
||||
|
{ |
||||
|
public CollectionChangedTracker(INotifyCollectionChanged collection) |
||||
|
{ |
||||
|
collection.CollectionChanged += this.CollectionChanged; |
||||
|
} |
||||
|
|
||||
|
public NotifyCollectionChangedEventArgs Args { get; private set; } |
||||
|
|
||||
|
public void Reset() |
||||
|
{ |
||||
|
this.Args = null; |
||||
|
} |
||||
|
|
||||
|
private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) |
||||
|
{ |
||||
|
if (this.Args != null) |
||||
|
{ |
||||
|
throw new Exception("CollectionChanged called more than once."); |
||||
|
} |
||||
|
|
||||
|
this.Args = e; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,154 @@ |
|||||
|
// -----------------------------------------------------------------------
|
||||
|
// <copyright file="PerspexDictionaryTests.cs" company="Steven Kirk">
|
||||
|
// Copyright 2015 MIT Licence. See licence.md for more information.
|
||||
|
// </copyright>
|
||||
|
// -----------------------------------------------------------------------
|
||||
|
|
||||
|
namespace Perspex.Base.UnitTests.Collections |
||||
|
{ |
||||
|
using System.Collections.Generic; |
||||
|
using System.Collections.Specialized; |
||||
|
using Perspex.Collections; |
||||
|
using Xunit; |
||||
|
|
||||
|
public class PerspexDictionaryTests |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void Adding_Item_Should_Raise_CollectionChanged() |
||||
|
{ |
||||
|
var target = new PerspexDictionary<string, string>(); |
||||
|
var tracker = new CollectionChangedTracker(target); |
||||
|
|
||||
|
target.Add("foo", "bar"); |
||||
|
|
||||
|
Assert.NotNull(tracker.Args); |
||||
|
Assert.Equal(NotifyCollectionChangedAction.Add, tracker.Args.Action); |
||||
|
Assert.Equal(-1, tracker.Args.NewStartingIndex); |
||||
|
Assert.Equal(1, tracker.Args.NewItems.Count); |
||||
|
Assert.Equal(new KeyValuePair<string, string>("foo", "bar"), tracker.Args.NewItems[0]); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Adding_Item_Should_Raise_PropertyChanged() |
||||
|
{ |
||||
|
var target = new PerspexDictionary<string, string>(); |
||||
|
var tracker = new PropertyChangedTracker(target); |
||||
|
|
||||
|
target.Add("foo", "bar"); |
||||
|
|
||||
|
Assert.Equal(new[] { "Count", "Item[foo]" }, tracker.Names); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Assigning_Item_Should_Raise_CollectionChanged_Add() |
||||
|
{ |
||||
|
var target = new PerspexDictionary<string, string>(); |
||||
|
var tracker = new CollectionChangedTracker(target); |
||||
|
|
||||
|
target["foo"] = "bar"; |
||||
|
|
||||
|
Assert.NotNull(tracker.Args); |
||||
|
Assert.Equal(NotifyCollectionChangedAction.Add, tracker.Args.Action); |
||||
|
Assert.Equal(-1, tracker.Args.NewStartingIndex); |
||||
|
Assert.Equal(1, tracker.Args.NewItems.Count); |
||||
|
Assert.Equal(new KeyValuePair<string, string>("foo", "bar"), tracker.Args.NewItems[0]); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Assigning_Item_Should_Raise_CollectionChanged_Replace() |
||||
|
{ |
||||
|
var target = new PerspexDictionary<string, string>(); |
||||
|
|
||||
|
target["foo"] = "baz"; |
||||
|
var tracker = new CollectionChangedTracker(target); |
||||
|
target["foo"] = "bar"; |
||||
|
|
||||
|
Assert.NotNull(tracker.Args); |
||||
|
Assert.Equal(NotifyCollectionChangedAction.Replace, tracker.Args.Action); |
||||
|
Assert.Equal(-1, tracker.Args.NewStartingIndex); |
||||
|
Assert.Equal(1, tracker.Args.NewItems.Count); |
||||
|
Assert.Equal(new KeyValuePair<string, string>("foo", "bar"), tracker.Args.NewItems[0]); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Assigning_Item_Should_Raise_PropertyChanged_Add() |
||||
|
{ |
||||
|
var target = new PerspexDictionary<string, string>(); |
||||
|
var tracker = new PropertyChangedTracker(target); |
||||
|
|
||||
|
target["foo"] = "bar"; |
||||
|
|
||||
|
Assert.Equal(new[] { "Count", "Item[foo]" }, tracker.Names); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Assigning_Item_Should_Raise_PropertyChanged_Replace() |
||||
|
{ |
||||
|
var target = new PerspexDictionary<string, string>(); |
||||
|
|
||||
|
target["foo"] = "baz"; |
||||
|
var tracker = new PropertyChangedTracker(target); |
||||
|
target["foo"] = "bar"; |
||||
|
|
||||
|
Assert.Equal(new[] { "Item[foo]" }, tracker.Names); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Removing_Item_Should_Raise_CollectionChanged() |
||||
|
{ |
||||
|
var target = new PerspexDictionary<string, string>(); |
||||
|
|
||||
|
target["foo"] = "bar"; |
||||
|
var tracker = new CollectionChangedTracker(target); |
||||
|
target.Remove("foo"); |
||||
|
|
||||
|
Assert.NotNull(tracker.Args); |
||||
|
Assert.Equal(NotifyCollectionChangedAction.Remove, tracker.Args.Action); |
||||
|
Assert.Equal(-1, tracker.Args.OldStartingIndex); |
||||
|
Assert.Equal(1, tracker.Args.OldItems.Count); |
||||
|
Assert.Equal(new KeyValuePair<string, string>("foo", "bar"), tracker.Args.OldItems[0]); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Removing_Item_Should_Raise_PropertyChanged() |
||||
|
{ |
||||
|
var target = new PerspexDictionary<string, string>(); |
||||
|
|
||||
|
target["foo"] = "bar"; |
||||
|
var tracker = new PropertyChangedTracker(target); |
||||
|
target.Remove("foo"); |
||||
|
|
||||
|
Assert.Equal(new[] { "Count", "Item[foo]" }, tracker.Names); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Clearing_Collection_Should_Raise_CollectionChanged() |
||||
|
{ |
||||
|
var target = new PerspexDictionary<string, string>(); |
||||
|
|
||||
|
target["foo"] = "bar"; |
||||
|
target["baz"] = "qux"; |
||||
|
var tracker = new CollectionChangedTracker(target); |
||||
|
target.Clear(); |
||||
|
|
||||
|
Assert.NotNull(tracker.Args); |
||||
|
Assert.Equal(NotifyCollectionChangedAction.Remove, tracker.Args.Action); |
||||
|
Assert.Equal(-1, tracker.Args.OldStartingIndex); |
||||
|
Assert.Equal(2, tracker.Args.OldItems.Count); |
||||
|
Assert.Equal(new KeyValuePair<string, string>("foo", "bar"), tracker.Args.OldItems[0]); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Clearing_Collection_Should_Raise_PropertyChanged() |
||||
|
{ |
||||
|
var target = new PerspexDictionary<string, string>(); |
||||
|
|
||||
|
target["foo"] = "bar"; |
||||
|
target["baz"] = "qux"; |
||||
|
var tracker = new PropertyChangedTracker(target); |
||||
|
target.Clear(); |
||||
|
|
||||
|
Assert.Equal(new[] { "Count", "Item[]" }, tracker.Names); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
// -----------------------------------------------------------------------
|
||||
|
// <copyright file="PropertyChangedTracker.cs" company="Steven Kirk">
|
||||
|
// Copyright 2015 MIT Licence. See licence.md for more information.
|
||||
|
// </copyright>
|
||||
|
// -----------------------------------------------------------------------
|
||||
|
|
||||
|
namespace Perspex.Base.UnitTests.Collections |
||||
|
{ |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.ComponentModel; |
||||
|
|
||||
|
internal class PropertyChangedTracker |
||||
|
{ |
||||
|
public PropertyChangedTracker(INotifyPropertyChanged obj) |
||||
|
{ |
||||
|
this.Names = new List<string>(); |
||||
|
obj.PropertyChanged += this.PropertyChanged; |
||||
|
} |
||||
|
|
||||
|
public List<string> Names { get; private set; } |
||||
|
|
||||
|
public void Reset() |
||||
|
{ |
||||
|
this.Names.Clear(); |
||||
|
} |
||||
|
|
||||
|
private void PropertyChanged(object sender, PropertyChangedEventArgs e) |
||||
|
{ |
||||
|
this.Names.Add(e.PropertyName); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue