Overview

Namespace: TheTechIdea.Beep.Winform.Controls.RadioGroup

Assembly: TheTechIdea.Beep.Winform.Controls.dll

Inheritance: BeepRadioGroup → BaseControl → Control

Implements: IBeepComponent, IBeepUIComponent (via BaseControl)

Toolbox: Beep Controls category, display name "Beep Radio Group Advanced"

Source files: 5 partial class files (main 763 lines, Properties 583, EventHandlers 93, Input 116, Drawing 193), 11 renderers, 7 helpers, 3 model configs, 1 hierarchical variant (1155 lines)

BeepRadioGroup is a comprehensive, owner-draw radio button group control that supports single-selection (radio) and multi-selection (checkbox) modes with 11 distinct visual render styles. It uses a pluggable renderer architecture (IRadioGroupRenderer), a layout helper for vertical/horizontal/grid/flow arrangements, a hit-test helper for precise mouse/keyboard interaction, and a state helper for selection tracking. All rendering is done via GDI+ owner-draw — no child Windows controls are created. The control provides full WCAG 2.2 accessibility support via AccessibleObject overrides, per-item disabled states, validation (IsRequired, HasError, ErrorMessage), and data-binding through SetValue/GetValue.

A companion control, BeepHierarchicalRadioGroup (1155 lines), extends the concept with tree-structured items, expand/collapse buttons, hierarchy lines, and keyboard navigation (Right to expand, Left to collapse or focus parent).

Key Features

  • 11 render styles: Material, Card, Chip, Circular, Flat, Checkbox, Button, Toggle, Segmented, Pill, Tile
  • Single and multiple selection modes: Switch between radio-button (exclusive) and checkbox (multi-select) behavior via AllowMultipleSelection
  • 4 orientation layouts: Vertical, Horizontal, Grid (with configurable ColumnCount), Flow
  • 4 alignment modes: Left, Center, Right, Stretch
  • Pluggable renderers: Each render style implements IRadioGroupRenderer with RenderItem, MeasureItem, GetContentArea, GetSelectorArea, RenderGroupDecorations, Cleanup
  • Per-item states: Each item tracks IsSelected, IsHovered, IsFocused, IsPressed, IsEnabled, IsError, AnimationProgress, Index
  • Per-item disabled support: Disable/enable individual items by text value using DisableItem, EnableItem, SetItemsEnabled, EnableAllItems, IsItemDisabled
  • Image support: Items render images from ImagePath with configurable MaxImageSize (default 24x24)
  • Validation: IsRequired property, HasError state, ErrorMessage, Validate() method returning bool
  • Keyboard navigation: Arrow keys in all orientations, Tab/Shift+Tab for focus movement, Space/Enter for selection
  • WCAG 2.2 accessibility: Custom AccessibleObject (Grouping role) with item-level BeepRadioGroupItemAccessibleObject supporting RadioButton/CheckButton roles, Bounds, HitTest, DoDefaultAction, State, Value
  • Style/color profiles: RadioGroupStyleConfig and RadioGroupColorConfig for runtime customization with expandable property editor support
  • SubText support: Items can display secondary descriptive text below the main label
  • Data-binding: SetValue accepts string, List<string>, SimpleItem, List<SimpleItem>; GetValue returns selected item(s)
  • DPI-aware rendering: Full DPI scaling via OnDpiScaleChanged override with renderer re-initialization
  • Focus-aware rendering: Renderers can implement IFocusAwareRenderer for WCAG-compliant focus rings (min 2px, 3:1 contrast)
  • Design-time support: Placeholder rendering in Visual Studio designer showing style, item count, and control info
  • IBeepUIComponent integration: Fires ValueChanged via InvokeOnValueChanged on selection change with LinkedProperty support

Properties

