< Summary - PropertyGridHelpers Code Coverage

Information
Class: PropertyGridHelpers.Controls.FlagCheckedListBox
Assembly: PropertyGridHelpers
File(s): c:\agent\_work\9\s\Code\PropertyGridHelpers\Controls\FlagCheckedListBox.cs
Tag: PropertyGridHelpers Build_2025.7.15.1_#485
Line coverage
100%
Covered lines: 100
Uncovered lines: 0
Coverable lines: 100
Total lines: 391
Line coverage: 100%
Branch coverage
100%
Covered branches: 34
Total branches: 34
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
FlagCheckedListBox()60----
.ctor()--100%11100%
Dispose(...)20----
Dispose(...)--100%11100%
InitializeComponent()20100%11100%
Add(...)50----
Add(...)--100%11100%
Add(...)40----
Clear()30100%11100%
Add(...)40100%11100%
Add(...)30----
OnItemCheck(...)111100%44100%
OnItemCheck(...)101----
UpdateCheckedItems(...)210----
UpdateCheckedItems(...)--100%66100%
UpdateCheckedItems(...)190----
UpdateCheckedItems(...)270----
UpdateCheckedItems(...)--100%1212100%
UpdateCheckedItems(...)230----
GetCurrentValue()130100%44100%
GetCurrentValue()120----
FillEnumMembers()190100%44100%
FillEnumMembers()190----
ApplyEnumValue()50100%11100%
ApplyEnumValue()50----
get_EnumValue()--100%11100%
set_EnumValue(...)--100%22100%
get_Converter()--100%11100%
get_Value()--100%11100%
set_Value(...)--100%22100%
get_Context()--100%11100%
set_Context(...)--100%11100%
get_Culture()--100%11100%
set_Culture(...)--100%11100%

File(s)

c:\agent\_work\9\s\Code\PropertyGridHelpers\Controls\FlagCheckedListBox.cs

