Browse Source

Added ResetBehavior to PerspexList.

As sometimes you want to know something is a reset rather than a remove.
Steven Kirk 10 years ago
parent
commit
d87bcde7be

+ 7 - 0
src/Perspex.Animation/PropertyTransitions.cs

@@ -10,5 +10,12 @@ namespace Perspex.Animation
     /// </summary>
     public class PropertyTransitions : PerspexList<PropertyTransition>
     {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="PropertyTransitions"/> class.
+        /// </summary>
+        public PropertyTransitions()
+        {
+            ResetBehavior = ResetBehavior.Remove;
+        }
     }
 }

+ 92 - 4
src/Perspex.Base/Collections/PerspexList.cs

@@ -10,15 +10,47 @@ using System.Linq;
 
 namespace Perspex.Collections
 {
+    /// <summary>
+    /// Describes the action notified on a clear of a <see cref="PerspexList{T}"/>.
+    /// </summary>
+    public enum ResetBehavior
+    {
+        /// <summary>
+        /// Clearing the list notifies a with a 
+        /// <see cref="NotifyCollectionChangedAction.Reset"/>.
+        /// </summary>
+        Reset,
+
+        /// <summary>
+        /// Clearing the list notifies a with a
+        /// <see cref="NotifyCollectionChangedAction.Remove"/>.
+        /// </summary>
+        Remove,
+    }
+
     /// <summary>
     /// A notifying list.
     /// </summary>
     /// <typeparam name="T">The type of the list items.</typeparam>
     /// <remarks>
+    /// <para>
     /// PerspexList is similar to <see cref="System.Collections.ObjectModel.ObservableCollection{T}"/>
-    /// except that when the <see cref="Clear"/> method is called, it notifies with a
-    /// <see cref="NotifyCollectionChangedAction.Remove"/> action, passing the items that were
-    /// removed.
+    /// with a few added features:
+    /// </para>
+    /// 
+    /// <list type="bullet">
+    /// <item>
+    /// It can be configured to notify the <see cref="CollectionChanged"/> event with a
+    /// <see cref="NotifyCollectionChangedAction.Remove"/> action instead of a
+    /// <see cref="NotifyCollectionChangedAction.Reset"/> when the list is cleared by
+    /// setting <see cref="ResetBehavior"/> to <see cref="ResetBehavior.Remove"/>.
+    /// removed
+    /// </item>
+    /// <item>
+    /// A <see cref="Validate"/> function can be used to validate each item before insertion.
+    /// removed
+    /// </item>
+    /// </list>
     /// </remarks>
     public class PerspexList<T> : IPerspexList<T>, IList, INotifyCollectionChanged, INotifyPropertyChanged
     {
@@ -65,6 +97,17 @@ namespace Perspex.Collections
         /// </summary>
         public int Count => _inner.Count;
 
+        /// <summary>
+        /// Gets or sets the reset behavior of the list.
+        /// </summary>
+        public ResetBehavior ResetBehavior { get; set; }
+
+        /// <summary>
+        /// Gets or sets a validation routine that can be used to validate items before they are
+        /// added.
+        /// </summary>
+        public Action<T> Validate { get; set; }
+
         /// <inheritdoc/>
         bool IList.IsFixedSize => false;
 
@@ -97,6 +140,8 @@ namespace Perspex.Collections
 
             set
             {
+                Validate?.Invoke(value);
+
                 T old = _inner[index];
                 _inner[index] = value;
 
@@ -128,6 +173,7 @@ namespace Perspex.Collections
         /// <param name="item">The item.</param>
         public void Add(T item)
         {
+            Validate?.Invoke(item);
             int index = _inner.Count;
             _inner.Add(item);
             NotifyAdd(new[] { item }, index);
@@ -141,6 +187,14 @@ namespace Perspex.Collections
         {
             Contract.Requires<ArgumentNullException>(items != null);
 
+            if (Validate != null)
+            {
+                foreach (var item in items)
+                {
+                    Validate(item);
+                }
+            }
+
             int index = _inner.Count;
             _inner.AddRange(items);
             NotifyAdd((items as IList) ?? items.ToList(), index);
@@ -153,7 +207,7 @@ namespace Perspex.Collections
         {
             var old = _inner;
             _inner = new List<T>();
-            NotifyRemove(old, 0);
+            NotifyReset(old);
         }
 
         /// <summary>
@@ -204,6 +258,7 @@ namespace Perspex.Collections
         /// <param name="item">The item.</param>
         public void Insert(int index, T item)
         {
+            Validate?.Invoke(item);
             _inner.Insert(index, item);
             NotifyAdd(new[] { item }, index);
         }
@@ -217,6 +272,14 @@ namespace Perspex.Collections
         {
             Contract.Requires<ArgumentNullException>(items != null);
 
+            if (Validate != null)
+            {
+                foreach (var item in items)
+                {
+                    Validate(item);
+                }
+            }
+
             _inner.InsertRange(index, items);
             NotifyAdd((items as IList) ?? items.ToList(), index);
         }
@@ -367,5 +430,30 @@ namespace Perspex.Collections
 
             NotifyCountChanged();
         }
+
+        /// <summary>
+        /// Raises the <see cref="CollectionChanged"/> event with a reset action.
+        /// </summary>
+        /// <param name="t">The items that were removed.</param>
+        private void NotifyReset(IList t)
+        {
+            if (CollectionChanged != null)
+            {
+                NotifyCollectionChangedEventArgs e;
+
+                if (ResetBehavior == ResetBehavior.Reset)
+                {
+                    e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
+                }
+                else
+                {
+                    e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, t, 0);
+                }
+
+                CollectionChanged(this, e);
+            }
+
+            NotifyCountChanged();
+        }
     }
 }

+ 2 - 0
src/Perspex.Controls/ColumnDefinitions.cs

@@ -17,6 +17,7 @@ namespace Perspex.Controls
         /// </summary>
         public ColumnDefinitions()
         {
+            ResetBehavior = ResetBehavior.Remove;
         }
 
         /// <summary>
@@ -24,6 +25,7 @@ namespace Perspex.Controls
         /// </summary>
         /// <param name="s">A string representation of the column definitions.</param>
         public ColumnDefinitions(string s)
+            : this()
         {
             AddRange(GridLength.ParseLengths(s, CultureInfo.InvariantCulture).Select(x => new ColumnDefinition(x)));
         }

+ 3 - 1
src/Perspex.Controls/Control.cs

@@ -271,7 +271,9 @@ namespace Perspex.Controls
             {
                 if (_logicalChildren == null)
                 {
-                    _logicalChildren = new PerspexList<ILogical>();
+                    var list = new PerspexList<ILogical>();
+                    list.ResetBehavior = ResetBehavior.Remove;
+                    _logicalChildren = list;
                 }
 
                 return _logicalChildren;

+ 2 - 0
src/Perspex.Controls/Controls.cs

@@ -16,6 +16,7 @@ namespace Perspex.Controls
         /// </summary>
         public Controls()
         {
+            ResetBehavior = ResetBehavior.Remove;
         }
 
         /// <summary>
@@ -25,6 +26,7 @@ namespace Perspex.Controls
         public Controls(IEnumerable<IControl> items)
             : base(items)
         {
+            ResetBehavior = ResetBehavior.Remove;
         }
     }
 }