PropertyTypeDefaultDescription
UseThemeColorsbooltrueUse theme colors instead of style-based colors. Propagates to all renderers.
StyleBeepControlStyleBeepControlStyle.Material3The visual control style/painter for rendering the radio group.
TextFontFontSegoe UI 9ptFont for item text. Setting this disables UseThemeFont.
IsChildboolfalseWhether this control is a child of another Beep control.
ItemsList<SimpleItem>empty listCollection of items. Supports CollectionEditor in designer. [DesignerSerializationVisibility.Content]
AllowMultipleSelectionboolfalseToggle between single-select (radio) and multi-select (checkbox) mode.
SelectedValuestringnullCurrently selected value in single selection mode. Setting updates visuals.
SelectedValuesList<string>empty listAll selected values in multiple selection mode. [Hidden in designer]
SelectedItemsList<SimpleItem>empty listThe selected item objects. [Browsable(false)]
SelectedCountint0Number of currently selected items. [Browsable(false)]
RadioGroupStyleRadioGroupRenderStyleMaterialThe visual render style for items. Changing this switches the active renderer.
OrientationRadioGroupOrientationVerticalLayout direction: Vertical, Horizontal, Grid, or Flow.
AlignmentRadioGroupAlignmentLeftItem alignment within the control: Left, Center, Right, Stretch.
ItemSpacingint8Spacing between items in pixels (minimum 0).
ItemPaddingPaddingPadding(8)Padding around each individual item.
ColumnCountint1Number of columns when Orientation = Grid. Must be > 0.
AutoSizeItemsbooltrueWhether items auto-size to their content. Enables/disables layout helper AutoSize.
MaxImageSizeSize(24, 24)Maximum image rendering size. Propagates to all IImageAwareRenderer renderers.
StyleProfileRadioGroupStyleConfignew instanceRuntime style profile: BorderRadius, ShowShadow, ShadowColor, ShadowOffset, ControlStyle, BorderWidth, RecommendedItemHeight/Spacing/Padding. [ExpandableObjectConverter]
ColorProfileRadioGroupColorConfignew instanceRuntime color profile when UseThemeColors=false. Contains GroupBackgroundColor, TextColor.
IsRequiredboolfalseWhether a selection is required. Triggers validation.
HasErrorboolfalseWhether the control is in an error state. [new keyword, Category: Validation]
ErrorMessagestring""The error message to display when validation fails.

Methods

MethodSignatureDescription
AddItemvoid AddItem(SimpleItem item)Adds an item and triggers full layout update.
AddItemvoid AddItem(string text, string imagePath = null, string subText = null)Convenience overload; creates a SimpleItem and calls AddItem.
RemoveItembool RemoveItem(SimpleItem item)Removes an item; returns true if found and removed.
ClearItemsvoid ClearItems()Clears all items and triggers layout update.
SelectItembool SelectItem(string value)Selects item by string value via state helper.
DeselectItembool DeselectItem(string value)Deselects item by string value via state helper.
ClearSelectionvoid ClearSelection()Clears all current selections.
SelectAllvoid SelectAll()Selects all items (only when AllowMultipleSelection is true).
RegisterRenderervoid RegisterRenderer(RadioGroupRenderStyle style, IRadioGroupRenderer renderer)Registers or replaces a custom renderer for the given style.
Validatebool Validate()Runs selection validation; returns true if valid, false if error.
DisableItemvoid DisableItem(string itemText)Disables a specific item by its text value (adds to HashSet).
EnableItemvoid EnableItem(string itemText)Re-enables a previously disabled item (removes from HashSet).
IsItemDisabledbool IsItemDisabled(string itemText)Checks whether a specific item is in the disabled set.
SetItemsEnabledvoid SetItemsEnabled(IEnumerable<string> items, bool enabled)Batch enables or disables multiple items at once.
EnableAllItemsvoid EnableAllItems()Clears the disabled items set and enables everything.
SetValueoverride void SetValue(object value)Accepts string, List<string>, SimpleItem, or List<SimpleItem>.
GetValueoverride object GetValue()Returns SelectedItems (multi) or first selected item (single).
Resetbool Reset()Clears selection; returns true if value changed.
ApplyThemeoverride void ApplyTheme()Applies font theme, updates all renderers with current theme token set, resets layout.

