< Summary - PropertyGridHelpers Code Coverage

Information
Class: PropertyGridHelpers.UIEditors.AutoCompleteComboBoxEditor
Assembly: PropertyGridHelpers
File(s): c:\agent\_work\9\s\Code\PropertyGridHelpers\UIEditors\AutoCompleteComboBoxEditor.cs
Tag: PropertyGridHelpers Build_2025.7.15.1_#485
Line coverage
100%
Covered lines: 78
Uncovered lines: 0
Coverable lines: 78
Total lines: 237
Line coverage: 100%
Branch coverage
100%
Covered branches: 48
Total branches: 48
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

MethodBlocks covered Blocks not covered Branch coverage Crap Score Cyclomatic complexity Line coverage
get_Converter()--100%11100%
EditValue(...)760----
EditValue(...)--100%1616100%
EditValue(...)690----
ResolveValues(...)180----
ResolveValues(...)--100%88100%
ResolveValues(...)170----
ValidateSetup(...)541----
ValidateSetup(...)--100%2424100%
ValidateSetup(...)451----

File(s)

c:\agent\_work\9\s\Code\PropertyGridHelpers\UIEditors\AutoCompleteComboBoxEditor.cs

#LineLine coverage
 1using PropertyGridHelpers.Attributes;
 2using PropertyGridHelpers.Controls;
 3using System;
 4using System.ComponentModel;
 5using System.Drawing.Design;
 6using System.Globalization;
 7using System.Linq;
 8using System.Reflection;
 9using System.Windows.Forms;
 10using System.Windows.Forms.Design;
 11
 12namespace PropertyGridHelpers.UIEditors
 13{
 14    /// <summary>
 15    /// Provides a <see cref="UITypeEditor"/> that shows an auto-complete-enabled drop-down list using an
 16    /// <see cref="AutoCompleteComboBox"/>, allowing quick selection of string values within a <see cref="PropertyGrid"/
 17    /// </summary>
 18    /// <remarks>
 19    /// This editor supports auto-complete behavior based on a configurable
 20    /// <see cref="AutoCompleteSource"/> and <see cref="AutoCompleteMode"/> as defined by the
 21    /// <see cref="AutoCompleteSetupAttribute"/>.
 22    ///
 23    /// For <see cref="AutoCompleteSource.CustomSource"/>, the list of suggestions can be provided:
 24    /// <list type="bullet">
 25    /// <item><description>Directly via string array</description></item>
 26    /// <item><description>From an enum's names</description></item>
 27    /// <item><description>From a public static <c>string[] Values</c> property on a class</description></item>
 28    /// </list>
 29    ///
 30    /// When configured with an enum type, the editor displays the enum's names, and returns the
 31    /// selected enum value rather than a string. Otherwise, the selected string is returned.
 32    /// </remarks>
 33    /// <example>
 34    /// Apply this editor to a string property like this:
 35    /// <code>
 36    /// [AutoCompleteSetup(AutoCompleteSource.FileSystem)]
 37    /// [Editor(typeof(AutoCompleteComboBoxEditor), typeof(UITypeEditor))]
 38    /// public string FilePath { get; set; }
 39    ///
 40    /// [AutoCompleteSetup(typeof(ConsoleColor))]
 41    /// [Editor(typeof(AutoCompleteComboBoxEditor), typeof(UITypeEditor))]
 42    /// public string FavoriteColor { get; set; }
 43    ///
 44    /// [AutoCompleteSetup("Red", "Green", "Blue")]
 45    /// [Editor(typeof(AutoCompleteComboBoxEditor), typeof(UITypeEditor))]
 46    /// public string CustomColor { get; set; }
 47    /// </code>
 48    /// </example>
 49    /// <seealso cref="DropDownVisualizer{T}"/>
 50    /// <seealso cref="AutoCompleteComboBox"/>
 51    /// <seealso cref="AutoCompleteSetupAttribute"/>
 52    public class AutoCompleteComboBoxEditor
 53        : DropDownVisualizer<AutoCompleteComboBox>
 54    {
 55        /// <summary>
 56        /// Gets or sets the <see cref="EnumConverter"/> used to convert between
 57        /// the displayed text in the combo box and the corresponding enum value.
 58        /// </summary>
 59        /// <remarks>
 60        /// This converter supports mapping from the display name to the actual enum constant,
 61        /// ensuring that the editor returns the correct enum value even when the display text differs.
 62        /// </remarks>
 63        public EnumConverter Converter
 64        {
 3265            get; set;
 66        }
 67
 68        /// <summary>
 69        /// Displays an <see cref="AutoCompleteComboBox"/> editor in a dropdown and returns the
 70        /// updated value after editing completes. Supports dynamic item lists and
 71        /// enum mapping when configured.
 72        /// </summary>
 73        /// <param name="context">Provides context information about the design-time environment.</param>
 74        /// <param name="provider">A service provider that can provide an <see cref="IWindowsFormsEditorService"/>.</par
 75        /// <param name="value">The current value of the property being edited.</param>
 76        /// <returns>
 77        /// The edited value as returned by the control. This is typically a string,
 78        /// but may be the corresponding enum value if the editor is configured with
 79        /// an enum type. If editing is canceled or fails, returns the original value.
 80        /// </returns>
 81        public override object EditValue(
 82            ITypeDescriptorContext context,
 83            IServiceProvider provider,
 84            object value)
 6485        {
 86            // Check for the attribute and apply the source before showing the dropdown
 7287            if (context?.PropertyDescriptor != null)
 6088            {
 6889                var sourceAttr = (AutoCompleteSetupAttribute)context.PropertyDescriptor.Attributes[typeof(AutoCompleteSe
 90
 6891                if (sourceAttr != null)
 5692                {
 6493                    ValidateSetup(sourceAttr, context);
 3294                    DropDownControl.AutoCompleteMode = sourceAttr.AutoCompleteMode;
 3295                    DropDownControl.AutoCompleteSource = sourceAttr.AutoCompleteSource;
 3296                    DropDownControl.DropDownStyle = sourceAttr.DropDownStyle;
 97
 3298                    if (sourceAttr.AutoCompleteSource == AutoCompleteSource.CustomSource)
 2499                    {
 32100                        DropDownControl.AutoCompleteCustomSource.Clear();
 32101                        DropDownControl.Items.Clear();
 102
 103                        object[] items;
 104
 32105                        var providerType = sourceAttr.ProviderType ?? context.PropertyDescriptor.PropertyType;
 106
 32107                        if (Converter != null && providerType.IsEnum)
 12108                        {
 109#if NET5_0_OR_GREATER
 4110                            items = [.. Enum.GetValues(providerType)
 4111                                .Cast<object>()
 12112                                .Select(e => new ItemWrapper<object>(
 12113                                    Converter.ConvertToString(context, CultureInfo.CurrentCulture, e), e))];
 114#else
 16115                            items = Enum.GetValues(providerType)
 16116                                .Cast<object>()
 16117                                .Select(e => new ItemWrapper<object>(
 16118                                    Converter.ConvertToString(context, CultureInfo.CurrentCulture, e), e))
 16119                                .ToArray();
 120#endif
 12121                        }
 122                        else
 28123                            items = ResolveValues(sourceAttr, context.PropertyDescriptor);
 124
 125#if NET5_0_OR_GREATER
 16126                        if (items[0] is ItemWrapper<object>)
 4127                            DropDownControl.AutoCompleteCustomSource.AddRange(
 12128                                [.. items.Cast<ItemWrapper<object>>().Select(i => i.DisplayText)]);
 129                        else
 12130                            DropDownControl.AutoCompleteCustomSource.AddRange(
 44131                                [.. items.Select(i => i.ToString())]);
 132#else
 16133                        if (items[0] is ItemWrapper<object>)
 16134                            DropDownControl.AutoCompleteCustomSource.AddRange(
 16135                                items.Cast<ItemWrapper<object>>().Select(i => i.DisplayText).ToArray());
 136                        else
 16137                            DropDownControl.AutoCompleteCustomSource.AddRange(
 16138                                items.Select(i => i.ToString()).ToArray());
 139#endif
 140
 32141                        DropDownControl.Items.AddRange(items);
 24142                    }
 24143                }
 28144            }
 145
 40146            return base.EditValue(context, provider, value);
 32147        }
 148
 149        /// <summary>
 150        /// Resolves the values.
 151        /// </summary>
 152        /// <param name="setup">The setup.</param>
 153        /// <param name="propDesc">The property desc.</param>
 154        /// <returns></returns>
 155        private static string[] ResolveValues(AutoCompleteSetupAttribute setup, PropertyDescriptor propDesc)
 20156        {
 157            // If the AutoCompleteSetupAttribute has Values set, use them directly
 28158            if (setup.Values?.Length > 0)
 20159                return setup.Values;
 160
 161            // If the ProviderType is set, try to get the static string[] Values property
 24162            var type = setup.ProviderType ?? propDesc.PropertyType;
 163
 24164            if (type.IsEnum)
 20165                return Enum.GetNames(type);
 166
 167            const string propName = "Values";
 20168            var prop = type.GetProperty(propName, BindingFlags.Public | BindingFlags.Static);
 169#if NET35
 170            return (string[])prop.GetValue(null, null);
 171#else
 20172            return (string[])prop.GetValue(null);
 173#endif
 20174        }
 175
 176        /// <summary>
 177        /// Validates the setup.
 178        /// </summary>
 179        /// <param name="setup">The setup.</param>
 180        /// <param name="context">The context.</param>
 181        /// <exception cref="ArgumentNullException">setup - AutoCompleteSetupAttribute must be assigned to the property 
 182        /// <exception cref="InvalidOperationException">
 183        /// At least one value must be specified when using AutoCompleteSourceMode.Values.
 184        /// or
 185        /// ProviderType could not be determined.
 186        /// or
 187        /// The enum '{providerType.Name}' does not define any members.
 188        /// or
 189        /// The type '{providerType.FullName}' must define a public static property named '{propName}'.
 190        /// or
 191        /// The '{propName}' property on '{providerType.FullName}' must be of type string[].
 192        /// or
 193        /// The '{propName}' property on '{providerType.FullName}' returned no items.
 194        /// </exception>
 195        private static void ValidateSetup(AutoCompleteSetupAttribute setup, ITypeDescriptorContext context)
 56196        {
 197            // If somehow setup is null, add the code in here to throw an exception.  Currently this routine
 198            // is called after a check to see if the setup is null, so this should never happen.
 64199            if (setup.AutoCompleteSource == AutoCompleteSource.CustomSource)
 64200                switch (setup.Mode)
 201                {
 202                    case AutoCompleteSetupAttribute.SourceMode.Values:
 28203                        if (setup.Values == null || setup.Values.Length == 0)
 24204                            throw new InvalidOperationException("At least one value must be specified when using AutoCom
 12205                        break;
 206
 207                    case AutoCompleteSetupAttribute.SourceMode.Provider:
 52208                        var providerType = setup.ProviderType ?? context.PropertyDescriptor.PropertyType;
 52209                        if (providerType.IsEnum)
 20210                        {
 28211                            var names = Enum.GetNames(providerType);
 28212                            if (names.Length == 0)
 20213                                throw new InvalidOperationException($"The enum '{providerType.Name}' does not define any
 16214                        }
 215                        else
 32216                        {
 217                            const string propName = "Values";
 40218                            var prop = providerType.GetProperty(propName, BindingFlags.Public | BindingFlags.Static)
 40219                                ?? throw new InvalidOperationException($"The type '{providerType.FullName}' must define 
 220
 36221                            if (prop.PropertyType != typeof(string[]))
 20222                                throw new InvalidOperationException($"The '{propName}' property on '{providerType.FullNa
 223
 224#if NET35
 225                            var values = (string[])prop.GetValue(null, null);
 226#else
 32227                            var values = (string[])prop.GetValue(null);
 228#endif
 32229                            if (values == null || values.Length == 0)
 28230                                throw new InvalidOperationException($"The '{propName}' property on '{providerType.FullNa
 12231                        }
 232
 20233                        break;
 234                }
 32235        }
 236    }
 237}