+ 2 - 0
src/Perspex.Controls/RowDefinitions.cs

@@ -17,6 +17,7 @@ namespace Perspex.Controls
         /// </summary>
         public RowDefinitions()
         {
+            ResetBehavior = ResetBehavior.Remove;
         }
 
         /// <summary>
@@ -24,6 +25,7 @@ namespace Perspex.Controls
         /// </summary>
         /// <param name="s">A string representation of the row definitions.</param>
         public RowDefinitions(string s)
+            : this()
         {
             AddRange(GridLength.ParseLengths(s, CultureInfo.InvariantCulture).Select(x => new RowDefinition(x)));
         }

+ 7 - 0
src/Perspex.Controls/Templates/DataTemplates.cs

@@ -10,5 +10,12 @@ namespace Perspex.Controls.Templates
     /// </summary>
     public class DataTemplates : PerspexList<IDataTemplate>
     {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="DataTemplates"/> class.
+        /// </summary>
+        public DataTemplates()
+        {
+            ResetBehavior = ResetBehavior.Remove;
+        }
     }
 }

+ 1 - 0
src/Perspex.SceneGraph/Visual.cs

@@ -124,6 +124,7 @@ namespace Perspex
             });
 
             _visualChildren = new PerspexList<IVisual>();
+            _visualChildren.ResetBehavior = ResetBehavior.Remove;
             _visualChildren.CollectionChanged += VisualChildrenChanged;
         }
 

+ 20 - 0
tests/Perspex.Base.UnitTests/Collections/PerspexListTests.cs

@@ -158,12 +158,32 @@ namespace Perspex.Base.UnitTests.Collections
             Assert.True(raised);
         }
 
+        [Fact]
+        public void Clearing_Items_Should_Raise_CollectionChanged_Reset()
+        {
+            var target = new PerspexList<int>(new[] { 1, 2, 3 });
+            var raised = false;
+
+            target.CollectionChanged += (s, e) =>
+            {
+                Assert.Equal(target, s);
+                Assert.Equal(NotifyCollectionChangedAction.Reset, e.Action);
+
+                raised = true;
+            };
+
+            target.Clear();
+
+            Assert.True(raised);
+        }
+
         [Fact]
         public void Clearing_Items_Should_Raise_CollectionChanged_Remove()
         {
             var target = new PerspexList<int>(new[] { 1, 2, 3 });
             var raised = false;
 
+            target.ResetBehavior = ResetBehavior.Remove;
             target.CollectionChanged += (s, e) =>
             {
                 Assert.Equal(target, s);