#LineLine coverage
 1using PropertyGridHelpers.Support;
 2using PropertyGridHelpers.UIEditors;
 3using System;
 4using System.ComponentModel;
 5using System.Globalization;
 6using System.Windows.Forms;
 7using System.Drawing.Design;
 8
 9namespace PropertyGridHelpers.Controls
 10{
 11    /// <summary>
 12    /// A custom <see cref="CheckedListBox"/> control designed for editing
 13    /// properties backed by [Flags] enumerations. It presents each enum value
 14    /// as a checkbox, allowing users to select multiple values that combine
 15    /// into a composite flag.
 16    ///
 17    /// This control is typically hosted in a drop-down editor (e.g.,
 18    /// <see cref="UITypeEditor"/>) using <see cref="DropDownVisualizer{TControl}"/>
 19    /// to support property editing within a <see cref="PropertyGrid"/>.
 20    /// </summary>
 21    /// <remarks>
 22    /// When the associated enum is set via the <see cref="EnumValue"/> property,
 23    /// the control automatically populates with all defined enum values and
 24    /// checks those corresponding to the current composite value.
 25    /// As checkboxes are toggled, the internal representation is updated accordingly.
 26    ///
 27    /// This control assumes that the enum is decorated with the
 28    /// <see cref="FlagsAttribute"/>; an exception will be thrown otherwise.
 29    ///
 30    /// Optional display customization is supported via a custom
 31    /// <see cref="EnumConverter"/> assigned to
 32    /// <see cref="Converter"/>.
 33    /// </remarks>
 34    /// <example>
 35    /// Typical usage:
 36    /// <code>
 37    /// [Editor(typeof(FlagEnumUIEditor), typeof(UITypeEditor))]
 38    /// public MyFlagsEnum FlagsProperty { get; set; }
 39    /// </code>
 40    /// </example>
 41    /// <seealso cref="CheckedListBox"/>
 42    /// <seealso cref="IDropDownEditorControl"/>
 43    /// <seealso cref="DropDownVisualizer{TControl}"/>
 44    /// <seealso cref="FlagEnumUIEditor"/>
 45    /// <seealso cref="FlagEnumUIEditor{T}"/>
 46    public partial class FlagCheckedListBox
 47        : CheckedListBox, IDropDownEditorControl
 48    {
 49        /// <summary>
 50        /// Initializes a new instance of the <see cref="FlagCheckedListBox" /> class.
 51        /// </summary>
 12852        public FlagCheckedListBox() =>
 12853            InitializeComponent();
 54
 55        /// <summary>
 56        /// Occurs when the user has finished editing the selection
 57        /// and commits the current value.
 58        /// </summary>
 12859        public event EventHandler ValueCommitted = delegate { };
 60
 61        /// <summary>
 62        /// Releases unmanaged and - optionally - managed resources.
 63        /// </summary>
 64        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release
 12865        protected override void Dispose(bool disposing) => base.Dispose(disposing);
 66
 67        #region Component Designer generated code ^^^^^^^^^
 68
 69        /// <summary>
 70        /// Initializes the component.
 71        /// </summary>
 72        private void InitializeComponent() =>
 12873            CheckOnClick = true;
 74
 75        #endregion
 76
 77        /// <summary>
 78        /// Adds a new item to the checklist with the specified bitwise value and display caption.
 79        /// </summary>
 80        /// <param name="value">The bitwise integer value representing a flag.</param>
 81        /// <param name="caption">The display text for the item.</param>
 82        /// <returns>
 83        /// A <see cref="FlagCheckedListBoxItem"/> representing the added item.
 84        /// </returns>
 85        public FlagCheckedListBoxItem Add(int value, string caption)
 18886        {
 19687            var item = new FlagCheckedListBoxItem(value, caption);
 19688            _ = Items.Add(item);
 19689            return item;
 18890        }
 91
 92        /// <summary>
 93        /// Removes all items from the checklist and resets its state.
 94        /// </summary>
 95        public void Clear() =>
 2096            Items.Clear();
 97
 98        /// <summary>
 99        /// Adds an existing <see cref="FlagCheckedListBoxItem"/> to the checklist.
 100        /// </summary>
 101        /// <param name="item">The item to add.</param>
 102        /// <returns>
 103        /// The added <see cref="FlagCheckedListBoxItem"/>.
 104        /// </returns>
 105        public FlagCheckedListBoxItem Add(FlagCheckedListBoxItem item)
 44106        {
 52107            _ = Items.Add(item);
 48108            return item;
 40109        }
 110
 111        /// <summary>
 112        /// Raises the <see cref="CheckedListBox.ItemCheck" /> event.
 113        /// </summary>
 114        /// <param name="ice">The <see cref="ItemCheckEventArgs" /> instance containing the event data.</param>
 115        protected override void OnItemCheck(ItemCheckEventArgs ice)
 104116        {
 112117            base.OnItemCheck(ice);
 118
 112119            if (ice == null) return;
 120
 112121            if (isUpdatingCheckStates)
 92122                return;
 123
 124            // Get the checked/unchecked item
 36125            var item = Items[ice.Index] as FlagCheckedListBoxItem;
 126            // Update other items
 36127            UpdateCheckedItems(item, ice.NewValue);
 112128        }
 129
 130        /// <summary>
 131        /// Updates the check state of all items based on the provided bitwise value.
 132        /// </summary>
 133        /// <param name="value">
 134        /// A bitwise integer representing the currently active flags.
 135        /// </param>
 136        protected internal void UpdateCheckedItems(int value)
 84137        {
 92138            isUpdatingCheckStates = true;
 139
 140            // Iterate over all items
 584141            for (var i = 0; i < Items.Count; i++)
 216142            {
 224143                var item = Items[i] as FlagCheckedListBoxItem;
 144
 224145                switch (item.Value)
 146                {
 147                    case 0:
 48148                        SetItemChecked(i, value == 0);
 48149                        break;
 150                    default:
 151                        // If the bit for the current item is on in the bit value, check it
 192152                        SetItemChecked(i, (item.Value & value) == item.Value && item.Value != 0);
 184153                        break;
 154                }
 216155            }
 156
 92157            isUpdatingCheckStates = false;
 92158        }
 159
 160        /// <summary>
 161        /// Updates the check states of items in response to a change in one item,
 162        /// maintaining the consistency of the composite flag value.
 163        /// </summary>
 164        /// <param name="composite">The item that changed.</param>
 165        /// <param name="cs">The new check state of that item.</param>
 166        protected internal void UpdateCheckedItems(
 167            FlagCheckedListBoxItem composite,
 168            CheckState cs)
 44169        {
 170            // If the value of the item is 0, call directly.
 52171            if (composite?.Value == 0)
 20172                UpdateCheckedItems(0);
 173
 174            // Get the total value of all checked items
 52175            var sum = 0;
 200176            for (var i = 0; i < Items.Count; i++)
 64177            {
 72178                var item = Items[i] as FlagCheckedListBoxItem;
 179
 180                // If item is checked, add its value to the sum.
 72181                if (GetItemChecked(i))
 36182                    sum |= item.Value;
 64183            }
 184
 185            // Check if composite is null before accessing its Value
 52186            if (composite != null)
 40187            {
 188                // If the item has been unchecked, remove its bits from the sum
 48189                if (cs == CheckState.Unchecked)
 20190                    sum &= ~composite.Value;
 191                // If the item has been checked, combine its bits with the sum
 192                else
 44193                    sum |= composite.Value;
 40194            }
 195
 196            // Update all items in the CheckListBox based on the final bit value
 52197            UpdateCheckedItems(sum);
 52198        }
 199
 200        private bool isUpdatingCheckStates;
 201
 202        /// <summary>
 203        /// Gets the composite integer value representing all currently checked flags.
 204        /// </summary>
 205        /// <returns>
 206        /// An <see cref="int"/> value encoding the combined flags of checked items.
 207        /// </returns>
 208        public int GetCurrentValue()
 64209        {
 72210            var sum = 0;
 211
 480212            for (var i = 0; i < Items.Count; i++)
 184213            {
 192214                var item = Items[i] as FlagCheckedListBoxItem;
 215
 192216                if (GetItemChecked(i))
 80217                    sum |= item.Value;
 184218            }
 219
 72220            return sum;
 64221        }
 222
 223        /// <summary>
 224        /// The type of the enum currently bound to this control.
 225        /// </summary>
 226        private Type enumType;
 227        /// <summary>
 228        /// The current enum value represented by the checked items.
 229        /// </summary>
 230        private Enum enumValue;
 231
 232        /// <summary>
 233        /// Populates the checklist with entries corresponding to each member of the bound enum type.
 234        /// </summary>
 235        private void FillEnumMembers()
 44236        {
 420237            foreach (var name in Enum.GetNames(enumType))
 156238            {
 164239                var val = Enum.Parse(enumType, name);
 164240                var caption = Converter == null ? name : (string)Converter.ConvertTo(Context, Culture, val, typeof(strin
 164241                var intVal = (int)Convert.ChangeType(val, typeof(int), CultureInfo.CurrentCulture);
 242
 164243                _ = Add(intVal, caption);
 156244            }
 52245        }
 246
 247        /// <summary>
 248        /// Synchronizes the check state of items in the list to reflect the current <see cref="EnumValue"/>.
 249        /// </summary>
 250        /// <remarks>
 251        /// This ensures the visual checklist matches the current bitwise value.
 252        /// </remarks>
 253        private void ApplyEnumValue()
 44254        {
 52255            var intVal = (int)Convert.ChangeType(enumValue, typeof(int), CultureInfo.CurrentCulture);
 52256            UpdateCheckedItems(intVal);
 52257        }
 258
 259        /// <summary>
 260        /// Gets or sets the current enum value represented by the checked items
 261        /// in the list. This property serves as the main interface for binding
 262        /// a flags-based enum to the control.
 263        /// </summary>
 264        /// <remarks>
 265        /// <para>
 266        /// When setting this property, the control performs the following:
 267        /// </para>
 268        /// <list type="bullet">
 269        ///   <item><description>Verifies the provided value is a valid enum marked with the <see cref="FlagsAttribute"/
 270        ///   <item><description>Clears any existing items in the list.</description></item>
 271        ///   <item><description>Populates the list with all members of the enum type.</description></item>
 272        ///   <item><description>Automatically checks the items corresponding to the current flags set in the value.</de
 273        /// </list>
 274        /// <para>
 275        /// When getting this property, it returns the composite enum value
 276        /// corresponding to the checked items.
 277        /// </para>
 278        /// <para>
 279        /// You must set this property to initialize the control's content.
 280        /// Attempting to use the control without setting a valid enum value
 281        /// will result in incorrect or empty state.
 282        /// </para>
 283        /// </remarks>
 284        /// <exception cref="ArgumentNullException">
 285        /// Thrown when the provided value is <c>null</c>.
 286        /// </exception>
 287        /// <exception cref="ArgumentException">
 288        /// Thrown if the value is not an enum or is not decorated with the <see cref="FlagsAttribute"/>.
 289        /// </exception>
 290        /// <example>
 291        /// <code>
 292        /// var listBox = new FlagCheckedListBox();
 293        /// listBox.EnumValue = MyFlagsEnum.OptionA | MyFlagsEnum.OptionC;
 294        /// var selected = listBox.EnumValue; // Returns combined enum value
 295        /// </code>
 296        /// </example>
 297        /// <value>
 298        /// A <see cref="Enum"/> value representing the checked state of the control.
 299        /// </value>
 300        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
 301        public Enum EnumValue
 302        {
 303            get
 40304            {
 48305                var e = Enum.ToObject(enumType, GetCurrentValue());
 48306                return (Enum)e;
 40307            }
 308            set
 52309            {
 310#if NET5_0_OR_GREATER
 44311                ArgumentNullException.ThrowIfNull(value);
 312#else
 16313                if (value == null)
 16314                    throw new ArgumentNullException(nameof(value));
 315#endif
 56316                enumType = value.GetType();
 317
 56318                if (!Attribute.IsDefined(enumType, typeof(FlagsAttribute)))
 20319                    throw new ArgumentException($"Enum type '{enumType.Name}' must be an enum and have the [Flags] attri
 52320                Items.Clear();
 52321                enumValue = value;              // Store the current enum value
 52322                FillEnumMembers();              // Add items for enum members
 52323                ApplyEnumValue();               // Check/uncheck items depending on enum value
 52324            }
 325        }
 326
 327        /// <summary>
 328        /// Gets or sets the <see cref="EnumConverter"/> used to control how the enum
 329        /// values are displayed in the list.
 330        /// </summary>
 331        /// <remarks>
 332        /// This property allows you to provide a custom converter that defines
 333        /// how enum values are rendered in the UI—for example, displaying localized
 334        /// or user-friendly names instead of the raw enum identifiers.
 335        ///
 336        /// If no converter is set, the enum field names are used as-is.
 337        /// This is especially useful when the enum values are annotated with custom
 338        /// attributes (e.g., <c>EnumTextAttribute</c>) and you want those descriptions
 339        /// to be shown in the UI.
 340        ///
 341        /// This property should be assigned before setting <see cref="EnumValue"/>,
 342        /// as it affects how the list is populated.
 343        /// </remarks>
 344        /// <value>
 345        /// A custom <see cref="EnumConverter"/> instance for customizing enum display.
 346        /// </value>
 347        /// <example>
 348        /// <code>
 349        /// var listBox = new FlagCheckedListBox();
 350        /// listBox.Converter = new EnumTextConverter(typeof(MyFlagsEnum));
 351        /// listBox.EnumValue = MyFlagsEnum.OptionA | MyFlagsEnum.OptionB;
 352        /// </code>
 353        /// </example>
 354        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
 355        public EnumConverter Converter
 356        {
 284357            get; set;
 358        }
 359
 360        /// <summary>
 361        /// Gets or sets the composite enum value represented by the checked items,
 362        /// in a non-generic interface-friendly way.
 363        /// </summary>
 364        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
 365        public object Value
 366        {
 20367            get => EnumValue;
 24368            set => EnumValue = value is Enum e ? e : throw new ArgumentException("Value must be an enum");
 369        }
 370
 371        /// <summary>
 372        /// Gets or sets the type descriptor context in which the control is being used.
 373        /// </summary>
 374        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
 375        public ITypeDescriptorContext Context
 376        {
 104377            get;
 4378            set;
 379        }
 380
 381        /// <summary>
 382        /// Gets or sets the culture to use for localization or formatting.
 383        /// </summary>
 384        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
 385        public CultureInfo Culture
 386        {
 104387            get;
 4388            set;
 389        }
 390    }
 391}