BeepRadioGroup
A modern, flexible radio group control with 11 render styles, multiple selection modes, layouts, per-item states, and WCAG 2.2 accessibility support
Table of Contents
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
IRadioGroupRendererwithRenderItem,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
ImagePathwith configurableMaxImageSize(default 24x24) - Validation:
IsRequiredproperty,HasErrorstate,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-levelBeepRadioGroupItemAccessibleObjectsupporting RadioButton/CheckButton roles, Bounds, HitTest, DoDefaultAction, State, Value - Style/color profiles:
RadioGroupStyleConfigandRadioGroupColorConfigfor runtime customization with expandable property editor support - SubText support: Items can display secondary descriptive text below the main label
- Data-binding:
SetValueaccepts string, List<string>, SimpleItem, List<SimpleItem>;GetValuereturns selected item(s) - DPI-aware rendering: Full DPI scaling via
OnDpiScaleChangedoverride with renderer re-initialization - Focus-aware rendering: Renderers can implement
IFocusAwareRendererfor 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
ValueChangedviaInvokeOnValueChangedon selection change with LinkedProperty support
Properties
| Property | Type | Default | Description |
|---|---|---|---|
| UseThemeColors | bool | true | Use theme colors instead of style-based colors. Propagates to all renderers. |
| Style | BeepControlStyle | BeepControlStyle.Material3 | The visual control style/painter for rendering the radio group. |
| TextFont | Font | Segoe UI 9pt | Font for item text. Setting this disables UseThemeFont. |
| IsChild | bool | false | Whether this control is a child of another Beep control. |
| Items | List<SimpleItem> | empty list | Collection of items. Supports CollectionEditor in designer. [DesignerSerializationVisibility.Content] |
| AllowMultipleSelection | bool | false | Toggle between single-select (radio) and multi-select (checkbox) mode. |
| SelectedValue | string | null | Currently selected value in single selection mode. Setting updates visuals. |
| SelectedValues | List<string> | empty list | All selected values in multiple selection mode. [Hidden in designer] |
| SelectedItems | List<SimpleItem> | empty list | The selected item objects. [Browsable(false)] |
| SelectedCount | int | 0 | Number of currently selected items. [Browsable(false)] |
| RadioGroupStyle | RadioGroupRenderStyle | Material | The visual render style for items. Changing this switches the active renderer. |
| Orientation | RadioGroupOrientation | Vertical | Layout direction: Vertical, Horizontal, Grid, or Flow. |
| Alignment | RadioGroupAlignment | Left | Item alignment within the control: Left, Center, Right, Stretch. |
| ItemSpacing | int | 8 | Spacing between items in pixels (minimum 0). |
| ItemPadding | Padding | Padding(8) | Padding around each individual item. |
| ColumnCount | int | 1 | Number of columns when Orientation = Grid. Must be > 0. |
| AutoSizeItems | bool | true | Whether items auto-size to their content. Enables/disables layout helper AutoSize. |
| MaxImageSize | Size | (24, 24) | Maximum image rendering size. Propagates to all IImageAwareRenderer renderers. |
| StyleProfile | RadioGroupStyleConfig | new instance | Runtime style profile: BorderRadius, ShowShadow, ShadowColor, ShadowOffset, ControlStyle, BorderWidth, RecommendedItemHeight/Spacing/Padding. [ExpandableObjectConverter] |
| ColorProfile | RadioGroupColorConfig | new instance | Runtime color profile when UseThemeColors=false. Contains GroupBackgroundColor, TextColor. |
| IsRequired | bool | false | Whether a selection is required. Triggers validation. |
| HasError | bool | false | Whether the control is in an error state. [new keyword, Category: Validation] |
| ErrorMessage | string | "" | The error message to display when validation fails. |
Methods
| Method | Signature | Description |
|---|---|---|
| AddItem | void AddItem(SimpleItem item) | Adds an item and triggers full layout update. |
| AddItem | void AddItem(string text, string imagePath = null, string subText = null) | Convenience overload; creates a SimpleItem and calls AddItem. |
| RemoveItem | bool RemoveItem(SimpleItem item) | Removes an item; returns true if found and removed. |
| ClearItems | void ClearItems() | Clears all items and triggers layout update. |
| SelectItem | bool SelectItem(string value) | Selects item by string value via state helper. |
| DeselectItem | bool DeselectItem(string value) | Deselects item by string value via state helper. |
| ClearSelection | void ClearSelection() | Clears all current selections. |
| SelectAll | void SelectAll() | Selects all items (only when AllowMultipleSelection is true). |
| RegisterRenderer | void RegisterRenderer(RadioGroupRenderStyle style, IRadioGroupRenderer renderer) | Registers or replaces a custom renderer for the given style. |
| Validate | bool Validate() | Runs selection validation; returns true if valid, false if error. |
| DisableItem | void DisableItem(string itemText) | Disables a specific item by its text value (adds to HashSet). |
| EnableItem | void EnableItem(string itemText) | Re-enables a previously disabled item (removes from HashSet). |
| IsItemDisabled | bool IsItemDisabled(string itemText) | Checks whether a specific item is in the disabled set. |
| SetItemsEnabled | void SetItemsEnabled(IEnumerable<string> items, bool enabled) | Batch enables or disables multiple items at once. |
| EnableAllItems | void EnableAllItems() | Clears the disabled items set and enables everything. |
| SetValue | override void SetValue(object value) | Accepts string, List<string>, SimpleItem, or List<SimpleItem>. |
| GetValue | override object GetValue() | Returns SelectedItems (multi) or first selected item (single). |
| Reset | bool Reset() | Clears selection; returns true if value changed. |
| ApplyTheme | override void ApplyTheme() | Applies font theme, updates all renderers with current theme token set, resets layout. |
Events
| Event | Args | Description |
|---|---|---|
| SelectionChanged | SelectionChangedEventArgs | Fired when selection changes (add/remove). Updates states and accessibility. |
| ItemSelectionChanged | ItemSelectionChangedEventArgs | Fired when an individual item's selection state toggles. Triggers ValueChanged. |
| ItemClicked | ItemClickEventArgs | Fired when an item is clicked. In single mode, selects; in multi mode, toggles. |
| ItemDoubleClicked | ItemClickEventArgs | Fired when an item is double-clicked via HitTestHelper. |
| ItemHoverEnter | ItemHoverEventArgs | Fired when the mouse enters an item's bounds. |
| ItemHoverLeave | ItemHoverEventArgs | Fired 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:
- Calls
base.ApplyTheme()for base theme tokens (background, foreground, border) - Uses
RadioGroupFontHelpers.ApplyFontTheme(this, Style, _currentTheme)to resolve fonts from theme tokens based on the activeStyle(Material3, Flat, etc.) _isApplyingThemeFontguard prevents the TextFont setter from disabling UseThemeFont during theme application- Propagates
_currentThemeto the active renderer via_currentRenderer.UpdateTheme(_currentTheme) - Updates all registered renderers in the
_renderersdictionary by callingUpdateThemeon each - Re-assigns the
ItemMeasurerdelegate to use the current renderer'sMeasureItem - 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:
| File | Responsibility |
|---|---|
| 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:
| Helper | File | Responsibility |
|---|---|---|
| RadioGroupLayoutHelper | Helpers/RadioGroupLayoutHelper.cs | Item rectangle calculation (CalculateItemRectangles), orientation layout, alignment application, column count for grid layout, auto-size calculation, DPI-aware size computation |
| RadioGroupHitTestHelper | Helpers/RadioGroupHitTestHelper.cs | Mouse hit-testing against item rectangles, item click/hover/enter/leave detection, keyboard navigation (arrow keys respecting orientation), tab focus movement |
| RadioGroupStateHelper | Helpers/RadioGroupStateHelper.cs | Selection state tracking (single/multi), SelectValue, DeselectValue, ToggleItem, SelectAll, ClearSelection, SelectedCount, SelectedItems, SelectedValue, SelectedValues |
| RadioGroupFontHelpers | Helpers/RadioGroupFontHelpers.cs | Theme font resolution for different render styles and control styles; resolves appropriate font sizes and weights |
| RadioGroupThemeHelpers | Helpers/RadioGroupThemeHelpers.cs | Theme color and style resolution for renderers based on active BeepControlStyle |
| RadioGroupStyleHelpers | Helpers/RadioGroupStyleHelpers.cs | Style profile application helpers |
| RadioGroupIconHelpers | Helpers/RadioGroupIconHelpers.cs | Icon rendering helpers for items with ImagePath |
Models:
| Model | File | Purpose |
|---|---|---|
| RadioGroupStyleConfig | Models/RadioGroupStyleConfig.cs | BorderRadius, ShowShadow, ShadowColor, ShadowOffset, ControlStyle, BorderWidth, RecommendedItemHeight/Spacing/Padding. [ExpandableObjectConverter] |
| RadioGroupColorConfig | Models/RadioGroupColorConfig.cs | GroupBackgroundColor, TextColor (used when UseThemeColors=false) |
| RadioGroupColorTokens | Models/RadioGroupColorTokens.cs | Color 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.
| Style | Renderer Class | Description | Multi-Select |
|---|---|---|---|
| Circular | CircularRadioRenderer | Traditional circular radio buttons with fill animation | No |
| Checkbox | CheckboxRadioRenderer | Square checkbox-style selection with check mark | Yes |
| Material | MaterialRadioRenderer | Material Design ripple radio buttons (default style) | Yes |
| Card | CardRadioRenderer | Card-based selection with elevation/shadow (modern web style) | Yes |
| Chip | ChipRadioRenderer | Chip/pill-style selection badges with fill toggle | Yes |
| Flat | FlatRadioRenderer | Modern flat design with accent underline indicator | Yes |
| Button | ButtonRadioRenderer | Toggle-button group style with border and fill | No |
| Toggle | ToggleRadioRenderer | Toggle switch style with animated thumb slider | Yes |
| Segmented | SegmentedRadioRenderer | iOS-style segmented control (connected buttons, zero spacing) | No |
| Pill | PillRadioRenderer | Fully rounded capsule-shaped selectable buttons | Yes |
| Tile | TileRadioRenderer | Large touch-friendly tiles (Windows 8/10 style, best for Grid layout) | Yes |
| Custom | (user-registered) | RegisterRenderer(Custom, yourRenderer) - any IRadioGroupRenderer | Varies |
Renderer interface methods:
Initialize(BaseControl, IBeepTheme)— Setup on activationRenderItem(Graphics, SimpleItem, Rectangle, RadioItemState)— Draw a single itemMeasureItem(SimpleItem, Graphics)— Calculate minimum item size for layoutGetContentArea(Rectangle)— Content bounds within item rect (excluding margins/borders)GetSelectorArea(Rectangle)— Radio button/checkbox clickable areaRenderGroupDecorations(Graphics, Rectangle, ...)— Group-level visual effectsUpdateTheme(IBeepTheme)— Theme change notificationCleanup()— 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.