Files
YMhut-box-C-/参考/wpfui-main/docs/architecture/decisions/ADR-002-control-library-architecture.md
T
2026-07-06 23:05:40 +08:00

324 lines
9.5 KiB
Markdown

# ADR-002: Control Library Architecture
## Status
Accepted
## Context
The WPF UI library provides 77+ Fluent Design System controls. The architecture must support:
- Clear code organization
- XAML implicit styling
- Type-safe intellisense
- Easy discovery for developers
- Maintainable codebase at scale
## Decision
### Folder-Per-Control Structure
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:**
- Physical isolation prevents coupling between controls
- Easy to locate all files related to a control
- Supports partial class decomposition for complex controls
- Clear ownership boundaries
### Paired .cs + .xaml Files
Each control consists of:
1. **{ControlName}.cs** - Control class with code-behind
2. **{ControlName}.xaml** - ResourceDictionary with implicit style
**Control Class Pattern:**
```csharp
// 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:**
```xaml
<!-- 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>
```
### Flat Namespace Strategy
**All controls use a single namespace:** `Wpf.Ui.Controls`
```csharp
// Physical path: Controls/Button/Button.cs
namespace Wpf.Ui.Controls; // NOT Wpf.Ui.Controls.Button
// ReSharper disable once CheckNamespace // Suppress warning
```
**Rationale:**
- Simpler XAML namespace mapping (`xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"`)
- No need for consumers to know physical folder structure
- Consistent with WPF framework controls (all in System.Windows.Controls)
- Better intellisense experience (all controls in one namespace dropdown)
**Trade-off:** IDE warning suppression required (`IDE0130: CheckNamespace`)
### Partial Class Decomposition
Complex controls split across multiple files:
**NavigationView example:**
- **NavigationView.Base.cs** - Core logic, template application
- **NavigationView.Properties.cs** - 27 dependency properties
- **NavigationView.Events.cs** - 7 routed events
- **NavigationView.Navigation.cs** - Navigation journal logic
- **NavigationView.TemplateParts.cs** - Template part fields and bindings
- **NavigationView.AttachedProperties.cs** - Attached properties
**ContentDialog example:**
- **ContentDialog.cs** - Main implementation (714 lines)
- **ContentDialog.FocusBehavior.cs** - Keyboard focus management
**Benefits:**
- Files stay under 300-500 lines (typically)
- Clear separation of concerns
- Easy to navigate specific aspects
- Reduces merge conflicts
**Naming Convention:** `{ControlName}.{Concern}.cs`
### DependencyProperty Pattern
**Registration:**
```csharp
/// <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:**
```csharp
[Bindable(true)]
[Category("Appearance")]
public IconElement? Icon
{
get => (IconElement?)GetValue(IconProperty);
set => SetValue(IconProperty, value);
}
```
**Callback Pattern:**
```csharp
private static void OnIconChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is Button button)
{
button.UpdateIconVisibility();
}
}
```
### Capability Interfaces
Controls implement interfaces for cross-cutting capabilities:
#### IAppearanceControl
```csharp
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
#### IIconControl
```csharp
public interface IIconControl
{
IconElement? Icon { get; set; }
}
```
**Used by:** Button, NavigationViewItem, AutoSuggestBox
#### IThemeControl
```csharp
public interface IThemeControl
{
Appearance.ApplicationTheme ApplicationTheme { get; }
}
```
**Used by:** TitleBar, controls that need direct theme awareness
**Benefits:**
- Type-safe capability detection
- Shared behavior implementation
- Consistent property naming across controls
## Control Categories
### Window Chrome
FluentWindow, TitleBar, ClientAreaBorder, Window
### Navigation
NavigationView, NavigationViewItem, BreadcrumbBar, TabControl, TabView, Menu
### Buttons
Button, HyperlinkButton, DropDownButton, SplitButton, ToggleButton, ToggleSwitch
### Text Input
TextBox, PasswordBox, RichTextBox, AutoSuggestBox, NumberBox
### Dialogs & Overlays
ContentDialog, ContentDialogHost, MessageBox, Flyout, Snackbar, SnackbarPresenter
### Data Display
Card, InfoBar, InfoBadge, Badge, ListView, DataGrid, TreeView
### Pickers
CalendarDatePicker, DatePicker, TimePicker, ColorPicker
### Progress & Feedback
ProgressBar, ProgressRing, RatingControl, ThumbRate
### Icons
IconElement, FontIcon, SymbolIcon, ImageIcon, IconSourceElement
### Layout
Anchor, Page, Frame, Expander, Separator, Slider
## Enforcement
### MUST Follow
1. **Each control in its own subfolder** under `Controls/{ControlName}/`
2. **Paired .cs + .xaml files** with matching names
3. **Flat namespace** `Wpf.Ui.Controls` for all controls
4. **Static constructor** with `DefaultStyleKeyProperty.OverrideMetadata`
5. **Implicit style** in XAML with `TargetType` (no `x:Key`)
6. **`OverridesDefaultStyle=True`** in all control styles
7. **`SnapsToDevicePixels=True`** in all control styles
8. **XML documentation** with `<summary>` and `<example>` for public API
### MUST NOT Do
1. **Never nest controls** in subdirectories (keep flat under Controls/)
2. **Never use different namespace** from `Wpf.Ui.Controls`
3. **Never create keyed styles** as primary style (use implicit TargetType)
4. **Never reference controls** by folder structure in documentation
### Verification
- IDE0130 (CheckNamespace) suppressed in .editorconfig
- WpfAnalyzers enforces DependencyProperty correctness
- StyleCop rules enforce XML documentation (SA1600 suppressed for internal members)
## Consequences
### Positive
- **Highly Discoverable:** Single namespace for all 77+ controls
- **Scalable:** Adding new controls doesn't affect existing structure
- **Maintainable:** Clear boundaries and partial class decomposition
- **Type-Safe:** Interface-based capabilities enable polymorphism
- **Consistent:** Uniform naming and organization patterns
### Negative
- **IDE Warnings:** Namespace/folder mismatch requires suppression
- **Large Directory:** 77+ control folders in single directory
- **No Categorization:** Physical structure doesn't reflect logical categories
- **Partial Class Complexity:** Large controls split across many files
## Alternatives Considered
### Nested Category Folders
```
Controls/
├── Buttons/
│ ├── Button/
│ └── ToggleSwitch/
└── Navigation/
└── NavigationView/
```
**Rejected:** Would require category-specific namespaces or deeper folder/namespace mismatch.
### Category-Based Namespaces
```csharp
namespace Wpf.Ui.Controls.Buttons;
namespace Wpf.Ui.Controls.Navigation;
```
**Rejected:** Requires consumers to know category classification. Inconsistent with WPF framework patterns.
### Single File Per Control
**Rejected:** Controls like NavigationView have 1000+ lines. Unmanageable in single file.
## References
- WPF Framework Control Architecture: `System.Windows.Controls` namespace
- WinUI 3 Control Architecture: Flat namespace strategy
- [WPF Control Authoring](https://docs.microsoft.com/dotnet/desktop/wpf/controls/control-authoring-overview)