Item model: TheTechIdea.Beep.Winform.Controls.Models.SimpleItem  ·  Row renderer: BeepListItem  ·  Shared ItemTemplate: BeepItemTemplates.SimpleItem

The hard rule

Every list / dropdown / menu / selectable-collection control in this library uses SimpleItem as its item model. This is a rule, not a default. Never give a Beep list control an object / string / arbitrary POCO item model, and never lean on DisplayMemberPath to render some other type — the item is a SimpleItem, and it renders through its Text / Value / icon surface as a themed BeepListItem row.

Because it is the same model that WinForms and the Beep.Wpf.Data.Integrated dialogs pass around, a list built in one place flows unchanged to another. When a control needs to show a list, it should host a BeepListBox in its template rather than a raw ListBox — that inherits SimpleItem rendering, theming and selection for free. BeepLovPopup is the canonical example.

Two usings mark a SimpleItem-bearing control (mirror BeepListBox.cs):
using TheTechIdea.Beep.Vis.Modules;
using TheTechIdea.Beep.Winform.Controls.Models;

The SimpleItem model

SimpleItem is class SimpleItem : IEquatable<SimpleItem>, INotifyPropertyChanged in namespace TheTechIdea.Beep.Winform.Controls.Models (defined in Beep.Vis.Modules2.0/SimpleItem.cs). Yes, it lives in a WinForms-named namespace — that is intentional: it is the shared data shape across the whole Beep stack, so do not define a WPF replacement. The members a Beep list row reads:

MemberTypeRole
TextstringPrimary display text of the row.
SubTextstringSecondary line (used by Avatar / Card variants).
DescriptionstringLonger descriptive text / stored selection detail.
ImagePathstringLeading icon — an embedded SVG name, pack URI, file path or path geometry (see the icon pipeline).
ValueobjectThe underlying key / bound value of the item.
ChildrenBindingList<SimpleItem>Child items for hierarchical (tree / sub-menu) lists.
IsCheckedboolCheck state — bound by the CheckBox variant.
IsSelectedboolSelected state — bound by the Radio variant.

Reference shape (BeepListBox)

BeepListBox extends the native System.Windows.Controls.ListBox and is the canonical shape every Beep list control mirrors. Two things define that shape:

  • A bindable items DP named ListItems (typed IEnumerable) whose change callback forwards to the inherited ItemsSource — so consumers write ListItems="{Binding ...}" without ever touching ItemsSource.
  • A SelectedItem re-typed as SimpleItem with the new keyword, hiding the base object-typed property so consumers read .Text / .Value directly without casting.
public partial class BeepListBox : System.Windows.Controls.ListBox
{
    // Bindable source — forwards to the inherited ItemsSource.
    public static readonly DependencyProperty ListItemsProperty =
        DependencyProperty.Register(nameof(ListItems), typeof(IEnumerable),
            typeof(BeepListBox), new FrameworkPropertyMetadata(null, OnListItemsChanged));

    public IEnumerable ListItems
    {
        get => (IEnumerable)GetValue(ListItemsProperty);
        set => SetValue(ListItemsProperty, value);
    }

    private static void OnListItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is BeepListBox lb) lb.ItemsSource = e.NewValue as IEnumerable;
    }

    // Hides ListBox.SelectedItem (object) so consumers can read .Text / .Value.
    public new SimpleItem SelectedItem
    {
        get => base.SelectedItem as SimpleItem;
        set => base.SelectedItem = value;
    }
}

The full sweep added this same model surface to every collection control via a per-control Beep<Name>.Items.cs partial (so the original stub stays untouched). Selector-based controls (ListBox / ComboBox / TabControl) get ListItems + new SimpleItem SelectedItem: BeepListBox, BeepComboBox, BeepDropDownCheckBoxSelect, BeepScrollList, BeepListofValuesBox, BeepTabControl. ItemsControl / ContentControl carriers get ListItems forwarding only (BeepSideMenu, BeepChipGroup, BeepNavBar, BeepTreeView, BeepBreadcrumb, and more).

BeepListItem & the shared ItemTemplate

BeepListItem is the row renderer used by every flat-list control. Each SimpleItem row is materialized as a BeepListItem whose Item DP (typed SimpleItem) is the row's data. Flat-list controls assign the single, skin-independent BeepItemTemplates.SimpleItem as their ItemTemplate — set as a local value in the constructor so it survives the fe.Style = skinStyle swap that BeepChrome performs when the skin changes.

