Index: Core/Common/src/Core.Common.Gui/PropertyBag/PropertySpec.cs =================================================================== diff -u -r7de564e0db6835c6821f0ce2303a983a2758e7d0 -r8de8647e95dd220a1a4d78980e88af32ee9d2d3f --- Core/Common/src/Core.Common.Gui/PropertyBag/PropertySpec.cs (.../PropertySpec.cs) (revision 7de564e0db6835c6821f0ce2303a983a2758e7d0) +++ Core/Common/src/Core.Common.Gui/PropertyBag/PropertySpec.cs (.../PropertySpec.cs) (revision 8de8647e95dd220a1a4d78980e88af32ee9d2d3f) @@ -68,9 +68,8 @@ /// does not match the target type. /// Property is an instance property but is null. /// is of incorrect type. - /// An error occurred while setting - /// the property value. The property - /// indicates the reason for the error. + /// An error occurred while setting the property value. The + /// property indicates the reason for the error. /// /// Calling this method while property /// has no setter. @@ -98,10 +97,87 @@ } catch (TargetInvocationException e) { - var message = string.Format("Something when wrong while setting property with value '{0}'; Check InnerException for more information.", + var message = string.Format("Something went wrong while setting property with value '{0}'; Check InnerException for more information.", newValue); throw new ArgumentException(message, "newValue", e); } } + + /// + /// Gets the property value represented by this instance of some object instance. + /// + /// The instance that holds the property to be retrieved. + /// When + /// + /// Represented property is an index-property. + /// Property getter is not available. + /// does not match the target type. + /// Property is an instance property but is null. + /// An error occurred while setting the property value. The + /// property indicates the reason for the error. + /// + /// Calling this method while property + /// has no getter. + public object GetValue(object instance) + { + var getMethodInfo = propertyInfo.GetGetMethod(); + if (getMethodInfo == null) + { + throw new InvalidOperationException("Property lacks public getter!"); + } + + try + { + return getMethodInfo.Invoke(instance, new object[0]); + } + catch (TargetException e) + { + object type = instance == null ? null : instance.GetType(); + var message = string.Format("Are you calling GetValue on the correct instance? Expected '{0}', but was '{1}'", + propertyInfo.DeclaringType, type); + throw new ArgumentException(message, "instance", e); + } + catch (TargetInvocationException e) + { + throw new ArgumentException("Something went wrong while getting property; Check InnerException for more information.", "instance", e); + } + } + + /// + /// Determines whether the captured property is decorated with [is non custom expandable object property]. + /// that is configured to use . + /// + /// Returns true if a is declared using + /// , false if no match has been found or when + /// the type converter inherits from . + /// Custom implementations of is + /// likely to have behavior that Core.Common.Gui namespace cannot account for. As + /// such those properties will be considered not having the expandable object type converter. + public bool IsNonCustomExpandableObjectProperty() + { + var typeConverterClassName = propertyInfo.GetCustomAttributes(typeof(TypeConverterAttribute), false) + .OfType() + .Select(tca => tca.ConverterTypeName) + .Where(n => !string.IsNullOrEmpty(n)); + foreach (string typeName in typeConverterClassName) + { + try + { + var type = Type.GetType(typeName); + if (type != null) + { + if (typeof(ExpandableObjectConverter) == type) + { + return true; + } + } + } + catch (Exception) + { + // gulp + } + } + return false; + } } } \ No newline at end of file Index: Core/Common/src/Core.Common.Gui/PropertyBag/PropertySpecDescriptor.cs =================================================================== diff -u -rd91a531792e48f042fa6c28ef8c0e16b8fa06fba -r8de8647e95dd220a1a4d78980e88af32ee9d2d3f --- Core/Common/src/Core.Common.Gui/PropertyBag/PropertySpecDescriptor.cs (.../PropertySpecDescriptor.cs) (revision d91a531792e48f042fa6c28ef8c0e16b8fa06fba) +++ Core/Common/src/Core.Common.Gui/PropertyBag/PropertySpecDescriptor.cs (.../PropertySpecDescriptor.cs) (revision 8de8647e95dd220a1a4d78980e88af32ee9d2d3f) @@ -7,11 +7,26 @@ namespace Core.Common.Gui.PropertyBag { + /// + /// A that works for properties captured in a + /// and handles Dynamic attributes. + /// + /// The following dynamic attributes are supported: + /// + /// + /// + /// public class PropertySpecDescriptor : PropertyDescriptor { private readonly PropertySpec item; private readonly object instance; + /// + /// Initializes a new instance of the class + /// for a given . + /// + /// The property spec. + /// The instance which has the property captured in . public PropertySpecDescriptor(PropertySpec propertySpec, object instance) : base(propertySpec.Name, propertySpec.Attributes) { @@ -43,8 +58,11 @@ { get { - ReEvaluateAttributes(); - return base.IsBrowsable; + if (Attributes.Matches(new DynamicVisibleAttribute())) + { + return DynamicVisibleAttribute.IsVisible(instance, item.Name); + } + return !Attributes.Matches(BrowsableAttribute.No); } } @@ -61,108 +79,28 @@ return false; } - public override object GetValue(object component) - { - return ReEvaluateAttributes().Value; - } - public override void ResetValue(object component) { } - public override void SetValue(object component, object value) + public override object GetValue(object component) { - item.SetValue(component, value); - } - - public override bool ShouldSerializeValue(object component) - { - return false; - } - - private PropertySpecEventArgs ReEvaluateAttributes() - { - UpdateDynamicAttributes(); - - // Have the property bag raise an event to get the current value of the property: - var e = new PropertySpecEventArgs(item, null); - 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) + var propertyValue = item.GetValue(component); + if (item.IsNonCustomExpandableObjectProperty()) { - 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)); - } - } + return new DynamicPropertyBag(propertyValue); } - - item.Attributes = attributeList.ToArray(); + return propertyValue; } - /// - /// Raises the GetValue event. - /// - /// A PropertySpecEventArgs that contains the event data. - /// - private void OnGetValue(PropertySpecEventArgs e, PropertySpec propertySpec) + public override void SetValue(object component, object value) { - 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; + item.SetValue(component, value); } - private bool IsNestedExpandablePropertiesObject(System.Reflection.PropertyInfo propertyInfo) + public override bool ShouldSerializeValue(object component) { - 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; } } Index: Core/Common/test/Core.Common.Gui.Test/PropertyBag/DynamicPropertyBagTest.cs =================================================================== diff -u -rd91a531792e48f042fa6c28ef8c0e16b8fa06fba -r8de8647e95dd220a1a4d78980e88af32ee9d2d3f --- Core/Common/test/Core.Common.Gui.Test/PropertyBag/DynamicPropertyBagTest.cs (.../DynamicPropertyBagTest.cs) (revision d91a531792e48f042fa6c28ef8c0e16b8fa06fba) +++ Core/Common/test/Core.Common.Gui.Test/PropertyBag/DynamicPropertyBagTest.cs (.../DynamicPropertyBagTest.cs) (revision 8de8647e95dd220a1a4d78980e88af32ee9d2d3f) @@ -57,7 +57,7 @@ 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 GivenClassWithDynamicReadOnlyAttribute_WhenNotReadOnly_ThenTypeDescriptorDoesNotHaveReadOnlyAttribute() @@ -88,23 +88,6 @@ } [Test] - public void DynamicPropertyBagResolvesDynamicAttributes() - { - var dynamicPropertyBag = new DynamicPropertyBag(new TestProperties()); - - var propertyDescriptorCollection = ((ICustomTypeDescriptor) dynamicPropertyBag).GetProperties(); - - var namePropertyDescriptor = propertyDescriptorCollection.Find("Name", false); - - namePropertyDescriptor.GetValue(dynamicPropertyBag); - - // asserts - Assert.IsTrue(namePropertyDescriptor.Attributes.Matches(new DynamicReadOnlyAttribute()), "Dynamic ReadOnly attribute was not added"); - - Assert.IsTrue(namePropertyDescriptor.Attributes.Matches(new ReadOnlyAttribute(true)), "Dynamic ReadOnly attribute was not resolved to static attribute: wrong."); - } - - [Test] public void DynamicPropertyBagResolvesDynamicVisibleAttributes() { var propertyObject = new TestProperties @@ -133,17 +116,6 @@ } [Test] - [ExpectedException(typeof(MissingMethodException), ExpectedMessage = "DynamicReadOnlyValidationMethod niet gevonden (of geen 'public' toegankelijkheid). Klasse: Core.Common.Gui.Test.PropertyBag.DynamicPropertyBagTest+TestWithoutValidationMethodClassProperties.")] - public void ThrowsExceptionOnTypoInDynamicAttributeFunction() - { - var dynamicPropertyBag = new DynamicPropertyBag(new TestWithoutValidationMethodClassProperties()); - var propertyDescriptorCollection = ((ICustomTypeDescriptor) dynamicPropertyBag).GetProperties(); - var namePropertyDescriptor = propertyDescriptorCollection.Find("Name", false); - - namePropertyDescriptor.GetValue(dynamicPropertyBag); - } - - [Test] public void DynamicPropertyBagMaintainsDesiredOrdering() { var dynamicPropertyBag = new DynamicPropertyBag(new TestOrderedProperties()); @@ -170,7 +142,7 @@ var dynamicPropertyBag = new DynamicPropertyBag(testProperties); var propertiesCollection = ((ICustomTypeDescriptor) dynamicPropertyBag).GetProperties(); - var wrappedValue = propertiesCollection[0].GetValue(dynamicPropertyBag); + 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"); @@ -196,17 +168,6 @@ } [Test] - [ExpectedException(typeof(MissingMethodException), ExpectedMessage = "DynamicReadOnlyValidationMethod heeft een incorrect aantal argumenten. Zou er één moeten zijn. Klasse: Core.Common.Gui.Test.PropertyBag.DynamicPropertyBagTest+TestInvalidValidationMethodClassProperties.")] - public void ThrowsExceptionOnInvalidValidationMethod() - { - var dynamicPropertyBag = new DynamicPropertyBag(new TestInvalidValidationMethodClassProperties()); - var propertyDescriptorCollection = ((ICustomTypeDescriptor) dynamicPropertyBag).GetProperties(); - var namePropertyDescriptor = propertyDescriptorCollection.Find("Name", false); - - namePropertyDescriptor.GetValue(dynamicPropertyBag); - } - - [Test] public void PropertyWithNoSetterAreReadOnly() { var dynamicPropertyBag = new DynamicPropertyBag(new TestWithoutSetterPropertyClassProperties()); @@ -217,62 +178,8 @@ Assert.IsTrue(propertyDescriptorCollection[1].Attributes.Matches(new ReadOnlyAttribute(true))); } - [Test] - [ExpectedException(typeof(MissingMethodException), ExpectedMessage = "Slechts één DynamicReadOnlyValidationMethod toegestaan per klasse: Core.Common.Gui.Test.PropertyBag.DynamicPropertyBagTest+TestWithTwoValidationMethodsClassProperties.")] - public void OnlySingleValidationMethodIsAllowed() - { - var dynamicPropertyBag = new DynamicPropertyBag(new TestWithTwoValidationMethodsClassProperties()); - - var propertyDescriptorCollection = ((ICustomTypeDescriptor) dynamicPropertyBag).GetProperties(); - var namePropertyDescriptor = propertyDescriptorCollection.Find("Name", false); - - namePropertyDescriptor.GetValue(dynamicPropertyBag); - } - #region Test Classes - private class TestWithNestedPropertiesClassProperties - { - [TypeConverter(typeof(ExpandableObjectConverter))] - public TestProperties SubProperties { get; set; } - } - - private class TestInvalidValidationMethodClassProperties - { - [DynamicReadOnly] - public string Name { get; set; } - - [DynamicReadOnlyValidationMethod] - public bool InvalidMethod() //method is invalid because it does not accept a string - { - return false; - } - } - - private class TestWithoutValidationMethodClassProperties - { - [DynamicReadOnly] - public string Name { get; set; } - } - - private class TestWithTwoValidationMethodsClassProperties - { - [DynamicReadOnly] - public string Name { get; set; } - - [DynamicReadOnlyValidationMethod] - public bool Method1(string property) - { - return false; - } - - [DynamicReadOnlyValidationMethod] - public bool Method2(string property) - { - return false; - } - } - private class TestOrderedProperties { [PropertyOrder(3)] @@ -288,6 +195,12 @@ public string PropTwo { get; set; } } + private class TestWithNestedPropertiesClassProperties + { + [TypeConverter(typeof(ExpandableObjectConverter))] + public TestProperties SubProperties { get; set; } + } + private class TestProperties { public TestProperties() @@ -333,7 +246,7 @@ } } - public class TestWithoutSetterPropertyClassProperties + private class TestWithoutSetterPropertyClassProperties { public string PrivateSetter { get; private set; } Index: Core/Common/test/Core.Common.Gui.Test/PropertyBag/PropertySpecDescriptorTest.cs =================================================================== diff -u -rd91a531792e48f042fa6c28ef8c0e16b8fa06fba -r8de8647e95dd220a1a4d78980e88af32ee9d2d3f --- Core/Common/test/Core.Common.Gui.Test/PropertyBag/PropertySpecDescriptorTest.cs (.../PropertySpecDescriptorTest.cs) (revision d91a531792e48f042fa6c28ef8c0e16b8fa06fba) +++ Core/Common/test/Core.Common.Gui.Test/PropertyBag/PropertySpecDescriptorTest.cs (.../PropertySpecDescriptorTest.cs) (revision 8de8647e95dd220a1a4d78980e88af32ee9d2d3f) @@ -1,4 +1,5 @@ -using System.ComponentModel; +using System; +using System.ComponentModel; using Core.Common.Gui.Attributes; using Core.Common.Gui.PropertyBag; @@ -11,13 +12,31 @@ public class PropertySpecDescriptorTest { [Test] + public void ParameteredConstructor_IsPropertyReadOnlyProperty_ExpectedValues() + { + // Setup + var instance = new ClassWithProperties(); + var propertyInfo = instance.GetType().GetProperty("IsPropertyReadOnly"); + var spec = new PropertySpec(propertyInfo); + + // Call + var propertyDescriptor = new PropertySpecDescriptor(spec, instance); + + // Assert + Assert.AreEqual(spec.GetType(), propertyDescriptor.ComponentType); + Assert.IsFalse(propertyDescriptor.IsReadOnly); + Assert.IsTrue(propertyDescriptor.IsBrowsable); + Assert.AreEqual(propertyInfo.PropertyType, propertyDescriptor.PropertyType); + } + + [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 instance = new ClassWithProperties{ IsPropertyReadOnly = isPropertyReadOnly }; + var propertySpec = new PropertySpec(instance.GetType().GetProperty("IntegerPropertyWithDynamicReadOnly")); var descriptor = new PropertySpecDescriptor(propertySpec, instance); // Call @@ -27,22 +46,258 @@ Assert.AreEqual(isPropertyReadOnly, isReadOnly); } - private class TestClass + [Test] + public void IsReadOnly_PropertyHasReadOnlyTrueAttribute_ReturnTrue() { + // Setup + var instance = new ClassWithProperties(); + var spec = new PropertySpec(instance.GetType().GetProperty("PropertyWithReadOnlyAttribute")); + var descriptor = new PropertySpecDescriptor(spec, instance); + + // Call + var isReadOnly = descriptor.IsReadOnly; + + // Assert + Assert.IsTrue(isReadOnly); + } + + [Test] + public void IsReadOnly_PropertyHasReadOnlyFalseAttribute_ReturnFalse() + { + // Setup + var instance = new ClassWithProperties(); + var spec = new PropertySpec(instance.GetType().GetProperty("PropertyWithReadOnlyFalseAttribute")); + var descriptor = new PropertySpecDescriptor(spec, instance); + + // Call + var isReadOnly = descriptor.IsReadOnly; + + // Assert + Assert.IsFalse(isReadOnly); + } + + [Test] + public void IsReadOnly_PropertyHasNoAttributeAndOnlyGetter_ReturnTrue() + { + // Setup + var instance = new ClassWithProperties(); + var spec = new PropertySpec(instance.GetType().GetProperty("PropertyWithOnlyGetter")); + var descriptor = new PropertySpecDescriptor(spec, instance); + + // Call + var isReadOnly = descriptor.IsReadOnly; + + // Assert + Assert.IsTrue(isReadOnly); + } + + [Test] + [TestCase(false)] + [TestCase(true)] + public void IsBrowsable_PropertyHasDynamicBrowsableProperty_ReturnExpectedValue(bool isPropertyVisible) + { + // Setup + var instance = new ClassWithProperties { IsPropertyBrowsable = isPropertyVisible }; + var propertySpec = new PropertySpec(instance.GetType().GetProperty("IntegerPropertyWithDynamicVisibility")); + var descriptor = new PropertySpecDescriptor(propertySpec, instance); + + // Call + var isBrowsable = descriptor.IsBrowsable; + + // Assert + Assert.AreEqual(isPropertyVisible, isBrowsable); + } + + [Test] + public void IsIsBrowsable_PropertyHasBrowsableTrueAttribute_ReturnTrue() + { + // Setup + var instance = new ClassWithProperties(); + var spec = new PropertySpec(instance.GetType().GetProperty("PropertyWithBrowsableAttribute")); + var descriptor = new PropertySpecDescriptor(spec, instance); + + // Call + var isBrowsable = descriptor.IsBrowsable; + + // Assert + Assert.IsTrue(isBrowsable); + } + + [Test] + public void IsBrowsable_PropertyHasBrowsableFalseAttribute_ReturnFalse() + { + // Setup + var instance = new ClassWithProperties(); + var spec = new PropertySpec(instance.GetType().GetProperty("PropertyWithBrowsableFalseAttribute")); + var descriptor = new PropertySpecDescriptor(spec, instance); + + // Call + var isBrowsable = descriptor.IsBrowsable; + + // Assert + Assert.IsFalse(isBrowsable); + } + + [Test] + public void CanResetValue_ReturnFalse() + { + // Setup + var instance = new ClassWithProperties(); + var spec = new PropertySpec(instance.GetType().GetProperty("PropertyWithOnlyGetter")); + var propertyDescriptor = new PropertySpecDescriptor(spec, instance); + + // Call + var canReset = propertyDescriptor.CanResetValue(instance); + + // Assert + Assert.IsFalse(canReset); + } + + [Test] + public void ShouldSerializeValue_ReturnFalse() + { + // Setup + var instance = new ClassWithProperties(); + var spec = new PropertySpec(instance.GetType().GetProperty("PropertyWithOnlyGetter")); + var propertyDescriptor = new PropertySpecDescriptor(spec, instance); + + // Call + var shouldSerializeValue = propertyDescriptor.ShouldSerializeValue(instance); + + // Assert + Assert.IsFalse(shouldSerializeValue); + } + + [Test] + public void GetValue_SimpleValueProperty_ReturnPropertyValue() + { + // Setup + var instance = new ClassWithProperties(); + var spec = new PropertySpec(instance.GetType().GetProperty("PropertyWithOnlyGetter")); + var propertyDescriptor = new PropertySpecDescriptor(spec, instance); + + // Call + var value = propertyDescriptor.GetValue(instance); + + // Assert + Assert.AreEqual(instance.PropertyWithOnlyGetter, value); + } + + [Test] + public void GetValue_ObjectValueProperty_ReturnPropertyValue() + { + // Setup + var instance = new ClassWithProperties(); + var spec = new PropertySpec(instance.GetType().GetProperty("ComplexSubProperty")); + var propertyDescriptor = new PropertySpecDescriptor(spec, instance); + + // Call + var value = propertyDescriptor.GetValue(instance); + + // Assert + Assert.AreSame(instance.ComplexSubProperty, value); + } + + [Test] + public void GetValue_ObjectValuePropertyWithExpandableObjectConverterAttribute_ReturnPropertyValueWrappedInDynamicPropertyBag() + { + // Setup + var instance = new ClassWithProperties(); + var spec = new PropertySpec(instance.GetType().GetProperty("ComplexSubPropertyWithExandableObjectConverter")); + var propertyDescriptor = new PropertySpecDescriptor(spec, instance); + + // Call + var value = propertyDescriptor.GetValue(instance); + + // Assert + var dynamicPropertyBag = (DynamicPropertyBag)value; + Assert.IsNotNull(dynamicPropertyBag); + Assert.AreSame(instance.ComplexSubPropertyWithExandableObjectConverter, dynamicPropertyBag.WrappedObject); + } + + private class ClassWithProperties + { + public ClassWithProperties() + { + IsPropertyReadOnly = false; + IsPropertyBrowsable = true; + ComplexSubPropertyWithExandableObjectConverter = new AnotherClassWithProperties + { + Comment = "I have nice type converter, right?" + }; + ComplexSubProperty = new AnotherClassWithProperties + { + Comment = "Don't want your type converter!" + }; + } + + #region IsReadOnly state influencing testing members + + public string PropertyWithOnlyGetter + { + get + { + return "I only have a getter."; + } + } + public bool IsPropertyReadOnly { get; set; } + [ReadOnly(true)] + public double PropertyWithReadOnlyAttribute { get; set; } + + [ReadOnly(false)] + public double PropertyWithReadOnlyFalseAttribute { get; set; } + [DynamicReadOnly] - public int IntegerProperty { get; set; } + public int IntegerPropertyWithDynamicReadOnly { get; set; } [DynamicReadOnlyValidationMethod] public bool IsReadOnly(string propertyName) { - if (propertyName == "IntegerProperty") + if (propertyName == "IntegerPropertyWithDynamicReadOnly") { return IsPropertyReadOnly; } - return ReadOnlyAttribute.Default.IsReadOnly; + throw new NotImplementedException(); } + + #endregion + + #region IsBrowsable state influencing testing members + + public bool IsPropertyBrowsable { get; set; } + + [Browsable(true)] + public double PropertyWithBrowsableAttribute { get; set; } + + [Browsable(false)] + public double PropertyWithBrowsableFalseAttribute { get; set; } + + [DynamicVisible] + public int IntegerPropertyWithDynamicVisibility { get; set; } + + [DynamicVisibleValidationMethod] + public bool IsVisible(string propertyName) + { + if (propertyName == "IntegerPropertyWithDynamicVisibility") + { + return IsPropertyBrowsable; + } + throw new NotImplementedException(); + } + + #endregion + + [TypeConverter(typeof(ExpandableObjectConverter))] + public AnotherClassWithProperties ComplexSubPropertyWithExandableObjectConverter { get; set; } + + public AnotherClassWithProperties ComplexSubProperty { get; set; } } + + private class AnotherClassWithProperties + { + public string Comment { get; set; } + } } } \ No newline at end of file Index: Core/Common/test/Core.Common.Gui.Test/PropertyBag/PropertySpecTest.cs =================================================================== diff -u -r7de564e0db6835c6821f0ce2303a983a2758e7d0 -r8de8647e95dd220a1a4d78980e88af32ee9d2d3f --- Core/Common/test/Core.Common.Gui.Test/PropertyBag/PropertySpecTest.cs (.../PropertySpecTest.cs) (revision 7de564e0db6835c6821f0ce2303a983a2758e7d0) +++ Core/Common/test/Core.Common.Gui.Test/PropertyBag/PropertySpecTest.cs (.../PropertySpecTest.cs) (revision 8de8647e95dd220a1a4d78980e88af32ee9d2d3f) @@ -197,6 +197,133 @@ Assert.IsNull(exception.InnerException); } + [Test] + public void GetValue_ProperInstanceType_ReturnPropertyValue() + { + // Setup + var target = new ClassWithProperties + { + IntegerProperty = 5 + }; + + var propertySpec = new PropertySpec(target.GetType().GetProperty("IntegerProperty")); + + // Call + var value = propertySpec.GetValue(target); + + // Assert + Assert.AreEqual(target.IntegerProperty, value); + } + + [Test] + public void GetValue_PropertyHasNoPublicGetter_ThrowInvalidOperationException() + { + // Setup + var target = new ClassWithProperties(); + + var propertySpec = new PropertySpec(target.GetType().GetProperty("DoublePropertyWithOnlyPublicSet")); + + // Call + TestDelegate call = () => propertySpec.GetValue(target); + + // Assert + var message = Assert.Throws(call).Message; + Assert.AreEqual("Property lacks public getter!", message); + } + + [Test] + public void GetValue_IncorrectInstanceType_ThrowArgumentException() + { + // Setup + var target = new ClassWithProperties(); + + var propertySpec = new PropertySpec(target.GetType().GetProperty("IntegerProperty")); + + // Call + TestDelegate call = () => propertySpec.GetValue(new object()); + + // Assert + var exception = Assert.Throws(call); + Assert.IsInstanceOf(exception.InnerException); + } + + [Test] + public void GetValue_InstanceIsNull_ThrowArgumentException() + { + // Setup + var target = new ClassWithProperties(); + + var propertySpec = new PropertySpec(target.GetType().GetProperty("IntegerProperty")); + + // Call + TestDelegate call = () => propertySpec.GetValue(null); + + // Assert + var exception = Assert.Throws(call); + Assert.IsInstanceOf(exception.InnerException); + } + + [Test] + public void IsNonCustomExpandableObjectProperty_PropertyWithoutTypeConverter_ReturnFalse() + { + // Setup + var target = new ClassWithProperties(); + + var propertySpec = new PropertySpec(target.GetType().GetProperty("IntegerProperty")); + + // Call + var hasExpandableObjectTypeConverter = propertySpec.IsNonCustomExpandableObjectProperty(); + + // Assert + Assert.False(hasExpandableObjectTypeConverter); + } + + [Test] + public void IsNonCustomExpandableObjectProperty_PropertyWithExpandableObjectTypeConverter_ReturnTrue() + { + // Setup + var target = new ClassWithProperties(); + + var propertySpec = new PropertySpec(target.GetType().GetProperty("StringPropertyWithExpandableObjectConverter")); + + // Call + var hasExpandableObjectTypeConverter = propertySpec.IsNonCustomExpandableObjectProperty(); + + // Assert + Assert.True(hasExpandableObjectTypeConverter); + } + + [Test] + public void IsNonCustomExpandableObjectProperty_PropertyWithCustomExpandableObjectTypeConverter_ReturnFalse() + { + // Setup + var target = new ClassWithProperties(); + + var propertySpec = new PropertySpec(target.GetType().GetProperty("StringPropertyWithCustomExpandableObjectConverter")); + + // Call + var hasExpandableObjectTypeConverter = propertySpec.IsNonCustomExpandableObjectProperty(); + + // Assert + Assert.False(hasExpandableObjectTypeConverter, + "As we cannot copy the same behavior of a ExpandableObjectConverter with customizations, we should not recognize it as such."); + } + + [Test] + public void IsNonCustomExpandableObjectProperty_PropertyWithSomeTypeConverter_ReturnFalse() + { + // Setup + var target = new ClassWithProperties(); + + var propertySpec = new PropertySpec(target.GetType().GetProperty("StringPropertyWithSomeTypeConverter")); + + // Call + var hasExpandableObjectTypeConverter = propertySpec.IsNonCustomExpandableObjectProperty(); + + // Assert + Assert.False(hasExpandableObjectTypeConverter); + } + private class ClassWithProperties { public int IntegerProperty { get; set; } @@ -215,6 +342,8 @@ public double DoublePropertyWithOnlyPublicGet { get; private set; } + public double DoublePropertyWithOnlyPublicSet { private get; set; } + public double DoublePropertyWithOnlyGetter { get @@ -229,12 +358,31 @@ [Browsable(true)] public bool BoolPropertyWithAttributes { get; set; } + + [TypeConverter(typeof(ExpandableObjectConverter))] + public string StringPropertyWithExpandableObjectConverter { get; set; } + + [TypeConverter(typeof(CustomExpandableObjectConverter))] + public string StringPropertyWithCustomExpandableObjectConverter { get; set; } + + [TypeConverter(typeof(SomeTypeConverter))] + public string StringPropertyWithSomeTypeConverter { get; set; } } private class InheritorSettingPropertyToNotBrowsable : ClassWithProperties { [Browsable(false)] public override string StringPropertyWithAttributes { get; set; } } + + private class SomeTypeConverter : TypeConverter + { + + } + + private class CustomExpandableObjectConverter : ExpandableObjectConverter + { + + } } } \ No newline at end of file