Control Architecture
The contract every Beep* control follows: derive the native
WPF primitive, override the default style key in a static constructor, implement only the
capability interfaces you need, and put 100% of the chrome in XAML. No BaseControl,
no code-built visual tree, no WinForms, no hardcoded colours.
BaseControl-based WinForms port. Every leading WPF library (WpfUI,
MaterialDesignInXAML, HandyControl, MahApps) inherits the native primitive and layers small
capability interfaces plus pure-XAML templates. Beep.WPF follows that industry pattern — see
WPF_MODERNIZATION_PLAN.md.
On this page
The control contract
Every control in TheTechIdea.Beep.WPF.Controls obeys the same seven rules. They are
not style preferences — a control that breaks one of them will not theme, will not skin, or will
not compile against the modernized library.
| # | Rule | What it means |
|---|---|---|
| 1 | Derive the native primitive | The class extends the matching
System.Windows.Controls.* type (Button, TextBox,
ListBox, …), inheriting its events, commands, keyboard and automation for free. |
| 2 | No BaseControl | BaseControl.cs was deleted.
There is nothing to derive; referencing it breaks the build with CS0246. |
| 3 | Static-ctor default style key | A static constructor calls
DefaultStyleKeyProperty.OverrideMetadata so the XAML default style resolves. |
| 4 | Capability interfaces only | Implement IBeepThemeable and
only the other capability interfaces the control actually needs. See
Capability Interfaces. |
| 5 | No code-built visual tree | The constructor never builds a
Grid/Border/StackPanel. All chrome lives in the
ControlTemplate. |
| 6 | No legacy override kinds | No
override ApplyTheme / OnControlStyleApplied / SetValue / GetValue — those
are the signatures of pre-modernization code and do not compile here. |
| 7 | No WinForms, no hardcoded XAML colours | No
System.Windows.Forms / System.Drawing; no
Color="#RRGGBB" or Brushes.Black in a template — colours come
from DynamicResource chrome tokens. |
[ToolboxItem(true)] and
[Category("Beep Controls")] so it appears in the Visual Studio toolbox with a
parameterless constructor and a resolvable default style.
Canonical control skeleton
This is the shape to mirror when adding or modernizing a control. Only the DPs that add value are
declared; Background, Foreground, Padding,
FontSize, Command, etc. come from the native base.
namespace TheTechIdea.Beep.WPF.Controls;
/// <summary>
/// WPF-modern BeepName — thin subclass of the native primitive, implementing
/// only the capability interfaces it needs. All chrome lives in
/// Themes/Controls/BeepName.xaml via BeepChrome tokens + DynamicResource.
/// </summary>
[ToolboxItem(true)]
[Category("Beep Controls")]
public partial class BeepName : System.Windows.Controls.NativeBase, IBeepThemeable
{
static BeepName()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(BeepName),
new FrameworkPropertyMetadata(typeof(BeepName)));
}
// BeepChrome CLR wrappers — theme + control-style opt-in
public string Theme
{
get => BeepChrome.GetTheme(this);
set => BeepChrome.SetTheme(this, value);
}
public BeepControlStyle ControlStyle
{
get => BeepChrome.GetControlStyle(this);
set => BeepChrome.SetControlStyle(this, value);
}
public BeepName() { /* no code-built visual tree; the template renders it */ }
}
The static constructor is the load-bearing line: DefaultStyleKeyProperty.OverrideMetadata
tells WPF to look for a Style TargetType="{x:Type beep:BeepName}" — which is resolved
against Themes/Generic.xaml (declared by [assembly: ThemeInfo]).
Forbidden patterns
These are the exact signals of pre-modernization code. If you see one while modernizing a legacy stub, it must go.
| Forbidden | Use instead |
|---|---|
public class BeepName : BaseControl | Derive the native primitive (see the reference table below). |
public override void ApplyTheme() { … } | A DP callback or template trigger; chrome reacts to tokens, not to a virtual. |
public override void OnControlStyleApplied() { … } | The skin's keyed
style is applied by BeepChrome; no override needed. |
override SetValue / GetValue | Never override
DependencyObject virtuals — declare a DependencyProperty. |
using System.Windows.Forms; / System.Drawing; | WPF types only. |
Color="#RRGGBB" / new SolidColorBrush(...) in a template |
{DynamicResource BeepName.Background} — a chrome token. |
Constructor builds a Grid/Border | Put the visual tree in
the ControlTemplate in Themes/Controls/BeepName.xaml. |
Native-primitive base reference
Picking the base is the single most important decision when adding a control — it determines which
events, properties and automation you inherit. Choose the closest native primitive; fall back to
ContentControl only when none fits, and UserControl only for genuine
multi-element composites.
| Control kind | Native base | Example Beep controls |
|---|---|---|
| Button-like (text + icon) | System.Windows.Controls.Button | BeepButton |
| Text input | System.Windows.Controls.TextBox | BeepTextBox, BeepText |
| Password input | System.Windows.Controls.PasswordBox | BeepPasswordBox |
| Single-choice / toggle | CheckBox / RadioButton / ToggleButton | BeepCheckBox, BeepRadioButton, BeepToggleSwitch |
| List / dropdown / items | ListBox / ComboBox / ItemsControl | BeepListBox, BeepComboBox, BeepChipGroup |
| Date / time | Calendar (wrap DatePicker for input) | BeepCalendar, BeepDatePicker |
| Range / slider | Slider / ScrollBar | BeepSlider, BeepScrollBar |
| Progress | System.Windows.Controls.ProgressBar | BeepProgressBar |
| Tree | System.Windows.Controls.TreeView | BeepTreeView |
| Data grid | System.Windows.Controls.DataGrid | BeepDataGrid |
| Tab / toolbar / status | TabControl / ToolBar / StatusBar | BeepTabControl, BeepToolBar |
| Menu / context menu | Menu / ContextMenu / MenuItem | BeepMenuBar, BeepContextMenu |
| Card / badge / panel / label | ContentControl / Label | BeepCard, BeepBadge, BeepLabel |
| Window / page / frame | Window / Page / Frame | BeepWindow |
| No primitive fits | System.Windows.Controls.ContentControl | BeepListItem, BeepBadgeChip |
| Multi-element composite (last resort) | System.Windows.Controls.UserControl | BeepStepperBar, BeepListofValuesBox |
Background,
Foreground, BorderBrush, Padding, Margin,
FontSize, IsEnabled, Visibility, Command,
Click and friends already come from the WPF base. Only declare DPs that add real value
(icons, appearance, validation, corner radius). Use AddOwner when you need to share a
native DP; never shadow one.
Where the files live
A control is spread across a small, fixed set of locations. Learn them once and every control is predictable.
| Path | Purpose |
|---|---|
BeepName.cs (+ BeepName.Props.cs) | The control class and its dependency properties. Thin — mostly DPs and CLR wrappers. |
Themes/Controls/BeepName.xaml | The default Style +
ControlTemplate. All chrome via DynamicResource; all icons via
BeepImage; PART_-named elements for OnApplyTemplate. |
Themes/ControlDefaults.xaml | The single source of truth for chrome
tokens (BeepName.Background, BeepName.CornerRadius, per-state
brushes). Loaded at runtime; templates resolve these keys. Missing key ⇒ null ⇒ no chrome. |
Themes/Styles/{skin}.xaml | The skin (design system) — one keyed style
per control per skin, e.g. Material3BeepButtonStyle. Also carries that skin's
palette. Loaded by BeepChrome.OnControlStyleChanged from a pack URI. |
Themes/Generic.xaml | The WPF default-style entry point (declared by
[assembly: ThemeInfo]). What DefaultStyleKeyProperty.OverrideMetadata
resolves against. |
Controls/BeepChrome.cs | The attached-property mixin:
ControlStyle, Theme, state brushes, and the skin-loading /
skin-application logic. |
Controls/Theming/IBeep*.cs | The capability interface definitions. |
BeepControlStyle enum: Material3, Fluent, Apple, …) and holds one style per control in
Themes/Styles/. A theme is the colours and fonts for a skin.
Setting ControlStyle drives the theme; the skin's per-control style carries that theme's
colours onto every control.
Recipe: add or modernize a control
The end-to-end procedure. When modernizing a legacy stub, steps 1–2 are the demolition; steps 3–8 are the rebuild.
- Read the existing file (if modernizing). Find the legacy override kinds
(
ApplyTheme/OnControlStyleApplied/SetValue/GetValue), everyBaseControlreference, WinForms-typed DPs, andSystem.Drawing. All go. - Pick the native primitive base from the reference table — the most important decision.
- Add the static ctor with
DefaultStyleKeyProperty.OverrideMetadata(typeof(BeepName), …), plus[ToolboxItem(true)]/[Category("Beep Controls")]. - Implement
IBeepThemeableand any other capability interface the control needs; add theBeepChromeCLR wrappers. Use public members forIBeepIconHostso it is satisfied implicitly. - Add chrome tokens to
Themes/ControlDefaults.xamlmatching the keys the template will reference (SolidColorBrushfor colours,Thicknessfor paddings,CornerRadiusfor radii,Doublefor sizes). - Write
Themes/Controls/BeepName.xaml— aStyle TargetType="{x:Type beep:BeepName}"with aControlTemplate. All chrome viaDynamicResource; icons inside the themedRootBorder, never as siblings;PART_-name anything code-behind wires inOnApplyTemplate. - Add per-skin styles (recommended, separate commit) in
Themes/Styles/{skin}.xamlas{Skin}BeepNameStyle, reusing the same token key names. Validate the default works before shadowing it with a per-skin override. - Drop the code-built visual tree (if modernizing) and build:
dotnet build … -c Debug⇒ 0 errors. Then verify live in a consumer app that the control renders and re-skins onControlStylechange.
<!-- Themes/Controls/BeepName.xaml — the default style -->
<Style TargetType="{x:Type beep:BeepName}">
<Setter Property="Background" Value="{DynamicResource BeepName.Background}"/>
<Setter Property="Foreground" Value="{DynamicResource BeepName.Foreground}"/>
<Setter Property="Padding" Value="{DynamicResource BeepName.Padding}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type beep:BeepName}">
<Border x:Name="RootBorder"
Background="{TemplateBinding Background}"
BorderBrush="{DynamicResource BeepName.BorderBrush}"
BorderThickness="{DynamicResource BeepName.BorderThickness}"
CornerRadius="{DynamicResource BeepName.CornerRadius}"
Padding="{TemplateBinding Padding}">
<ContentPresenter VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>