SimpleItem & List Controls
The hard convention that binds the whole library together: every
list, dropdown, menu and selectable-collection control uses one shared item model —
SimpleItem — renders each row through BeepListItem, and picks its
row look from a per-skin ItemVariant draw mode.
TheTechIdea.Beep.Winform.Controls.Models.SimpleItem ·
Row renderer: BeepListItem ·
Shared ItemTemplate: BeepItemTemplates.SimpleItem
On this page
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.
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:
| Member | Type | Role |
|---|---|---|
Text | string | Primary display text of the row. |
SubText | string | Secondary line (used by Avatar / Card variants). |
Description | string | Longer descriptive text / stored selection detail. |
ImagePath | string | Leading icon — an embedded SVG name, pack URI, file path or path geometry (see the icon pipeline). |
Value | object | The underlying key / bound value of the item. |
Children | BindingList<SimpleItem> | Child items for hierarchical (tree / sub-menu) lists. |
IsChecked | bool | Check state — bound by the CheckBox variant. |
IsSelected | bool | Selected 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(typedIEnumerable) whose change callback forwards to the inheritedItemsSource— so consumers writeListItems="{Binding ...}"without ever touchingItemsSource. - A
SelectedItemre-typed asSimpleItemwith thenewkeyword, hiding the baseobject-typed property so consumers read.Text/.Valuedirectly 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>
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:
| Variant | Row look |
|---|---|
Standard | Text only. |
WithIcons (default) | Leading icon + text. |
CheckBox | Checkbox + text, bound to SimpleItem.IsChecked. |
Radio | Radio button + text, bound to SimpleItem.IsSelected. |
Avatar | Circular avatar image + text + subtext. |
Card | Elevated card: icon + title + description. |
Compact | Dense single-line text, tight padding. |
Chip | Pill / 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.
{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:
| Control | Why it is exempt |
|---|---|
BeepDataGrid | A 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. |
BeepBindingNavigator | Navigates an arbitrary record set, not a SimpleItem list. |
BeepDataNavigator | Same — navigates arbitrary records via ItemsSource. |