Events

EventArgsDescription
SelectionChangedSelectionChangedEventArgsFired when selection changes (add/remove). Updates states and accessibility.
ItemSelectionChangedItemSelectionChangedEventArgsFired when an individual item's selection state toggles. Triggers ValueChanged.
ItemClickedItemClickEventArgsFired when an item is clicked. In single mode, selects; in multi mode, toggles.
ItemDoubleClickedItemClickEventArgsFired when an item is double-clicked via HitTestHelper.
ItemHoverEnterItemHoverEventArgsFired when the mouse enters an item's bounds.
ItemHoverLeaveItemHoverEventArgsFired when the mouse leaves an item's bounds.

All event handlers are wired through helper classes (RadioGroupHitTestHelper for mouse/keyboard, RadioGroupStateHelper for selection). The ItemClicked handler respects per-item disabled state (skipping disabled items). Selection events propagate to IBeepUIComponent.ValueChanged through InvokeOnValueChanged on the base control. The ValidateSelection() method automatically sets HasError and ErrorMessage when IsRequired and no items are selected.

Theming

BeepRadioGroup applies theming through the ApplyTheme() override:

  1. Calls base.ApplyTheme() for base theme tokens (background, foreground, border)
  2. Uses RadioGroupFontHelpers.ApplyFontTheme(this, Style, _currentTheme) to resolve fonts from theme tokens based on the active Style (Material3, Flat, etc.)
  3. _isApplyingThemeFont guard prevents the TextFont setter from disabling UseThemeFont during theme application
  4. Propagates _currentTheme to the active renderer via _currentRenderer.UpdateTheme(_currentTheme)
  5. Updates all registered renderers in the _renderers dictionary by calling UpdateTheme on each
  6. Re-assigns the ItemMeasurer delegate to use the current renderer's MeasureItem
  7. Calls RequestVisualRefresh(resetLayout: true) for batched repaint

Font resolution chain: RadioGroupFontHelpers.ApplyFontTheme reads theme font tokens appropriate for the render style and BeepControlStyle. When UseThemeFont is true, the helper overrides the TextFont property from theme tokens. Setting TextFont manually disables UseThemeFont (unless _isApplyingThemeFont guard is active).

Color resolution: When UseThemeColors is true (default), the theme's SideMenuBackColor is used for the background during rendering. When false, colors from ColorProfile are applied (GroupBackgroundColor → BackColor, TextColor → ForeColor). Each renderer independently resolves its own colors from the theme based on the active ControlStyle.

DPI scaling: On OnDpiScaleChanged, all renderers receive UpdateTheme with the current theme, layout is recalculated, hit-test data is rebuilt, and a full visual refresh is requested.

Examples

Example 1: Basic Material-Style Radio Group

var radioGroup = new BeepRadioGroup
{
    Location = new Point(20, 20),
    Size = new Size(300, 200),
    RadioGroupStyle = RadioGroupRenderStyle.Material,
    Orientation = RadioGroupOrientation.Vertical,
    ItemSpacing = 8
};

radioGroup.AddItem("Option A");
radioGroup.AddItem("Option B", "icons/star.svg");
radioGroup.AddItem("Option C", null, "Recommended choice");

radioGroup.SelectionChanged += (s, e) =>
{
    Console.WriteLine($"Selected: {radioGroup.SelectedValue}");
};

this.Controls.Add(radioGroup);

Example 2: Multiple Selection with Checkbox Renderer

var multiGroup = new BeepRadioGroup
{
    AllowMultipleSelection = true,
    RadioGroupStyle = RadioGroupRenderStyle.Checkbox,
    Orientation = RadioGroupOrientation.Vertical,
    Size = new Size(300, 250)
};

