更新客户端渲染,更新了壳
This commit is contained in:
@@ -0,0 +1,323 @@
|
||||
# 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)
|
||||
Reference in New Issue
Block a user