Index: Core/Common/src/Core.Common.Gui/PropertyBag/DynamicPropertyBag.cs =================================================================== diff -u -r7de564e0db6835c6821f0ce2303a983a2758e7d0 -rd91a531792e48f042fa6c28ef8c0e16b8fa06fba --- Core/Common/src/Core.Common.Gui/PropertyBag/DynamicPropertyBag.cs (.../DynamicPropertyBag.cs) (revision 7de564e0db6835c6821f0ce2303a983a2758e7d0) +++ Core/Common/src/Core.Common.Gui/PropertyBag/DynamicPropertyBag.cs (.../DynamicPropertyBag.cs) (revision d91a531792e48f042fa6c28ef8c0e16b8fa06fba) @@ -42,52 +42,6 @@ return WrappedObject.ToString(); } - /// - /// Raises the GetValue event. - /// - /// A PropertySpecEventArgs that contains the event data. - /// - internal void OnGetValue(PropertySpecEventArgs e, PropertySpec propertySpec) - { - var attributeList = new List(); - attributeList.AddRange(propertySpec.Attributes.ToList()); - - //check all of the attributes: if we find a dynamic one, evaluate it and possibly add/overwrite a static attribute - foreach (Attribute customAttribute in propertySpec.Attributes) - { - if (customAttribute is DynamicReadOnlyAttribute) - { - attributeList.RemoveAll(x => x is ReadOnlyAttribute); - - if (DynamicReadOnlyAttribute.IsReadOnly(WrappedObject, propertySpec.Name)) - { - //condition is true: the dynamic attribute should be applied (as static attribute) - attributeList.Add(new ReadOnlyAttribute(true)); //add static read only attribute - } - } - - if (customAttribute is DynamicVisibleAttribute) - { - attributeList.RemoveAll(x => x is BrowsableAttribute); - - if (!DynamicVisibleAttribute.IsVisible(WrappedObject, propertySpec.Name)) - { - attributeList.Add(new BrowsableAttribute(false)); - } - } - } - - propertySpec.Attributes = attributeList.ToArray(); - - var propertyInfo = WrappedObject.GetType().GetProperty(propertySpec.Name); - var value = propertyInfo.GetValue(WrappedObject, null); - - var isNestedPropertiesObject = IsNestedExpandablePropertiesObject(propertyInfo); - - // if nested properties object, wrap in DynamicPropertyBag to provide support for things like DynamicReadOnly - e.Value = isNestedPropertiesObject ? new DynamicPropertyBag(value) : value; - } - private void Initialize(object propertyObject) { WrappedObject = propertyObject; @@ -98,31 +52,6 @@ } } - private bool IsNestedExpandablePropertiesObject(System.Reflection.PropertyInfo propertyInfo) - { - try - { - var typeConverterAttributes = propertyInfo.GetCustomAttributes(typeof(TypeConverterAttribute), false); - foreach (TypeConverterAttribute typeConverterAttribute in typeConverterAttributes) - { - var typeString = typeConverterAttribute.ConverterTypeName; - var type = Type.GetType(typeString); - if (type != null) - { - if (typeof(ExpandableObjectConverter) == type) - { - return true; - } - } - } - } - catch (Exception) - { - //gulp - } - return false; - } - #region ICustomTypeDescriptor explicit interface definitions #region Implementations delegated to System.ComponentModel.TypeDescriptor @@ -142,7 +71,7 @@ return TypeDescriptor.GetComponentName(this, true); } - public System.ComponentModel.TypeConverter GetConverter() + public TypeConverter GetConverter() { return TypeDescriptor.GetConverter(this, true); } @@ -176,7 +105,7 @@ public PropertyDescriptor GetDefaultProperty() { - return Properties.Count > 0 ? new PropertySpecDescriptor(Properties[0], this, Properties[0].Name, null) : null; + return Properties.Count > 0 ? new PropertySpecDescriptor(Properties[0], WrappedObject) : null; } public PropertyDescriptorCollection GetProperties(Attribute[] attributes) @@ -190,21 +119,9 @@ foreach (PropertySpec property in Properties) { - var attrs = new ArrayList(); + // Create a new property descriptor for the property item, and add it to the list. + var pd = new PropertySpecDescriptor(property, WrappedObject); - // Additionally, append the custom attributes associated with the - // PropertySpec, if any. - if (property.Attributes != null) - { - attrs.AddRange(property.Attributes); - } - - Attribute[] attrArray = (Attribute[])attrs.ToArray(typeof(Attribute)); - - // Create a new property descriptor for the property item, and add - // it to the list. - var pd = new PropertySpecDescriptor(property, this, property.Name, attrArray); - var propertyOrderAttribute = property.Attributes != null ? property.Attributes.OfType().FirstOrDefault() : null; if (propertyOrderAttribute != null) { Index: Core/Common/src/Core.Common.Gui/PropertyBag/PropertySpecDescriptor.cs =================================================================== diff -u -r7de564e0db6835c6821f0ce2303a983a2758e7d0 -rd91a531792e48f042fa6c28ef8c0e16b8fa06fba --- Core/Common/src/Core.Common.Gui/PropertyBag/PropertySpecDescriptor.cs (.../PropertySpecDescriptor.cs) (revision 7de564e0db6835c6821f0ce2303a983a2758e7d0) +++ Core/Common/src/Core.Common.Gui/PropertyBag/PropertySpecDescriptor.cs (.../PropertySpecDescriptor.cs) (revision d91a531792e48f042fa6c28ef8c0e16b8fa06fba) @@ -1,19 +1,22 @@ using System; +using System.Collections.Generic; using System.ComponentModel; +using System.Linq; +using Core.Common.Gui.Attributes; + namespace Core.Common.Gui.PropertyBag { public class PropertySpecDescriptor : PropertyDescriptor { - private readonly DynamicPropertyBag bag; private readonly PropertySpec item; + private readonly object instance; - public PropertySpecDescriptor(PropertySpec item, DynamicPropertyBag bag, string name, Attribute[] attrs) - : - base(name, attrs) + public PropertySpecDescriptor(PropertySpec propertySpec, object instance) + : base(propertySpec.Name, propertySpec.Attributes) { - this.bag = bag; - this.item = item; + item = propertySpec; + this.instance = instance; } public override Type ComponentType @@ -28,7 +31,11 @@ { get { - return (Attributes.Matches(ReadOnlyAttribute.Yes)); + if (Attributes.Matches(new DynamicReadOnlyAttribute())) + { + return DynamicReadOnlyAttribute.IsReadOnly(instance, item.Name); + } + return Attributes.Matches(ReadOnlyAttribute.Yes); } } @@ -76,12 +83,87 @@ private PropertySpecEventArgs ReEvaluateAttributes() { - // Have the property bag raise an event to get the current value - // of the property and evaluate the dynamic attributes + UpdateDynamicAttributes(); + + // Have the property bag raise an event to get the current value of the property: var e = new PropertySpecEventArgs(item, null); - bag.OnGetValue(e, e.Property); - AttributeArray = e.Property.Attributes; + OnGetValue(e, e.Property); + AttributeArray = e.Property.Attributes; // TODO: Override AttributeArray to reroute to 'item.Attributes' return e; } + + private void UpdateDynamicAttributes() + { + var attributeList = new List(); + attributeList.AddRange(item.Attributes.ToList()); + + //check all of the attributes: if we find a dynamic one, evaluate it and possibly add/overwrite a static attribute + foreach (Attribute customAttribute in item.Attributes) + { + if (customAttribute is DynamicReadOnlyAttribute) + { + attributeList.RemoveAll(x => x is ReadOnlyAttribute); + + if (DynamicReadOnlyAttribute.IsReadOnly(instance, item.Name)) + { + //condition is true: the dynamic attribute should be applied (as static attribute) + attributeList.Add(new ReadOnlyAttribute(true)); //add static read only attribute + } + } + + if (customAttribute is DynamicVisibleAttribute) + { + attributeList.RemoveAll(x => x is BrowsableAttribute); + + if (!DynamicVisibleAttribute.IsVisible(instance, item.Name)) + { + attributeList.Add(new BrowsableAttribute(false)); + } + } + } + + item.Attributes = attributeList.ToArray(); + } + + /// + /// Raises the GetValue event. + /// + /// A PropertySpecEventArgs that contains the event data. + /// + private void OnGetValue(PropertySpecEventArgs e, PropertySpec propertySpec) + { + var propertyInfo = instance.GetType().GetProperty(propertySpec.Name); + var value = propertyInfo.GetValue(instance, null); + + var isNestedPropertiesObject = IsNestedExpandablePropertiesObject(propertyInfo); + + // if nested properties object, wrap in DynamicPropertyBag to provide support for things like DynamicReadOnly + e.Value = isNestedPropertiesObject ? new DynamicPropertyBag(value) : value; + } + + private bool IsNestedExpandablePropertiesObject(System.Reflection.PropertyInfo propertyInfo) + { + try + { + var typeConverterAttributes = propertyInfo.GetCustomAttributes(typeof(TypeConverterAttribute), false); + foreach (TypeConverterAttribute typeConverterAttribute in typeConverterAttributes) + { + var typeString = typeConverterAttribute.ConverterTypeName; + var type = Type.GetType(typeString); + if (type != null) + { + if (typeof(ExpandableObjectConverter) == type) + { + return true; + } + } + } + } + catch (Exception) + { + //gulp + } + return false; + } } } \ No newline at end of file Index: Core/Common/test/Core.Common.Gui.Test/Core.Common.Gui.Test.csproj =================================================================== diff -u -rb92517dbc9956b930a6cfc7f96a1f762dfa41cf6 -rd91a531792e48f042fa6c28ef8c0e16b8fa06fba --- Core/Common/test/Core.Common.Gui.Test/Core.Common.Gui.Test.csproj (.../Core.Common.Gui.Test.csproj) (revision b92517dbc9956b930a6cfc7f96a1f762dfa41cf6) +++ Core/Common/test/Core.Common.Gui.Test/Core.Common.Gui.Test.csproj (.../Core.Common.Gui.Test.csproj) (revision d91a531792e48f042fa6c28ef8c0e16b8fa06fba) @@ -63,6 +63,7 @@ + Index: Core/Common/test/Core.Common.Gui.Test/PropertyBag/DynamicPropertyBagTest.cs =================================================================== diff -u -r7de564e0db6835c6821f0ce2303a983a2758e7d0 -rd91a531792e48f042fa6c28ef8c0e16b8fa06fba --- Core/Common/test/Core.Common.Gui.Test/PropertyBag/DynamicPropertyBagTest.cs (.../DynamicPropertyBagTest.cs) (revision 7de564e0db6835c6821f0ce2303a983a2758e7d0) +++ Core/Common/test/Core.Common.Gui.Test/PropertyBag/DynamicPropertyBagTest.cs (.../DynamicPropertyBagTest.cs) (revision d91a531792e48f042fa6c28ef8c0e16b8fa06fba) @@ -8,67 +8,83 @@ using NUnit.Framework; -using CategoryComponentModelAttribute = System.ComponentModel.CategoryAttribute; - namespace Core.Common.Gui.Test.PropertyBag { [TestFixture] public class DynamicPropertyBagTest { [Test] - public void DynamicPropertyBagReturnsCorrectProperties() + public void ParameteredConstructor_ExpectedValues() { - var dynamicPropertyBag = new DynamicPropertyBag(new TestProperties()); + // Setup + var propertyObject = new object(); - Assert.AreEqual(4, dynamicPropertyBag.Properties.Count, "Expected property count wrong"); + // Call + var dynamicPropertyBag = new DynamicPropertyBag(propertyObject); + + // Assert + CollectionAssert.IsEmpty(dynamicPropertyBag.Properties, + "Object has no properties, therefore bag should have none too."); + Assert.AreSame(propertyObject, dynamicPropertyBag.WrappedObject); } [Test] - public void DynamicPropertyBagCopiesStaticAttributes() + public void GivenTestProperties_WhenConstructing_ThenCorrectNumberOfPropertiesCreated() { - var dynamicPropertyBag = new DynamicPropertyBag(new TestProperties()); + // Setup + var propertyObject = new TestProperties(); - PropertySpec nameProperty = null; - PropertySpec descriptionProperty = null; + // Call + var dynamicPropertyBag = new DynamicPropertyBag(propertyObject); - foreach (PropertySpec spec in dynamicPropertyBag.Properties) - { - if (spec.Name.Equals("Description")) - { - descriptionProperty = spec; - } - else if (spec.Name.Equals("Name")) - { - nameProperty = spec; - } - } + // Assert + Assert.AreEqual(4, dynamicPropertyBag.Properties.Count, "Expected property count wrong"); + } - // asserts - Assert.IsTrue(nameProperty.Attributes.Any(x => x.GetType() == typeof(CategoryComponentModelAttribute)), "Static Category attribute not copied!"); + [Test] + public void GivenTestProperties_WhenConstructing_ThenPropertySpecsHaveAttributesSet() + { + // Setup + var propertyObject = new TestProperties(); - CollectionAssert.Contains(descriptionProperty.Attributes, ReadOnlyAttribute.Yes, "Static ReadOnly attribute not copied!"); - } + // Call + 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 descriptionPropertySpec = dynamicPropertyBag.Properties.OfType().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 DynamicPropertyBagResolvesDynamicAttributesToNothing() + public void GivenClassWithDynamicReadOnlyAttribute_WhenNotReadOnly_ThenTypeDescriptorDoesNotHaveReadOnlyAttribute() { + // Setup var testProperties = new TestProperties { IsNameReadOnly = false }; + // 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 dynamicPropertyBag = new DynamicPropertyBag(testProperties); - var propertyDescriptorCollection = ((ICustomTypeDescriptor) dynamicPropertyBag).GetProperties(); + // Assert + PropertyDescriptorCollection propertyDescriptorCollection = dynamicPropertyBag.GetProperties(); + PropertyDescriptor namePropertyDescriptor = propertyDescriptorCollection.Find("Name", false); - var namePropertyDescriptor = propertyDescriptorCollection.Find("Name", false); - - namePropertyDescriptor.GetValue(dynamicPropertyBag); - - // asserts - Assert.IsTrue(namePropertyDescriptor.Attributes.Matches(new DynamicReadOnlyAttribute()),"Dynamic ReadOnly attribute was not added"); - - Assert.IsFalse(namePropertyDescriptor.Attributes.Matches(new ReadOnlyAttribute(true)), "Inactive dynamic ReadOnly attribute was resolved to static attribute: wrong."); + 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] @@ -287,7 +303,7 @@ /// [DynamicReadOnly] [DynamicVisible] - [CategoryComponentModelAttribute("General")] + [System.ComponentModel.Category("General")] public string Name { get; set; } public bool IsNameReadOnly { get; set; } Index: Core/Common/test/Core.Common.Gui.Test/PropertyBag/PropertySpecDescriptorTest.cs =================================================================== diff -u --- Core/Common/test/Core.Common.Gui.Test/PropertyBag/PropertySpecDescriptorTest.cs (revision 0) +++ Core/Common/test/Core.Common.Gui.Test/PropertyBag/PropertySpecDescriptorTest.cs (revision d91a531792e48f042fa6c28ef8c0e16b8fa06fba) @@ -0,0 +1,48 @@ +using System.ComponentModel; + +using Core.Common.Gui.Attributes; +using Core.Common.Gui.PropertyBag; + +using NUnit.Framework; + +namespace Core.Common.Gui.Test.PropertyBag +{ + [TestFixture] + public class PropertySpecDescriptorTest + { + [Test] + [TestCase(false)] + [TestCase(true)] + public void IsReadOnly_PropertyHasDynamicReadOnlyProperty_ReturnExpectedValue(bool isPropertyReadOnly) + { + // Setup + var instance = new TestClass{ IsPropertyReadOnly = isPropertyReadOnly }; + var propertySpec = new PropertySpec(instance.GetType().GetProperty("IntegerProperty")); + var descriptor = new PropertySpecDescriptor(propertySpec, instance); + + // Call + var isReadOnly = descriptor.IsReadOnly; + + // Assert + Assert.AreEqual(isPropertyReadOnly, isReadOnly); + } + + private class TestClass + { + public bool IsPropertyReadOnly { get; set; } + + [DynamicReadOnly] + public int IntegerProperty { get; set; } + + [DynamicReadOnlyValidationMethod] + public bool IsReadOnly(string propertyName) + { + if (propertyName == "IntegerProperty") + { + return IsPropertyReadOnly; + } + return ReadOnlyAttribute.Default.IsReadOnly; + } + } + } +} \ No newline at end of file