multiGroup.AddItem("Feature A");
multiGroup.AddItem("Feature B");
multiGroup.AddItem("Feature C");
multiGroup.AddItem("Feature D");

// Pre-select multiple items
multiGroup.SetValue(new List<string> { "Feature A", "Feature C" });

// Get all selected values
List<string> selected = multiGroup.SelectedValues; // ["Feature A", "Feature C"]

// Toggle individual item
multiGroup.DeselectItem("Feature A");

// Select all
multiGroup.SelectAll();

Example 3: Segmented Control (Horizontal Button Group)

var segmented = new BeepRadioGroup
{
    RadioGroupStyle = RadioGroupRenderStyle.Segmented,
    Orientation = RadioGroupOrientation.Horizontal,
    ItemSpacing = 0, // Segmented controls have no gaps
    Size = new Size(400, 44)
};

segmented.AddItem("Daily");
segmented.AddItem("Weekly");
segmented.AddItem("Monthly");
segmented.AddItem("Yearly");

segmented.SelectedValue = "Daily";
segmented.ItemClicked += (s, e) =>
    Console.WriteLine($"View: {e.Item.Text}");

Example 4: Per-Item Disabling and Validation

var validatedGroup = new BeepRadioGroup
{
    IsRequired = true,
    RadioGroupStyle = RadioGroupRenderStyle.Card,
    Size = new Size(350, 220)
};

validatedGroup.AddItem("Credit Card", "icons/credit-card.svg");
validatedGroup.AddItem("PayPal", "icons/paypal.svg");
validatedGroup.AddItem("Bank Transfer", "icons/bank.svg", "2-3 business days");
validatedGroup.AddItem("Crypto", "icons/crypto.svg");

validatedGroup.DisableItem("Crypto"); // Unavailable option

if (!validatedGroup.Validate())
{
    validatedGroup.HasError = true;
    validatedGroup.ErrorMessage = "Payment method is required";
}

validatedGroup.Reset();
validatedGroup.EnableAllItems();

Example 5: Grid Layout with Toggle Renderer

var toggleGrid = new BeepRadioGroup
{
    AllowMultipleSelection = true,
    RadioGroupStyle = RadioGroupRenderStyle.Toggle,
    Orientation = RadioGroupOrientation.Grid,
    ColumnCount = 3,
    ItemSpacing = 10,
    Size = new Size(500, 180),
    MaxImageSize = new Size(32, 32)
};

toggleGrid.AddItem("Wi-Fi", "icons/wifi.svg");
toggleGrid.AddItem("Bluetooth", "icons/bluetooth.svg");
toggleGrid.AddItem("NFC", "icons/nfc.svg");
toggleGrid.AddItem("GPS", "icons/gps.svg");
toggleGrid.AddItem("Data", "icons/data.svg");
toggleGrid.AddItem("Hotspot", "icons/hotspot.svg");

toggleGrid.SetItemsEnabled(
    new[] { "Wi-Fi", "Bluetooth", "Data" }, true);
toggleGrid.SetItemsEnabled(
    new[] { "NFC", "GPS", "Hotspot" }, false);

Example 6: Custom Renderer Registration

var group = new BeepRadioGroup();
group.RegisterRenderer(RadioGroupRenderStyle.Custom, new MyCustomRadioRenderer());
group.RadioGroupStyle = RadioGroupRenderStyle.Custom;
// MyCustomRadioRenderer must implement IRadioGroupRenderer

Example 7: Data-Binding Integration

// Single selection binding
radioGroup.BoundProperty = "PaymentMethod";
radioGroup.SetValue("CreditCard");

// Get value from IBeepUIComponent
var selected = (SimpleItem)radioGroup.GetValue();

// Multiple selection binding
multiGroup.SetValue(new List<string> { "A", "B" });
var selectedList = (List<SimpleItem>)multiGroup.GetValue();

// Reset returns bool indicating if value changed
bool didChange = radioGroup.Reset();

