BeepListofValuesBox
Oracle Forms-style LOV selector with popup grid, async loading, key validation, and recent-selection history
Table of Contents
Overview
Namespace: TheTechIdea.Beep.Winform.Controls
Assembly: TheTechIdea.Beep.Winform.Controls
Inheritance: BeepListofValuesBox → BaseControl → System.Windows.Forms.UserControl
BeepListofValuesBox is an Oracle Forms-inspired List-of-Values (LOV) selector control.
It combines an editable key textbox (left ~22% of the control width) with a read-only value display
area (right ~78%), and a dropdown popup (BeepLovPopup) providing a multi-column
BeepGridPro-backed search grid. Full key validation ensures typed keys exist in the
list; invalid entries trigger error text + red background + notification tooltips.
The popup supports optional async item loading with cancellation and spinner feedback, a recent-selection chip strip (max 5), search filtering across Value/Text/Description fields, custom column definitions, and complete keyboard navigation (F9 / Alt+Down / Escape / Enter).
Key Features
- Split Layout: Editable key (BeepTextBox, ~22%) + painted read-only value area (~78%).
- Key Validation:
ValidateKey()checks typed keys againstListItems. Invalid keys show inline ErrorText + notification + revert to last valid key. - Multi-Column Popup: BeepGridPro popup with optional explicit column
definitions (
LovColumns). Falls back to auto-generated Key + Display Value columns. - Async Loading: Optional
ItemsLoaderdelegate. Popup opens immediately with spinner, loads on background thread, populates grid on completion. CancellationToken-aware. - Recent History: Last 5 selections persisted across popup opens. Clickable chips in the popup. Getters/setters for external persistence (e.g. user settings).
- Search Filtering: Text-box in popup filters by Value, Text, and Description on every keystroke. Shows "X of Y records" count label.
- Keyboard Shortcuts: F9 (Oracle Forms standard — pre-seeds search from key field), Alt+Down (Windows combo standard), Delete/Backspace (clear selection when key field unfocused), Escape (close popup).
- Key Badge Pill: Optional coloured rounded pill displaying the raw key in the value area. Accent-colour background with auto-contrasting text (black or white).
- DPI-Aware Painting: Value area, badge pill, and placeholder text scaled via
BeepThemesManager.DpiScaleX/Y. - Theme Propagation: Theme forwarded to key TextBox, popup form, popup grid, popup buttons, search box, and recent-panel chips.
- Trailing Icon: ChevronDown icon from SvgsUI as the dropdown trigger. Clickable, handled by BaseControl's built-in trailing-icon infrastructure (no separate BeepButton child).
- IBeepComponent: SetValue (SimpleItem or string), GetValue (returns SimpleItem), LabelText/HelperText shadow with auto-enable.
Properties (12 total)
| Property | Type | Default | Category | Description |
|---|---|---|---|---|
ListItems | List<SimpleItem> | new List<SimpleItem>() | Data | Collection of SimpleItem (Value=key, Text=display, optional Description). Design-time serializable. Updates display value. |
SelectedKey | string | "" | Data | Current key (SimpleItem.Value). Setting validates: valid keys update display; invalid revert to last valid with error feedback. |
SelectedDisplayValue | string | "" | Data | Read-only. Derived text from item matching SelectedKey (SimpleItem.Text). Private set via UpdateDisplayValue(). |
ShowKeyBadge | bool | true | LOV | Show coloured pill badge with raw key in the value area. Accent-colour bg with contrast text. |
SelectedItem | SimpleItem | null | — | Read-only [Browsable(false)]. Returns the SimpleItem matching SelectedKey from ListItems. |
LovTitle | string | "Select Value" | LOV | Title text shown in the popup header. |
MaxPopupHeight | int | 360 | LOV | Maximum pixel height of the popup form. Clamped to header+footer+60px minimum. |
LovColumns | List<BeepColumnConfig> | new List() | LOV | Optional explicit grid column definitions. Empty = auto Key+Display Value columns. |
ItemsLoader | Func<CancellationToken, Task<List<SimpleItem>>>? | null | — | Async item factory. Non-null triggers spinner+background load on popup open. [Browsable(false)]. |
RecentSelections | List<SimpleItem> | empty copy | — | Get/set recent selection history (oldest-first, max 5). Can persist across sessions. [Browsable(false)]. |
LabelText (new) | string | "" | LOV | Shadow of BaseControl.LabelText. Non-empty value auto-enables LabelTextOn. |
HelperText (new) | string | "" | LOV | Shadow of BaseControl.HelperText. Non-empty value auto-enables HelperTextOn. |
Methods
| Method | Visibility | Signature | Description |
|---|---|---|---|
Reset | public | void Reset() | Clears ListItems, key field, display value, last valid key, and invalidates. |
SetValue | public | void SetValue(object value) | Sets by SimpleItem or string key. IBeepComponent override. |
GetValue | public | object GetValue() | Returns SimpleItem matching SelectedKey, or null. |
ApplyTheme | public | void ApplyTheme() | Rebuilds fonts (LovFontHelpers), themes key TextBox, propagates to popup if open. |
ClearSelection | private | void ClearSelection() | Clears key+display+lastValid, raises SelectionChanged, invalidates. |
ValidateKey | private | bool ValidateKey(string key) | True if key is empty or exists in ListItems (matched by Value.ToString()). |
UpdateDisplayValue | private | void UpdateDisplayValue() | Sets _selectedDisplayValue from item matching SelectedKey. Invalidates. |
SetSelectedItem | private | void SetSelectedItem(SimpleItem) | Sets key TextBox, lastValid, display value, fires SelectionChanged. |
OpenPopup | private async | void OpenPopup(string preloadSearch="") | Creates/configures/shows popup. Async path with ItemsLoader, sync path with ListItems. |
PaintValueArea | private | void PaintValueArea(Graphics, Rectangle) | Paints value area: bg fill, optional key badge pill (rounded rect + contrast text), display text, placeholder. |
RebuildFonts | private | void RebuildFonts() | Rebuilds _fieldFont and _badgeFont from theme via LovFontHelpers. Disposes old instances. |
OnSelectionChanged | protected virtual | void OnSelectionChanged() | Raises SelectionChanged event. |
Events
| Event | Args Type | Fire Point | Description |
|---|---|---|---|
SelectionChanged | EventHandler | SetSelectedItem or ClearSelection | Raised when the selected item changes or is cleared. |
The Popup (BeepLovPopup)
BeepLovPopup is a borderless Form with the following components:
- Header Panel (44px): BeepTextBox search box + BeepButton close ("✕").
- Recent Items Panel (34px, optional): FlowLayoutPanel of BeepButton chips showing recent selections, most recent first. Each chip click accepts the item immediately.
- Grid (BeepGridPro, Fill dock): Configurable columns, single-select, read-only. RowSelectionChanged tracks _pendingSelection. Double-click = Accept.
- Footer Panel (44px): Count label ("12 records" / "5 of 20"), Cancel + OK BeepButtons.
- Loading Overlay: Semi-transparent panel with animated Braille spinner ("⠇ Loading…") and "⚠ Load failed: ..." error state. Positioned over grid area.
Layout constants: HeaderH=44, FooterH=44, MinPopupW=280, MaxVisRows=12, BorderPx=1. The form paints a 1px themed border in OnPaint. Position is calculated below the owner control (or above if insufficient space) using Screen.WorkingArea bounds.
Key methods: ShowAt(items, screenOrigin, ownerWidth, preloadSearch),
ApplyLovTheme() (themes all child BeepControls and the grid),
BeginLoading() / EndLoading(items) (spinner lifecycle),
LoadItemsAsync(loader, preloadSearch) (cancellable async load),
Accept() (pushes recent + fires ItemAccepted + hides),
Cancel() (cancels in-flight load + fires Cancelled + hides).
The popup auto-closes on OnDeactivate and handles keyboard (Escape=Cancel,
Enter/F9=Accept). Grid is auto-configured with Key + Display Value columns when LovColumns is empty.
Layout & Painting
The control uses a two-region layout:
- Key TextBox (left): A
BeepTextBoxwith IsChild=true and IsFrameless=true. Positioned atcontentRect.Left + padding, width = 22% of content area (min 40px). Height =_keyTextBox.PreferredHeight. Vertically centred. - Value Area (right): Painted directly in
PaintValueArea, not a child control. Width = content area minus key width minus spacing. Background filled with_currentTheme.PanelBackColor. Contains:- Placeholder text: "Select a value…" when no key and no display value.
- Key badge pill: When
ShowKeyBadge && hasKey && _badgeFont != null. Rounded rectangle with AccentColor fill, contrast text (black/white via perceived luminance). - Display text:
_selectedDisplayValuein theme ForeColor.
AdjustLayout() is called from InitLayout and OnResize.
It calls GetAdjustedContentRect() (painter-aware, excludes trailing icon area),
computes key and value widths, and positions the internal TextBox. DPI scaling uses
BeepThemesManager.DpiScaleX/Y for all pixel calculations.
Keyboard Navigation
| Key | Action | Condition |
|---|---|---|
F9 | Opens LOV popup | Pre-seeds search from key field text |
Alt+Down | Opens LOV popup | Windows combobox standard |
Delete / Backspace | Clears selection | Key field NOT focused |
Escape | Closes popup | Popup visible |
All handled via ProcessCmdKey(ref Message, Keys) override. Returns
true for handled keys, preventing further processing.
Recent-Selection History
The popup maintains a Queue<SimpleItem> _recentItems capped at
MaxRecentItems = 5. On Accept(), PushRecent(item):
removes any duplicate, enqueues the new item, evicts oldest if >5.
History is synchronised between the popup and the owner control via:
_recentHistory field in BeepListofValuesBox, RecentItems get/set on
BeepLovPopup, and RecentSelections get/set on BeepListofValuesBox. The history
survives popup open/close cycles. Users can persist RecentSelections to
application settings and restore on next session.
The recent panel (_recentPanel) is a FlowLayoutPanel with a strip of BeepButton
chips ordered most-recent first, showing the item's Text or Value truncated to 18 characters.
Each chip has a themed appearance and its Tag holds the SimpleItem for click handling.
Theming
- Fonts:
LovFontHelpers.GetLovFontFromTheme(theme)andGetBadgeFontFromTheme(theme)resolve fonts. Cached in _fieldFont/_badgeFont, rebuilt on ApplyTheme. Disposed on theme change if different instances. - Key TextBox: Theme, UseThemeColors, Font set via
_keyTextBox.Theme,_keyTextBox.UseThemeColors, and_keyTextBox.Font. ApplyTheme called. - Popup:
_lovPopup.LovTheme,_lovPopup.UseThemeColors,_lovPopup.CurrentThemeset.ApplyLovTheme()propagates to header/footer panels, grid, search box, all BeepButton children, count label, loading overlay, and recent-panel chips. - Badge Pill: Background =
_currentTheme.AccentColor. Foreground =ContrastForeColor(bg)— black (Color.FromArgb(30,30,30)) if luminance > 0.55, white otherwise. Perceived luminance via ITU-R BT.601 formula. - Value Area: Background =
_currentTheme.PanelBackColor. Text =_currentTheme.ForeColor. Placeholder =_currentTheme.SecondaryTextColor. - Trailing Icon:
TrailingIconPath = SvgsUI.ChevronDown, set in InitializeComponents(). BaseControl paints it with theming support.
Examples
Basic Usage — Static Items
var lov = new BeepListofValuesBox
{
ListItems = new List<SimpleItem>
{
new SimpleItem { Value = "US", Text = "United States" },
new SimpleItem { Value = "GB", Text = "United Kingdom" },
new SimpleItem { Value = "FR", Text = "France" },
new SimpleItem { Value = "DE", Text = "Germany" }
},
LovTitle = "Select Country",
ShowKeyBadge = true,
Width = 400
};
lov.SelectionChanged += (s, e) =>
Console.WriteLine($"{lov.SelectedKey} = {lov.SelectedDisplayValue}");
Programmatic Selection
lov.SelectedKey = "FR"; // ok
lov.SelectedKey = "XX"; // error + revert + tooltip
lov.SetValue(new SimpleItem { Value = "DE", Text = "Germany" });
lov.SetValue("GB"); // string key
lov.Reset(); // clear everything
Async Loading
var asyncLov = new BeepListofValuesBox
{
LovTitle = "User Search", Width = 400,
ItemsLoader = async (ct) =>
{
await Task.Delay(500, ct);
return new List<SimpleItem>
{
new SimpleItem { Value = "1", Text = "Alice" },
new SimpleItem { Value = "2", Text = "Bob" }
};
}
};
Custom Columns
var columns = new BeepListofValuesBox
{
ListItems = products, LovTitle = "Products",
LovColumns = new List<BeepColumnConfig>
{
new BeepColumnConfig { ColumnName = "Value", ColumnCaption = "SKU", Width = 100 },
new BeepColumnConfig { ColumnName = "Text", ColumnCaption = "Name", Width = 200 },
new BeepColumnConfig { ColumnName = "Description", ColumnCaption = "Category", Width = 150 }
}
};
Persisting Recent History
// Restore
lov.RecentSelections = Properties.Settings.Default.LovHistory;
// Save on close
FormClosing += (_,_) => {
Properties.Settings.Default.LovHistory = lov.RecentSelections;
Properties.Settings.Default.Save();
};
Label and Helper Text
var labeled = new BeepListofValuesBox
{
LabelText = "Country Code",
HelperText = "Type or press F9 to search",
ListItems = countries, Width = 400
};
Validation & Error Feedback
When a key is typed that doesn't exist in ListItems:
ErrorText(BaseControl) is set to "Invalid key — not in the list."HasErroris set totrue, which triggers BaseControl's error visual (typically red border or background).- A notification tooltip is shown via
ShowNotification("Invalid key. Please enter a valid value from the list.", ToolTipType.Warning, 2000). - The key field text reverts to
_lastValidKey(the most recent valid key). - When a valid key is subsequently entered,
HasErroris cleared andErrorTextis set tostring.Empty.
Popup Lifecycle
- Open: User presses F9, Alt+Down, or clicks the trailing chevron icon.
OpenPopup()creates/configuresBeepLovPopup(lazy created, reused). - Configure: Popup receives LovTitle, LovColumns, MaxPopupHeight, theme, and RecentItems. Columns auto-generated if LovColumns is empty.
- Load: If ItemsLoader is non-null,
LoadItemsAsync()shows spinner, runs the loader on a background thread with CancellationToken, and calls EndLoading on completion. Otherwise uses ListItems synchronously. - Interact: User types in search (filters by Value/Text/Description), browses grid rows, and accepts (OK/double-click/Enter/F9) or cancels (Cancel/Escape/deactivate).
- Close:
Accept()pushes to recent, fires ItemAccepted, hides.Cancel()cancels async load, fires Cancelled, hides. Auto-close on deactivate.
Architecture
| File | Lines | Responsibility |
|---|---|---|
BeepListofValuesBox.cs | ~728 | Main: fields (_keyTextBox BeepTextBox, _selectedDisplayValue, _lovPopup, _items, _lastValidKey, _fieldFont, _badgeFont, _showKeyBadge, _recentHistory), properties (ListItems, SelectedKey, SelectedDisplayValue, ShowKeyBadge, SelectedItem, LovTitle, MaxPopupHeight, LovColumns, ItemsLoader, RecentSelections, LabelText, HelperText), constructor/InitializeComponents (creates key TextBox with IsChild/IsFrameless, wires TrailingIconClicked to OpenPopup, forwards mouse events), InitLayout, layout (AdjustLayout, GetHeight, OnResize), drawing (DrawContent, Draw, PaintValueArea with DPI-scaled badge pill and display text, ScaleLogicalX/Y, BuildRoundedPath, ContrastForeColor), event handlers (LovPopup_ItemAccepted, LovPopup_Cancelled, OpenPopup async/sync, ClearSelection, ProcessCmdKey F9/Alt+Down/Delete/Backspace/Escape, KeyTextBox_TextChanged with error+tooltip), helpers (ValidateKey, UpdateLastValidKey, UpdateDisplayValue, SetSelectedItem, Reset, OnSelectionChanged), theme (ApplyTheme, RebuildFonts, SetValue, GetValue), Dispose (popup, key textbox, fonts). |
BeepLovPopup.cs | ~767 | Popup Form: child controls (_headerPanel, _searchBox BeepTextBox, _closeButton BeepButton, _grid BeepGridPro, _footerPanel, _countLabel, _cancelButton, _okButton), data (_allItems, _filteredItems, _pendingSelection, _loadCts), recent items (_recentPanel, _recentItems Queue, MaxRecentItems=5), loading state (_loadingOverlay, _loadingLabel, _spinnerTimer, SpinnerFrames[]), theme (LovTheme, UseThemeColors, CurrentTheme), config (LovTitle, LovColumns, MaxPopupHeight), result (SelectedItem, RecentItems), events (ItemAccepted, Cancelled, SearchChanged), public API (ShowAt, ApplyLovTheme, BeginLoading, EndLoading, LoadItemsAsync), recent helpers (UpdateRecentPanel, PushRecent), grid column config (ConfigureGridColumns, RebindGrid), layout helpers (PositionHeaderControls, PositionFooterControls, PositionLoadingOverlay, OnResize, OnPaint border), filtering (SearchBox_TextChanged, FilterItems, UpdateCount), selection (Grid_RowSelectionChanged, Accept, Cancel), keyboard (OnKeyDown Escape/Enter/F9), OnDeactivate auto-close, Dispose. |
Models/LovStyleConfig.cs | ~30 | Style configuration presets. |
Models/LovColorConfig.cs | ~30 | Colour config per theme per state. |
Helpers/LovFontHelpers.cs | ~40 | GetLovFontFromTheme, GetBadgeFontFromTheme. |
Helpers/LovIconHelpers.cs | ~30 | Default icon paths. |
Helpers/LovThemeHelpers.cs | ~30 | Theme colour resolution. |
Helpers/LovStyleHelpers.cs | ~30 | Style-specific metrics. |