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