ListBox Internals
BeepListBox internal architecture — painter system, selection model, drag-drop, keyboard navigation, accessibility, and virtual scrolling
Table of Contents
Overview
BeepListBox is structured across multiple partial class files, each handling a distinct aspect of the control's behavior. The list box integrates with the 42-item painter system (see listbox-painters.html), supports single and multi selection with keyboard modifiers, drag-drop reordering, full keyboard navigation including type-to-search, UIA accessibility patterns, and virtual scrolling for large datasets.
The internal architecture separates concerns via a helper decomposition similar to GridX, but simpler: rendering delegates to IListBoxPainter implementations, selection state is tracked in a dedicated SelectionManager, and drag-drop uses WinForms standard drag operations with custom rendering of drop indicators.
Partial File Map
BeepListBox is spread across multiple partial class files in the source tree:
| File Group | Contains | Key Members |
|---|---|---|
| Properties | Public properties | Items, ItemStyle, SelectionMode, MultiSelect, ItemHeight, DataSource, DisplayMember, ValueMember, VirtualMode, ItemCount, Sorted |
| Methods | Public methods | AddItem(), RemoveItem(), ClearItems(), GetItemAt(), ScrollToItem(), EnsureVisible(), BeginUpdate(), EndUpdate(), FindItem() |
| Events | Event declarations | SelectedIndexChanged, ItemClicked, ItemDoubleClicked, SelectionChanging, ItemDragStarted, ItemsReordered, DrawItem |
| Drawing | Paint logic | OnPaint(), OnPaintBackground(), painter delegation to IListBoxPainter, item measuring via BaseListBoxPainter.MeasureItem() |
| Selection | Selection state machine | SelectionManager class, SelectedIndices, SelectedItems, SelectAll(), ClearSelection(), single/multi/extended modes |
| DragDrop | Drag-drop reorder | OnMouseDown(), OnDragOver(), OnDragDrop(), DoDragDrop(), drop indicator rendering, AllowDragReorder property |
| Keyboard | Keyboard handling | OnKeyDown(), arrow/PageUp/PageDown/Home/End navigation, type-to-search buffer, focus tracking, FocusedItemIndex |
| Accessibility | UIA provider | BeepListBoxAccessibleObject, BeepListBoxItemAccessibleObject, UIA patterns: Selection, Invoke, Scroll, role mapping |
| VirtualMode | Virtual scrolling | RetrieveVirtualItem event, CacheVirtualItems event, item recycling pool, visible window management |
| Items | Item data model | BeepListItem class: Text, SubText, ImagePath, Tag, IsEnabled, IsSeparator, custom data binding |
Selection System
The SelectionManager class (internal) manages all selection state with three modes:
| Mode | Enum Value | Behavior |
|---|---|---|
| None | SelectionMode.None | Items cannot be selected |
| Single | SelectionMode.One | Only one item can be selected at a time; clicking another deselects the previous |
| MultiSimple | SelectionMode.MultiSimple | Click toggles selection; Ctrl+click adds/removes from selection; no range selection |
| MultiExtended | SelectionMode.MultiExtended | Full multi-select: Ctrl+click toggles, Shift+click selects range, Ctrl+A selects all |
Selection events fire in order: SelectionChanging (cancelable) → selection changes → SelectedIndexChanged. The SelectedItems property returns a List<BeepListItem> of currently selected items. SelectedIndices returns List<int> index positions.
listBox.SelectionMode = SelectionMode.MultiExtended;
listBox.SelectionChanging += (s, e) =>
{
if (someCondition) e.Cancel = true;
};
listBox.SelectedIndexChanged += (s, e) =>
{
Console.WriteLine($"Selected: {listBox.SelectedItems.Count} items");
};Drag-Drop
Drag-drop reordering is controlled by the AllowDragReorder property (default: false). When enabled, items can be dragged to reorder within the list.
| Phase | Event / Method | Description |
|---|---|---|
| Initiate | OnMouseDown → DoDragDrop | Mouse down on an item starts drag; movement beyond threshold begins drag operation |
| Drag Over | OnDragOver | Drop position is resolved: above item, below item, or onto item; drop indicator line is drawn |
| Drop | OnDragDrop | Item is removed from original position and inserted at drop position; ItemsReordered event fires |
| Visual | Drop indicator | A colored horizontal line painted between items at the drop insertion point; style follows active theme |
Drop position resolution checks the Y-coordinate of the mouse relative to each item's bounds. If the mouse is in the top third, the drop position is before that item; bottom third = after; middle = onto (insert after). During drag, the dragged item is rendered semi-transparent at the cursor position.
Keyboard Navigation
BeepListBox implements full keyboard navigation in OnKeyDown:
| Key | Action |
|---|---|
| ↑ / ↓ | Move focus up/down one item. In single-select mode, also changes selection. In multi-extended mode, when Shift is held, extends selection range. |
| Page Up / Page Down | Move focus by one visible page. Selection behavior matches arrow keys. |
| Home / End | Move focus to first/last item. |
| Space | In multi-select mode: toggles selection of the focused item. In single-select: selects the focused item. |
| Ctrl+A | Selects all items (multi-extended mode only). |
| Character keys | Type-to-search: typing characters selects the next item starting with those characters. A 1-second search buffer accumulates characters; after timeout, buffer resets. |
The type-to-search feature maintains a StringBuilder buffer and a Timer. Each character press appends to the buffer and restarts the timer. When the timer expires, the buffer is cleared. The search is case-insensitive and wraps from the current position.
Accessibility
BeepListBox implements UI Automation (UIA) provider pattern via two custom accessible object classes:
| Class | Purpose |
|---|---|
BeepListBoxAccessibleObject | UIA provider for the list box control itself. Supports the SelectionPattern (canSelectMultiple, getSelection, IsSelectionRequired) and ScrollPattern (VerticallyScrollable, VerticalViewSize, SetScrollPercent). Reports ControlType.List to screen readers. |
BeepListBoxItemAccessibleObject | UIA provider for each item. Supports SelectionItemPattern (IsSelected, Select, AddToSelection, RemoveFromSelection) and InvokePattern (Invoke → triggers ItemClicked). Reports ControlType.ListItem with Name = item text. |
Accessible properties set: Name (control label), Role (AccessibleRole.List), Description (from tooltip if set). The control overrides CreateAccessibilityInstance() to return BeepListBoxAccessibleObject. Item runtime IDs are constructed from Handle + item index for stable UIA identification.
Virtual Mode
Virtual mode is enabled via VirtualMode = true and ItemCount. Instead of storing items in memory, the list box fires RetrieveVirtualItem to fetch items on demand as they scroll into view.
| Member | Type | Description |
|---|---|---|
VirtualMode | bool | Enables virtual data mode; when true, Items collection is not used |
ItemCount | int | Total number of virtual items (must be set when VirtualMode is true) |
RetrieveVirtualItem | Event | Fired when a BeepListItem is needed for display; handler must populate and return it |
CacheVirtualItems | Event | Fired to suggest caching a range of items (pre-fetch optimization) |
ItemRecycling | Internal | A pool of ~50 item objects is recycled; items leaving the visible window are returned to the pool |
Virtual scrolling is efficient for lists with millions of items. Only ~20-50 visible items are materialized at a time. The scrollbar uses ItemCount to compute its range, and RetrieveVirtualItem is called with the index each time an item enters the viewport.
Examples
Multi-Select with Drag Reorder
var listBox = new BeepListBox
{
SelectionMode = SelectionMode.MultiExtended,
AllowDragReorder = true,
ItemStyle = ListBoxType.CardList
};
for (int i = 0; i < 20; i++)
listBox.Items.Add(new BeepListItem { Text = $"Item {i + 1}" });
listBox.ItemsReordered += (s, e) =>
{
Console.WriteLine($"Item moved: {e.OldIndex} → {e.NewIndex}");
};
listBox.SelectAll();Virtual Mode
var listBox = new BeepListBox
{
VirtualMode = true,
ItemCount = 1000000
};
listBox.RetrieveVirtualItem += (s, e) =>
{
e.Item = new BeepListItem
{
Text = $"Virtual Row {e.Index}",
SubText = $"Index: {e.Index:N0}",
Tag = _data[e.Index] // backing array
};
};
listBox.CacheVirtualItems += (s, e) =>
{
for (int i = e.StartIndex; i <= e.EndIndex; i++)
{
if (!_cache.ContainsKey(i))
_cache[i] = _fetchData(i);
}
};Type-to-Search
var listBox = new BeepListBox();
listBox.Items.Add(new BeepListItem { Text = "Apple" });
listBox.Items.Add(new BeepListItem { Text = "Application" });
listBox.Items.Add(new BeepListItem { Text = "Apricot" });
listBox.Items.Add(new BeepListItem { Text = "Banana" });
listBox.Items.Add(new BeepListItem { Text = "Blackberry" });
listBox.SetSelected(0, true);
listBox.Focus();