From 19e8339aced9cc23366bff7edd169429d0593286 Mon Sep 17 00:00:00 2001 From: Daniel Cazzulino Date: Thu, 24 Oct 2013 13:41:21 -0300 Subject: [PATCH] Added support for richer Added/Removed events on collections and containers. #50 --- Core.UnitTests/ComponentModelFixture.cs | 117 ++++++++++++++++++++++++ Core/Collection.cs | 11 ++- Core/Component.cs | 14 ++- Core/Container.cs | 8 ++ Core/ValueHandler.cs | 1 + Source/ICollection.cs | 3 + Source/IComponent.cs | 1 + Source/IContainer.cs | 3 + Source/IProductStore.cs | 9 +- Source/IWarehouse.cs | 42 +++++++++ Source/NuPattern.csproj | 2 + Source/ValueEvent.cs | 68 ++++++++++++++ 12 files changed, 270 insertions(+), 9 deletions(-) create mode 100644 Source/IWarehouse.cs create mode 100644 Source/ValueEvent.cs diff --git a/Core.UnitTests/ComponentModelFixture.cs b/Core.UnitTests/ComponentModelFixture.cs index 2cb19f6..6c6bf3a 100644 --- a/Core.UnitTests/ComponentModelFixture.cs +++ b/Core.UnitTests/ComponentModelFixture.cs @@ -17,6 +17,20 @@ public void when_creating_element_then_references_parent_product() Assert.Same(child.Parent, product); } + [Fact] + public void when_creating_element_then_raises_component_added() + { + var product = new Product("Foo", "IFoo"); + var added = default(IComponent); + + product.ComponentAdded += (sender, args) => added = args.Value; + + var child = product.CreateElement("Storage", "IStorage"); + + Assert.NotNull(added); + Assert.Same(added, child); + } + [Fact] public void when_element_new_name_is_duplicated_with_other_element_then_throws() { @@ -40,6 +54,44 @@ public void when_element_new_name_is_duplicated_with_container_property_then_thr Assert.Throws(() => child.Name = "Element"); } + [Fact] + public void when_element_name_changed_then_raises_property_changing() + { + var product = new Product("Foo", "IFoo"); + var changed = false; + + product.PropertyChanging += (sender, args) => + { + changed = true; + Assert.Equal("Name", args.PropertyName); + Assert.Equal("Foo", args.OldValue); + Assert.Equal("Bar", args.NewValue); + }; + + product.Name = "Bar"; + + Assert.True(changed); + } + + [Fact] + public void when_element_name_changed_then_raises_property_changed() + { + var product = new Product("Foo", "IFoo"); + var changed = false; + + product.PropertyChanged += (sender, args) => + { + changed = true; + Assert.Equal("Name", args.PropertyName); + Assert.Equal("Foo", args.OldValue); + Assert.Equal("Bar", args.NewValue); + }; + + product.Name = "Bar"; + + Assert.True(changed); + } + [Fact] public void when_creating_collection_then_references_parent_product() { @@ -133,6 +185,21 @@ public void when_deleting_element_then_removes_from_parent_components() Assert.Equal(0, product.Components.Count()); } + [Fact] + public void when_deleting_element_then_raises_component_removed() + { + var product = new Product("Foo", "IFoo"); + var removed = default(IComponent); + var child = product.CreateElement("Storage", "IStorage"); + + product.ComponentRemoved += (sender, args) => removed = args.Value; + + child.Delete(); + + Assert.NotNull(removed); + Assert.Same(removed, child); + } + [Fact] public void when_deleting_collection_item_then_removes_from_parent_collection() { @@ -145,6 +212,22 @@ public void when_deleting_collection_item_then_removes_from_parent_collection() Assert.Equal(0, collection.Items.Count()); } + [Fact] + public void when_deleting_collection_item_then_raises_item_deleted() + { + var product = new Product("Foo", "IFoo"); + var collection = product.CreateCollection("Collection", "ICollection"); + var child = collection.CreateItem("Item", "IItem"); + + var deleted = default(IElement); + collection.ItemRemoved += (sender, args) => deleted = args.Value; + + child.Delete(); + + Assert.NotNull(deleted); + Assert.Same(child, deleted); + } + [Fact] public void when_deleting_element_then_disposes_it() { @@ -293,6 +376,22 @@ public void when_creating_collection_item_then_can_access_it() Assert.Same(item, collection.Items.First()); } + [Fact] + public void when_creating_collection_item_then_raises_item_added() + { + var product = new Product("Product", "IProduct"); + var collection = product.CreateCollection("Collection", "ICollection"); + + var item = default(IElement); + + collection.ItemAdded += (sender, args) => item = args.Value; + + var created = collection.CreateItem("Item", "IItem"); + + Assert.NotNull(item); + Assert.Same(created, item); + } + [Fact] public void when_creating_collection_item_then_can_access_product() { @@ -347,6 +446,24 @@ public void when_property_changes_then_notifies_component() Assert.Equal("bar", changed.NewValue); } + [Fact] + public void when_property_changing_then_notifies_component() + { + var product = new Product("Product", "IProduct"); + product.CreateProperty("key").SetValue("foo"); + + var changing = default(PropertyChangeEventArgs); + + product.PropertyChanging += (sender, args) => changing = args; + + product.Set("key", "bar"); + + Assert.NotNull(changing); + Assert.Equal("key", changing.PropertyName); + Assert.Equal("foo", changing.OldValue); + Assert.Equal("bar", changing.NewValue); + } + [Fact] public void when_property_set_to_same_existing_value_then_does_not_raise_propertychanged() { diff --git a/Core/Collection.cs b/Core/Collection.cs index 5e5bc70..7eb943b 100644 --- a/Core/Collection.cs +++ b/Core/Collection.cs @@ -10,6 +10,9 @@ internal class Collection : Container, ICollection { private List items = new List(); + public event ValueEventHandler ItemAdded = (sender, args) => { }; + public event ValueEventHandler ItemRemoved = (sender, args) => { }; + public Collection(string name, string schemaId, Component parent) : base(name, schemaId, parent) { @@ -46,6 +49,9 @@ public Element CreateItem(string name, string schemaId) element.PropertyChanged += OnItemChanged; element.Disposed += OnItemDisposed; items.Add(element); + + ItemAdded(this, element); + return element; } @@ -78,7 +84,10 @@ internal void DeleteItem(Component component) // After the delete, the component is disposed, which will // call our OnComponentDisposed, at which point we unsubscribe // the property changed event. - items.Remove((Element)component); + var element = (Element)component; + + items.Remove(element); + ItemRemoved(this, element); } private void OnItemChanged(object sender, EventArgs args) diff --git a/Core/Component.cs b/Core/Component.cs index e60516a..55a6ce5 100644 --- a/Core/Component.cs +++ b/Core/Component.cs @@ -21,6 +21,8 @@ internal abstract class Component : IComponent, IDisposable, ILineInfo public event EventHandler PropertyChanged = (sender, args) => { }; + public event EventHandler PropertyChanging = (sender, args) => { }; + public Component(string name, string schemaId, Component parent) { this.Name = name; @@ -43,8 +45,11 @@ public string Name { if (value != name) { - OnRenaming(name, value); + var oldValue = name; + OnRenaming(oldValue, value); + RaisePropertyChanging("Name", oldValue, value); name = value; + RaisePropertyChanged("Name", oldValue, value); } } } @@ -208,8 +213,6 @@ protected virtual void OnRenaming(string oldName, string newName) var container = Parent as Container; if (container != null) container.ThrowIfDuplicateRename(oldName, newName); - - RaisePropertyChanged("Name", oldName, newName); } internal void DeleteProperty(Property property) @@ -217,6 +220,11 @@ internal void DeleteProperty(Property property) properties.Remove(property.Name); } + internal void RaisePropertyChanging(string propertyName, object oldValue, object newValue) + { + PropertyChanging(this, new PropertyChangeEventArgs(propertyName, oldValue, newValue)); + } + internal void RaisePropertyChanged(string propertyName, object oldValue, object newValue) { PropertyChanged(this, new PropertyChangeEventArgs(propertyName, oldValue, newValue)); diff --git a/Core/Container.cs b/Core/Container.cs index ada54f9..4d926ac 100644 --- a/Core/Container.cs +++ b/Core/Container.cs @@ -10,6 +10,9 @@ internal abstract class Container : Component, IContainer { private List components = new List(); + public event ValueEventHandler ComponentAdded = (sender, args) => { }; + public event ValueEventHandler ComponentRemoved = (sender, args) => { }; + public Container(string name, string schemaId, Component parent) : base(name, schemaId, parent) { @@ -43,6 +46,8 @@ public Collection CreateCollection(string name, string schemaId) collection.PropertyChanged += OnComponentChanged; collection.Disposed += OnComponentDisposed; components.Add(collection); + ComponentAdded(this, collection); + return collection; } @@ -63,6 +68,8 @@ public Element CreateElement(string name, string schemaId) element.PropertyChanged += OnComponentChanged; element.Disposed += OnComponentDisposed; components.Add(element); + ComponentAdded(this, element); + return element; } @@ -72,6 +79,7 @@ internal void DeleteComponent(Component component) // call our OnComponentDisposed, at which point we unsubscribe // the property changed event. components.Remove(component); + ComponentRemoved(this, component); } protected override void Dispose(bool disposing) diff --git a/Core/ValueHandler.cs b/Core/ValueHandler.cs index ff9d96b..a601086 100644 --- a/Core/ValueHandler.cs +++ b/Core/ValueHandler.cs @@ -27,6 +27,7 @@ public static void Set(Property property, object value) // TODO: do nothing if property name starts with "$" or "_"? if (!Object.Equals(oldValue, value)) { + property.Owner.RaisePropertyChanging(property.Name, oldValue, value); property.SetValue(value); property.Owner.RaisePropertyChanged(property.Name, oldValue, value); } diff --git a/Source/ICollection.cs b/Source/ICollection.cs index afaea12..053a807 100644 --- a/Source/ICollection.cs +++ b/Source/ICollection.cs @@ -6,6 +6,9 @@ public interface ICollection : IContainer { + event ValueEventHandler ItemAdded; + event ValueEventHandler ItemRemoved; + IEnumerable Items { get; } new ICollectionInfo Schema { get; } diff --git a/Source/IComponent.cs b/Source/IComponent.cs index c37b18c..6fbf699 100644 --- a/Source/IComponent.cs +++ b/Source/IComponent.cs @@ -8,6 +8,7 @@ public interface IComponent : IInstance, IVisitableInstance, IAnnotated { event EventHandler PropertyChanged; + event EventHandler PropertyChanging; IComponentContext Context { get; } diff --git a/Source/IContainer.cs b/Source/IContainer.cs index 5cb922c..a3b67e5 100644 --- a/Source/IContainer.cs +++ b/Source/IContainer.cs @@ -5,6 +5,9 @@ public interface IContainer : IComponent { + event ValueEventHandler ComponentAdded; + event ValueEventHandler ComponentRemoved; + IEnumerable Components { get; } ICollection CreateCollection(string name, string schemaId); diff --git a/Source/IProductStore.cs b/Source/IProductStore.cs index 6e098c3..dc2ea3a 100644 --- a/Source/IProductStore.cs +++ b/Source/IProductStore.cs @@ -7,16 +7,15 @@ public interface IProductStore { string Name { get; } + // event Closed; + // event Disposed; == Closed + // Close(); + IEnumerable Products { get; } IProduct CreateProduct(string name, string toolkitId, string schemaId); void Load(IProgress progress); void Save(IProgress progress); - - ///// - ///// Resolves a component at the store scope level. - ///// - //T Resolve(); } } \ No newline at end of file diff --git a/Source/IWarehouse.cs b/Source/IWarehouse.cs new file mode 100644 index 0000000..4ea6c4b --- /dev/null +++ b/Source/IWarehouse.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NuPattern +{ + // ISolutionViewModel + // event ElementActivated; + // event CollectionChanged (index, added/removed, etc.) + + public interface IWarehouse + { + // event ElementCreated; -> Traverse(tree) -> nodo.Remove(); + // event ElementDeleted; + // event ElementDeleting; + // event ElementInstantiated; + // event ElementLoaded; + // event ElementPropertyChanged; + // Collection.Items prop? + // Container.Elements ? + // event CollectionChanged ? + + // ctor(IElement) => propertychanged (port) -> IIS configure + // IElement.PropertyChanged. + // IWebService + // PropertyChanged("Storage") + // Changed("Items") -> Changed("Buckets") + // IStorage Storage + // PropertyChanged("Name") + // Name { get; set; } + // IBuckets Buckets -> Changed("Items") + // IBucket[0].Port -> Changed("Port") + + // Open(storeFile); + // SaveAll(); + // CloseAll(); + + + IEnumerable Stores { get; } + } +} \ No newline at end of file diff --git a/Source/NuPattern.csproj b/Source/NuPattern.csproj index 7c3b630..eaa6825 100644 --- a/Source/NuPattern.csproj +++ b/Source/NuPattern.csproj @@ -82,6 +82,7 @@ + True @@ -134,6 +135,7 @@ + diff --git a/Source/ValueEvent.cs b/Source/ValueEvent.cs new file mode 100644 index 0000000..8225f00 --- /dev/null +++ b/Source/ValueEvent.cs @@ -0,0 +1,68 @@ +using System; + +namespace NuPattern +{ + /// + /// Represents the method that will handle an event. + /// + /// The source of the event. + /// An that contains the event data. + /// The type of value generated by the event. + public delegate void ValueEventHandler(object sender, ValueEventArgs e); + + /// + /// Provides a static factory method for creating the arguments + /// class for events that publish a single value. + /// + public static class ValueEventArgs + { + /// + /// Creates an arguments class for the given value. + /// + /// + /// This helper static method makes it possible to avoid specifying + /// the type of the event args and just pass its value. + /// + public static ValueEventArgs Create(TValue value) + { + return new ValueEventArgs(value); + } + } + + /// + /// Arguments for events that publish a single value. + /// + /// The type of the value. + public class ValueEventArgs : EventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The value. + public ValueEventArgs(TValue value) + { + this.Value = value; + } + + /// + /// Gets the value. + /// + public TValue Value { get; private set; } + + /// + /// Performs an explicit conversion from to a value. + /// + public static explicit operator TValue(ValueEventArgs args) + { + return args.Value; + } + + /// + /// Performs an implicit conversion from a value to . + /// + public static implicit operator ValueEventArgs(TValue value) + { + return new ValueEventArgs(value); + } + } +}