├── .editorconfig ├── .gitattributes ├── .gitignore ├── DynamicDescriptors.sln ├── build ├── build-alpha.ps1 ├── build-release.ps1 └── push-to-nuget.ps1 ├── license.txt ├── readme.md ├── src └── DynamicDescriptors │ ├── DictionaryPropertyDescriptor.cs │ ├── DictionaryTypeDescriptor.cs │ ├── DynamicDescriptor.cs │ ├── DynamicDescriptors.csproj │ ├── DynamicPropertyDescriptor.cs │ ├── DynamicPropertyDescriptorComparer.cs │ ├── DynamicTypeDescriptor.cs │ ├── Preconditions.cs │ ├── Reflect.cs │ └── StandardValuesStringConverter.cs └── test └── DynamicDescriptors.Tests ├── DictionaryPropertyDescriptorTests.cs ├── DictionaryTypeDescriptorTests.cs ├── DynamicDescriptorTests.cs ├── DynamicDescriptors.Tests.csproj ├── DynamicPropertyDescriptorComparerTests.cs ├── DynamicPropertyDescriptorTests.cs ├── DynamicTypeDescriptorTests.cs ├── MockCustomTypeDescriptor.cs ├── MockPropertyDescriptor.cs ├── MockUITypeEditor.cs ├── ReflectTests.cs └── StandardValuesStringConverterTests.cs /.editorconfig: -------------------------------------------------------------------------------- 1 | [root] = true 2 | 3 | [*] 4 | indent_style = space 5 | insert_final_newline = true 6 | trim_trailing_whitespace = true 7 | 8 | [*.csproj] 9 | indent_size = 2 10 | 11 | [*.cs] 12 | indent_size = 4 13 | csharp_style_namespace_declarations = file_scoped:warning 14 | dotnet_naming_rule.instance_fields_should_be_camel_case.severity = suggestion 15 | dotnet_naming_rule.instance_fields_should_be_camel_case.style = instance_field_style 16 | dotnet_naming_rule.instance_fields_should_be_camel_case.symbols = instance_fields 17 | dotnet_naming_style.instance_field_style.capitalization = camel_case 18 | dotnet_naming_style.instance_field_style.required_prefix = _ 19 | dotnet_naming_symbols.instance_fields.applicable_kinds = field 20 | dotnet_sort_system_directives_first = true 21 | dotnet_style_object_initializer = false 22 | dotnet_style_predefined_type_for_locals_parameters_members = true 23 | dotnet_style_predefined_type_for_member_access = false 24 | dotnet_style_prefer_inferred_anonymous_type_member_names = false 25 | dotnet_style_qualification_for_event = false 26 | dotnet_style_qualification_for_field = false 27 | dotnet_style_qualification_for_method = false 28 | dotnet_style_qualification_for_property = false 29 | dotnet_style_readonly_field = true 30 | dotnet_style_require_accessibility_modifiers = for_non_interface_members 31 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vs/ 2 | bin/ 3 | obj/ 4 | artifacts/ 5 | -------------------------------------------------------------------------------- /DynamicDescriptors.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26228.4 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DynamicDescriptors", "src\DynamicDescriptors\DynamicDescriptors.csproj", "{8A217CE0-D8F7-426B-B93A-AFC7D8470FC2}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DynamicDescriptors.Tests", "test\DynamicDescriptors.Tests\DynamicDescriptors.Tests.csproj", "{D417C0BF-C5EB-40F1-80D2-5AC88FC4A491}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {8A217CE0-D8F7-426B-B93A-AFC7D8470FC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {8A217CE0-D8F7-426B-B93A-AFC7D8470FC2}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {8A217CE0-D8F7-426B-B93A-AFC7D8470FC2}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {8A217CE0-D8F7-426B-B93A-AFC7D8470FC2}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {D417C0BF-C5EB-40F1-80D2-5AC88FC4A491}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {D417C0BF-C5EB-40F1-80D2-5AC88FC4A491}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {D417C0BF-C5EB-40F1-80D2-5AC88FC4A491}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {D417C0BF-C5EB-40F1-80D2-5AC88FC4A491}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /build/build-alpha.ps1: -------------------------------------------------------------------------------- 1 | $root = Resolve-Path (Join-Path $PSScriptRoot "..") 2 | $output = "$root/artifacts" 3 | $projects = @( 4 | "$root/src/DynamicDescriptors/DynamicDescriptors.csproj" 5 | ) 6 | 7 | $timestamp = git log -1 --format=%ct 8 | 9 | foreach ($project in $projects) { 10 | dotnet pack $project --configuration Release --output $output --version-suffix "alpha.$timestamp" -p:ContinuousIntegrationBuild=true 11 | } 12 | -------------------------------------------------------------------------------- /build/build-release.ps1: -------------------------------------------------------------------------------- 1 | $root = Resolve-Path (Join-Path $PSScriptRoot "..") 2 | $output = "$root/artifacts" 3 | $projects = @( 4 | "$root/src/DynamicDescriptors/DynamicDescriptors.csproj" 5 | ) 6 | 7 | foreach ($project in $projects) { 8 | dotnet pack $project --configuration Release --output $output -p:ContinuousIntegrationBuild=true 9 | } 10 | -------------------------------------------------------------------------------- /build/push-to-nuget.ps1: -------------------------------------------------------------------------------- 1 | $root = Resolve-Path (Join-Path $PSScriptRoot "..") 2 | $output = "$root/artifacts" 3 | foreach ($package in Get-ChildItem -Path $output -Filter "*.nupkg") { 4 | nuget push $package.FullName -Source https://api.nuget.org/v3/index.json 5 | } 6 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2012-2022 Matthew King 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # DynamicDescriptors 2 | 3 | The WinForms [PropertyGrid](http://msdn.microsoft.com/en-us/library/system.windows.forms.propertygrid.aspx) is really useful, but it isn't particularly easy to customize - especially when binding to objects that are out of your control. DynamicDescriptors helps alleviate this pain by providing a runtime-customizable implementation of ICustomTypeDescriptor that can be bound to the PropertyGrid. 4 | 5 | Properties can be customized with an easy-to-use fluent interface: 6 | 7 | ```csharp 8 | var instanceToBind = new ExampleClass(); 9 | 10 | var descriptor = DynamicDescriptor.CreateFromInstance(instanceToBind); 11 | 12 | descriptor.GetDynamicProperty("PropertyOne") // Get the property using its name. 13 | .SetDisplayName("Property #1") 14 | .SetDescription("The first property") 15 | .SetCategory("Example category"); 16 | 17 | descriptor.GetDynamicProperty((ExampleClass x) => x.Property2) // Get the property using an expression. 18 | .SetDisplayName("Property #2") 19 | .SetDescription("The second property") 20 | .SetCategory("Example category"); 21 | 22 | propertyGrid.SelectedObject = descriptor; 23 | ``` 24 | 25 | ## Binding to an object instance 26 | 27 | We can create a DynamicDescriptor for an object instance: 28 | 29 | ```csharp 30 | var instance = new ExampleClass(); 31 | 32 | var descriptor = DynamicDescriptor.CreateFromInstance(instance); 33 | ``` 34 | 35 | ## Binding to a dictionary 36 | 37 | We can create a DynamicDescriptor backed by a dictionary. This will act as if the dictionary key/value pairs are properties of a bound object: 38 | 39 | ```csharp 40 | var data = new Dictionary(); 41 | data["Property1"] = "hello"; 42 | data["Property2"] = "world"; 43 | 44 | var descriptor = DynamicDescriptor.CreateFromDictionary(data); 45 | ``` 46 | 47 | We can also supply type information: 48 | 49 | ```csharp 50 | var data = new Dictionary(); 51 | data["Property1"] = "value"; 52 | data["Property2"] = 1; 53 | 54 | var types = new Dictionary(); 55 | types["Property1"] = typeof(string); 56 | types["Property2"] = typeof(int); 57 | 58 | var descriptor = DynamicDescriptor.CreateFromDictionary(data, types); 59 | ``` 60 | 61 | ## What can be customized? 62 | 63 | **DisplayName:** 64 | 65 | ```csharp 66 | descriptor.GetDynamicProperty("PropertyName").SetDisplayName("Property display name"); 67 | ``` 68 | 69 | This modifies the value returned by the [DisplayName property](http://msdn.microsoft.com/en-us/library/system.componentmodel.memberdescriptor.displayname.aspx). 70 | 71 | **Description:** 72 | 73 | ```csharp 74 | descriptor.GetDynamicProperty("PropertyName").SetDescription("A description of the property"); 75 | ``` 76 | 77 | This modifies the value returned by the [Description property](http://msdn.microsoft.com/en-us/library/system.componentmodel.memberdescriptor.description.aspx). 78 | 79 | **Category:** 80 | 81 | ```csharp 82 | descriptor.GetDynamicProperty("PropertyName").SetCategory("Category name"); 83 | ``` 84 | 85 | This modifies the value returned by the [Category property](http://msdn.microsoft.com/en-us/library/system.componentmodel.memberdescriptor.category.aspx). 86 | 87 | **Converter:** 88 | 89 | ```csharp 90 | TypeConverter converter = /* your custom type converter */; 91 | descriptor.GetDynamicProperty("PropertyName").SetConverter(converter); 92 | ``` 93 | 94 | This modifies the value returned by the [Converter property](http://msdn.microsoft.com/en-us/library/system.componentmodel.propertydescriptor.converter.aspx). 95 | 96 | **IsReadOnly:** 97 | 98 | ```csharp 99 | descriptor.GetDynamicProperty("PropertyName").SetReadOnly(true); 100 | ``` 101 | 102 | This modifies the value returned by the [IsReadOnly property](http://msdn.microsoft.com/en-us/library/system.componentmodel.propertydescriptor.isreadonly.aspx). 103 | 104 | **Property order:** 105 | 106 | ```csharp 107 | descriptor.GetDynamicProperty("PropertyOne").SetPropertyOrder(1); 108 | descriptor.GetDynamicProperty("PropertyTwo").SetPropertyOrder(2); 109 | descriptor.GetDynamicProperty("PropertyThree").SetPropertyOrder(3); 110 | ``` 111 | 112 | This modifies the order in which properties are returned by the [GetProperties method](http://msdn.microsoft.com/en-us/library/hc91c96t.aspx). 113 | 114 | ## Installation 115 | 116 | Just grab it from [NuGet](https://www.nuget.org/packages/DynamicDescriptors/) 117 | 118 | ``` 119 | PM> Install-Package DynamicDescriptors 120 | ``` 121 | 122 | ## License and copyright 123 | 124 | Copyright Matthew King 2012-2022. 125 | Distributed under the [MIT License](http://opensource.org/licenses/MIT). Refer to license.txt for more information. 126 | -------------------------------------------------------------------------------- /src/DynamicDescriptors/DictionaryPropertyDescriptor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | 5 | namespace DynamicDescriptors; 6 | 7 | /// 8 | /// A dictionary-backed implementation of . 9 | /// 10 | internal sealed class DictionaryPropertyDescriptor : PropertyDescriptor 11 | { 12 | /// 13 | /// A dictionary mapping property names to property values. 14 | /// 15 | private readonly IDictionary _data; 16 | 17 | /// 18 | /// The name of the property. 19 | /// 20 | private readonly string _propertyName; 21 | 22 | /// 23 | /// The type of the property. 24 | /// 25 | private readonly Type _propertyType; 26 | 27 | /// 28 | /// Initializes a new instance of the class. 29 | /// 30 | /// The dictionary that backs this property descriptor. 31 | /// The name of the property. 32 | /// The type of the property. 33 | public DictionaryPropertyDescriptor(IDictionary data, string propertyName, Type propertyType) 34 | : base(Preconditions.CheckNotNullOrEmpty(propertyName, nameof(propertyName)), null) 35 | { 36 | _data = data ?? throw new ArgumentNullException(nameof(data)); 37 | _propertyName = propertyName; 38 | _propertyType = propertyType ?? throw new ArgumentNullException(nameof(propertyType)); 39 | } 40 | 41 | /// 42 | /// Returns a value indicating returns whether resetting an object changes its value. 43 | /// 44 | /// The component to test for reset capability. 45 | /// true if resetting the component changes its value; otherwise, false. 46 | public override bool CanResetValue(object component) 47 | { 48 | return false; 49 | } 50 | 51 | /// 52 | /// Gets the type of the component this property is bound to. 53 | /// 54 | public override Type ComponentType => null; 55 | 56 | /// 57 | /// Returns the current value of the property on a component. 58 | /// 59 | /// 60 | /// The component with the property for which to retrieve the value. 61 | /// 62 | /// The value of a property for a given component. 63 | public override object GetValue(object component) 64 | { 65 | return _data[_propertyName]; 66 | } 67 | 68 | /// 69 | /// Gets a value indicating whether this property is read-only. 70 | /// 71 | public override bool IsReadOnly => false; 72 | 73 | /// 74 | /// Gets the type of the property. 75 | /// 76 | public override Type PropertyType => _propertyType; 77 | 78 | /// 79 | /// Resets the value for this property of the component to the default value. 80 | /// 81 | /// 82 | /// The component with the property value that is to be reset to the default value. 83 | /// 84 | public override void ResetValue(object component) 85 | { 86 | _data[_propertyName] = null; 87 | OnValueChanged(component, EventArgs.Empty); 88 | } 89 | 90 | /// 91 | /// Sets the value of the component to a different value. 92 | /// 93 | /// 94 | /// The component with the property value that is to be set. 95 | /// 96 | /// 97 | /// The new value. 98 | /// 99 | public override void SetValue(object component, object value) 100 | { 101 | _data[_propertyName] = value; 102 | OnValueChanged(component, EventArgs.Empty); 103 | } 104 | 105 | /// 106 | /// Determines a value indicating whether the value of this property needs to be 107 | /// persisted. 108 | /// 109 | /// 110 | /// The component with the property to be examined for persistence. 111 | /// 112 | /// true if the property should be persisted; otherwise, false. 113 | public override bool ShouldSerializeValue(object component) 114 | { 115 | return false; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/DynamicDescriptors/DictionaryTypeDescriptor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | 5 | namespace DynamicDescriptors; 6 | 7 | /// 8 | /// A dictionary-backed implementation of . 9 | /// 10 | internal sealed class DictionaryTypeDescriptor : CustomTypeDescriptor, ICustomTypeDescriptor, INotifyPropertyChanged 11 | { 12 | /// 13 | /// Occurs when a property value changes. 14 | /// 15 | public event PropertyChangedEventHandler PropertyChanged; 16 | 17 | /// 18 | /// A dictionary mapping property names to property values. 19 | /// 20 | private readonly IDictionary _data; 21 | 22 | /// 23 | /// A list containing the properties associated with this type descriptor. 24 | /// 25 | private readonly List _propertyDescriptors; 26 | 27 | /// 28 | /// Initializes a new instance of the class. 29 | /// 30 | /// A dictionary mapping property names to property values. 31 | public DictionaryTypeDescriptor(IDictionary data) 32 | : this(data, null) { } 33 | 34 | /// 35 | /// Initializes a new instance of the class. 36 | /// 37 | /// A dictionary mapping property names to property values. 38 | /// A dictionary mapping property names to property types. 39 | public DictionaryTypeDescriptor(IDictionary data, IDictionary types) 40 | { 41 | if (data == null) 42 | { 43 | throw new ArgumentNullException(nameof(data)); 44 | } 45 | 46 | _data = data; 47 | _propertyDescriptors = new List(); 48 | 49 | foreach (var pair in _data) 50 | { 51 | if (types == null || !types.TryGetValue(pair.Key, out var type)) 52 | { 53 | type = pair.Value?.GetType() ?? typeof(object); 54 | } 55 | 56 | var propertyDescriptor = new DictionaryPropertyDescriptor(data, pair.Key, type); 57 | propertyDescriptor.AddValueChanged(this, (s, e) => OnPropertyChanged(pair.Key)); 58 | 59 | _propertyDescriptors.Add(propertyDescriptor); 60 | } 61 | } 62 | 63 | /// 64 | /// Returns an object that contains the property described by the specified property 65 | /// descriptor. 66 | /// 67 | /// 68 | /// A that represents the property whose owner is to be found. 69 | /// 70 | /// An object that represents the owner of the specified property. 71 | public override object GetPropertyOwner(PropertyDescriptor pd) 72 | { 73 | return this; 74 | } 75 | 76 | /// 77 | /// Returns a collection of property descriptors for the object represented by this type 78 | /// descriptor. 79 | /// 80 | /// 81 | /// A containing the property descriptions for the object 82 | /// represented by this type descriptor. 83 | /// 84 | public override PropertyDescriptorCollection GetProperties() 85 | { 86 | return GetProperties(null); 87 | } 88 | 89 | /// 90 | /// Returns a collection of property descriptors for the object represented by this type 91 | /// descriptor. 92 | /// 93 | /// 94 | /// An array of attributes to use as a filter. This can be null. 95 | /// 96 | /// 97 | /// A containing the property descriptions for the object 98 | /// represented by this type descriptor. 99 | /// 100 | public override PropertyDescriptorCollection GetProperties(Attribute[] attributes) 101 | { 102 | return new PropertyDescriptorCollection(_propertyDescriptors.ToArray()); 103 | } 104 | 105 | /// 106 | /// Raises the event. 107 | /// 108 | /// The name of the property that changed. 109 | private void OnPropertyChanged(string propertyName) 110 | { 111 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/DynamicDescriptors/DynamicDescriptor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | 5 | namespace DynamicDescriptors; 6 | 7 | /// 8 | /// Facilitates the creation of custom type descriptors. 9 | /// 10 | public static class DynamicDescriptor 11 | { 12 | /// 13 | /// Returns a new instance that will supply 14 | /// dynamic custom type information for the specified object. 15 | /// 16 | /// 17 | /// The type of the object for which dynamic custom type information will be supplied. 18 | /// 19 | /// 20 | /// The object for which dynamic custom type information will be supplied. 21 | /// 22 | /// 23 | /// A new instance that will supply dynamic 24 | /// custom type information for the specified object. 25 | /// 26 | public static DynamicTypeDescriptor CreateFromInstance(T instance) 27 | { 28 | if (instance == null) 29 | { 30 | throw new ArgumentNullException(nameof(instance)); 31 | } 32 | 33 | var provider = TypeDescriptor.GetProvider(instance); 34 | var descriptor = provider.GetTypeDescriptor(instance); 35 | return new DynamicTypeDescriptor(descriptor); 36 | } 37 | 38 | /// 39 | /// Returns a new instance that wraps the specified 40 | /// instance. 41 | /// 42 | /// 43 | /// The instance to wrap. 44 | /// 45 | /// 46 | /// A new instance that wraps the specified 47 | /// instance. 48 | /// 49 | public static DynamicTypeDescriptor CreateFromDescriptor(ICustomTypeDescriptor descriptor) 50 | { 51 | if (descriptor == null) 52 | { 53 | throw new ArgumentNullException(nameof(descriptor)); 54 | } 55 | 56 | return new DynamicTypeDescriptor(descriptor); 57 | } 58 | 59 | /// 60 | /// Returns a new instance that exposes properties 61 | /// defined by the data present in the specified dictionary. 62 | /// 63 | /// A dictionary mapping property names to property values. 64 | /// 65 | /// A new instance that exposes properties defined by 66 | /// the data present in the specified dictionary. 67 | /// 68 | public static DynamicTypeDescriptor CreateFromDictionary(IDictionary data) 69 | { 70 | if (data == null) 71 | { 72 | throw new ArgumentNullException(nameof(data)); 73 | } 74 | 75 | var descriptor = new DictionaryTypeDescriptor(data); 76 | 77 | return new DynamicTypeDescriptor(descriptor); 78 | } 79 | 80 | /// 81 | /// Returns a new instance that exposes properties 82 | /// defined by the data present in the specified dictionary. 83 | /// 84 | /// A dictionary mapping property names to property values. 85 | /// A dictionary mapping property names to property types. 86 | /// 87 | /// A new instance that exposes properties defined 88 | /// by the data present in the specified dictionary. 89 | /// 90 | public static DynamicTypeDescriptor CreateFromDictionary(IDictionary data, IDictionary types) 91 | { 92 | if (data == null) 93 | { 94 | throw new ArgumentNullException(nameof(data)); 95 | } 96 | 97 | var descriptor = new DictionaryTypeDescriptor(data, types); 98 | 99 | return new DynamicTypeDescriptor(descriptor); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/DynamicDescriptors/DynamicDescriptors.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | DynamicDescriptors 5 | DynamicDescriptors 6 | Runtime-customizable type and property descriptors. 7 | Matthew King 8 | Copyright 2012-2022 Matthew King. 9 | https://github.com/MatthewKing/DynamicDescriptors 10 | MIT 11 | dynamic;descriptor; 12 | true 13 | true 14 | true 15 | snupkg 16 | 1.8.0 17 | 18 | 19 | 20 | net40;netstandard2.0 21 | latest 22 | true 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | <_Parameter1>DynamicDescriptors.Tests 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/DynamicDescriptors/DynamicPropertyDescriptor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | 5 | namespace DynamicDescriptors; 6 | 7 | /// 8 | /// A runtime-customizable implementation of . 9 | /// 10 | public sealed class DynamicPropertyDescriptor : PropertyDescriptor 11 | { 12 | /// 13 | /// The on which this is based. 14 | /// 15 | private readonly PropertyDescriptor _descriptor; 16 | 17 | /// 18 | /// A dictionary mapping editor base types to editor instances. 19 | /// 20 | private readonly IDictionary _editorDictionary; 21 | 22 | /// 23 | /// Gets or sets a value indicating whether the dynamic property descriptor is active. 24 | /// If not, it won't be returned by the 25 | /// or methods. 26 | /// 27 | private bool _active; 28 | 29 | /// 30 | /// If this value is not null, it will be returned by the Category property; 31 | /// otherwise, the base descriptor's Category property will be returned. 32 | /// 33 | private string _categoryOverride; 34 | 35 | /// 36 | /// If this value is not null, it will be returned by the Converter property; 37 | /// otherwise, the base descriptor's Converter property will be returned. 38 | /// 39 | private TypeConverter _converterOverride; 40 | 41 | /// 42 | /// If this value is not null, it will be returned by the Description property; 43 | /// otherwise, the base descriptor's Description property will be returned. 44 | /// 45 | private string _descriptionOverride; 46 | 47 | /// 48 | /// If this value is not null, it will be returned by the DisplayName property; 49 | /// otherwise, the base descriptor's DisplayName property will be returned. 50 | /// 51 | private string _displayNameOverride; 52 | 53 | /// 54 | /// If this value is not null, it will be returned by the IsReadOnly property; 55 | /// otherwise, the base descriptor's IsReadOnly property will be returned. 56 | /// 57 | private bool? _isReadOnlyOverride; 58 | 59 | /// 60 | /// The order in which this property will be retrieved from its type descriptor. 61 | /// 62 | private int? _propertyOrder; 63 | 64 | /// 65 | /// Initializes a new instance of the class. 66 | /// 67 | /// 68 | /// The on which this will be based. 69 | /// 70 | public DynamicPropertyDescriptor(PropertyDescriptor descriptor) 71 | : base(Preconditions.CheckNotNull(descriptor, nameof(descriptor))) 72 | { 73 | _descriptor = descriptor; 74 | _editorDictionary = new Dictionary(); 75 | _active = true; 76 | } 77 | 78 | /// 79 | /// Gets or sets a value indicating whether the dynamic property descriptor is active. 80 | /// If not, it won't be returned by the 81 | /// or methods. 82 | /// 83 | public bool Active 84 | { 85 | get => _active; 86 | set => _active = value; 87 | } 88 | 89 | /// 90 | /// Returns a value indicating whether resetting an object changes its value. 91 | /// 92 | /// The component to test for reset capability. 93 | /// true if resetting the component changes its value; otherwise, false. 94 | public override bool CanResetValue(object component) 95 | { 96 | return _descriptor.CanResetValue(component); 97 | } 98 | 99 | /// 100 | /// Gets the name of the category to which the member belongs, 101 | /// as specified in the . 102 | /// 103 | public override string Category => _categoryOverride ?? _descriptor.Category; 104 | 105 | /// 106 | /// Gets the type of the component this property is bound to. 107 | /// 108 | public override Type ComponentType => _descriptor.ComponentType; 109 | 110 | /// 111 | /// Gets the type converter for this property. 112 | /// 113 | public override TypeConverter Converter => _converterOverride ?? _descriptor.Converter; 114 | 115 | /// 116 | /// Gets the description of the member, 117 | /// as specified in the . 118 | /// 119 | public override string Description => _descriptionOverride ?? _descriptor.Description; 120 | 121 | /// 122 | /// Gets the name that can be displayed in a window, such as a Properties window. 123 | /// 124 | public override string DisplayName => _displayNameOverride ?? _descriptor.DisplayName; 125 | 126 | /// 127 | /// Gets an editor of the specified type. 128 | /// 129 | /// 130 | /// The base type of editor, which is used to differentiate between multiple 131 | /// editors that a property supports. 132 | /// 133 | /// 134 | /// An instance of the requested editor type, or null if an editor cannot be found. 135 | /// 136 | public override object GetEditor(Type editorBaseType) 137 | { 138 | if (editorBaseType != null) 139 | { 140 | if (_editorDictionary.TryGetValue(editorBaseType, out var editor)) 141 | { 142 | return editor; 143 | } 144 | } 145 | 146 | return _descriptor.GetEditor(editorBaseType); 147 | } 148 | 149 | /// 150 | /// Returns the current value of the property on a component. 151 | /// 152 | /// 153 | /// The component with the property for which to retrieve the value. 154 | /// 155 | /// The value of a property for a given component. 156 | public override object GetValue(object component) 157 | { 158 | return _descriptor.GetValue(component); 159 | } 160 | 161 | /// 162 | /// Gets a value indicating whether this property is read-only. 163 | /// 164 | public override bool IsReadOnly => _isReadOnlyOverride ?? _descriptor.IsReadOnly; 165 | 166 | /// 167 | /// Gets the order in which this property will be retrieved from its type descriptor. 168 | /// 169 | public int? PropertyOrder => _propertyOrder; 170 | 171 | /// 172 | /// Gets the type of the property. 173 | /// 174 | public override Type PropertyType => _descriptor.PropertyType; 175 | 176 | /// 177 | /// Resets the value for this property of the component to the default value. 178 | /// 179 | /// 180 | /// The component with the property value that is to be reset to the default value. 181 | /// 182 | public override void ResetValue(object component) 183 | { 184 | _descriptor.ResetValue(component); 185 | OnValueChanged(component, EventArgs.Empty); 186 | } 187 | 188 | /// 189 | /// Sets the value of the component to a different value. 190 | /// 191 | /// 192 | /// The component with the property value that is to be set. 193 | /// 194 | /// 195 | /// The new value. 196 | /// 197 | public override void SetValue(object component, object value) 198 | { 199 | _descriptor.SetValue(component, value); 200 | OnValueChanged(component, EventArgs.Empty); 201 | } 202 | 203 | /// 204 | /// Determines a value indicating whether the value of this property needs to be persisted. 205 | /// 206 | /// 207 | /// The component with the property to be examined for persistence. 208 | /// 209 | /// true if the property should be persisted; otherwise, false. 210 | public override bool ShouldSerializeValue(object component) 211 | { 212 | return _descriptor.ShouldSerializeValue(component); 213 | } 214 | 215 | #region Fluent interface 216 | 217 | /// 218 | /// Sets value that determines whether the dynamic property descriptor is active. 219 | /// 220 | /// 221 | /// The value that determines whether the dynamic property descriptor is active. 222 | /// 223 | /// This instance. 224 | public DynamicPropertyDescriptor SetActive(bool active) 225 | { 226 | _active = active; 227 | return this; 228 | } 229 | 230 | /// 231 | /// Sets the override for the property. 232 | /// 233 | /// The new value for the property. 234 | /// This instance. 235 | public DynamicPropertyDescriptor SetCategory(string category) 236 | { 237 | _categoryOverride = category; 238 | return this; 239 | } 240 | 241 | /// 242 | /// Sets the override for the property. 243 | /// 244 | /// The new value for the property. 245 | /// This instance. 246 | public DynamicPropertyDescriptor SetConverter(TypeConverter converter) 247 | { 248 | _converterOverride = converter; 249 | return this; 250 | } 251 | 252 | /// 253 | /// Sets the override for the property. 254 | /// 255 | /// The new value for the property. 256 | /// This instance. 257 | public DynamicPropertyDescriptor SetDescription(string description) 258 | { 259 | _descriptionOverride = description; 260 | return this; 261 | } 262 | 263 | /// 264 | /// Sets the override for the property. 265 | /// 266 | /// The new value for the property. 267 | /// This instance. 268 | public DynamicPropertyDescriptor SetDisplayName(string displayName) 269 | { 270 | _displayNameOverride = displayName; 271 | return this; 272 | } 273 | 274 | /// 275 | /// Sets the editor for this type. 276 | /// 277 | /// 278 | /// The base type of editor, which is used to differentiate between multiple editors 279 | /// that a property supports. 280 | /// 281 | /// 282 | /// An instance of the requested editor type. 283 | /// 284 | /// This instance. 285 | public DynamicPropertyDescriptor SetEditor(Type editorBaseType, object editor) 286 | { 287 | if (editorBaseType != null) 288 | { 289 | if (_editorDictionary.ContainsKey(editorBaseType)) 290 | { 291 | if (editor == null) 292 | { 293 | _editorDictionary.Remove(editorBaseType); 294 | } 295 | else 296 | { 297 | _editorDictionary[editorBaseType] = editor; 298 | } 299 | } 300 | else 301 | { 302 | if (editor != null) 303 | { 304 | _editorDictionary.Add(editorBaseType, editor); 305 | } 306 | } 307 | } 308 | 309 | return this; 310 | } 311 | 312 | /// 313 | /// Sets the order in which this property will be retrieved from its type descriptor. 314 | /// 315 | /// The order in which this property will be retrieved. 316 | /// This instance. 317 | public DynamicPropertyDescriptor SetPropertyOrder(int? propertyOrder) 318 | { 319 | _propertyOrder = propertyOrder; 320 | return this; 321 | } 322 | 323 | /// 324 | /// Sets the override for the property. 325 | /// 326 | /// The new value for the property. 327 | /// This instance. 328 | public DynamicPropertyDescriptor SetReadOnly(bool? readOnly) 329 | { 330 | _isReadOnlyOverride = readOnly; 331 | return this; 332 | } 333 | 334 | #endregion 335 | } 336 | -------------------------------------------------------------------------------- /src/DynamicDescriptors/DynamicPropertyDescriptorComparer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace DynamicDescriptors; 5 | 6 | /// 7 | /// An implementation of for instances. 8 | /// 9 | internal sealed class DynamicPropertyDescriptorComparer : IComparer 10 | { 11 | /// 12 | /// The to use to compare property names when unable to use property order. 13 | /// 14 | private static readonly StringComparer sc = StringComparer.OrdinalIgnoreCase; 15 | 16 | /// 17 | /// Compares two instances and returns a value indicating 18 | /// whether one is less than, equal to, or greater than the other. 19 | /// 20 | /// The first instance to compare. 21 | /// The second instance to compare. 22 | /// 23 | /// A value that is less than zero if x is less than y, zero if x equals y, 24 | /// or greater than zero if x is greater than y. 25 | /// 26 | public int Compare(DynamicPropertyDescriptor x, DynamicPropertyDescriptor y) 27 | { 28 | if (x == null) 29 | { 30 | if (y == null) 31 | { 32 | // two nulls shall be considered equal. 33 | return 0; 34 | } 35 | else 36 | { 37 | // y comes first by virtue of it not being null. 38 | return 1; 39 | } 40 | } 41 | else 42 | { 43 | if (y == null) 44 | { 45 | // x comes first by virtue of it not being null. 46 | return -1; 47 | } 48 | else 49 | { 50 | if (x.PropertyOrder.HasValue) 51 | { 52 | if (y.PropertyOrder.HasValue) 53 | { 54 | if (x.PropertyOrder.Value > y.PropertyOrder.Value) 55 | { 56 | // y comes first by virtue of it having a lower property order. 57 | return 1; 58 | } 59 | else if (y.PropertyOrder.Value > x.PropertyOrder.Value) 60 | { 61 | // x comes first by virtue of it having a lower property order. 62 | return -1; 63 | } 64 | else 65 | { 66 | // property orders are the same, so use alphabetical order. 67 | return sc.Compare(x.Name, y.Name); 68 | } 69 | } 70 | else 71 | { 72 | // x comes first by virtue of it having a property order. 73 | return -1; 74 | } 75 | } 76 | else 77 | { 78 | if (y.PropertyOrder.HasValue) 79 | { 80 | // y comes first by virtue of it having a property order. 81 | return 1; 82 | } 83 | else 84 | { 85 | // neither have a property order, so use alphabetical order. 86 | return sc.Compare(x.Name, y.Name); 87 | } 88 | } 89 | } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/DynamicDescriptors/DynamicTypeDescriptor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | 7 | namespace DynamicDescriptors; 8 | 9 | /// 10 | /// A runtime-customizable implementation of . 11 | /// 12 | public sealed class DynamicTypeDescriptor : CustomTypeDescriptor, ICustomTypeDescriptor, INotifyPropertyChanged 13 | { 14 | /// 15 | /// Occurs when a property value changes. 16 | /// 17 | public event PropertyChangedEventHandler PropertyChanged; 18 | 19 | /// 20 | /// A list containing the properties associated with this type descriptor. 21 | /// 22 | private readonly IList _dynamicProperties; 23 | 24 | /// 25 | /// Comparer to use when sorting a list of dynamic property descriptors. 26 | /// 27 | private readonly IComparer _comparer; 28 | 29 | /// 30 | /// Initializes a new instance of the class. 31 | /// 32 | /// The parent custom type descriptor. 33 | public DynamicTypeDescriptor(ICustomTypeDescriptor parent) 34 | : base(Preconditions.CheckNotNull(parent, nameof(parent))) 35 | { 36 | _dynamicProperties = new List(); 37 | _comparer = new DynamicPropertyDescriptorComparer(); 38 | 39 | foreach (PropertyDescriptor propertyDescriptor in base.GetProperties()) 40 | { 41 | var dynamicPropertyDescriptor = new DynamicPropertyDescriptor(propertyDescriptor); 42 | dynamicPropertyDescriptor.AddValueChanged(this, (s, e) => OnPropertyChanged(propertyDescriptor.Name)); 43 | 44 | _dynamicProperties.Add(dynamicPropertyDescriptor); 45 | } 46 | } 47 | 48 | /// 49 | /// Returns a collection of property descriptors for the object represented by this type 50 | /// descriptor. 51 | /// 52 | /// 53 | /// A containing the property descriptions 54 | /// for the object represented by this type descriptor. 55 | /// 56 | public override PropertyDescriptorCollection GetProperties() 57 | { 58 | return GetProperties(null); 59 | } 60 | 61 | /// 62 | /// Returns a collection of property descriptors for the object represented by this type 63 | /// descriptor. 64 | /// 65 | /// 66 | /// An array of attributes to use as a filter. This can be null. 67 | /// 68 | /// 69 | /// A containing the property descriptions 70 | /// for the object represented by this type descriptor. 71 | /// 72 | public override PropertyDescriptorCollection GetProperties(Attribute[] attributes) 73 | { 74 | var properties = new List(); 75 | 76 | foreach (var property in _dynamicProperties) 77 | { 78 | if (attributes == null || property.Attributes.Contains(attributes)) 79 | { 80 | if (property.Active) 81 | { 82 | properties.Add(property); 83 | } 84 | } 85 | } 86 | 87 | properties.Sort(_comparer); 88 | 89 | return new PropertyDescriptorCollection(properties.ToArray()); 90 | } 91 | 92 | /// 93 | /// Returns the specified dynamic property descriptor for the object represented by this 94 | /// type descriptor. 95 | /// 96 | /// The name of the property. 97 | /// 98 | /// The specified dynamic property descriptor for the object represented by this type 99 | /// descriptor. 100 | /// 101 | public DynamicPropertyDescriptor GetDynamicProperty(string propertyName) 102 | { 103 | foreach (var property in _dynamicProperties) 104 | { 105 | if (string.Equals(property.Name, propertyName)) 106 | { 107 | return property; 108 | } 109 | } 110 | 111 | return null; 112 | } 113 | 114 | /// 115 | /// Returns the specified dynamic property descriptor for the object represented by this 116 | /// type descriptor. 117 | /// 118 | /// Type containing the property. 119 | /// Type of the property. 120 | /// 121 | /// An expression representing a function mapping an instance of type TSource to a 122 | /// property of type TProperty. 123 | /// 124 | /// 125 | /// The specified dynamic property descriptor for the object represented by this type 126 | /// descriptor. 127 | /// 128 | public DynamicPropertyDescriptor GetDynamicProperty(Expression> propertyExpression) 129 | { 130 | var propertyName = Reflect.GetPropertyName(propertyExpression); 131 | return GetDynamicProperty(propertyName); 132 | } 133 | 134 | /// 135 | /// Returns an enumerator that iterates through the sequence containing all dynamic 136 | /// property descriptors for the object represented by this type descriptor. 137 | /// 138 | /// 139 | /// An enumerator that iterates through the sequence containing all dynamic property 140 | /// descriptors for the object represented by this type descriptor. 141 | /// 142 | public IEnumerable GetDynamicProperties() 143 | { 144 | // This should return all dynamic properties, even those that are inactive. 145 | 146 | return _dynamicProperties 147 | .OrderBy(x => x.PropertyOrder ?? int.MaxValue) 148 | .ToArray(); 149 | } 150 | 151 | /// 152 | /// Raises the event. 153 | /// 154 | /// The name of the property that changed. 155 | private void OnPropertyChanged(string propertyName) 156 | { 157 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/DynamicDescriptors/Preconditions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DynamicDescriptors; 4 | 5 | /// 6 | /// Provides internal precondition helper methods. 7 | /// 8 | internal static class Preconditions 9 | { 10 | /// 11 | /// Provides 'is not null' parameter validation. 12 | /// 13 | /// The type of the parameter to validate. 14 | /// The value of the parameter. 15 | /// The name of the parameter. 16 | /// The value of the parameter (if it was not null). 17 | public static T CheckNotNull(T value, string parameterName) 18 | where T : class 19 | { 20 | if (value == null) 21 | { 22 | throw new ArgumentNullException(parameterName); 23 | } 24 | 25 | return value; 26 | } 27 | 28 | /// 29 | /// Provides 'is not null or an empty string' parameter validation. 30 | /// 31 | /// The value of the parameter. 32 | /// The name of the parameter. 33 | /// The value of the parameter (if it was not null or empty). 34 | public static string CheckNotNullOrEmpty(string value, string parameterName) 35 | { 36 | if (value == null) 37 | { 38 | throw new ArgumentNullException(parameterName); 39 | } 40 | 41 | if (value.Length == 0) 42 | { 43 | throw new ArgumentException($"{parameterName} should be an empty string.", parameterName); 44 | } 45 | 46 | return value; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/DynamicDescriptors/Reflect.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using System.Reflection; 4 | 5 | namespace DynamicDescriptors; 6 | 7 | /// 8 | /// Provides various reflection-related methods. 9 | /// 10 | internal static class Reflect 11 | { 12 | /// 13 | /// Returns the name of the property referred to by the specified property expression. 14 | /// 15 | /// 16 | /// Type containing the property. 17 | /// 18 | /// 19 | /// Type of the property. 20 | /// 21 | /// 22 | /// An representing a Func mapping an instance of type TSource 23 | /// to an instance of type TProperty. 24 | /// 25 | /// 26 | /// The name of the property referred to by the specified property expression. 27 | /// 28 | public static string GetPropertyName(Expression> propertyExpression) 29 | { 30 | if (propertyExpression == null) 31 | { 32 | throw new ArgumentNullException(nameof(propertyExpression)); 33 | } 34 | 35 | return GetPropertyInfo(propertyExpression).Name; 36 | } 37 | 38 | /// 39 | /// Returns a instance for the property referred to by the specified 40 | /// property expression. 41 | /// 42 | /// 43 | /// Type containing the property. 44 | /// 45 | /// 46 | /// Type of the property. 47 | /// 48 | /// 49 | /// An representing a Func mapping an instance of type TSource to an instance 50 | /// of type TProperty. 51 | /// 52 | /// 53 | /// A instance for the property referred to by the specified 54 | /// property expression. 55 | /// 56 | public static PropertyInfo GetPropertyInfo(Expression> propertyExpression) 57 | { 58 | if (propertyExpression == null) 59 | { 60 | throw new ArgumentNullException(nameof(propertyExpression)); 61 | } 62 | 63 | var member = propertyExpression.Body as MemberExpression; 64 | if (member == null) 65 | { 66 | throw new ArgumentException($"Expression '{propertyExpression}' refers to a method, not a property."); 67 | } 68 | 69 | var propertyInfo = member.Member as PropertyInfo; 70 | if (propertyInfo == null) 71 | { 72 | throw new ArgumentException($"Expression '{propertyExpression}' refers to a field, not a property."); 73 | } 74 | 75 | return propertyInfo; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/DynamicDescriptors/StandardValuesStringConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | 6 | namespace DynamicDescriptors; 7 | 8 | /// 9 | /// A that provides a collection of standard values. 10 | /// 11 | public sealed class StandardValuesStringConverter : StringConverter 12 | { 13 | /// 14 | /// An empty StandardValuesCollection, to be returned when the values factory is not populated. 15 | /// 16 | private static readonly StandardValuesCollection EmptyStandardValuesCollection = new StandardValuesCollection(new string[] { }); 17 | 18 | /// 19 | /// A factory that supplies the standard values to be supported. 20 | /// 21 | private readonly Func _valuesFactory; 22 | 23 | /// 24 | /// Initializes a new instance of the class. 25 | /// 26 | /// 27 | /// An enumerator that iterates through the sequence of standard values to be supported. 28 | /// 29 | public StandardValuesStringConverter(IEnumerable values) 30 | { 31 | _valuesFactory = values != null 32 | ? () => values.ToArray() 33 | : null; 34 | } 35 | 36 | /// 37 | /// Initializes a new instance of the class. 38 | /// 39 | /// A function that supplies the standard values to be supported. 40 | public StandardValuesStringConverter(Func valuesFactory) 41 | { 42 | _valuesFactory = valuesFactory; 43 | } 44 | 45 | /// 46 | /// Returns a value indicating whether this object supports a standard set of values that 47 | /// can be picked from a list, using the specified context. 48 | /// 49 | /// 50 | /// An that provides a format context. 51 | /// 52 | /// 53 | /// true if should be called to 54 | /// find a common set of values the object supports; otherwise, false. 55 | /// 56 | public override bool GetStandardValuesSupported(ITypeDescriptorContext context) 57 | { 58 | return true; 59 | } 60 | 61 | /// 62 | /// Returns whether the collection of standard values returned from 63 | /// is an exclusive list of 64 | /// possible values, using the specified context. 65 | /// 66 | /// 67 | /// An that provides a format context. 68 | /// 69 | /// 70 | /// true if the returned from 71 | /// is an exhaustive list of 72 | /// possible values; false if other values are possible. 73 | /// 74 | public override bool GetStandardValuesExclusive(ITypeDescriptorContext context) 75 | { 76 | return true; 77 | } 78 | 79 | /// 80 | /// Returns a collection of standard string values. 81 | /// 82 | /// 83 | /// An that provides a format context that 84 | /// can be used to extract additional information about the environment from which this 85 | /// converter is invoked. This parameter or properties of this parameter can be null. 86 | /// 87 | /// 88 | /// A that holds a standard set of valid values, 89 | /// or null if the data type does not support a standard set of values. 90 | /// 91 | public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) 92 | { 93 | return _valuesFactory != null 94 | ? new StandardValuesCollection(_valuesFactory.Invoke()) 95 | : EmptyStandardValuesCollection; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /test/DynamicDescriptors.Tests/DictionaryPropertyDescriptorTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using FluentAssertions; 4 | using Xunit; 5 | 6 | namespace DynamicDescriptors.Tests; 7 | 8 | public sealed class DictionaryPropertyDescriptorTests 9 | { 10 | [Fact] 11 | public void Constructor_DataIsNull_ThrowsArgumentNullException() 12 | { 13 | IDictionary data = null; 14 | string propertyName = "propertyName"; 15 | Type propertyType = typeof(object); 16 | 17 | var action = () => new DictionaryPropertyDescriptor(data, propertyName, propertyType); 18 | action.Should().Throw(); 19 | } 20 | 21 | [Fact] 22 | public void Constructor_PropertyNameIsNull_ThrowsArgumentNullException() 23 | { 24 | IDictionary data = new Dictionary(); 25 | string propertyName = null; 26 | Type propertyType = typeof(object); 27 | 28 | var action = () => new DictionaryPropertyDescriptor(data, propertyName, propertyType); 29 | action.Should().Throw(); 30 | } 31 | 32 | [Fact] 33 | public void Constructor_PropertyNameIsEmpty_ThrowsArgumentException() 34 | { 35 | IDictionary data = new Dictionary(); 36 | string propertyName = String.Empty; 37 | Type propertyType = typeof(object); 38 | 39 | var action = () => new DictionaryPropertyDescriptor(data, propertyName, propertyType); 40 | action.Should().Throw(); 41 | } 42 | 43 | [Fact] 44 | public void Constructor_PropertyTypeIsNull_ThrowsArgumentNullException() 45 | { 46 | IDictionary data = new Dictionary(); 47 | string propertyName = "propertyName"; 48 | Type propertyType = null; 49 | 50 | var action = () => new DictionaryPropertyDescriptor(data, propertyName, propertyType); 51 | action.Should().Throw(); 52 | } 53 | 54 | [Fact] 55 | public void GetValue_ReturnsValueFromUnderlyingDictionary() 56 | { 57 | var data = new Dictionary(); 58 | data.Add("Property1", "Value1"); 59 | data.Add("Property2", 2); 60 | 61 | var pd1 = new DictionaryPropertyDescriptor(data, "Property1", typeof(string)); 62 | pd1.GetValue(null).Should().Be("Value1"); 63 | 64 | var pd2 = new DictionaryPropertyDescriptor(data, "Property2", typeof(int)); 65 | pd2.GetValue(null).Should().Be(2); 66 | } 67 | 68 | [Fact] 69 | public void SetValue_ChangesValueInUnderlyingDictionaryToSpecifiedValue() 70 | { 71 | var data = new Dictionary(); 72 | data.Add("Property1", "Value1"); 73 | data.Add("Property2", 2); 74 | 75 | var pd1 = new DictionaryPropertyDescriptor(data, "Property1", typeof(string)); 76 | pd1.SetValue(null, "Modified"); 77 | data["Property1"].Should().Be("Modified"); 78 | 79 | var pd2 = new DictionaryPropertyDescriptor(data, "Property2", typeof(int)); 80 | pd2.SetValue(null, 0); 81 | data["Property2"].Should().Be(0); 82 | } 83 | 84 | [Fact] 85 | public void ResetValue_ChangesValueInUnderlyingDictionaryToNull() 86 | { 87 | var data = new Dictionary(); 88 | data.Add("Property1", "Value1"); 89 | data.Add("Property2", 2); 90 | 91 | var pd1 = new DictionaryPropertyDescriptor(data, "Property1", typeof(string)); 92 | pd1.ResetValue(null); 93 | data["Property1"].Should().BeNull(); 94 | 95 | var pd2 = new DictionaryPropertyDescriptor(data, "Property2", typeof(int)); 96 | pd2.ResetValue(null); 97 | data["Property2"].Should().BeNull(); 98 | } 99 | 100 | [Fact] 101 | public void PropertyType_ReturnsSpecifiedType() 102 | { 103 | var data = new Dictionary(); 104 | data.Add("Property1", "Value1"); 105 | data.Add("Property2", 2); 106 | 107 | var pd1 = new DictionaryPropertyDescriptor(data, "Property1", typeof(string)); 108 | pd1.PropertyType.Should().Be(typeof(string)); 109 | 110 | var pd2 = new DictionaryPropertyDescriptor(data, "Property2", typeof(int)); 111 | pd2.PropertyType.Should().Be(typeof(int)); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /test/DynamicDescriptors.Tests/DictionaryTypeDescriptorTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using FluentAssertions; 4 | using Xunit; 5 | 6 | namespace DynamicDescriptors.Tests; 7 | 8 | public sealed class DictionaryTypeDescriptorTests 9 | { 10 | [Fact] 11 | public void Constructor_DataDictionaryIsNull_ThrowsArgumentNullException() 12 | { 13 | var action1 = () => new DictionaryTypeDescriptor(null); 14 | action1.Should().Throw(); 15 | 16 | var action2 = () => new DictionaryTypeDescriptor(null, null); 17 | action2.Should().Throw(); 18 | } 19 | 20 | [Fact] 21 | public void GetPropertyOwner_ReturnsTypeDescriptor() 22 | { 23 | var data = new Dictionary(); 24 | var typeDescriptor = new DictionaryTypeDescriptor(data); 25 | var propertyDescriptor = new MockPropertyDescriptor(); 26 | 27 | typeDescriptor.GetPropertyOwner(propertyDescriptor).Should().Be(typeDescriptor); 28 | } 29 | 30 | [Fact] 31 | public void GetProperties_ReturnsPropertiesFromDataDictionary() 32 | { 33 | var data = new Dictionary(); 34 | data["Property1"] = "value1"; 35 | data["Property2"] = 2; 36 | 37 | var typeDescriptor = new DictionaryTypeDescriptor(data); 38 | 39 | var properties = typeDescriptor.GetProperties(); 40 | 41 | properties[0].Name.Should().Be("Property1"); 42 | properties[1].Name.Should().Be("Property2"); 43 | } 44 | 45 | [Fact] 46 | public void GetProperties_TypeIncluded_UsesSpecifiedType() 47 | { 48 | var data = new Dictionary(); 49 | data["Property1"] = "value1"; 50 | data["Property2"] = 2; 51 | 52 | var types = new Dictionary(); 53 | types["Property1"] = typeof(string); 54 | types["Property2"] = typeof(int); 55 | 56 | var typeDescriptor = new DictionaryTypeDescriptor(data, types); 57 | 58 | var properties = typeDescriptor.GetProperties(); 59 | 60 | properties[0].Name.Should().Be("Property1"); 61 | properties[0].PropertyType.Should().Be(typeof(string)); 62 | properties[1].Name.Should().Be("Property2"); 63 | properties[1].PropertyType.Should().Be(typeof(int)); 64 | } 65 | 66 | [Fact] 67 | public void GetProperties_TypeOmitted_UsesInferredType() 68 | { 69 | var data = new Dictionary(); 70 | data["Property1"] = "value1"; 71 | data["Property2"] = 2; 72 | data["Property3"] = null; 73 | 74 | var typeDescriptor = new DictionaryTypeDescriptor(data); 75 | 76 | var properties = typeDescriptor.GetProperties(); 77 | 78 | properties[0].Name.Should().Be("Property1"); 79 | properties[0].PropertyType.Should().Be(typeof(string)); 80 | properties[1].Name.Should().Be("Property2"); 81 | properties[1].PropertyType.Should().Be(typeof(int)); 82 | properties[2].Name.Should().Be("Property3"); 83 | properties[2].PropertyType.Should().Be(typeof(object)); 84 | } 85 | 86 | [Fact] 87 | public void PropertySet_RaisesPropertyChangedEvent() 88 | { 89 | var data = new Dictionary(); 90 | data["Property1"] = "value1"; 91 | data["Property2"] = "value2"; 92 | 93 | string propertyChanged = null; 94 | 95 | var descriptor = new DictionaryTypeDescriptor(data); 96 | descriptor.PropertyChanged += (s, e) => 97 | { 98 | propertyChanged = e.PropertyName; 99 | }; 100 | 101 | var properties = descriptor.GetProperties(); 102 | properties[0].SetValue(descriptor, "modified"); 103 | 104 | data["Property1"].Should().Be("modified"); 105 | propertyChanged.Should().Be("Property1"); 106 | } 107 | 108 | [Fact] 109 | public void PropertyReset_RaisesPropertyChangedEvent() 110 | { 111 | var data = new Dictionary(); 112 | data["Property1"] = "value1"; 113 | data["Property2"] = "value2"; 114 | 115 | string propertyChanged = null; 116 | 117 | var descriptor = new DictionaryTypeDescriptor(data); 118 | descriptor.PropertyChanged += (s, e) => 119 | { 120 | propertyChanged = e.PropertyName; 121 | }; 122 | 123 | var properties = descriptor.GetProperties(); 124 | properties[0].ResetValue(descriptor); 125 | 126 | data["Property1"].Should().Be(null); 127 | propertyChanged.Should().Be("Property1"); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /test/DynamicDescriptors.Tests/DynamicDescriptorTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using FluentAssertions; 4 | using Xunit; 5 | 6 | namespace DynamicDescriptors.Tests; 7 | 8 | public sealed class DynamicDescriptorTests 9 | { 10 | [Fact] 11 | public void CreateFromInstance_InstanceIsNull_ThrowsArgumentNullException() 12 | { 13 | var action = () => DynamicDescriptor.CreateFromInstance(null); 14 | action.Should().Throw(); 15 | } 16 | 17 | [Fact] 18 | public void CreateFromInstance_InstanceIsNotNull_ReturnsNewDynamicTypeDescriptorInstance() 19 | { 20 | var instance = new object(); 21 | var descriptor = DynamicDescriptor.CreateFromInstance(instance); 22 | 23 | descriptor.Should().NotBeNull(); 24 | } 25 | 26 | [Fact] 27 | public void CreateFromDescriptor_DescriptorIsNull_ThrowsArgumentNullException() 28 | { 29 | var action = () => DynamicDescriptor.CreateFromDescriptor(null); 30 | action.Should().Throw(); 31 | } 32 | 33 | [Fact] 34 | public void CreateFromDescriptor_DescriptorIsNotNull_ReturnsNewDynamicTypeDescriptorInstance() 35 | { 36 | var baseDescriptor = new MockCustomTypeDescriptor(); 37 | var descriptor = DynamicDescriptor.CreateFromDescriptor(baseDescriptor); 38 | 39 | descriptor.Should().NotBeNull(); 40 | } 41 | 42 | [Fact] 43 | public void CreateFromDictionary_DataDictionaryIsNull_ThrowsArgumentNullException() 44 | { 45 | var action1 = () => DynamicDescriptor.CreateFromDictionary(null); 46 | action1.Should().Throw(); 47 | 48 | var action2 = () => DynamicDescriptor.CreateFromDictionary(null, null); 49 | action2.Should().Throw(); 50 | } 51 | 52 | [Fact] 53 | public void CreateFromDictionary_DataDictionaryIsNotNull_TypeDictionaryNotPresent_ReturnsNewDynamicTypeDescriptorInstance() 54 | { 55 | var data = new Dictionary(); 56 | var descriptor = DynamicDescriptor.CreateFromDictionary(data); 57 | 58 | descriptor.Should().NotBeNull(); 59 | } 60 | 61 | [Fact] 62 | public void CreateFromDictionary_DataDictionaryIsNotNull_TypeDictionaryPresent_ReturnsNewDynamicTypeDescriptorInstance() 63 | { 64 | var data = new Dictionary(); 65 | var types = new Dictionary(); 66 | var descriptor = DynamicDescriptor.CreateFromDictionary(data, types); 67 | 68 | descriptor.Should().NotBeNull(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /test/DynamicDescriptors.Tests/DynamicDescriptors.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net7.0 5 | latest 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /test/DynamicDescriptors.Tests/DynamicPropertyDescriptorComparerTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Xunit; 3 | 4 | namespace DynamicDescriptors.Tests; 5 | 6 | public sealed class DynamicPropertyDescriptorComparerTests 7 | { 8 | [Fact] 9 | public void Compare_XIsNullAndYIsNot_YComesFirst() 10 | { 11 | var comparer = new DynamicPropertyDescriptorComparer(); 12 | var x = default(DynamicPropertyDescriptor); 13 | var y = new DynamicPropertyDescriptor(new MockPropertyDescriptor("Y")); 14 | var result = comparer.Compare(x, y); 15 | result.Should().BeGreaterThan(0); 16 | } 17 | 18 | [Fact] 19 | public void Compare_YIsNullAndXIsNot_XComesFirst() 20 | { 21 | var comparer = new DynamicPropertyDescriptorComparer(); 22 | var x = new DynamicPropertyDescriptor(new MockPropertyDescriptor("X")); 23 | var y = default(DynamicPropertyDescriptor); 24 | var result = comparer.Compare(x, y); 25 | result.Should().BeLessThan(0); 26 | } 27 | 28 | [Fact] 29 | public void Compare_BothXAndYAreNull_ValuesAreEqual() 30 | { 31 | var comparer = new DynamicPropertyDescriptorComparer(); 32 | var x = default(DynamicPropertyDescriptor); 33 | var y = default(DynamicPropertyDescriptor); 34 | var result = comparer.Compare(x, y); 35 | result.Should().Be(0); 36 | } 37 | 38 | [Fact] 39 | public void Compare_XHasAPropertyOrderButYDoesNot_XComesFirst() 40 | { 41 | var comparer = new DynamicPropertyDescriptorComparer(); 42 | var x = new DynamicPropertyDescriptor(new MockPropertyDescriptor("X")).SetPropertyOrder(1); 43 | var y = new DynamicPropertyDescriptor(new MockPropertyDescriptor("Y")).SetPropertyOrder(null); 44 | var result = comparer.Compare(x, y); 45 | result.Should().BeLessThan(0); 46 | } 47 | 48 | [Fact] 49 | public void Compare_YHasAPropertyOrderButXDoesNot_YComesFirst() 50 | { 51 | var comparer = new DynamicPropertyDescriptorComparer(); 52 | var x = new DynamicPropertyDescriptor(new MockPropertyDescriptor("X")).SetPropertyOrder(null); 53 | var y = new DynamicPropertyDescriptor(new MockPropertyDescriptor("Y")).SetPropertyOrder(1); 54 | var result = comparer.Compare(x, y); 55 | result.Should().BeGreaterThan(0); 56 | } 57 | 58 | [Fact] 59 | public void Compare_BothHaveAPropertyOrderButXComesFirst_XComesFirst() 60 | { 61 | var comparer = new DynamicPropertyDescriptorComparer(); 62 | var x = new DynamicPropertyDescriptor(new MockPropertyDescriptor("X")).SetPropertyOrder(0); 63 | var y = new DynamicPropertyDescriptor(new MockPropertyDescriptor("Y")).SetPropertyOrder(1); 64 | var result = comparer.Compare(x, y); 65 | result.Should().BeLessThan(0); 66 | } 67 | 68 | [Fact] 69 | public void Compare_BothHaveAPropertyOrderButYComesFirst_YComesFirst() 70 | { 71 | var comparer = new DynamicPropertyDescriptorComparer(); 72 | var x = new DynamicPropertyDescriptor(new MockPropertyDescriptor("X")).SetPropertyOrder(1); 73 | var y = new DynamicPropertyDescriptor(new MockPropertyDescriptor("Y")).SetPropertyOrder(0); 74 | var result = comparer.Compare(x, y); 75 | result.Should().BeGreaterThan(0); 76 | } 77 | 78 | [Fact] 79 | public void Compare_BothHaveEqualPropertyOrderButXComesFirstAlphabetically_XComesFirst() 80 | { 81 | var comparer = new DynamicPropertyDescriptorComparer(); 82 | var x = new DynamicPropertyDescriptor(new MockPropertyDescriptor("0")).SetPropertyOrder(0); 83 | var y = new DynamicPropertyDescriptor(new MockPropertyDescriptor("1")).SetPropertyOrder(0); 84 | var result = comparer.Compare(x, y); 85 | result.Should().BeLessThan(0); 86 | } 87 | 88 | [Fact] 89 | public void Compare_BothHaveEqualPropertyOrderButYComesFirstAlphabetically_YComesFirst() 90 | { 91 | var comparer = new DynamicPropertyDescriptorComparer(); 92 | var x = new DynamicPropertyDescriptor(new MockPropertyDescriptor("1")).SetPropertyOrder(0); 93 | var y = new DynamicPropertyDescriptor(new MockPropertyDescriptor("0")).SetPropertyOrder(0); 94 | var result = comparer.Compare(x, y); 95 | result.Should().BeGreaterThan(0); 96 | } 97 | 98 | [Fact] 99 | public void Compare_NeitherValueHasAPropertyOrderButXComesFirstAlphabetically_XComesFirst() 100 | { 101 | var comparer = new DynamicPropertyDescriptorComparer(); 102 | var x = new DynamicPropertyDescriptor(new MockPropertyDescriptor("0")); 103 | var y = new DynamicPropertyDescriptor(new MockPropertyDescriptor("1")); 104 | var result = comparer.Compare(x, y); 105 | result.Should().BeLessThan(0); 106 | } 107 | 108 | [Fact] 109 | public void Compare_NeitherValueHasAPropertyOrderButYComesFirstAlphabetically_YComesFirst() 110 | { 111 | var comparer = new DynamicPropertyDescriptorComparer(); 112 | var x = new DynamicPropertyDescriptor(new MockPropertyDescriptor("1")); 113 | var y = new DynamicPropertyDescriptor(new MockPropertyDescriptor("0")); 114 | var result = comparer.Compare(x, y); 115 | result.Should().BeGreaterThan(0); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /test/DynamicDescriptors.Tests/DynamicPropertyDescriptorTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using FluentAssertions; 4 | using Xunit; 5 | 6 | namespace DynamicDescriptors.Tests; 7 | 8 | public sealed class DynamicPropertyDescriptorTests 9 | { 10 | [Fact] 11 | public void Constructor_DescriptorIsNull_ThrowsArgumentNullException() 12 | { 13 | var action = () => new DynamicPropertyDescriptor(null); 14 | action.Should().Throw(); 15 | } 16 | 17 | [Fact] 18 | public void CanResetValue_ReturnsResultOfDescriptorCanResetValue() 19 | { 20 | var mockDescriptor = new MockPropertyDescriptor(); 21 | var dynamicDescriptor = new DynamicPropertyDescriptor(mockDescriptor); 22 | 23 | var component = new object(); 24 | 25 | mockDescriptor.CanResetValueResult = true; 26 | dynamicDescriptor.CanResetValue(component).Should().BeTrue(); 27 | mockDescriptor.CanResetValueComponent.Should().Be(component); 28 | 29 | mockDescriptor.CanResetValueResult = false; 30 | dynamicDescriptor.CanResetValue(component).Should().BeFalse(); 31 | mockDescriptor.CanResetValueComponent.Should().Be(component); 32 | } 33 | 34 | [Fact] 35 | public void ComponentType_ReturnsResultOfDescriptorComponentType() 36 | { 37 | var mockDescriptor = new MockPropertyDescriptor(); 38 | var dynamicDescriptor = new DynamicPropertyDescriptor(mockDescriptor); 39 | 40 | var type = typeof(string); 41 | mockDescriptor.ComponentTypeResult = type; 42 | dynamicDescriptor.ComponentType.Should().Be(type); 43 | } 44 | 45 | [Fact] 46 | public void GetValue_ReturnsResultOfDescriptorGetValue() 47 | { 48 | var mockDescriptor = new MockPropertyDescriptor(); 49 | var dynamicDescriptor = new DynamicPropertyDescriptor(mockDescriptor); 50 | 51 | var component = new object(); 52 | var result = new object(); 53 | 54 | mockDescriptor.GetValueResult = result; 55 | dynamicDescriptor.GetValue(component).Should().Be(result); 56 | mockDescriptor.GetValueComponent.Should().Be(component); 57 | } 58 | 59 | [Fact] 60 | public void PropertyType_ReturnsResultOfDescriptorPropertyType() 61 | { 62 | var mockDescriptor = new MockPropertyDescriptor(); 63 | var dynamicDescriptor = new DynamicPropertyDescriptor(mockDescriptor); 64 | 65 | var type = typeof(string); 66 | mockDescriptor.PropertyTypeResult = type; 67 | dynamicDescriptor.PropertyType.Should().Be(type); 68 | } 69 | 70 | [Fact] 71 | public void ResetValue_CallsDescriptorResetValue() 72 | { 73 | var mockDescriptor = new MockPropertyDescriptor(); 74 | var dynamicDescriptor = new DynamicPropertyDescriptor(mockDescriptor); 75 | 76 | var component = new object(); 77 | dynamicDescriptor.ResetValue(component); 78 | mockDescriptor.ResetValueComponent.Should().Be(component); 79 | mockDescriptor.ResetValueCalled.Should().BeTrue(); 80 | } 81 | 82 | [Fact] 83 | public void SetValue_CallsDescriptorSetValue() 84 | { 85 | var mockDescriptor = new MockPropertyDescriptor(); 86 | var dynamicDescriptor = new DynamicPropertyDescriptor(mockDescriptor); 87 | 88 | var component = new object(); 89 | var value = new object(); 90 | dynamicDescriptor.SetValue(component, value); 91 | 92 | mockDescriptor.SetValueComponent.Should().Be(component); 93 | mockDescriptor.SetValueValue.Should().Be(value); 94 | mockDescriptor.SetValueCalled.Should().BeTrue(); 95 | } 96 | 97 | [Fact] 98 | public void ShouldSerializeValue_ReturnsResultOfDescriptorShouldSerializeValue() 99 | { 100 | var mockDescriptor = new MockPropertyDescriptor(); 101 | var dynamicDescriptor = new DynamicPropertyDescriptor(mockDescriptor); 102 | 103 | var component = new object(); 104 | 105 | mockDescriptor.ShouldSerializeValueResult = true; 106 | dynamicDescriptor.ShouldSerializeValue(component).Should().BeTrue(); 107 | mockDescriptor.ShouldSerializeValueComponent.Should().Be(component); 108 | 109 | mockDescriptor.ShouldSerializeValueResult = false; 110 | dynamicDescriptor.ShouldSerializeValue(component).Should().BeFalse(); 111 | mockDescriptor.ShouldSerializeValueComponent.Should().Be(component); 112 | } 113 | 114 | [Fact] 115 | public void Category_NoOverride_ReturnsDescriptorValue() 116 | { 117 | var mockDescriptor = new MockPropertyDescriptor(); 118 | mockDescriptor.CategoryResult = "Base"; 119 | 120 | var dynamicDescriptor = new DynamicPropertyDescriptor(mockDescriptor); 121 | 122 | dynamicDescriptor.Category.Should().Be("Base"); 123 | } 124 | 125 | [Fact] 126 | public void Category_Override_ReturnsOverrideValue() 127 | { 128 | var mockDescriptor = new MockPropertyDescriptor(); 129 | mockDescriptor.CategoryResult = "Base"; 130 | 131 | var dynamicDescriptor = new DynamicPropertyDescriptor(mockDescriptor); 132 | dynamicDescriptor.SetCategory("Override"); 133 | 134 | dynamicDescriptor.Category.Should().Be("Override"); 135 | } 136 | 137 | [Fact] 138 | public void Converter_NoOverride_ReturnsDescriptorValue() 139 | { 140 | var converter = new TypeConverter(); 141 | 142 | var mockDescriptor = new MockPropertyDescriptor(); 143 | mockDescriptor.ConverterResult = converter; 144 | 145 | var dynamicDescriptor = new DynamicPropertyDescriptor(mockDescriptor); 146 | 147 | dynamicDescriptor.Converter.Should().Be(converter); 148 | } 149 | 150 | [Fact] 151 | public void Converter_Override_ReturnsOverrideValue() 152 | { 153 | var converterBase = new TypeConverter(); 154 | var converterOverride = new TypeConverter(); 155 | 156 | var mockDescriptor = new MockPropertyDescriptor(); 157 | mockDescriptor.ConverterResult = converterBase; 158 | 159 | var dynamicDescriptor = new DynamicPropertyDescriptor(mockDescriptor); 160 | dynamicDescriptor.SetConverter(converterOverride); 161 | 162 | dynamicDescriptor.Converter.Should().Be(converterOverride); 163 | } 164 | 165 | [Fact] 166 | public void Description_NoOverride_ReturnsDescriptorValue() 167 | { 168 | var mockDescriptor = new MockPropertyDescriptor(); 169 | mockDescriptor.DescriptionResult = "Base"; 170 | 171 | var dynamicDescriptor = new DynamicPropertyDescriptor(mockDescriptor); 172 | 173 | dynamicDescriptor.Description.Should().Be("Base"); 174 | } 175 | 176 | [Fact] 177 | public void Description_Override_ReturnsOverrideValue() 178 | { 179 | var mockDescriptor = new MockPropertyDescriptor(); 180 | mockDescriptor.DescriptionResult = "Base"; 181 | 182 | var dynamicDescriptor = new DynamicPropertyDescriptor(mockDescriptor); 183 | dynamicDescriptor.SetDescription("Override"); 184 | 185 | dynamicDescriptor.Description.Should().Be("Override"); 186 | } 187 | 188 | [Fact] 189 | public void DisplayName_NoOverride_ReturnsDescriptorValue() 190 | { 191 | var mockDescriptor = new MockPropertyDescriptor(); 192 | mockDescriptor.DisplayNameResult = "Base"; 193 | 194 | var dynamicDescriptor = new DynamicPropertyDescriptor(mockDescriptor); 195 | 196 | dynamicDescriptor.DisplayName.Should().Be("Base"); 197 | } 198 | 199 | [Fact] 200 | public void DisplayName_Override_ReturnsOverrideValue() 201 | { 202 | var mockDescriptor = new MockPropertyDescriptor(); 203 | mockDescriptor.DisplayNameResult = "Base"; 204 | 205 | var dynamicDescriptor = new DynamicPropertyDescriptor(mockDescriptor); 206 | dynamicDescriptor.SetDisplayName("Override"); 207 | 208 | dynamicDescriptor.DisplayName.Should().Be("Override"); 209 | } 210 | 211 | [Theory] 212 | [InlineData(true)] 213 | [InlineData(false)] 214 | public void IsReadOnly_NoOverride_ReturnsDescriptorValue(bool value) 215 | { 216 | var mockDescriptor = new MockPropertyDescriptor(); 217 | mockDescriptor.IsReadOnlyResult = value; 218 | 219 | var dynamicDescriptor = new DynamicPropertyDescriptor(mockDescriptor); 220 | 221 | dynamicDescriptor.IsReadOnly.Should().Be(value); 222 | } 223 | 224 | [Theory] 225 | [InlineData(true, true)] 226 | [InlineData(true, false)] 227 | [InlineData(false, true)] 228 | [InlineData(false, false)] 229 | public void IsReadOnly_Override_ReturnsOverrideValue(bool descriptorValue, bool overrideValue) 230 | { 231 | var mockDescriptor = new MockPropertyDescriptor(); 232 | mockDescriptor.IsReadOnlyResult = descriptorValue; 233 | 234 | var dynamicDescriptor = new DynamicPropertyDescriptor(mockDescriptor); 235 | dynamicDescriptor.SetReadOnly(overrideValue); 236 | 237 | dynamicDescriptor.IsReadOnly.Should().Be(overrideValue); 238 | } 239 | 240 | [Fact] 241 | public void GetEditor_NoOverride_ReturnsDescriptorValue() 242 | { 243 | var baseEditor = new MockUITypeEditor(); 244 | 245 | var mockDescriptor = new MockPropertyDescriptor(); 246 | mockDescriptor.GetEditorResult = baseEditor; 247 | 248 | var dynamicDescriptor = new DynamicPropertyDescriptor(mockDescriptor); 249 | 250 | dynamicDescriptor.GetEditor(typeof(MockUITypeEditor)).Should().Be(baseEditor); 251 | } 252 | 253 | [Fact] 254 | public void GetEditor_Override_ReturnsOverrideValue() 255 | { 256 | var baseEditor = new MockUITypeEditor(); 257 | var overrideEditor = new MockUITypeEditor(); 258 | 259 | var mockDescriptor = new MockPropertyDescriptor(); 260 | mockDescriptor.GetEditorResult = baseEditor; 261 | 262 | var dynamicDescriptor = new DynamicPropertyDescriptor(mockDescriptor); 263 | dynamicDescriptor.SetEditor(typeof(MockUITypeEditor), overrideEditor); 264 | 265 | dynamicDescriptor.GetEditor(typeof(MockUITypeEditor)).Should().Be(overrideEditor); 266 | } 267 | 268 | [Fact] 269 | public void GetEditor_OverrideThenClear_ReturnsDescriptorValue() 270 | { 271 | var baseEditor = new MockUITypeEditor(); 272 | var overrideEditor = new MockUITypeEditor(); 273 | 274 | var mockDescriptor = new MockPropertyDescriptor(); 275 | mockDescriptor.GetEditorResult = baseEditor; 276 | 277 | var dynamicDescriptor = new DynamicPropertyDescriptor(mockDescriptor); 278 | dynamicDescriptor.SetEditor(typeof(MockUITypeEditor), overrideEditor); 279 | dynamicDescriptor.SetEditor(typeof(MockUITypeEditor), null); 280 | 281 | dynamicDescriptor.GetEditor(typeof(MockUITypeEditor)).Should().Be(baseEditor); 282 | } 283 | 284 | [Fact] 285 | public void GetEditor_MultipleOverrides_ReturnsMostRecentOverrideValue() 286 | { 287 | var baseEditor = new MockUITypeEditor(); 288 | var overrideEditor1 = new MockUITypeEditor(); 289 | var overrideEditor2 = new MockUITypeEditor(); 290 | 291 | var mockDescriptor = new MockPropertyDescriptor(); 292 | mockDescriptor.GetEditorResult = baseEditor; 293 | 294 | var dynamicDescriptor = new DynamicPropertyDescriptor(mockDescriptor); 295 | dynamicDescriptor.SetEditor(typeof(MockUITypeEditor), overrideEditor1); 296 | dynamicDescriptor.SetEditor(typeof(MockUITypeEditor), overrideEditor2); 297 | 298 | dynamicDescriptor.GetEditor(typeof(MockUITypeEditor)).Should().Be(overrideEditor2); 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /test/DynamicDescriptors.Tests/DynamicTypeDescriptorTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Linq; 4 | using FluentAssertions; 5 | using Xunit; 6 | 7 | namespace DynamicDescriptors.Tests; 8 | 9 | public sealed class DynamicTypeDescriptorTests 10 | { 11 | [AttributeUsage(AttributeTargets.Property)] 12 | private sealed class AttributeOne : Attribute { } 13 | 14 | [AttributeUsage(AttributeTargets.Property)] 15 | private sealed class AttributeTwo : Attribute { } 16 | 17 | private sealed class ExampleType 18 | { 19 | [AttributeOne] 20 | public string Property1 { get; set; } 21 | 22 | [AttributeTwo] 23 | public string Property2 { get; set; } 24 | 25 | [AttributeOne, AttributeTwo] 26 | public string Property3 { get; set; } 27 | 28 | public bool Property4 { get; set; } 29 | 30 | public string field; 31 | 32 | public string Method() { return null; } 33 | } 34 | 35 | [Fact] 36 | public void Constructor_ParentIsNull_ThrowsArgumentNullException() 37 | { 38 | var action = () => new DynamicTypeDescriptor(null); 39 | action.Should().Throw(); 40 | } 41 | 42 | [Fact] 43 | public void GetProperties_ReturnsNormalProperties() 44 | { 45 | var instance = new ExampleType(); 46 | var descriptor = DynamicDescriptor.CreateFromInstance(instance); 47 | var properties = descriptor.GetProperties(); 48 | 49 | bool containsProperty1 = false, containsProperty2 = false, containsProperty3 = false, containsProperty4 = false; 50 | foreach (PropertyDescriptor property in properties) 51 | { 52 | if (property.Name == "Property1") containsProperty1 = true; 53 | if (property.Name == "Property2") containsProperty2 = true; 54 | if (property.Name == "Property3") containsProperty3 = true; 55 | if (property.Name == "Property4") containsProperty4 = true; 56 | } 57 | 58 | containsProperty1.Should().BeTrue(); 59 | containsProperty2.Should().BeTrue(); 60 | containsProperty3.Should().BeTrue(); 61 | containsProperty4.Should().BeTrue(); 62 | } 63 | 64 | [Fact] 65 | public void GetProperties_SingleAttribute_ReturnsPropertiesWithThatAttribute() 66 | { 67 | var instance = new ExampleType(); 68 | var descriptor = DynamicDescriptor.CreateFromInstance(instance); 69 | var properties = descriptor.GetProperties(new Attribute[] { new AttributeOne() }); 70 | 71 | bool containsProperty1 = false, containsProperty2 = false, containsProperty3 = false, containsProperty4 = false; 72 | foreach (PropertyDescriptor property in properties) 73 | { 74 | if (property.Name == "Property1") containsProperty1 = true; 75 | if (property.Name == "Property2") containsProperty2 = true; 76 | if (property.Name == "Property3") containsProperty3 = true; 77 | if (property.Name == "Property4") containsProperty4 = true; 78 | } 79 | 80 | containsProperty1.Should().BeTrue(); 81 | containsProperty2.Should().BeFalse(); 82 | containsProperty3.Should().BeTrue(); 83 | containsProperty4.Should().BeFalse(); 84 | } 85 | 86 | [Fact] 87 | public void GetProperties_MultipleAttributes_ReturnsPropertiesWithAllOfThoseAttributes() 88 | { 89 | var instance = new ExampleType(); 90 | var descriptor = DynamicDescriptor.CreateFromInstance(instance); 91 | var properties = descriptor.GetProperties(new Attribute[] { new AttributeOne(), new AttributeTwo() }); 92 | 93 | bool containsProperty1 = false, containsProperty2 = false, containsProperty3 = false, containsProperty4 = false; 94 | foreach (PropertyDescriptor property in properties) 95 | { 96 | if (property.Name == "Property1") containsProperty1 = true; 97 | if (property.Name == "Property2") containsProperty2 = true; 98 | if (property.Name == "Property3") containsProperty3 = true; 99 | if (property.Name == "Property4") containsProperty4 = true; 100 | } 101 | 102 | containsProperty1.Should().BeFalse(); 103 | containsProperty2.Should().BeFalse(); 104 | containsProperty3.Should().BeTrue(); 105 | containsProperty4.Should().BeFalse(); 106 | } 107 | 108 | [Fact] 109 | public void GetProperties_SomePropertiesAreNotActive_ReturnsOnlyActiveProperties() 110 | { 111 | var instance = new ExampleType(); 112 | var descriptor = DynamicDescriptor.CreateFromInstance(instance); 113 | 114 | descriptor.GetDynamicProperty("Property1").SetActive(true); 115 | descriptor.GetDynamicProperty("Property2").SetActive(false); 116 | descriptor.GetDynamicProperty("Property3").SetActive(true); 117 | descriptor.GetDynamicProperty("Property4").SetActive(false); 118 | 119 | var properties = descriptor.GetProperties(); 120 | 121 | properties.Cast().Should().HaveCount(2); 122 | properties[0].Name.Should().Be("Property1"); 123 | properties[1].Name.Should().Be("Property3"); 124 | } 125 | 126 | [Fact] 127 | public void GetProperties_NullOrEmptyAttributeArray_ReturnsSameValueAsGetPropertiesWithoutAttributesSpecified() 128 | { 129 | var instance = new ExampleType(); 130 | var descriptor = DynamicDescriptor.CreateFromInstance(instance); 131 | 132 | descriptor.GetProperties(null).Should().BeEquivalentTo(descriptor.GetProperties()); 133 | descriptor.GetProperties(Array.Empty()).Should().BeEquivalentTo(descriptor.GetProperties()); 134 | } 135 | 136 | [Fact] 137 | public void GetProperties_NoPropertyOrderSet_ReturnsPropertiesInAlphabeticalOrder() 138 | { 139 | var instance = new ExampleType(); 140 | var descriptor = DynamicDescriptor.CreateFromInstance(instance); 141 | 142 | var properties = descriptor.GetProperties(); 143 | 144 | properties[0].Name.Should().Be("Property1"); 145 | properties[1].Name.Should().Be("Property2"); 146 | properties[2].Name.Should().Be("Property3"); 147 | properties[3].Name.Should().Be("Property4"); 148 | } 149 | 150 | [Fact] 151 | public void GetProperties_PropertyOrderSet_ReturnsPropertiesInOrderSpecifiedByPropertyOrder() 152 | { 153 | var instance = new ExampleType(); 154 | var descriptor = DynamicDescriptor.CreateFromInstance(instance); 155 | 156 | descriptor.GetDynamicProperty("Property1").SetPropertyOrder(3); 157 | descriptor.GetDynamicProperty("Property2").SetPropertyOrder(2); 158 | descriptor.GetDynamicProperty("Property3").SetPropertyOrder(1); 159 | descriptor.GetDynamicProperty("Property4").SetPropertyOrder(0); 160 | 161 | var properties = descriptor.GetProperties(); 162 | 163 | properties[0].Name.Should().Be("Property4"); 164 | properties[1].Name.Should().Be("Property3"); 165 | properties[2].Name.Should().Be("Property2"); 166 | properties[3].Name.Should().Be("Property1"); 167 | } 168 | 169 | [Fact] 170 | public void GetProperties_PropertyOrderSet_MultiplePropertiesHaveTheSameOrder_ReturnThosePropertiesInAlphabeticalOrder() 171 | { 172 | var instance = new ExampleType(); 173 | var descriptor = DynamicDescriptor.CreateFromInstance(instance); 174 | 175 | descriptor.GetDynamicProperty("Property1").SetPropertyOrder(1); 176 | descriptor.GetDynamicProperty("Property2").SetPropertyOrder(1); 177 | descriptor.GetDynamicProperty("Property3").SetPropertyOrder(0); 178 | descriptor.GetDynamicProperty("Property4").SetPropertyOrder(0); 179 | 180 | var properties = descriptor.GetProperties(); 181 | 182 | properties[0].Name.Should().Be("Property3"); 183 | properties[1].Name.Should().Be("Property4"); 184 | properties[2].Name.Should().Be("Property1"); 185 | properties[3].Name.Should().Be("Property2"); 186 | } 187 | 188 | [Fact] 189 | public void GetProperties_SomePropertyOrdersSet_ReturnsPropertiesInOrderSpecifiedByPropertyOrderThenOtherPropertiesInAlphabeticalOrder() 190 | { 191 | var instance = new ExampleType(); 192 | var descriptor = DynamicDescriptor.CreateFromInstance(instance); 193 | 194 | descriptor.GetDynamicProperty("Property1").SetPropertyOrder(null); 195 | descriptor.GetDynamicProperty("Property2").SetPropertyOrder(null); 196 | descriptor.GetDynamicProperty("Property3").SetPropertyOrder(1); 197 | descriptor.GetDynamicProperty("Property4").SetPropertyOrder(0); 198 | 199 | var properties = descriptor.GetProperties(); 200 | 201 | properties[0].Name.Should().Be("Property4"); 202 | properties[1].Name.Should().Be("Property3"); 203 | properties[2].Name.Should().Be("Property1"); 204 | properties[3].Name.Should().Be("Property2"); 205 | } 206 | 207 | [Fact] 208 | public void GetDynamicProperty_PropertyNameDoesNotMatchAProperty_ReturnsNull() 209 | { 210 | var instance = new ExampleType(); 211 | var descriptor = DynamicDescriptor.CreateFromInstance(instance); 212 | 213 | descriptor.GetDynamicProperty(null).Should().BeNull(); 214 | descriptor.GetDynamicProperty("NotAValidPropertyName").Should().BeNull(); 215 | } 216 | 217 | [Fact] 218 | public void GetDynamicProperty_PropertyNameMatchesAProperty_ReturnsDynamicPropertyDescriptorForThatProperty() 219 | { 220 | var instance = new ExampleType(); 221 | var descriptor = DynamicDescriptor.CreateFromInstance(instance); 222 | 223 | var propertyDescriptor = descriptor.GetDynamicProperty("Property1"); 224 | 225 | propertyDescriptor.Should().NotBeNull(); 226 | propertyDescriptor.Name.Should().Be("Property1"); 227 | } 228 | 229 | [Fact] 230 | public void GetDynamicProperty_ExpressionRefersToSomethingOtherThanAProperty_ReturnsNull() 231 | { 232 | var instance = new ExampleType(); 233 | var descriptor = DynamicDescriptor.CreateFromInstance(instance); 234 | 235 | var onField = () => descriptor.GetDynamicProperty((ExampleType o) => o.field); 236 | onField.Should().Throw().WithMessage("Expression 'o => o.field' refers to a field, not a property."); 237 | 238 | var onMethod = () => descriptor.GetDynamicProperty((ExampleType o) => o.Method()); 239 | onMethod.Should().Throw().WithMessage("Expression 'o => o.Method()' refers to a method, not a property."); 240 | } 241 | 242 | [Fact] 243 | public void GetDynamicProperty_ExpressionRefersToAReferenceTypeProperty_ReturnsDynamicPropertyDescriptorForThatProperty() 244 | { 245 | var instance = new ExampleType(); 246 | var descriptor = DynamicDescriptor.CreateFromInstance(instance); 247 | 248 | var propertyDescriptor = descriptor.GetDynamicProperty((ExampleType o) => o.Property1); 249 | 250 | propertyDescriptor.Should().NotBeNull(); 251 | propertyDescriptor.Name.Should().Be("Property1"); 252 | } 253 | 254 | [Fact] 255 | public void GetDynamicProperty_ExpressionRefersToAValueTypeProperty_ReturnsDynamicPropertyDescriptorForThatProperty() 256 | { 257 | var instance = new ExampleType(); 258 | var descriptor = DynamicDescriptor.CreateFromInstance(instance); 259 | 260 | var propertyDescriptor = descriptor.GetDynamicProperty((ExampleType o) => o.Property4); 261 | 262 | propertyDescriptor.Should().NotBeNull(); 263 | propertyDescriptor.Name.Should().Be("Property4"); 264 | } 265 | 266 | [Fact] 267 | public void GetDynamicProperties_AllPropertiesActive_ReturnsSequenceContainingAllDynamicProperties() 268 | { 269 | var instance = new ExampleType(); 270 | var descriptor = DynamicDescriptor.CreateFromInstance(instance); 271 | 272 | var properties = descriptor.GetDynamicProperties().ToArray(); 273 | 274 | properties.Should().HaveCount(4); 275 | properties[0].Name.Should().Be("Property1"); 276 | properties[1].Name.Should().Be("Property2"); 277 | properties[2].Name.Should().Be("Property3"); 278 | properties[3].Name.Should().Be("Property4"); 279 | } 280 | 281 | [Fact] 282 | public void GetDynamicProperties_SomePropertiesInactive_ReturnsSequenceContainingAllDynamicProperties() 283 | { 284 | var instance = new ExampleType(); 285 | var descriptor = DynamicDescriptor.CreateFromInstance(instance); 286 | descriptor.GetDynamicProperty((ExampleType o) => o.Property1).SetActive(false); 287 | descriptor.GetDynamicProperty((ExampleType o) => o.Property4).SetActive(false); 288 | 289 | var properties = descriptor.GetDynamicProperties().ToArray(); 290 | 291 | properties.Should().HaveCount(4); 292 | properties[0].Name.Should().Be("Property1"); 293 | properties[1].Name.Should().Be("Property2"); 294 | properties[2].Name.Should().Be("Property3"); 295 | properties[3].Name.Should().Be("Property4"); 296 | } 297 | 298 | [Fact] 299 | public void PropertySet_RaisesPropertyChangedEvent() 300 | { 301 | string propertyChanged = null; 302 | 303 | var instance = new ExampleType(); 304 | var descriptor = DynamicDescriptor.CreateFromInstance(instance); 305 | descriptor.PropertyChanged += (s, e) => 306 | { 307 | propertyChanged = e.PropertyName; 308 | }; 309 | 310 | var property = descriptor.GetDynamicProperty(nameof(instance.Property1)); 311 | property.SetValue(descriptor, "modified"); 312 | 313 | instance.Property1.Should().Be("modified"); 314 | propertyChanged.Should().Be("Property1"); 315 | } 316 | 317 | [Fact] 318 | public void PropertyReset_RaisesPropertyChangedEvent() 319 | { 320 | string propertyChanged = null; 321 | 322 | var instance = new ExampleType(); 323 | var descriptor = DynamicDescriptor.CreateFromInstance(instance); 324 | descriptor.PropertyChanged += (s, e) => 325 | { 326 | propertyChanged = e.PropertyName; 327 | }; 328 | 329 | var property = descriptor.GetDynamicProperty(nameof(instance.Property1)); 330 | property.ResetValue(descriptor); 331 | 332 | instance.Property1.Should().BeNull(); 333 | propertyChanged.Should().Be("Property1"); 334 | } 335 | } 336 | -------------------------------------------------------------------------------- /test/DynamicDescriptors.Tests/MockCustomTypeDescriptor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | 4 | namespace DynamicDescriptors.Tests; 5 | 6 | internal sealed class MockCustomTypeDescriptor : ICustomTypeDescriptor 7 | { 8 | public AttributeCollection GetAttributes() 9 | { 10 | return new AttributeCollection(); 11 | } 12 | 13 | public string GetClassName() 14 | { 15 | return null; 16 | } 17 | 18 | public string GetComponentName() 19 | { 20 | return null; 21 | } 22 | 23 | public TypeConverter GetConverter() 24 | { 25 | return null; 26 | } 27 | 28 | public EventDescriptor GetDefaultEvent() 29 | { 30 | return null; 31 | } 32 | 33 | public PropertyDescriptor GetDefaultProperty() 34 | { 35 | return null; 36 | } 37 | 38 | public object GetEditor(Type editorBaseType) 39 | { 40 | return null; 41 | } 42 | 43 | public EventDescriptorCollection GetEvents(Attribute[] attributes) 44 | { 45 | return new EventDescriptorCollection(new EventDescriptor[] { }); 46 | } 47 | 48 | public EventDescriptorCollection GetEvents() 49 | { 50 | return GetEvents(null); 51 | } 52 | 53 | public PropertyDescriptorCollection GetProperties(Attribute[] attributes) 54 | { 55 | return new PropertyDescriptorCollection(new PropertyDescriptor[] { }); 56 | } 57 | 58 | public PropertyDescriptorCollection GetProperties() 59 | { 60 | return GetProperties(null); 61 | } 62 | 63 | public object GetPropertyOwner(PropertyDescriptor pd) 64 | { 65 | return null; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /test/DynamicDescriptors.Tests/MockPropertyDescriptor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | 4 | namespace DynamicDescriptors.Tests; 5 | 6 | internal sealed class MockPropertyDescriptor : PropertyDescriptor 7 | { 8 | public MockPropertyDescriptor() 9 | : this("MockProperty") { } 10 | 11 | public MockPropertyDescriptor(string name) 12 | : base(name, new Attribute[] { }) { } 13 | 14 | public Type ComponentTypeResult { get; set; } 15 | public override Type ComponentType { get { return this.ComponentTypeResult; } } 16 | 17 | public bool IsReadOnlyResult { get; set; } 18 | public override bool IsReadOnly { get { return this.IsReadOnlyResult; } } 19 | 20 | public Type PropertyTypeResult { get; set; } 21 | public override Type PropertyType { get { return this.PropertyTypeResult; } } 22 | 23 | public object GetEditorResult { get; set; } 24 | public object GetEditorEditorBaseType { get; private set; } 25 | public bool GetEditorCalled { get; private set; } 26 | public override object GetEditor(Type editorBaseType) 27 | { 28 | GetEditorCalled = true; 29 | GetEditorEditorBaseType = editorBaseType; 30 | return this.GetEditorResult; 31 | } 32 | 33 | public object GetValueResult { get; set; } 34 | public object GetValueComponent { get; private set; } 35 | public bool GetValueCalled { get; private set; } 36 | public override object GetValue(object component) 37 | { 38 | GetValueCalled = true; 39 | GetValueComponent = component; 40 | return this.GetValueResult; 41 | } 42 | 43 | public object SetValueComponent { get; private set; } 44 | public object SetValueValue { get; private set; } 45 | public bool SetValueCalled { get; private set; } 46 | public override void SetValue(object component, object value) 47 | { 48 | SetValueCalled = true; 49 | SetValueComponent = component; 50 | SetValueValue = value; 51 | } 52 | 53 | public object ResetValueComponent { get; private set; } 54 | public bool ResetValueCalled { get; private set; } 55 | public override void ResetValue(object component) 56 | { 57 | ResetValueCalled = true; 58 | ResetValueComponent = component; 59 | } 60 | 61 | public bool CanResetValueResult { get; set; } 62 | public object CanResetValueComponent { get; private set; } 63 | public bool CanResetValueCalled { get; private set; } 64 | public override bool CanResetValue(object component) 65 | { 66 | CanResetValueCalled = true; 67 | CanResetValueComponent = component; 68 | return CanResetValueResult; 69 | } 70 | 71 | public bool ShouldSerializeValueResult { get; set; } 72 | public object ShouldSerializeValueComponent { get; private set; } 73 | public bool ShouldSerializeValueCalled { get; private set; } 74 | public override bool ShouldSerializeValue(object component) 75 | { 76 | ShouldSerializeValueCalled = true; 77 | ShouldSerializeValueComponent = component; 78 | return ShouldSerializeValueResult; 79 | } 80 | 81 | public string CategoryResult { get; set; } 82 | public override string Category { get { return CategoryResult; } } 83 | 84 | public TypeConverter ConverterResult { get; set; } 85 | public override TypeConverter Converter { get { return ConverterResult; } } 86 | 87 | public string DescriptionResult { get; set; } 88 | public override string Description { get { return DescriptionResult; } } 89 | 90 | public string DisplayNameResult { get; set; } 91 | public override string DisplayName { get { return DisplayNameResult; } } 92 | } 93 | -------------------------------------------------------------------------------- /test/DynamicDescriptors.Tests/MockUITypeEditor.cs: -------------------------------------------------------------------------------- 1 | namespace DynamicDescriptors.Tests; 2 | 3 | internal sealed class MockUITypeEditor { } 4 | -------------------------------------------------------------------------------- /test/DynamicDescriptors.Tests/ReflectTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using FluentAssertions; 4 | using Xunit; 5 | 6 | namespace DynamicDescriptors.Tests; 7 | 8 | public sealed class ReflectTests 9 | { 10 | private sealed class ExampleClass 11 | { 12 | public string field; 13 | public string Method() { return null; } 14 | public string Property { get; set; } 15 | } 16 | 17 | [Fact] 18 | public void GetPropertyName_ExpressionRefersToAMethod_ThrowsArgumentException() 19 | { 20 | const string message = "Expression 'o => o.Method()' refers to a method, not a property."; 21 | var action = () => Reflect.GetPropertyName(o => o.Method()); 22 | action.Should().Throw().WithMessage(message); 23 | } 24 | 25 | [Fact] 26 | public void GetPropertyName_ExpressionRefersToAField_ThrowsArgumentException() 27 | { 28 | const string message = "Expression 'o => o.field' refers to a field, not a property."; 29 | var action = () => Reflect.GetPropertyName(o => o.field); 30 | action.Should().Throw().WithMessage(message); 31 | } 32 | 33 | [Fact] 34 | public void GetPropertyName_ReturnsCorrectValue() 35 | { 36 | var value = Reflect.GetPropertyName(o => o.Property); 37 | var expected = "Property"; 38 | 39 | value.Should().Be(expected); 40 | } 41 | 42 | [Fact] 43 | public void GetPropertyInfo_ExpressionRefersToAMethod_ThrowsArgumentException() 44 | { 45 | const string message = "Expression 'o => o.Method()' refers to a method, not a property."; 46 | var action = () => Reflect.GetPropertyInfo(o => o.Method()); 47 | action.Should().Throw().WithMessage(message); 48 | } 49 | 50 | [Fact] 51 | public void GetPropertyInfo_ExpressionRefersToAField_ThrowsArgumentException() 52 | { 53 | const string message = "Expression 'o => o.field' refers to a field, not a property."; 54 | var action = () => Reflect.GetPropertyInfo(o => o.field); 55 | action.Should().Throw().WithMessage(message); 56 | } 57 | 58 | [Fact] 59 | public void GetPropertyInfo_ReturnsCorrectValue() 60 | { 61 | var value = Reflect.GetPropertyInfo(o => o.Property); 62 | var expected = typeof(ExampleClass).GetTypeInfo().GetProperty("Property"); 63 | 64 | value.Should().BeSameAs(expected); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /test/DynamicDescriptors.Tests/StandardValuesStringConverterTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using FluentAssertions; 5 | using Xunit; 6 | 7 | namespace DynamicDescriptors.Tests; 8 | 9 | public sealed class StandardValuesStringConverterTests 10 | { 11 | [Fact] 12 | public void EnumerableConstructor_ValuesIsNull_DoesNotThrowException() 13 | { 14 | var action = () => new StandardValuesStringConverter(null as IEnumerable); 15 | action.Should().NotThrow(); 16 | } 17 | 18 | [Fact] 19 | public void FuncConstructor_ValuesIsNull_DoesNotThrowException() 20 | { 21 | var action = () => new StandardValuesStringConverter(null as Func); 22 | action.Should().NotThrow(); 23 | } 24 | 25 | [Fact] 26 | public void GetStandardValues_NoValuesFactoryProvided_ReturnsEmptyCollection() 27 | { 28 | var converter1 = new StandardValuesStringConverter(null as Func); 29 | converter1.GetStandardValues().Cast().Should().BeEmpty(); 30 | 31 | var converter2 = new StandardValuesStringConverter(null as IEnumerable); 32 | converter2.GetStandardValues().Cast().Should().BeEmpty(); 33 | } 34 | 35 | [Fact] 36 | public void GetStandardValues_UsesDeferredExecution() 37 | { 38 | var divisor = 1; 39 | var values = Enumerable.Range(1, 10).Where(i => i % divisor == 0).Select(i => i.ToString()); 40 | var converter = new StandardValuesStringConverter(values); 41 | 42 | divisor = 2; 43 | converter.GetStandardValues().Cast().Should().ContainInOrder(new string[] { "2", "4", "6", "8", "10" }); 44 | 45 | divisor = 3; 46 | converter.GetStandardValues().Cast().Should().ContainInOrder(new string[] { "3", "6", "9" }); 47 | } 48 | } 49 | --------------------------------------------------------------------------------