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 GroupContainsKey Members
PropertiesPublic propertiesItems, ItemStyle, SelectionMode, MultiSelect, ItemHeight, DataSource, DisplayMember, ValueMember, VirtualMode, ItemCount, Sorted
MethodsPublic methodsAddItem(), RemoveItem(), ClearItems(), GetItemAt(), ScrollToItem(), EnsureVisible(), BeginUpdate(), EndUpdate(), FindItem()
EventsEvent declarationsSelectedIndexChanged, ItemClicked, ItemDoubleClicked, SelectionChanging, ItemDragStarted, ItemsReordered, DrawItem
DrawingPaint logicOnPaint(), OnPaintBackground(), painter delegation to IListBoxPainter, item measuring via BaseListBoxPainter.MeasureItem()
SelectionSelection state machineSelectionManager class, SelectedIndices, SelectedItems, SelectAll(), ClearSelection(), single/multi/extended modes
DragDropDrag-drop reorderOnMouseDown(), OnDragOver(), OnDragDrop(), DoDragDrop(), drop indicator rendering, AllowDragReorder property
KeyboardKeyboard handlingOnKeyDown(), arrow/PageUp/PageDown/Home/End navigation, type-to-search buffer, focus tracking, FocusedItemIndex
AccessibilityUIA providerBeepListBoxAccessibleObject, BeepListBoxItemAccessibleObject, UIA patterns: Selection, Invoke, Scroll, role mapping
VirtualModeVirtual scrollingRetrieveVirtualItem event, CacheVirtualItems event, item recycling pool, visible window management
ItemsItem data modelBeepListItem class: Text, SubText, ImagePath, Tag, IsEnabled, IsSeparator, custom data binding

Selection System

The SelectionManager class (internal) manages all selection state with three modes:

ModeEnum ValueBehavior
NoneSelectionMode.NoneItems cannot be selected
SingleSelectionMode.OneOnly one item can be selected at a time; clicking another deselects the previous
MultiSimpleSelectionMode.MultiSimpleClick toggles selection; Ctrl+click adds/removes from selection; no range selection
MultiExtendedSelectionMode.MultiExtendedFull 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.

PhaseEvent / MethodDescription
InitiateOnMouseDown → DoDragDropMouse down on an item starts drag; movement beyond threshold begins drag operation
Drag OverOnDragOverDrop position is resolved: above item, below item, or onto item; drop indicator line is drawn
DropOnDragDropItem is removed from original position and inserted at drop position; ItemsReordered event fires
VisualDrop indicatorA 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:

KeyAction
↑ / ↓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 DownMove focus by one visible page. Selection behavior matches arrow keys.
Home / EndMove focus to first/last item.
SpaceIn multi-select mode: toggles selection of the focused item. In single-select: selects the focused item.
Ctrl+ASelects all items (multi-extended mode only).
Character keysType-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:

ClassPurpose
BeepListBoxAccessibleObjectUIA provider for the list box control itself. Supports the SelectionPattern (canSelectMultiple, getSelection, IsSelectionRequired) and ScrollPattern (VerticallyScrollable, VerticalViewSize, SetScrollPercent). Reports ControlType.List to screen readers.
BeepListBoxItemAccessibleObjectUIA 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.

MemberTypeDescription
VirtualModeboolEnables virtual data mode; when true, Items collection is not used
ItemCountintTotal number of virtual items (must be set when VirtualMode is true)
RetrieveVirtualItemEventFired when a BeepListItem is needed for display; handler must populate and return it
CacheVirtualItemsEventFired to suggest caching a range of items (pre-fetch optimization)
ItemRecyclingInternalA 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();