The template itself is tiny — it just wraps the bound SimpleItem in a BeepListItem:

<DataTemplate xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
              xmlns:beep="clr-namespace:TheTechIdea.Beep.WPF.Controls;assembly=TheTechIdea.Beep.WPF.Controls">
    <beep:BeepListItem Item="{Binding}"/>
</DataTemplate>
Why local value? Setting ItemTemplate (and, on each row, the resolved variant ContentTemplate) as a local value means it outranks the style setters a skin swap applies — so the SimpleItem rendering never gets frozen on the default skin when the app re-skins live.

Draw modes — the ItemVariant enum

How a row draws is an ItemVariant — the WPF trim of the WinForms ~44 ListBoxType painters down to the core set:

VariantRow look
StandardText only.
WithIcons (default)Leading icon + text.
CheckBoxCheckbox + text, bound to SimpleItem.IsChecked.
RadioRadio button + text, bound to SimpleItem.IsSelected.
AvatarCircular avatar image + text + subtext.
CardElevated card: icon + title + description.
CompactDense single-line text, tight padding.
ChipPill / chip background around the text.

The variant is selected through the inherited BeepListItem.Variant attached property: set it once on the list and every row inherits it (FrameworkPropertyMetadataOptions.Inherits).

Variants are per-skin. Skin and variant are two independent axes. Every skin in Themes/Styles/*.xaml defines its own eight variant DataTemplates, keyed {DesignSystem}BeepListItemVariant.{Variant} (for example Material3BeepListItemVariant.Card), built from that skin's own tokens — so a Card in Material3 can differ structurally from a Card in Apple. BeepListItem.Variant.cs resolves the active skin's template via TryFindResource and assigns it to ContentTemplate as a local value, re-resolving in OnApplyTemplate when the skin changes. Themes/BeepListItemVariants.xaml is the fallback used when ControlStyle is None.

Adding a variant means adding the enum value and a {Skin}BeepListItemVariant.{New} template to every skin in Themes/Styles/*.xaml, plus the fallback in Themes/BeepListItemVariants.xaml. Never regress a list control to an object / DisplayMemberPath model.

XAML & C# examples

Bind a collection of SimpleItem through ListItems — no ItemsSource, no DisplayMemberPath:

<beep:BeepListBox ListItems="{Binding Countries}"
                  SelectedItem="{Binding SelectedCountry, Mode=TwoWay}"/>

Choose a row draw mode for the whole list — set the inherited attached property once and every row inherits it:

<beep:BeepListBox ListItems="{Binding People}"
                  beep:BeepListItem.Variant="Card"/>

<!-- checkbox rows, bound to SimpleItem.IsChecked -->
<beep:BeepListBox ListItems="{Binding Permissions}"
                  beep:BeepListItem.Variant="CheckBox"/>

The same attached property can be set in code:

BeepListItem.SetVariant(myListBox, ItemVariant.Chip);

Construct SimpleItem instances to feed a list — the icon is just a string (ImagePath), resolved by the icon pipeline:

using TheTechIdea.Beep.Winform.Controls.Models;

var items = new ObservableCollection
{
    new SimpleItem { Text = "Dashboard", ImagePath = "fi-tr-apps",   Value = "dashboard" },
    new SimpleItem { Text = "Reports",   ImagePath = "fi-tr-chart-pie", Value = "reports",
                     SubText = "Sales & revenue" },
    new SimpleItem { Text = "Settings",  ImagePath = "fi-tr-settings", Value = "settings",
                     IsChecked = true }
};

myListBox.ListItems = items;                 // ListItems, not ItemsSource
SimpleItem picked = myListBox.SelectedItem;  // typed SimpleItem — read .Text / .Value directly

Where SimpleItem does NOT apply

Not every data-bearing control is a SimpleItem list. A "list of values" is SimpleItem; a "grid or feed of arbitrary business records" is not. These stay on a plain ItemsSource of arbitrary objects and must never be routed through BeepListBox or forced onto a SimpleItem model:

ControlWhy it is exempt
BeepDataGridA separate beast entirely — a full data grid over arbitrary business records (its own columns, cells, editing, paging), bound via ItemsSource, with its own BEEPDATAGRID_ENHANCEMENT_PLAN.md.
BeepBindingNavigatorNavigates an arbitrary record set, not a SimpleItem list.
BeepDataNavigatorSame — navigates arbitrary records via ItemsSource.