docs/architecture/decisions/ADR-002-control-library-architecture.md
Accepted
The WPF UI library provides 77+ Fluent Design System controls. The architecture must support:
Each control resides in its own subfolder under src/Wpf.Ui/Controls/{ControlName}/:
Controls/
├── Button/
│ ├── Button.cs # Control class
│ └── Button.xaml # Implicit style ResourceDictionary
├── NavigationView/
│ ├── NavigationView.Base.cs # Core logic
│ ├── NavigationView.Properties.cs # Dependency properties
│ ├── NavigationView.Events.cs # Routed events
│ ├── NavigationView.Navigation.cs # Navigation logic
│ ├── NavigationView.TemplateParts.cs # Template part bindings
│ ├── NavigationView.AttachedProperties.cs
│ └── NavigationView.xaml # Implicit style
└── ContentDialog/
├── ContentDialog.cs
├── ContentDialog.FocusBehavior.cs # Focused concern
├── ContentDialogHost.cs # Host control
├── ContentDialogHostBehavior.cs
├── EventArgs/ # Supporting types
└── ContentDialog.xaml
Benefits:
Each control consists of:
Control Class Pattern:
// Controls/Button/Button.cs
namespace Wpf.Ui.Controls; // Flat namespace
// ReSharper disable once CheckNamespace
public class Button : System.Windows.Controls.Button, IAppearanceControl, IIconControl
{
static Button()
{
DefaultStyleKeyProperty.OverrideMetadata(
typeof(Button),
new FrameworkPropertyMetadata(typeof(Button))
);
}
// Dependency properties
public static readonly DependencyProperty IconProperty =
DependencyProperty.Register(nameof(Icon), ...);
}
XAML Style Pattern:
<!-- Controls/Button/Button.xaml -->
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:Wpf.Ui.Controls">
<Thickness x:Key="ButtonPadding">11,5,11,6</Thickness>
<Style TargetType="{x:Type controls:Button}">
<Setter Property="OverridesDefaultStyle" Value="True" />
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:Button}">
<!-- Control template here -->
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
All controls use a single namespace: Wpf.Ui.Controls
// Physical path: Controls/Button/Button.cs
namespace Wpf.Ui.Controls; // NOT Wpf.Ui.Controls.Button
// ReSharper disable once CheckNamespace // Suppress warning
Rationale:
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml")Trade-off: IDE warning suppression required (IDE0130: CheckNamespace)
Complex controls split across multiple files:
NavigationView example:
ContentDialog example:
Benefits:
Naming Convention: {ControlName}.{Concern}.cs
Registration:
/// <summary>Identifies the <see cref="Icon"/> dependency property.</summary>
public static readonly DependencyProperty IconProperty = DependencyProperty.Register(
nameof(Icon),
typeof(IconElement),
typeof(Button),
new PropertyMetadata(null, OnIconChanged, IconElement.Coerce)
);
CLR Wrapper:
[Bindable(true)]
[Category("Appearance")]
public IconElement? Icon
{
get => (IconElement?)GetValue(IconProperty);
set => SetValue(IconProperty, value);
}
Callback Pattern:
private static void OnIconChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is Button button)
{
button.UpdateIconVisibility();
}
}
Controls implement interfaces for cross-cutting capabilities:
public interface IAppearanceControl
{
ControlAppearance Appearance { get; set; }
}
public enum ControlAppearance
{
Primary, Secondary, Info, Dark, Light,
Danger, Success, Caution, Transparent
}
Used by: Button, Badge, Snackbar, HyperlinkButton
public interface IIconControl
{
IconElement? Icon { get; set; }
}
Used by: Button, NavigationViewItem, AutoSuggestBox
public interface IThemeControl
{
Appearance.ApplicationTheme ApplicationTheme { get; }
}
Used by: TitleBar, controls that need direct theme awareness
Benefits:
FluentWindow, TitleBar, ClientAreaBorder, Window
NavigationView, NavigationViewItem, BreadcrumbBar, TabControl, TabView, Menu
Button, HyperlinkButton, DropDownButton, SplitButton, ToggleButton, ToggleSwitch
TextBox, PasswordBox, RichTextBox, AutoSuggestBox, NumberBox
ContentDialog, ContentDialogHost, MessageBox, Flyout, Snackbar, SnackbarPresenter
Card, InfoBar, InfoBadge, Badge, ListView, DataGrid, TreeView
CalendarDatePicker, DatePicker, TimePicker, ColorPicker
ProgressBar, ProgressRing, RatingControl, ThumbRate
IconElement, FontIcon, SymbolIcon, ImageIcon, IconSourceElement
Anchor, Page, Frame, Expander, Separator, Slider
Controls/{ControlName}/Wpf.Ui.Controls for all controlsDefaultStyleKeyProperty.OverrideMetadataTargetType (no x:Key)OverridesDefaultStyle=True in all control stylesSnapsToDevicePixels=True in all control styles<summary> and <example> for public APIWpf.Ui.ControlsControls/
├── Buttons/
│ ├── Button/
│ └── ToggleSwitch/
└── Navigation/
└── NavigationView/
Rejected: Would require category-specific namespaces or deeper folder/namespace mismatch.
namespace Wpf.Ui.Controls.Buttons;
namespace Wpf.Ui.Controls.Navigation;
Rejected: Requires consumers to know category classification. Inconsistent with WPF framework patterns.
Rejected: Controls like NavigationView have 1000+ lines. Unmanageable in single file.
System.Windows.Controls namespace