Index: Core/Common/src/Core.Common.Gui/Core.Common.Gui.csproj =================================================================== diff -u -r3a8bff057967bdb42389382472f6ce55789a0ced -rd7ef66291b1a64881d4e26b8fc30c5f66f16d67a --- Core/Common/src/Core.Common.Gui/Core.Common.Gui.csproj (.../Core.Common.Gui.csproj) (revision 3a8bff057967bdb42389382472f6ce55789a0ced) +++ Core/Common/src/Core.Common.Gui/Core.Common.Gui.csproj (.../Core.Common.Gui.csproj) (revision d7ef66291b1a64881d4e26b8fc30c5f66f16d67a) @@ -131,9 +131,7 @@ - - Index: Core/Common/src/Core.Common.Gui/PropertyBag/DynamicPropertyBag.cs =================================================================== diff -u -r9838de527f1f407574730b4e4c04c0982773f9c9 -rd7ef66291b1a64881d4e26b8fc30c5f66f16d67a --- Core/Common/src/Core.Common.Gui/PropertyBag/DynamicPropertyBag.cs (.../DynamicPropertyBag.cs) (revision 9838de527f1f407574730b4e4c04c0982773f9c9) +++ Core/Common/src/Core.Common.Gui/PropertyBag/DynamicPropertyBag.cs (.../DynamicPropertyBag.cs) (revision d7ef66291b1a64881d4e26b8fc30c5f66f16d67a) @@ -17,19 +17,31 @@ { /// /// Instantiates a new instance of , wrapping another - /// object an exposing properties for that object. + /// object and exposing properties for that object. /// /// The object to be wrapped. + /// This class makes sure the following special attributes on properties are processed: + /// + /// + /// + /// + /// public DynamicPropertyBag(object propertyObject) { - Properties = new PropertySpecCollection(); - Initialize(propertyObject); + Properties = new HashSet(); + WrappedObject = propertyObject; + + foreach (var propertyInfo in propertyObject.GetType().GetProperties() + .OrderBy(x => x.MetadataToken)) + { + Properties.Add(new PropertySpec(propertyInfo)); + } } /// /// Gets the collection of properties contained within this . /// - public PropertySpecCollection Properties { get; private set; } + public ICollection Properties { get; private set; } /// /// Gets the object wrapped inside this @@ -41,16 +53,6 @@ return WrappedObject.ToString(); } - private void Initialize(object propertyObject) - { - WrappedObject = propertyObject; - - foreach (var propertyInfo in propertyObject.GetType().GetProperties().OrderBy(x => x.MetadataToken).ToArray()) - { - Properties.Add(new PropertySpec(propertyInfo)); - } - } - #region ICustomTypeDescriptor explicit interface definitions #region Implementations delegated to System.ComponentModel.TypeDescriptor @@ -80,11 +82,6 @@ return TypeDescriptor.GetDefaultEvent(this, true); } - public object GetEditor(Type editorBaseType) - { - return TypeDescriptor.GetEditor(this, editorBaseType, true); - } - public EventDescriptorCollection GetEvents() { return TypeDescriptor.GetEvents(this, true); @@ -95,54 +92,58 @@ return TypeDescriptor.GetEvents(this, attributes, true); } - public PropertyDescriptorCollection GetProperties() + public object GetEditor(Type editorBaseType) { - return GetProperties(new Attribute[0]); + return TypeDescriptor.GetEditor(this, editorBaseType, true); } #endregion public PropertyDescriptor GetDefaultProperty() { - return Properties.Count > 0 ? new PropertySpecDescriptor(Properties[0], WrappedObject) : null; + return Properties.Count > 0 ? new PropertySpecDescriptor(Properties.First(), WrappedObject) : null; } - public PropertyDescriptorCollection GetProperties(Attribute[] attributes) + public PropertyDescriptorCollection GetProperties() { - // Rather than passing this function on to the default TypeDescriptor, - // which would return the actual properties of PropertyBag, I construct - // a list here that contains property descriptors for the elements of the - // Properties list in the bag. - var props = new List(); - var propsToOrder = new List>(); + return GetProperties(new Attribute[0]); + } - foreach (PropertySpec property in Properties) - { - // Create a new property descriptor for the property item, and add it to the list. - var pd = new PropertySpecDescriptor(property, WrappedObject); + public PropertyDescriptorCollection GetProperties(Attribute[] attributesFilter) + { + var propertyDescriptorsToReturn = Properties.Select(p => new PropertySpecDescriptor(p, WrappedObject)) + .Where(t => ShouldDescriptorBeReturned(t, attributesFilter)); - var propertyOrderAttribute = property.Attributes != null ? property.Attributes.OfType().FirstOrDefault() : null; + var propertySpecDescriptors = OrderPropertyDescriptors(propertyDescriptorsToReturn); + return new PropertyDescriptorCollection(propertySpecDescriptors); + } + + private static PropertyDescriptor[] OrderPropertyDescriptors(IEnumerable propertyDescriptorsToReturn) + { + var unorderedProperties = new List(); + var propertiesWithOrdering = new List>(); + + foreach (PropertyDescriptor pd in propertyDescriptorsToReturn) + { + var propertyOrderAttribute = pd.Attributes.OfType().FirstOrDefault(); if (propertyOrderAttribute != null) { - propsToOrder.Add(new Tuple(propertyOrderAttribute.Order, pd)); + propertiesWithOrdering.Add(new Tuple(propertyOrderAttribute.Order, pd)); } else { - props.Add(pd); + unorderedProperties.Add(pd); } } + var orderedProperties = propertiesWithOrdering.OrderBy(p => p.Item1).Select(p => p.Item2); - var orderedProperties = propsToOrder.OrderBy(p => p.Item1).Select(p => p.Item2).ToList(); + return orderedProperties.Concat(unorderedProperties).ToArray(); + } - // Convert the list of PropertyDescriptors to a collection that the - // ICustomTypeDescriptor can use, and return it. - var browsableAttribute = attributes.OfType().FirstOrDefault(); - - var propertySpecDescriptors = (browsableAttribute != null) - ? orderedProperties.Concat(props).Where(p => p.IsBrowsable == browsableAttribute.Browsable) - : orderedProperties.Concat(props); - - return new PropertyDescriptorCollection(propertySpecDescriptors.ToArray()); + private static bool ShouldDescriptorBeReturned(PropertyDescriptor propertySpecDescriptor, IEnumerable attributesFilter) + { + var browsableAttribute = attributesFilter.OfType().FirstOrDefault(); + return browsableAttribute == null || propertySpecDescriptor.IsBrowsable == browsableAttribute.Browsable; } public object GetPropertyOwner(PropertyDescriptor pd) Index: Core/Common/src/Core.Common.Gui/PropertyBag/PropertySpec.cs =================================================================== diff -u -r8de8647e95dd220a1a4d78980e88af32ee9d2d3f -rd7ef66291b1a64881d4e26b8fc30c5f66f16d67a --- Core/Common/src/Core.Common.Gui/PropertyBag/PropertySpec.cs (.../PropertySpec.cs) (revision 8de8647e95dd220a1a4d78980e88af32ee9d2d3f) +++ Core/Common/src/Core.Common.Gui/PropertyBag/PropertySpec.cs (.../PropertySpec.cs) (revision d7ef66291b1a64881d4e26b8fc30c5f66f16d67a) @@ -44,7 +44,7 @@ /// class, such as /// and . /// - public Attribute[] Attributes { get; set; } + public Attribute[] Attributes { get; private set; } /// /// Gets the name of the property. Fisheye: Tag d7ef66291b1a64881d4e26b8fc30c5f66f16d67a refers to a dead (removed) revision in file `Core/Common/src/Core.Common.Gui/PropertyBag/PropertySpecCollection.cs'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag d7ef66291b1a64881d4e26b8fc30c5f66f16d67a refers to a dead (removed) revision in file `Core/Common/src/Core.Common.Gui/PropertyBag/PropertySpecEventArgs.cs'. Fisheye: No comparison available. Pass `N' to diff? Index: Core/Common/test/Core.Common.Gui.Test/PropertyBag/DynamicPropertyBagTest.cs =================================================================== diff -u -r8de8647e95dd220a1a4d78980e88af32ee9d2d3f -rd7ef66291b1a64881d4e26b8fc30c5f66f16d67a --- Core/Common/test/Core.Common.Gui.Test/PropertyBag/DynamicPropertyBagTest.cs (.../DynamicPropertyBagTest.cs) (revision 8de8647e95dd220a1a4d78980e88af32ee9d2d3f) +++ Core/Common/test/Core.Common.Gui.Test/PropertyBag/DynamicPropertyBagTest.cs (.../DynamicPropertyBagTest.cs) (revision d7ef66291b1a64881d4e26b8fc30c5f66f16d67a) @@ -1,7 +1,7 @@ using System; using System.ComponentModel; +using System.Drawing.Design; using System.Linq; -using System.Windows.Forms; using Core.Common.Gui.Attributes; using Core.Common.Gui.PropertyBag; @@ -51,43 +51,169 @@ var dynamicPropertyBag = new DynamicPropertyBag(propertyObject); // Assert - var namePropertySpec = dynamicPropertyBag.Properties.OfType().First(ps => ps.Name == "Name"); - Assert.IsTrue(namePropertySpec.Attributes.Any(a => a is System.ComponentModel.CategoryAttribute)); + var namePropertySpec = dynamicPropertyBag.Properties.First(ps => ps.Name == "Name"); + CollectionAssert.Contains(namePropertySpec.Attributes, new System.ComponentModel.CategoryAttribute("General"), + "Should have initialized Attributes of the property spec with declared Category(\"General\")."); - var descriptionPropertySpec = dynamicPropertyBag.Properties.OfType().First(ps => ps.Name == "Description"); + var descriptionPropertySpec = dynamicPropertyBag.Properties.First(ps => ps.Name == "Description"); CollectionAssert.Contains(descriptionPropertySpec.Attributes, ReadOnlyAttribute.Yes, "Should have initialized Attributes of the property spec with declared ReadOnlyAttribute."); } [Test] - public void GivenClassWithDynamicReadOnlyAttribute_WhenNotReadOnly_ThenTypeDescriptorDoesNotHaveReadOnlyAttribute() + public void ToString_ReturnToStringFromWrappedObject() { // Setup - var testProperties = new TestProperties - { - IsNameReadOnly = false - }; + var target = new TestProperties(); + var dynamicPropertyBag = new DynamicPropertyBag(target); - // Precondition - var namePropertyAttributes = testProperties.GetType().GetProperty("Name").GetCustomAttributes(true); - Assert.IsFalse(namePropertyAttributes.Any(a => a is ReadOnlyAttribute)); - CollectionAssert.Contains(namePropertyAttributes, new DynamicReadOnlyAttribute()); - Assert.IsFalse(testProperties.DynamicReadOnlyValidationMethod("Name")); + // Call + var text = dynamicPropertyBag.ToString(); + // Assert + Assert.AreEqual(target.ToString(), text); + } + + [Test] + public void GetAttributes_Always_ReturnEmpty() + { + // Setup + var target = new TestProperties(); + var dynamicPropertyBag = new DynamicPropertyBag(target); + // Call - var dynamicPropertyBag = new DynamicPropertyBag(testProperties); + var attributes = dynamicPropertyBag.GetAttributes(); // Assert - PropertyDescriptorCollection propertyDescriptorCollection = dynamicPropertyBag.GetProperties(); - PropertyDescriptor namePropertyDescriptor = propertyDescriptorCollection.Find("Name", false); + CollectionAssert.IsEmpty(attributes); + } - CollectionAssert.Contains(namePropertyDescriptor.Attributes, new DynamicReadOnlyAttribute(), - "DynamicReadOnlyAttribute declared on Name property should also be present on PropertyDescriptor."); - Assert.IsFalse(namePropertyDescriptor.Attributes.OfType().Any(a => a is ReadOnlyAttribute), - "As Name property has no ReadOnlyAttribute nor does DyanmicReadOnlyValidationMethod evaluate to true, no ReadOnlyAttribute should be on PropertyDescriptor."); + [Test] + public void GetClassName_Always_ReturnDynamicPropertyBagClassName() + { + // Setup + var target = new TestProperties(); + var dynamicPropertyBag = new DynamicPropertyBag(target); + + // Call + var className = dynamicPropertyBag.GetClassName(); + + // Assert + Assert.AreEqual(dynamicPropertyBag.GetType().FullName, className); } [Test] + public void GetComponentName_Always_ReturnNull() + { + // Setup + var target = new TestProperties(); + var dynamicPropertyBag = new DynamicPropertyBag(target); + + // Call + var componentName = dynamicPropertyBag.GetComponentName(); + + // Assert + Assert.IsNull(componentName); + } + + [Test] + public void GetConverter_Always_ReturnDefaultTypeConverter() + { + // Setup + var target = new TestProperties(); + var dynamicPropertyBag = new DynamicPropertyBag(target); + + // Call + var typeConverter = dynamicPropertyBag.GetConverter(); + + // Assert + Assert.AreEqual(TypeDescriptor.GetConverter(dynamicPropertyBag, true), typeConverter); + } + + [Test] + public void GetDefaultEvent_Always_ReturnNull() + { + // Setup + var target = new TestProperties(); + var dynamicPropertyBag = new DynamicPropertyBag(target); + + // Call + var eventDescriptor = dynamicPropertyBag.GetDefaultEvent(); + + // Assert + Assert.IsNull(eventDescriptor); + } + + [Test] + public void GetEvents_Always_ReturnEmpty() + { + // Setup + var target = new TestProperties(); + var dynamicPropertyBag = new DynamicPropertyBag(target); + + // Call + var events = dynamicPropertyBag.GetEvents(); + + // Assert + CollectionAssert.IsEmpty(events); + } + + [Test] + public void GetEventsParametered_Always_ReturnEmpty() + { + // Setup + var target = new TestProperties(); + var dynamicPropertyBag = new DynamicPropertyBag(target); + + // Call + var events = dynamicPropertyBag.GetEvents(new Attribute[0]); + + // Assert + CollectionAssert.IsEmpty(events); + } + + [Test] + public void GetEditor_Always_ReturnNull() + { + // Setup + var target = new TestProperties(); + var dynamicPropertyBag = new DynamicPropertyBag(target); + + // Call + var editor = dynamicPropertyBag.GetEditor(typeof(UITypeEditor)); + + // Assert + Assert.IsNull(editor); + } + + [Test] + public void GetDefaultProperty_ObjectHasNotProperties_ReturnNull() + { + // Setup + var dynamicPropertyBag = new DynamicPropertyBag(new object()); + + // Call + var defaultProperty = dynamicPropertyBag.GetDefaultProperty(); + + // Assert + Assert.IsNull(defaultProperty); + } + + [Test] + public void GetDefaultProperty_ObjectWithProperties_ReturnFirstFromProperties() + { + // Setup + var target = new TestProperties(); + var dynamicPropertyBag = new DynamicPropertyBag(target); + + // Call + var defaultProperty = dynamicPropertyBag.GetDefaultProperty(); + + // Assert + Assert.AreEqual(dynamicPropertyBag.Properties.First().Name, defaultProperty.Name); + } + + [Test] public void DynamicPropertyBagResolvesDynamicVisibleAttributes() { var propertyObject = new TestProperties @@ -116,68 +242,190 @@ } [Test] - public void DynamicPropertyBagMaintainsDesiredOrdering() + public void GetProperties_SomePropertiesWithOrderAttribute_ReturnElementsInDesiredOrdering() { + // Setup var dynamicPropertyBag = new DynamicPropertyBag(new TestOrderedProperties()); - var propertyDescriptorCollection = ((ICustomTypeDescriptor) dynamicPropertyBag).GetProperties(); + // Call + var propertyDescriptorCollection = dynamicPropertyBag.GetProperties(); - Assert.AreEqual("Name", propertyDescriptorCollection[3].DisplayName); - Assert.AreEqual("Description", propertyDescriptorCollection[2].DisplayName); - Assert.AreEqual("PropOne", propertyDescriptorCollection[1].DisplayName); - Assert.AreEqual("PropTwo", propertyDescriptorCollection[0].DisplayName); + // Assert + var index = 0; + Assert.AreEqual("PropTwo", propertyDescriptorCollection[index++].DisplayName); + Assert.AreEqual("PropOne", propertyDescriptorCollection[index++].DisplayName); + Assert.AreEqual("Description", propertyDescriptorCollection[index++].DisplayName); + Assert.AreEqual("Name", propertyDescriptorCollection[index++].DisplayName); + + var propThreeDescriptor = propertyDescriptorCollection.Find("PropThree", false); + Assert.GreaterOrEqual(propertyDescriptorCollection.IndexOf(propThreeDescriptor), index, + "PropThree is not decorated with PropertyOrderAttribute, therefore should come after those that are."); + var propFourDescriptor = propertyDescriptorCollection.Find("PropFour", false); + Assert.GreaterOrEqual(propertyDescriptorCollection.IndexOf(propFourDescriptor), index, + "PropFour is not decorated with PropertyOrderAttribute, therefore should come after those that are."); } [Test] public void DynamicPropertyBagWrapsNestedPropertyObjects() { - var subProperties = new TestProperties() + // Setup + var subProperties = new TestProperties { Name = "test" }; - var testProperties = new TestWithNestedPropertiesClassProperties() + var testProperties = new TestWithNestedPropertiesClassProperties { SubProperties = subProperties }; var dynamicPropertyBag = new DynamicPropertyBag(testProperties); - var propertiesCollection = ((ICustomTypeDescriptor) dynamicPropertyBag).GetProperties(); + var propertiesCollection = dynamicPropertyBag.GetProperties(); + + // Call var wrappedValue = propertiesCollection[0].GetValue(dynamicPropertyBag.WrappedObject); - // check the object properties are wrapped in a dynamic property bag - Assert.IsInstanceOf(wrappedValue, "Object properties wrapped in dynamic property bag"); + // Assert + var bag = (DynamicPropertyBag)wrappedValue; + Assert.AreSame(subProperties, bag.WrappedObject); } [Test] - public void DynamicPropertyBagPropagatesValueSetter() + public void GivenPropertyDescriptorFromDynamicPropertyBag_WhenSettingProperty_ThenWrappedObjectUpdated() { - var propertyGrid = new PropertyGrid(); - + // Setup var testProperties = new TestProperties { Name = "name" }; var dynamicPropertyBag = new DynamicPropertyBag(testProperties); - propertyGrid.SelectedObject = dynamicPropertyBag; + var newName = "newName"; - var expected = "newName"; - propertyGrid.SelectedGridItem.PropertyDescriptor.SetValue(testProperties, expected); + // Call + dynamicPropertyBag.GetProperties()["Name"].SetValue(testProperties, newName); - Assert.AreEqual(expected, testProperties.Name, "Name not correctly set"); + // Assert + Assert.AreEqual(newName, testProperties.Name); } [Test] - public void PropertyWithNoSetterAreReadOnly() + public void GivenPropertiesWithoutPublicSetter_WhenGettingPropertyDescriptors_ThenPropertiesDecoratedWithReadOnlyAttribute() { + // Setup var dynamicPropertyBag = new DynamicPropertyBag(new TestWithoutSetterPropertyClassProperties()); - var propertyDescriptorCollection = ((ICustomTypeDescriptor) dynamicPropertyBag).GetProperties(); - //check both properties have the readonly attribute - Assert.IsTrue(propertyDescriptorCollection[0].Attributes.Matches(new ReadOnlyAttribute(true))); - Assert.IsTrue(propertyDescriptorCollection[1].Attributes.Matches(new ReadOnlyAttribute(true))); + // Call + var propertyDescriptorCollection = dynamicPropertyBag.GetProperties(); + + // Assert + Assert.IsTrue(propertyDescriptorCollection[0].Attributes.Matches(ReadOnlyAttribute.Yes)); + Assert.IsTrue(propertyDescriptorCollection[1].Attributes.Matches(ReadOnlyAttribute.Yes)); } + [Test] + public void GetProperties_RepeatedCallForSameState_RetainSameElementOrderAndContents() + { + // Setup + var propertyObject = new TestOrderedProperties(); + + var dynamicPropertyBag = new DynamicPropertyBag(propertyObject); + + var originalProperties = dynamicPropertyBag.GetProperties(); + + // Call + for (int i = 0; i < 100; i++) + { + var currentProperties = dynamicPropertyBag.GetProperties(); + + // Assert + CollectionAssert.AreEqual(originalProperties, currentProperties); + } + } + + [Test] + public void GetProperties_RepeatedConstructionsForSameState_RetainSameElementOrderAndContents() + { + // Setup + var propertyObject = new TestOrderedProperties(); + + var originalProperties = new DynamicPropertyBag(propertyObject).GetProperties(); + + // Call + for (int i = 0; i < 100; i++) + { + var currentProperties = new DynamicPropertyBag(propertyObject).GetProperties(); + + // Assert + CollectionAssert.AreEqual(originalProperties, currentProperties); + } + } + + [Test] + public void GetProperties_BrowsableTrueFilter_ReturnOnlyPropertiesThatAreBrowsable() + { + // Setup + var propertyObject = new TestProperties + { + Visible = false + }; + var dynamicPropertyBag = new DynamicPropertyBag(propertyObject); + + // Call + var properties = dynamicPropertyBag.GetProperties(new Attribute[] + { + BrowsableAttribute.Yes + }); + + // Assert + Assert.Less(properties.Count, dynamicPropertyBag.Properties.Count); + Assert.IsNull(properties.Find("Name", false), + "Name is dynamically not browsable, therefore should not be returned."); + Assert.IsNotNull(properties.Find("Description", false)); + Assert.IsNotNull(properties.Find("IsNameReadOnly", false)); + Assert.IsNull(properties.Find("Visible", false), + "Visible is statically not browsable, therefore should not be returned."); + } + + [Test] + public void GetProperties_BrowsableNoFilter_ReturnOnlyPropertiesThatAreBrowsable() + { + // Setup + var propertyObject = new TestProperties + { + Visible = false + }; + var dynamicPropertyBag = new DynamicPropertyBag(propertyObject); + + // Call + var properties = dynamicPropertyBag.GetProperties(new Attribute[] + { + BrowsableAttribute.No + }); + + // Assert + Assert.Less(properties.Count, dynamicPropertyBag.Properties.Count); + Assert.IsNotNull(properties.Find("Name", false), + "Name is dynamically not browsable, therefore should be returned."); + Assert.IsNull(properties.Find("Description", false)); + Assert.IsNull(properties.Find("IsNameReadOnly", false)); + Assert.IsNotNull(properties.Find("Visible", false), + "Visible is statically not browsable, therefore should be returned."); + } + + [Test] + public void GetPropertyOwner_Always_ReturnWrappedObject() + { + // Setup + var propertyObject = new TestProperties(); + var dynamicPropertyBag = new DynamicPropertyBag(propertyObject); + + // Call + var owner = dynamicPropertyBag.GetPropertyOwner(null); + + // Assert + Assert.AreSame(propertyObject, owner); + } + #region Test Classes private class TestOrderedProperties @@ -193,6 +441,10 @@ [PropertyOrder(0)] public string PropTwo { get; set; } + + public int PropThree { get; set; } + + public int PropFour { get; set; } } private class TestWithNestedPropertiesClassProperties @@ -219,8 +471,10 @@ [System.ComponentModel.Category("General")] public string Name { get; set; } + [Browsable(true)] public bool IsNameReadOnly { get; set; } + [Browsable(false)] public bool Visible { get; set; } [ReadOnly(true)] //one static property @@ -244,6 +498,11 @@ { return Visible; } + + public override string ToString() + { + return Name; + } } private class TestWithoutSetterPropertyClassProperties