Architecture

BeepRadioGroup follows a clean separation-of-concerns architecture using helper classes and pluggable renderers:

FileResponsibility
BeepRadioGroup.cs (763 lines)Main control: constructor, public methods, BaseControl overrides, accessibility, Dispose, style/color profile application, internal helpers (UpdateAccessibilityMetadata, MarkLayoutDirty, RequestVisualRefresh)
BeepRadioGroup.Properties.cs (582 lines)All public properties with change notification, layout triggers, validation logic, per-item disabled HashSet
BeepRadioGroup.EventHandlers.cs (93 lines)Internal event handlers: OnItemClicked (selection logic), OnItemHoverEnter/Leave, OnHoveredIndexChanged, OnFocusedIndexChanged, OnPressedIndexChanged, OnSelectionChanged, OnItemSelectionChanged
BeepRadioGroup.Input.cs (116 lines)Mouse overrides (MouseMove/Click/Down/Up/DoubleClick) and keyboard overrides (KeyDown, ProcessTabKey) - all routes through HitTestHelper
BeepRadioGroup.Drawing.cs (193 lines)Layout calculation (UpdateLayout), item state update (UpdateItemStates), DrawContent (runtime + DesignMode placeholder), OnResize
BeepHierarchicalRadioGroup.cs (1155 lines)Tree-structured variant: hierarchy flattening, expand/collapse buttons, dotted indent lines, keyboard nav, DPI scaling

Helper classes:

HelperFileResponsibility
RadioGroupLayoutHelperHelpers/RadioGroupLayoutHelper.csItem rectangle calculation (CalculateItemRectangles), orientation layout, alignment application, column count for grid layout, auto-size calculation, DPI-aware size computation
RadioGroupHitTestHelperHelpers/RadioGroupHitTestHelper.csMouse hit-testing against item rectangles, item click/hover/enter/leave detection, keyboard navigation (arrow keys respecting orientation), tab focus movement
RadioGroupStateHelperHelpers/RadioGroupStateHelper.csSelection state tracking (single/multi), SelectValue, DeselectValue, ToggleItem, SelectAll, ClearSelection, SelectedCount, SelectedItems, SelectedValue, SelectedValues
RadioGroupFontHelpersHelpers/RadioGroupFontHelpers.csTheme font resolution for different render styles and control styles; resolves appropriate font sizes and weights
RadioGroupThemeHelpersHelpers/RadioGroupThemeHelpers.csTheme color and style resolution for renderers based on active BeepControlStyle
RadioGroupStyleHelpersHelpers/RadioGroupStyleHelpers.csStyle profile application helpers
RadioGroupIconHelpersHelpers/RadioGroupIconHelpers.csIcon rendering helpers for items with ImagePath

Models:

ModelFilePurpose
RadioGroupStyleConfigModels/RadioGroupStyleConfig.csBorderRadius, ShowShadow, ShadowColor, ShadowOffset, ControlStyle, BorderWidth, RecommendedItemHeight/Spacing/Padding. [ExpandableObjectConverter]
RadioGroupColorConfigModels/RadioGroupColorConfig.csGroupBackgroundColor, TextColor (used when UseThemeColors=false)
RadioGroupColorTokensModels/RadioGroupColorTokens.csColor token definitions for renderer theming

BeepHierarchicalRadioGroup

A companion control (BeepHierarchicalRadioGroup) in the same namespace extends the radio group concept with a tree-like hierarchical structure. Items use the SimpleItem.Children property (a BindingList<SimpleItem>) to define parent-child relationships. The hierarchy is flattened for rendering with DPI-aware indentation.

Key properties: RootItems (hierarchical item collection, CollectionEditor), ShowExpanderButtons (toggle +/- buttons, default true), IndentSize (pixels per level, default 20, scaled by DPI), RenderStyle (subset of 5 renderers: Material, Card, Chip, Circular, Flat). Supports single/multi selection with the same AllowMultipleSelection flag.

