更新客户端渲染,更新了壳
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
# Navigation System
|
||||
|
||||
> WPF UI v4.2.0 | Cross-Cutting Concern
|
||||
|
||||
## Overview
|
||||
|
||||
The WPF UI navigation system provides page-based navigation within `NavigationView`, with support for page caching, back stack management, transition animations, and lifecycle callbacks. It integrates with Microsoft.Extensions.DependencyInjection for type-based page resolution.
|
||||
|
||||
---
|
||||
|
||||
## Navigation Lifecycle
|
||||
|
||||
The following sequence diagram shows the complete flow from a `NavigationService.Navigate()` call through to the page being displayed with transition animations.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Consumer as Consumer Code
|
||||
participant NavService as NavigationService
|
||||
participant NavView as NavigationView
|
||||
participant Provider as INavigationViewPageProvider
|
||||
participant Cache as Page Cache
|
||||
participant Frame as Frame
|
||||
participant OldPage as Old Page (INavigationAware)
|
||||
participant NewPage as New Page (INavigationAware)
|
||||
participant Animator as TransitionAnimationProvider
|
||||
|
||||
Consumer->>NavService: Navigate(typeof(MyPage))
|
||||
NavService->>NavView: NavigateInternal(pageType)
|
||||
NavView->>NavView: Check if same page (skip if current)
|
||||
|
||||
NavView->>Provider: GetPage(pageType)
|
||||
Provider->>Provider: Resolve via IServiceProvider or Activator
|
||||
|
||||
alt NavigationCacheMode.Enabled or Required
|
||||
Provider->>Cache: Check cache for pageType
|
||||
Cache-->>Provider: Cached instance or null
|
||||
alt Cache hit
|
||||
Provider-->>NavView: Return cached page
|
||||
else Cache miss
|
||||
Provider->>Provider: Create new instance
|
||||
Provider->>Cache: Store in cache
|
||||
Provider-->>NavView: Return new page
|
||||
end
|
||||
else NavigationCacheMode.Disabled
|
||||
Provider->>Provider: Create new instance (always)
|
||||
Provider-->>NavView: Return new page
|
||||
end
|
||||
|
||||
NavView->>OldPage: OnNavigatedFrom()
|
||||
NavView->>Frame: Navigate(newPage)
|
||||
Frame->>Frame: Update Content
|
||||
NavView->>NewPage: OnNavigatedTo()
|
||||
|
||||
NavView->>Animator: ApplyTransition(frame, transition)
|
||||
Animator->>Animator: Check HardwareAcceleration.RenderingTier
|
||||
alt Tier >= 2 (hardware accelerated)
|
||||
Animator->>Frame: Apply Storyboard (FadeIn/SlideBottom/etc.)
|
||||
else Low rendering tier
|
||||
Animator-->>NavView: Skip animation
|
||||
end
|
||||
|
||||
NavView->>NavView: Push to back stack
|
||||
NavView->>NavView: Update selected menu item
|
||||
NavView-->>Consumer: Navigation complete
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Page Cache Mode
|
||||
|
||||
NavigationView supports three caching strategies via the `NavigationCacheMode` property on individual pages. The cache is maintained per-type within the `INavigationViewPageProvider` implementation.
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> PageRequested: Navigate(pageType)
|
||||
|
||||
state PageRequested {
|
||||
[*] --> CheckCacheMode
|
||||
|
||||
state CheckCacheMode <<choice>>
|
||||
CheckCacheMode --> CacheDisabled: Disabled
|
||||
CheckCacheMode --> CacheEnabled: Enabled
|
||||
CheckCacheMode --> CacheRequired: Required
|
||||
|
||||
state CacheDisabled {
|
||||
[*] --> CreateNew_D: Always create new instance
|
||||
CreateNew_D --> ReturnPage_D: Return new page
|
||||
}
|
||||
|
||||
state CacheEnabled {
|
||||
[*] --> LookupCache_E: Check cache
|
||||
LookupCache_E --> ReturnCached_E: Cache hit
|
||||
LookupCache_E --> CreateAndCache_E: Cache miss
|
||||
CreateAndCache_E --> ReturnPage_E: Store and return
|
||||
ReturnCached_E --> ReturnPage_E: Return cached
|
||||
}
|
||||
|
||||
state CacheRequired {
|
||||
[*] --> LookupCache_R: Check cache
|
||||
LookupCache_R --> ReturnCached_R: Cache hit (guaranteed after first)
|
||||
LookupCache_R --> CreateAndCache_R: First request only
|
||||
CreateAndCache_R --> ReturnPage_R: Store and return
|
||||
ReturnCached_R --> ReturnPage_R: Return cached
|
||||
}
|
||||
}
|
||||
|
||||
PageRequested --> PageDisplayed: Page resolved
|
||||
PageDisplayed --> [*]
|
||||
```
|
||||
|
||||
### Cache Mode Comparison
|
||||
|
||||
| Mode | First Visit | Subsequent Visits | Page State | Use Case |
|
||||
|------|-------------|-------------------|------------|----------|
|
||||
| **Disabled** | New instance | New instance | Lost on navigate away | Forms, transient views |
|
||||
| **Enabled** | New instance | Cached instance (if available) | Preserved while cached | Dashboard, lists |
|
||||
| **Required** | New instance | Always cached instance | Always preserved | Settings, stateful views |
|
||||
|
||||
---
|
||||
|
||||
## Key Components
|
||||
|
||||
### NavigationService
|
||||
|
||||
Thin wrapper around `INavigationView` that provides a service-oriented API for navigation. Registered in DI as `INavigationService`.
|
||||
|
||||
**Key methods:**
|
||||
- `Navigate(Type pageType)` — Navigate to a page by type
|
||||
- `Navigate(string pageTag)` — Navigate to a page by tag
|
||||
- `GoBack()` — Navigate to the previous page in the back stack
|
||||
- `SetNavigationControl(INavigationView)` — Bind to a NavigationView instance
|
||||
|
||||
### INavigationViewPageProvider
|
||||
|
||||
Abstraction for page instance resolution. Two implementations:
|
||||
1. **`DependencyInjectionNavigationViewPageProvider`** (from `Wpf.Ui.DependencyInjection`) — resolves pages via `IServiceProvider`
|
||||
2. **Manual/custom** — consumers can implement their own provider
|
||||
|
||||
### INavigationAware
|
||||
|
||||
Lifecycle interface for pages that need to respond to navigation events:
|
||||
- `OnNavigatedTo()` — Called when the page becomes the active view
|
||||
- `OnNavigatedFrom()` — Called when the page is navigated away from
|
||||
|
||||
### Transition Animations
|
||||
|
||||
`TransitionAnimationProvider` applies entry animations to navigated pages. Available transitions: `FadeIn`, `FadeInFromBottom`, `SlideFromBottom`, `SlideFromRight`, `SlideFromLeft`. Animations are skipped when `HardwareAcceleration.RenderingTier < 2`.
|
||||
|
||||
---
|
||||
|
||||
## Integration with DI
|
||||
|
||||
For hosted applications using `Microsoft.Extensions.Hosting`:
|
||||
|
||||
```csharp
|
||||
// In Program.cs or Startup
|
||||
services.AddNavigationViewPageProvider<DependencyInjectionNavigationViewPageProvider>();
|
||||
services.AddSingleton<INavigationService, NavigationService>();
|
||||
|
||||
// Pages registered in DI
|
||||
services.AddTransient<DashboardPage>();
|
||||
services.AddTransient<SettingsPage>();
|
||||
```
|
||||
|
||||
The `DependencyInjectionNavigationViewPageProvider` resolves pages from the DI container, respecting their registered lifetime (Transient, Scoped, Singleton).
|
||||
|
||||
---
|
||||
|
||||
## Back Stack
|
||||
|
||||
NavigationView maintains an internal back stack of previously visited page types. The `GoBack()` operation pops the most recent entry and navigates to it. The back stack is cleared when navigating to a page that is already in the stack (cycle prevention).
|
||||
|
||||
## Design Considerations
|
||||
|
||||
- **Static vs DI navigation:** Simple apps can use `NavigationService` directly; hosted apps use DI. Both paths are supported.
|
||||
- **Cache ownership:** The page cache lives in `INavigationViewPageProvider`, not in NavigationView. This allows DI-managed lifetimes to control cache behavior.
|
||||
- **Animation gating:** Transition animations check rendering tier to avoid jank on software-rendered systems.
|
||||
- **Thread safety:** Navigation must occur on the UI thread. `NavigationService` does not marshal calls.
|
||||
@@ -0,0 +1,176 @@
|
||||
# Cross-Cutting Concern: Testing
|
||||
|
||||
**Project**: WPF UI (wpfui) v4.2.0
|
||||
**Last Updated**: 2026-02-10
|
||||
|
||||
## Overview
|
||||
|
||||
WPF UI employs a two-tier testing strategy consisting of unit tests for isolated logic verification and integration tests for end-to-end UI automation. The testing infrastructure is intentionally lightweight, reflecting the library's nature as a visual control library where many behaviors require a running WPF application to validate.
|
||||
|
||||
## Test Pyramid
|
||||
|
||||
```
|
||||
/ Integration Tests \ 8 tests
|
||||
/ (FlaUI + Gallery App) \ End-to-end UI automation
|
||||
/________________________\
|
||||
/ \
|
||||
/ Unit Tests \ 6 tests
|
||||
/ (XUnit + NSubstitute) \ Isolated logic, pure functions
|
||||
/________________________________\
|
||||
```
|
||||
|
||||
| Layer | Scope | Framework | Assertion Library | Count |
|
||||
|-------|-------|-----------|-------------------|-------|
|
||||
| Unit Tests | Pure logic, extension methods, animation providers | XUnit 2.9.3 + NSubstitute 5.3.0 | `Xunit.Assert` | 6 |
|
||||
| Integration Tests | Window management, navigation, dialogs, title bar | XUnit v3 3.2.0 + FlaUI.UIA3 5.0.0 | AwesomeAssertions 9.3.0 | 8 |
|
||||
|
||||
## Test Framework Versions
|
||||
|
||||
All versions are managed centrally in `Directory.Packages.props`:
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| `xunit` | 2.9.3 | Unit test framework (v2-style API) |
|
||||
| `xunit.v3` | 3.2.0 | Integration test framework (v3 with `IAsyncLifetime`) |
|
||||
| `xunit.runner.visualstudio` | 3.1.5 | Visual Studio / `dotnet test` runner |
|
||||
| `Microsoft.NET.Test.Sdk` | 18.0.0 | .NET test SDK infrastructure |
|
||||
| `NSubstitute` | 5.3.0 | Mocking library for unit tests |
|
||||
| `AwesomeAssertions` | 9.3.0 | Fluent assertions (FluentAssertions successor) |
|
||||
| `FlaUI.Core` | 5.0.0 | UI automation core library |
|
||||
| `FlaUI.UIA3` | 5.0.0 | UIA3 automation adapter |
|
||||
| `coverlet.collector` | 6.0.4 | Code coverage collection |
|
||||
|
||||
## Test Naming Conventions
|
||||
|
||||
### Unit Tests
|
||||
|
||||
Pattern: `MethodName_ExpectedResult_WhenCondition`
|
||||
|
||||
```csharp
|
||||
[Fact]
|
||||
public void ApplyTransition_ReturnsFalse_WhenDurationIsLessThan10()
|
||||
```
|
||||
|
||||
Alternative pattern: `GivenX_Method_ExpectedResult`
|
||||
|
||||
```csharp
|
||||
[Fact]
|
||||
public void GivenAllRegularSymbols_Swap_ReturnsValidFilledSymbol()
|
||||
```
|
||||
|
||||
### Integration Tests
|
||||
|
||||
Pattern: `Subject_ShouldExpectedBehavior_WhenCondition`
|
||||
|
||||
```csharp
|
||||
[Fact]
|
||||
public async Task CloseButton_ShouldCloseWindow_WhenClicked()
|
||||
```
|
||||
|
||||
### Class Naming
|
||||
|
||||
- Unit test classes: `{ClassUnderTest}Tests` (e.g., `TransitionAnimationProviderTests`)
|
||||
- Integration test classes: `{Feature}Tests` (e.g., `TitleBarTests`, `NavigationTests`)
|
||||
- Integration test classes are `sealed`; unit test classes are not
|
||||
|
||||
## Test Directory Structure
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A["tests/"] --> B["Wpf.Ui.UnitTests/"]
|
||||
A --> C["Wpf.Ui.Gallery.IntegrationTests/"]
|
||||
|
||||
B --> B1["Wpf.Ui.UnitTests.csproj<br/><i>net10.0-windows</i>"]
|
||||
B --> B2["Animations/"]
|
||||
B --> B3["Extensions/"]
|
||||
B --> B4["GlobalUsings.cs"]
|
||||
B2 --> B2a["TransitionAnimationProviderTests.cs"]
|
||||
B3 --> B3a["SymbolExtensionsTests.cs"]
|
||||
|
||||
C --> C1["Wpf.Ui.Gallery.IntegrationTests.csproj<br/><i>net10.0-windows10.0.26100.0</i>"]
|
||||
C --> C2["Fixtures/"]
|
||||
C --> C3["WindowTests.cs"]
|
||||
C --> C4["TitleBarTests.cs"]
|
||||
C --> C5["NavigationTests.cs"]
|
||||
C --> C6["ContentDialogAutomationTests.cs"]
|
||||
C --> C7["xunit.runner.json"]
|
||||
C2 --> C2a["UiTest.cs <i>(base class)</i>"]
|
||||
C2 --> C2b["TestedApplication.cs <i>(app lifecycle)</i>"]
|
||||
|
||||
D["src/Wpf.Ui.FlaUI/"] --> D1["AutoSuggestBox.cs<br/><i>Custom FlaUI element</i>"]
|
||||
|
||||
style A fill:#f5f5f5,stroke:#333
|
||||
style B fill:#e3f2fd,stroke:#1565c0
|
||||
style C fill:#e8f5e9,stroke:#2e7d32
|
||||
style D fill:#fff3e0,stroke:#e65100
|
||||
```
|
||||
|
||||
## Test Infrastructure
|
||||
|
||||
### Unit Tests
|
||||
|
||||
Unit tests reference the core `Wpf.Ui` project directly and use NSubstitute for mocking WPF types (e.g., `UIElement`). Global usings are defined for common namespaces:
|
||||
|
||||
```
|
||||
System, System.Windows, NSubstitute, Xunit
|
||||
```
|
||||
|
||||
_Source: `tests/Wpf.Ui.UnitTests/GlobalUsings.cs`_
|
||||
|
||||
### Integration Tests
|
||||
|
||||
Integration tests use a custom infrastructure built on FlaUI:
|
||||
|
||||
- **`TestedApplication`** (`IAsyncLifetime`): Launches and manages the Gallery `.exe` process. Finds the executable in the test output directory. Uses `UIA3Automation` for UI element discovery.
|
||||
- **`UiTest`** (abstract base class, `IAsyncLifetime`): Provides helper methods for all UI tests:
|
||||
- `FindFirst(string automationId)` -- finds UI elements by automation ID
|
||||
- `FindFirst(Func<ConditionFactory, ConditionBase>)` -- finds by condition
|
||||
- `Wait(int seconds)` -- async delay for UI settling
|
||||
- `Enter(string value)` -- simulates keyboard text input
|
||||
- `Press(VirtualKeyShort)` -- simulates a key press
|
||||
|
||||
### Integration Test Runner Configuration
|
||||
|
||||
Tests run sequentially (no parallel test collections) with invariant culture:
|
||||
|
||||
```json
|
||||
{
|
||||
"parallelizeTestCollections": false,
|
||||
"diagnosticMessages": true,
|
||||
"culture": "invariant"
|
||||
}
|
||||
```
|
||||
|
||||
_Source: `tests/Wpf.Ui.Gallery.IntegrationTests/xunit.runner.json`_
|
||||
|
||||
## Run Commands
|
||||
|
||||
```bash
|
||||
# Run unit tests
|
||||
dotnet test tests/Wpf.Ui.UnitTests/Wpf.Ui.UnitTests.csproj
|
||||
|
||||
# Run integration tests (requires built Gallery app)
|
||||
dotnet test tests/Wpf.Ui.Gallery.IntegrationTests/Wpf.Ui.Gallery.IntegrationTests.csproj
|
||||
|
||||
# Run all tests with coverage
|
||||
dotnet test tests/Wpf.Ui.UnitTests/Wpf.Ui.UnitTests.csproj --collect:"XPlat Code Coverage"
|
||||
```
|
||||
|
||||
## Coverage Gaps and Observations
|
||||
|
||||
| Area | Current Coverage | Notes |
|
||||
|------|-----------------|-------|
|
||||
| Animations | 2 unit tests | `TransitionAnimationProvider` edge cases only |
|
||||
| Extensions | 4 unit tests | `SymbolExtensions.Swap()` and `GetString()` exhaustive enum tests |
|
||||
| Controls (77+) | 0 unit tests | Controls depend on WPF runtime; consider UI automation expansion |
|
||||
| Services | 0 unit tests | `INavigationService`, `IContentDialogService`, etc. are testable via mocks |
|
||||
| Theming | 0 tests | Static managers (`ApplicationThemeManager`) limit testability |
|
||||
| Win32 Interop | 0 tests | Requires OS-level interaction; integration tests more appropriate |
|
||||
| Window Chrome | 3 integration tests | TitleBar close/minimize/maximize buttons |
|
||||
| Navigation | 2 integration tests | AutoSuggestBox search and sidebar navigation |
|
||||
| Dialogs | 2 integration tests | ContentDialog result text and keyboard focus isolation |
|
||||
| Window | 1 integration test | Window title verification |
|
||||
|
||||
## CI/CD Integration
|
||||
|
||||
The PR validation workflow (`.github/workflows/wpf-ui-pr-validator.yaml`) currently only builds the Gallery app in Release mode. It does **not** run unit or integration tests as part of PR checks. Test execution is a local development responsibility.
|
||||
@@ -0,0 +1,213 @@
|
||||
# Theming and Appearance System
|
||||
|
||||
## Overview
|
||||
|
||||
The WPF UI library implements a comprehensive theming system that supports Light, Dark, and four high-contrast themes. The system automatically synchronizes with OS theme changes, manages accent colors, and provides window backdrop effects (Mica, Acrylic, Tabbed).
|
||||
|
||||
## Architecture Components
|
||||
|
||||
### Core Manager Classes
|
||||
|
||||
#### ApplicationThemeManager
|
||||
**Location:** `src/Wpf.Ui/Appearance/ApplicationThemeManager.cs`
|
||||
|
||||
Static class responsible for applying and managing application themes. Key functionality:
|
||||
|
||||
- **Apply(ApplicationTheme theme)** - Swaps theme resource dictionaries at runtime
|
||||
- **GetAppTheme()** - Retrieves current application theme
|
||||
- **Changed event** - ThemeChangedEvent delegate fires when theme changes globally
|
||||
|
||||
The manager uses URI-based resource dictionary swapping, searching application-level merged dictionaries for URIs containing 'wpf.ui;' and 'theme', then replacing them with the appropriate theme file.
|
||||
|
||||
#### ApplicationAccentColorManager
|
||||
**Location:** `src/Wpf.Ui/Appearance/ApplicationAccentColorManager.cs`
|
||||
|
||||
Static class for accent color management:
|
||||
|
||||
- **Apply(Color systemAccent, ApplicationTheme theme)** - Updates 20+ dynamic color resources
|
||||
- **GetColorizationColor()** - Retrieves system accent color
|
||||
- **ApplySystemAccent()** - Applies Windows system accent colors
|
||||
|
||||
Uses WinRT IUISettings3 COM interface to retrieve system accent colors (Accent, AccentLight1-3, AccentDark1-3) with registry fallback to DWM AccentColor.
|
||||
|
||||
**Dynamic Resources Updated:**
|
||||
- SystemAccentColor
|
||||
- AccentFillColorDefault
|
||||
- TextOnAccentFillColorPrimary
|
||||
- AccentFillColorSecondary
|
||||
- AccentFillColorTertiary
|
||||
- (20+ total accent-related resources)
|
||||
|
||||
#### SystemThemeWatcher
|
||||
**Location:** `src/Wpf.Ui/Appearance/SystemThemeWatcher.cs`
|
||||
|
||||
Static class providing automatic OS theme synchronization:
|
||||
|
||||
- **Watch(Window window)** - Hooks window to auto-sync theme with OS
|
||||
- **UnWatch(Window window)** - Removes synchronization hook
|
||||
|
||||
Implementation uses WndProc message interception via HwndSource to listen for:
|
||||
- `WM_DWMCOLORIZATIONCOLORCHANGED`
|
||||
- `WM_THEMECHANGED`
|
||||
- `WM_SYSCOLORCHANGE`
|
||||
|
||||
When detected, triggers `ApplicationThemeManager.ApplySystemTheme()`.
|
||||
|
||||
#### WindowBackgroundManager
|
||||
**Location:** `src/Wpf.Ui/Appearance/WindowBackgroundManager.cs`
|
||||
|
||||
Static class for window appearance management:
|
||||
|
||||
- **UpdateBackground(Window? window, ApplicationTheme applicationTheme, WindowBackdropType backdrop)** - Applies dark mode and backdrop effects
|
||||
- Manages WindowBackdrop effects (Mica, Acrylic, Tabbed)
|
||||
- Applies DWM window attributes for Windows 11+ visual effects
|
||||
|
||||
#### ResourceDictionaryManager
|
||||
**Location:** `src/Wpf.Ui/Appearance/ResourceDictionaryManager.cs`
|
||||
|
||||
Internal helper class for resource dictionary manipulation:
|
||||
|
||||
- Finds resource dictionaries by namespace/name matching
|
||||
- Swaps resource dictionaries by URI pattern
|
||||
- Handles Application.Current.Resources.MergedDictionaries traversal
|
||||
|
||||
## Theme Files
|
||||
|
||||
Six XAML resource dictionaries located in `src/Wpf.Ui/Resources/Theme/`:
|
||||
|
||||
1. **Light.xaml** - Light theme color scheme
|
||||
2. **Dark.xaml** - Dark theme color scheme
|
||||
3. **HC1.xaml** - High contrast theme variant 1
|
||||
4. **HC2.xaml** - High contrast theme variant 2
|
||||
5. **HCBlack.xaml** - High contrast black theme
|
||||
6. **HCWhite.xaml** - High contrast white theme
|
||||
|
||||
### Supporting Resources
|
||||
|
||||
Located in `src/Wpf.Ui/Resources/` (parent directory, not the `Theme/` subdirectory):
|
||||
|
||||
- **Accent.xaml** - Accent color definitions
|
||||
- **Palette.xaml** - Color palette system
|
||||
- **StaticColors.xaml** - Static color values
|
||||
- **Variables.xaml** - Theme variables
|
||||
|
||||
## Accent Color System
|
||||
|
||||
The accent color system provides dynamic, theme-aware colors derived from Windows system settings:
|
||||
|
||||
### Color Hierarchy
|
||||
```
|
||||
System Accent Color (from WinRT UISettings)
|
||||
├── Primary Accent (direct system color)
|
||||
├── Secondary Accent (lighter/darker variant)
|
||||
└── Tertiary Accent (additional variant)
|
||||
```
|
||||
|
||||
### WinRT Integration
|
||||
```csharp
|
||||
// Retrieves colors via WinRT IUISettings3 COM interface
|
||||
var uiSettings = new Windows.UI.ViewManagement.UISettings();
|
||||
var accent = uiSettings.GetColorValue(UIColorType.Accent);
|
||||
var accentLight1 = uiSettings.GetColorValue(UIColorType.AccentLight1);
|
||||
var accentDark1 = uiSettings.GetColorValue(UIColorType.AccentDark1);
|
||||
```
|
||||
|
||||
## WindowBackdrop Effects
|
||||
|
||||
Three backdrop effect types available via `WindowBackdropType` enum:
|
||||
|
||||
### Mica
|
||||
Windows 11+ translucent backdrop with desktop wallpaper bleed-through. Applied via `DwmSetWindowAttribute` with `DWMWA_SYSTEMBACKDROP_TYPE`.
|
||||
|
||||
### Acrylic
|
||||
Translucent acrylic material effect with blur. Requires Windows 10 Fall Creators Update or later.
|
||||
|
||||
### Tabbed
|
||||
Windows 11 tabbed window effect grouping windows in the taskbar.
|
||||
|
||||
### Implementation
|
||||
Effects are applied through DWM (Desktop Window Manager) APIs in `WindowBackgroundManager` and consumed by `FluentWindow` control.
|
||||
|
||||
## Theme Change Flow
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User
|
||||
participant App
|
||||
participant SystemThemeWatcher
|
||||
participant WndProc
|
||||
participant ApplicationThemeManager
|
||||
participant ResourceDictionaryManager
|
||||
participant UI
|
||||
|
||||
User->>App: Change OS Theme
|
||||
WndProc->>SystemThemeWatcher: WM_THEMECHANGED
|
||||
SystemThemeWatcher->>ApplicationThemeManager: ApplySystemTheme()
|
||||
ApplicationThemeManager->>ApplicationThemeManager: GetSystemTheme()
|
||||
ApplicationThemeManager->>ResourceDictionaryManager: UpdateDictionary("theme", newUri)
|
||||
ResourceDictionaryManager->>UI: Swap Theme XAML
|
||||
ApplicationThemeManager->>ApplicationThemeManager: Fire Changed Event
|
||||
ApplicationThemeManager->>UI: Trigger Visual Update
|
||||
UI->>User: Updated Appearance
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Theme Application
|
||||
```csharp
|
||||
// Apply dark theme
|
||||
ApplicationThemeManager.Apply(ApplicationTheme.Dark);
|
||||
|
||||
// Get current theme
|
||||
ApplicationTheme current = ApplicationThemeManager.GetAppTheme();
|
||||
```
|
||||
|
||||
### Automatic OS Synchronization
|
||||
```csharp
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
// Enable automatic theme synchronization
|
||||
Appearance.SystemThemeWatcher.Watch(this);
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Accent Color
|
||||
```csharp
|
||||
// Apply custom accent color
|
||||
Color myAccent = Color.FromRgb(0, 120, 215);
|
||||
ApplicationAccentColorManager.Apply(
|
||||
myAccent,
|
||||
ApplicationTheme.Dark,
|
||||
systemGlassColor: false,
|
||||
systemAccentColor: true
|
||||
);
|
||||
```
|
||||
|
||||
### XAML Theme Selection
|
||||
```xml
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<!-- Select theme via ThemesDictionary -->
|
||||
<ui:ThemesDictionary Theme="Dark" />
|
||||
<ui:ControlsDictionary />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
```
|
||||
|
||||
## Design Considerations
|
||||
|
||||
### Static Singleton Pattern
|
||||
The theme managers use static class design for simple, globally-accessible APIs. This pattern trades testability for API simplicity and ensures single-instance theme state across the application.
|
||||
|
||||
### Runtime Resource Swapping
|
||||
Theme changes occur via runtime resource dictionary replacement rather than restart-required configuration. This enables live theme switching without application restart.
|
||||
|
||||
### OS Integration
|
||||
Deep integration with Windows theme system via WndProc message hooks and WinRT UISettings ensures automatic synchronization with user preferences.
|
||||
|
||||
### Multi-Version Support
|
||||
Theme system gracefully degrades on older Windows versions, falling back to registry-based accent color detection when WinRT APIs are unavailable.
|
||||
@@ -0,0 +1,216 @@
|
||||
# Win32 Interop Architecture
|
||||
|
||||
> WPF UI v4.2.0 | Cross-Cutting Concern
|
||||
|
||||
## Overview
|
||||
|
||||
WPF UI relies heavily on Win32 interop to deliver Fluent Design features that are not natively available through the WPF framework. This includes DWM backdrop effects (Mica, Acrylic, Tabbed), dark mode title bars, window corner preferences, snap layout support, system tray icons, and taskbar progress indicators.
|
||||
|
||||
The interop layer follows a strict three-layer architecture that isolates raw platform calls from the rest of the library.
|
||||
|
||||
---
|
||||
|
||||
## Component Diagram
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "Layer 3 — High-Level Utilities & Controls"
|
||||
FluentWindow["FluentWindow<br/><i>Backdrop, corner prefs</i>"]
|
||||
TitleBar["TitleBar<br/><i>Custom chrome, snap layouts</i>"]
|
||||
STW["SystemThemeWatcher<br/><i>WndProc hooks</i>"]
|
||||
WBM["WindowBackgroundManager<br/><i>DWM backdrop effects</i>"]
|
||||
AACM["ApplicationAccentColorManager<br/><i>WinRT UISettings</i>"]
|
||||
TaskBar["TaskBarService<br/><i>COM ITaskbarList4</i>"]
|
||||
NotifyIcon["NotifyIcon (Tray)<br/><i>Shell_NotifyIcon</i>"]
|
||||
end
|
||||
|
||||
subgraph "Layer 2 — Managed Wrappers (Interop/)"
|
||||
UNM["UnsafeNativeMethods.cs<br/><i>Handle validation + safe wrappers</i>"]
|
||||
PI["PInvoke.cs<br/><i>Manual DllImport for<br/>SetWindowLongPtr (x86/x64)</i>"]
|
||||
UR["UnsafeReflection.cs<br/><i>Enum/struct unsafe casting</i>"]
|
||||
end
|
||||
|
||||
subgraph "Layer 1 — CsWin32 Source Generation"
|
||||
NMT["NativeMethods.txt<br/><i>35 function/type declarations</i>"]
|
||||
CsWin32["CsWin32 Generator<br/><i>Produces Windows.Win32 namespace</i>"]
|
||||
DWM["DwmSetWindowAttribute<br/>DwmIsCompositionEnabled"]
|
||||
User32["SetWindowLong<br/>GetWindowLong<br/>GetDpiForWindow"]
|
||||
Shell32["Shell_NotifyIcon<br/>ITaskbarList4"]
|
||||
end
|
||||
|
||||
FluentWindow --> UNM
|
||||
TitleBar --> UNM
|
||||
STW --> UNM
|
||||
WBM --> UNM
|
||||
AACM --> UNM
|
||||
TaskBar --> UNM
|
||||
NotifyIcon --> UNM
|
||||
|
||||
UNM --> CsWin32
|
||||
UNM --> PI
|
||||
UNM --> UR
|
||||
PI --> User32
|
||||
|
||||
NMT --> CsWin32
|
||||
CsWin32 --> DWM
|
||||
CsWin32 --> User32
|
||||
CsWin32 --> Shell32
|
||||
|
||||
style FluentWindow fill:#fff4e1,stroke:#f57f17
|
||||
style TitleBar fill:#fff4e1,stroke:#f57f17
|
||||
style STW fill:#e8f5e9,stroke:#2e7d32
|
||||
style WBM fill:#e8f5e9,stroke:#2e7d32
|
||||
style AACM fill:#e8f5e9,stroke:#2e7d32
|
||||
style TaskBar fill:#e1f5ff,stroke:#0277bd
|
||||
style NotifyIcon fill:#e1f5ff,stroke:#0277bd
|
||||
style UNM fill:#ffebee,stroke:#c62828
|
||||
style PI fill:#ffebee,stroke:#c62828
|
||||
style UR fill:#ffebee,stroke:#c62828
|
||||
style NMT fill:#f3e5f5,stroke:#6a1b9a
|
||||
style CsWin32 fill:#f3e5f5,stroke:#6a1b9a
|
||||
style DWM fill:#e0e0e0,stroke:#616161
|
||||
style User32 fill:#e0e0e0,stroke:#616161
|
||||
style Shell32 fill:#e0e0e0,stroke:#616161
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Three-Layer Architecture
|
||||
|
||||
### Layer 1: CsWin32 Source Generation
|
||||
|
||||
The project uses Microsoft's [CsWin32](https://github.com/microsoft/CsWin32) source generator to produce type-safe P/Invoke bindings at compile time.
|
||||
|
||||
- **Configuration**: `NativeMethods.txt` lists the Win32 functions and types needed by the library.
|
||||
- **Generated namespace**: `Windows.Win32`
|
||||
- **Foundation types**: `HWND`, `HRESULT`, `BOOL` from `Windows.Win32.Foundation`
|
||||
|
||||
**Key generated functions:**
|
||||
|
||||
| Function | Purpose |
|
||||
|----------|---------|
|
||||
| `DwmSetWindowAttribute` | Apply backdrop effects, dark mode, corner preferences |
|
||||
| `DwmIsCompositionEnabled` | Check if DWM composition is active |
|
||||
| `SetWindowLong` / `GetWindowLong` | Manipulate window styles (32-bit) |
|
||||
| `Shell_NotifyIcon` | System tray icon management |
|
||||
| `ITaskbarList4` | Taskbar progress overlay (COM interface) |
|
||||
|
||||
### Layer 2: Managed Wrappers (`src/Wpf.Ui/Interop/`)
|
||||
|
||||
Managed wrappers provide validated, exception-safe access to native APIs.
|
||||
|
||||
#### `UnsafeNativeMethods.cs` -- Handle-Validated Wrappers
|
||||
|
||||
Every method follows a defensive pattern:
|
||||
|
||||
1. Check that the handle is not `IntPtr.Zero`
|
||||
2. Verify the handle via `PInvoke.IsWindow()`
|
||||
3. Call the native API
|
||||
4. Catch any exception and return `false` or `null`
|
||||
|
||||
This pattern ensures that callers never receive unmanaged exceptions and that invalid window handles are rejected before reaching the OS.
|
||||
|
||||
#### `UnsafeReflection.cs` -- Unsafe Enum/Struct Casting
|
||||
|
||||
Provides unsafe casting between managed enums/structs and their Win32 equivalents. Used where direct marshalling is insufficient or where performance-critical paths avoid boxing.
|
||||
|
||||
#### `PInvoke.cs` -- Custom P/Invoke Declarations
|
||||
|
||||
Contains hand-written P/Invoke declarations for functions that CsWin32 does not generate or generates with incompatible signatures. Example: `SetWindowLongPtrW` requires platform-specific handling (different entry points on 32-bit vs 64-bit Windows).
|
||||
|
||||
### Layer 3: Utilities (`src/Wpf.Ui/Win32/`)
|
||||
|
||||
#### `Utilities.cs` -- OS Version Detection
|
||||
|
||||
Provides high-level queries about the running environment:
|
||||
|
||||
| Property | Logic |
|
||||
|----------|-------|
|
||||
| `IsOSWindows11OrNewer` | OS build number >= 22000 |
|
||||
| `IsCompositionEnabled` | Calls `DwmIsCompositionEnabled` |
|
||||
|
||||
These checks gate feature availability so that controls degrade gracefully on older Windows versions.
|
||||
|
||||
---
|
||||
|
||||
## API Surface by Windows Component
|
||||
|
||||
| API | Usage | Key Functions |
|
||||
|-----|-------|---------------|
|
||||
| **DWM** | Backdrop effects (Mica/Acrylic/Tabbed), dark mode, corner preferences | `DwmSetWindowAttribute`, `DwmIsCompositionEnabled` |
|
||||
| **User32** | Window style manipulation, message pump interception, snap layouts | `SetWindowLong`, `GetWindowLong`, `SetWindowLongPtr` |
|
||||
| **Shell32** | System tray icons, taskbar progress | `Shell_NotifyIcon`, `ITaskbarList4` COM |
|
||||
| **WinRT UISettings** | System accent colors (8-color palette) | `IUISettings3` COM interface |
|
||||
| **Registry** | Fallback for accent colors, OS version detection | `DWM\AccentColor`, `CurrentVersion` |
|
||||
| **WndProc** | Theme change detection, title bar hit testing | `WM_THEMECHANGED`, `WM_DWMCOLORIZATIONCOLORCHANGED`, `WM_NCHITTEST` |
|
||||
|
||||
---
|
||||
|
||||
## Interop Call Flow
|
||||
|
||||
The following diagram illustrates the typical call path from a consumer application through the interop layers to the Windows OS.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant App as Consumer App
|
||||
participant Control as WPF UI Control
|
||||
participant Unsafe as UnsafeNativeMethods
|
||||
participant CsWin32 as CsWin32 PInvoke
|
||||
participant OS as Windows OS
|
||||
|
||||
App->>Control: Set property (e.g., WindowBackdropType = Mica)
|
||||
Control->>Control: Validate state and OS version
|
||||
Control->>Unsafe: Call managed wrapper (e.g., ApplyWindowDarkMode)
|
||||
|
||||
Unsafe->>Unsafe: Check handle != IntPtr.Zero
|
||||
Unsafe->>CsWin32: PInvoke.IsWindow(hwnd)
|
||||
CsWin32->>OS: IsWindow()
|
||||
OS-->>CsWin32: BOOL result
|
||||
CsWin32-->>Unsafe: true/false
|
||||
|
||||
alt Handle is valid
|
||||
Unsafe->>CsWin32: DwmSetWindowAttribute(hwnd, attr, value)
|
||||
CsWin32->>OS: DwmSetWindowAttribute()
|
||||
OS-->>CsWin32: HRESULT
|
||||
CsWin32-->>Unsafe: success/failure
|
||||
Unsafe-->>Control: true
|
||||
else Handle is invalid or call fails
|
||||
Unsafe-->>Control: false (exception swallowed)
|
||||
end
|
||||
|
||||
Control-->>App: Property applied (or silently degraded)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Handling Pattern
|
||||
|
||||
The Win32 interop layer follows a deliberate error-swallowing strategy:
|
||||
|
||||
1. **Bare catch blocks are intentional.** Win32 APIs may fail unpredictably across OS versions, and there is no reliable way to enumerate all failure modes at compile time. Swallowing exceptions ensures the application continues to function, albeit without the requested visual effect.
|
||||
|
||||
2. **Graceful degradation is the design goal.** If a Mica backdrop cannot be applied (e.g., on Windows 10), the window falls back to a solid background. No exception propagates to the consumer.
|
||||
|
||||
3. **Handle validation is mandatory.** Every wrapper method must validate that the `HWND` is non-zero and represents a valid window before calling any native API. This prevents access violations from stale or recycled handles.
|
||||
|
||||
4. **Return values signal success.** Methods return `bool` (success/failure) or nullable types rather than throwing. Callers check return values to determine whether the native operation succeeded.
|
||||
|
||||
```
|
||||
Pattern:
|
||||
if (handle == IntPtr.Zero) return false;
|
||||
if (!PInvoke.IsWindow(handle)) return false;
|
||||
try {
|
||||
NativeCall(handle, ...);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Platform Considerations
|
||||
|
||||
- **32-bit vs 64-bit**: `SetWindowLongPtr` does not exist as a distinct entry point on 32-bit Windows. The custom `PInvoke.cs` handles this by routing to `SetWindowLong` on x86 and `SetWindowLongPtrW` on x64.
|
||||
- **Windows 10 vs 11**: Many DWM attributes (e.g., `DWMWA_SYSTEMBACKDROP_TYPE`) are only available on Windows 11 (build 22000+). The `Utilities` class gates these calls.
|
||||
- **COM activation**: `ITaskbarList4` and `IUISettings3` require COM activation. These are wrapped to handle `COMException` gracefully.
|
||||
Reference in New Issue
Block a user