Key features: Recursive hierarchy flattening (respects IsExpanded on each item), dotted-line drawing for parent-child relationship visualization, expand/collapse with clickable +/- ellipses, keyboard navigation (Right arrow to expand, Left arrow to collapse or focus parent), ExpandAll() / CollapseAll() bulk operations, FindItem(string guidId) recursive search, AddRootItem(SimpleItem) and AddChildItem(SimpleItem parent, SimpleItem child) for programmatic tree construction.

Events: All standard radio group events (SelectionChanged, ItemSelectionChanged, ItemClicked, ItemHoverEnter, ItemHoverLeave) plus ItemExpandedChanged (with ItemExpandedEventArgs carrying the item and its new IsExpanded state).

Data-binding: Supports SetValue/GetValue with string, List<string>, or SimpleItem. Has StyleProfile and ColorProfile matching the flat version. DPI-aware with S()/SF() scaling helpers. Accessibility reports selected count, expanded group count, and focused item.

Render Styles

All 11 renderers implement IRadioGroupRenderer. Each provides a unique visual appearance while respecting the same item state model and theme tokens.

StyleRenderer ClassDescriptionMulti-Select
CircularCircularRadioRendererTraditional circular radio buttons with fill animationNo
CheckboxCheckboxRadioRendererSquare checkbox-style selection with check markYes
MaterialMaterialRadioRendererMaterial Design ripple radio buttons (default style)Yes
CardCardRadioRendererCard-based selection with elevation/shadow (modern web style)Yes
ChipChipRadioRendererChip/pill-style selection badges with fill toggleYes
FlatFlatRadioRendererModern flat design with accent underline indicatorYes
ButtonButtonRadioRendererToggle-button group style with border and fillNo
ToggleToggleRadioRendererToggle switch style with animated thumb sliderYes
SegmentedSegmentedRadioRendereriOS-style segmented control (connected buttons, zero spacing)No
PillPillRadioRendererFully rounded capsule-shaped selectable buttonsYes
TileTileRadioRendererLarge touch-friendly tiles (Windows 8/10 style, best for Grid layout)Yes
Custom(user-registered)RegisterRenderer(Custom, yourRenderer) - any IRadioGroupRendererVaries

Renderer interface methods:

  • Initialize(BaseControl, IBeepTheme) — Setup on activation
  • RenderItem(Graphics, SimpleItem, Rectangle, RadioItemState) — Draw a single item
  • MeasureItem(SimpleItem, Graphics) — Calculate minimum item size for layout
  • GetContentArea(Rectangle) — Content bounds within item rect (excluding margins/borders)
  • GetSelectorArea(Rectangle) — Radio button/checkbox clickable area
  • RenderGroupDecorations(Graphics, Rectangle, ...) — Group-level visual effects
  • UpdateTheme(IBeepTheme) — Theme change notification
  • Cleanup() — Release GDI+ resources (fonts, pens, brushes)

Optional interfaces: IImageAwareRenderer (MaxImageSize property for image rendering), IFocusAwareRenderer (DrawFocusRing for WCAG 2.2 compliant 2px focus outlines with 3:1 contrast).

Renderers that don't support multiple selection (SupportsMultipleSelection = false: Circular, Button, Segmented) auto-switch AllowMultipleSelection to false when selected. The control handles this automatically in the RadioGroupStyle setter.

Enumerations

RadioGroupRenderStyle (from Renderers/IRadioGroupRenderer.cs)

12 values: Circular, Checkbox, Material, Card, Chip, Flat, Button, Toggle, Segmented, Pill, Tile, Custom.

RadioGroupOrientation (from Helpers/RadioGroupLayoutHelper.cs)

4 values: Vertical, Horizontal, Grid (uses ColumnCount), Flow.

RadioGroupAlignment (from Helpers/RadioGroupLayoutHelper.cs)

4 values: Left, Center, Right, Stretch.