diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 000000000..083cc79e8 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,12 @@ +{ + "permissions": { + "allow": [ + "mcp__microsoft_docs__microsoft_docs_search", + "Bash(dotnet tool *)", + "Bash(docfx docs/docfx.json)", + "mcp__microsoft_docs__microsoft_code_sample_search", + "mcp__context7__resolve-library-id", + "mcp__context7__query-docs" + ] + } +} diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index b9f694123..97f37dcc4 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "csharpier": { - "version": "1.0.1", + "version": "1.2.6", "commands": [ "csharpier" ], diff --git a/BannedSymbols.txt b/BannedSymbols.txt new file mode 100644 index 000000000..c3b3f733e --- /dev/null +++ b/BannedSymbols.txt @@ -0,0 +1,5 @@ +// https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/BannedApiAnalyzers.Help.md +// RS0030: Do not use banned APIs +T:System.DateTime;Don't use DateTime. Use DateTimeOffset instead. +P:System.DateTime.Now; Use System.DateTime.UtcNow instead. +M:System.DateTimeOffset.op_Implicit(System.DateTime); Do not implicitly cast DateTime to DateTimeOffset. diff --git a/Directory.Build.props b/Directory.Build.props index d77363ab0..d37b05a15 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,17 +4,17 @@ $(RepositoryDirectory)build\ - 4.2.0 + 4.2.1 4.2.0 lepo.co lepo.co WPF-UI - lepoco;toolkit;wpf;fluent;navigation;controls;design;icons;system;accent;theme;winui + ui;wpf;toolkit;fluent;navigation;controls;design;icons;uwp;xaml;xamarin;theme;winui;mvvm;mvc;core;web;webview;dotnet;desktop;wpf-ui MIT true - Copyright (C) 2021-2025 Leszek Pomianowski and WPF UI Contributors + Copyright (C) 2021-2026 Leszek Pomianowski and WPF UI Contributors https://github.com/lepoco/wpfui https://github.com/lepoco/wpfui/releases Icon.png @@ -26,10 +26,6 @@ - true - moderate - true - false true <_SilenceIsAotCompatibleUnsupportedWarning>true diff --git a/Directory.Build.targets b/Directory.Build.targets index fec00b0d4..710331ff1 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -20,9 +20,12 @@ - + + all + build; analyzers + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -56,6 +59,9 @@ build; analyzers + + + + + true + false + true + moderate + - - - + + + - - + + - - - - - + + + + + - - - + + + - - + + - + - + - - - + diff --git a/README.md b/README.md index 998a43919..e0fac0233 100644 --- a/README.md +++ b/README.md @@ -13,19 +13,14 @@ WPF UI provides the Fluent experience in your known and loved WPF framework. Int ![ua](https://user-images.githubusercontent.com/13592821/184498735-d296feb8-0f9b-45df-bc0d-b7f0b6f580ed.png) -## 🛟 Support plans +## Support To ensure you receive the expert guidance you need, we offer a variety of support plans designed to meet the diverse needs of our community. Whether you are looking to modernize your WPF applications or need assistance with our other libraries, our tailored support solutions are here to help. From priority email support to 24/7 dedicated assistance, we provide flexible plans to suit your project requirements. -[Take a look at the lepo.co support plans](https://lepo.co/support) +[Take a look at the lepo.co support plans](https://lepo.co/support) +[Sponsor WPF UI on GitHub](https://github.com/sponsors/pomianowski) -## 🤝 Help us keep working on this project - -Support the development of WPF UI and other innovative projects by becoming a sponsor on GitHub! Your monthly or one-time contributions help us continue to deliver high-quality, open-source solutions that empower developers worldwide. - -[Sponsor WPF UI on GitHub](https://github.com/sponsors/lepoco) - -## 🚀 Getting started +## Getting started For a starter guide see our [documentation](https://wpfui.lepo.co/documentation/). @@ -43,7 +38,7 @@ winget install 'WPF UI' The plugin for **Visual Studio 2022** let you easily create new projects using **WPF UI**. -## 📷 Screenshots +## Screenshots ![Demo App Sample](https://user-images.githubusercontent.com/13592821/166259110-0fb98120-fe34-4e6d-ab92-9f72ad7113c3.png) @@ -51,27 +46,23 @@ The plugin for **Visual Studio 2022** let you easily create new projects using * ![Store App Sample](https://user-images.githubusercontent.com/13592821/165918914-6948fb42-1ee1-4c36-870e-65bb8ffe3c8a.png) -## 🏗️ Works with Visual Studio Designer +## Works with Visual Studio Designer ![VS2022 Designer Preview](https://user-images.githubusercontent.com/13592821/165919228-0aa3a36c-fb37-4198-835e-53488845226c.png) -## ❤️ Custom Tray icon and menu in pure WPF +## Custom Tray icon and menu in pure WPF ![WPF UI Tray menu in WPF](https://user-images.githubusercontent.com/13592821/166259470-2d48a88e-47ce-4f8f-8f07-c9b110de64a5.png) -## ⚓ Custom Windows 11 SnapLayout available for TitleBar +## Custom Windows 11 SnapLayout available for TitleBar ![WPF UI Snap Layout for WPF](https://user-images.githubusercontent.com/13592821/166259869-e60d37e4-ded4-46bf-80d9-f92c47266f34.png) -## 📖 Documentation - -Documentation can be found at . We also have a [tutorial](#-getting-started) over there for newcomers. - -## 🚧 Development +## Development If you want to propose a new functionality or submit a bugfix, create a [Pull Request](https://github.com/lepoco/wpfui/compare/main...main) for the branch [main](https://github.com/lepoco/wpfui/tree/main). -## 📐 How to use? +## How to use? First, your application needs to load custom styles, add in the **MyApp\App.xaml** file: diff --git a/docs/architecture/CONSTITUTION.md b/docs/architecture/CONSTITUTION.md new file mode 100644 index 000000000..1d490e7f1 --- /dev/null +++ b/docs/architecture/CONSTITUTION.md @@ -0,0 +1,975 @@ +# WPF UI Project Constitution v4.2.0 + +> **Spec-Driven Development (SDD) Document** +> Single source of truth for coding conventions, boundary rules, and project-specific guidelines. +> Intended audience: AI coding agents and human contributors. + +--- + +## Table of Contents + +1. [Project Identity](#1-project-identity) +2. [Coding Conventions](#2-coding-conventions) + - [Namespaces](#21-namespaces) + - [Nullable Reference Types](#22-nullable-reference-types) + - [Private Fields](#23-private-fields) + - [File Headers](#24-file-headers) + - [XML Documentation](#25-xml-documentation) + - [Dependency Properties](#26-dependency-properties) + - [Control Authoring](#27-control-authoring) + - [XAML Styles](#28-xaml-styles) + - [Service Pattern](#29-service-pattern) + - [Error Handling](#210-error-handling) + - [Win32 Interop](#211-win32-interop) + - [Test Naming](#212-test-naming) + - [MVVM (Gallery / Samples)](#213-mvvm-gallery--samples) + - [Central Package Management](#214-central-package-management) + - [Commit Convention](#215-commit-convention) +3. [Boundary System](#3-boundary-system) + - [ALWAYS DO](#31-always-do) + - [ASK FIRST](#32-ask-first) + - [NEVER DO](#33-never-do) +4. [Rules Files Summary](#4-rules-files-summary) + +--- + +## 1. Project Identity + +| Field | Value | +|---|---| +| **Name** | WPF UI (wpfui) | +| **Version** | 4.2.0 | +| **Type** | Open-source WPF UI control library implementing Microsoft Fluent Design System | +| **License** | MIT | +| **Language** | C# 14 (`LangVersion: preview`) / XAML | +| **Platforms** | .NET 10/9/8 (windows TFMs) + .NET Framework 4.6.2/4.7.2/4.8.1 | +| **NuGet Package ID** | WPF-UI | +| **XAML Namespace** | `http://schemas.lepo.co/wpfui/2022/xaml` (prefix `ui`) | +| **Repository** | https://github.com/lepoco/wpfui | +| **Formatter** | CSharpier (110 char width, 4-space indent, no tabs) | +| **Analyzers** | StyleCop, AsyncFixer, IDisposableAnalyzers, WpfAnalyzers (all enforced in build) | +| **Test Frameworks** | XUnit v3, NSubstitute, AwesomeAssertions, FlaUI, Coverlet | +| **Assembly Signing** | `src/lepo.snk` for release builds | + +### Project Structure + +| Project | Purpose | TFMs | +|---|---|---| +| `src/Wpf.Ui/` | Core library (NuGet: WPF-UI) | net10.0-windows, net9.0-windows, net8.0-windows, net481, net472, net462 | +| `src/Wpf.Ui.Abstractions/` | Interfaces and abstractions | Includes netstandard2.0/2.1 | +| `src/Wpf.Ui.DependencyInjection/` | Microsoft.Extensions.DependencyInjection integration | Multi-target | +| `src/Wpf.Ui.Tray/` | System tray support | Multi-target | +| `src/Wpf.Ui.ToastNotifications/` | Toast notification support | Multi-target | +| `src/Wpf.Ui.Gallery/` | Demo/showcase application | net10.0-windows10.0.26100.0 | +| `src/Wpf.Ui.Extension/` | Visual Studio 2022 extension with project templates | VS SDK | +| `samples/` | Sample apps (MVVM, DI, console hosting) | Various | + +### Core Library Layout (`src/Wpf.Ui/`) + +| Directory | Contents | +|---|---| +| `Controls/` | Each control in its own subfolder with .cs + .xaml | +| `Appearance/` | `ApplicationThemeManager`, `ApplicationAccentColorManager` | +| `Win32/` | Raw P/Invoke declarations (native methods, structs, enums) | +| `Interop/` | Managed wrappers around Win32 APIs | +| `Converters/` | XAML value converters | +| `AutomationPeers/` | UI Automation/accessibility support | +| `Resources/Fonts/` | Embedded Fluent System Icons (Filled + Regular) | +| Root (`*.cs`) | Service interfaces (`I{Name}Service`) and implementations (`{Name}Service`) | + +--- + +## 2. Coding Conventions + +### 2.1 Namespaces + +All C# files use **file-scoped namespaces**. All controls use the flat `Wpf.Ui.Controls` namespace regardless of subfolder depth, with a ReSharper suppression comment. + +```csharp +// ReSharper disable once CheckNamespace +namespace Wpf.Ui.Controls; + +public class Button : System.Windows.Controls.Button, IAppearanceControl, IIconControl +{ + // ... +} +``` + +Services use the `Wpf.Ui` namespace: + +```csharp +namespace Wpf.Ui; + +public class SnackbarService : ISnackbarService +{ + // ... +} +``` + +Gallery app follows folder-based namespaces: + +```csharp +namespace Wpf.Ui.Gallery.ViewModels.Pages.BasicInput; + +public partial class AnchorViewModel : ViewModel +{ + // ... +} +``` + +### 2.2 Nullable Reference Types + +Nullable is enabled globally via `Directory.Build.props`. Use `is not null` pattern matching over `!= null`. + +```csharp +// Good +if (_presenter is null) +{ + throw new InvalidOperationException($"The SnackbarPresenter was never set"); +} + +_snackbar ??= new Snackbar(_presenter); + +// Good - nullable property types +public IconElement? Icon +{ + get => (IconElement?)GetValue(IconProperty); + set => SetValue(IconProperty, value); +} +``` + +### 2.3 Private Fields + +Private fields use `_camelCase` (underscore prefix). This is enforced by `.editorconfig` naming rules. + +```csharp +private bool _valueUpdating; +private SnackbarPresenter? _presenter; +private Snackbar? _snackbar; +``` + +In Gallery ViewModels with CommunityToolkit.Mvvm, the same convention applies: + +```csharp +[ObservableProperty] +private bool _isAnchorEnabled = true; +``` + +### 2.4 File Headers + +**C# files** -- MIT license header enforced as a build error via `IDE0073`: + +```csharp +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT. +// Copyright (C) Leszek Pomianowski and WPF UI Contributors. +// All Rights Reserved. +``` + +**XAML files** -- XML comment block with MIT license and Microsoft credit where applicable: + +```xml + +``` + +### 2.5 XML Documentation + +Public APIs require `` and `` tags. Examples show XAML usage with `` and HTML-escaped angle brackets. + +**Control class documentation:** + +```csharp +/// +/// Creates a hyperlink to web pages, files, email addresses, locations in the same page, +/// or anything else a URL can address. +/// +/// +/// +/// <ui:Anchor +/// NavigateUri="https://lepo.co/" /> +/// +/// +public class Anchor : Wpf.Ui.Controls.HyperlinkButton { } +``` + +**Control with multiple XAML examples:** + +```csharp +/// +/// Inherited from the , +/// adding . +/// +/// +/// +/// <ui:Button +/// Appearance="Primary" +/// Content="WPF UI button with font icon" +/// Icon="{ui:SymbolIcon Symbol=Fluent24}" /> +/// +/// +/// <ui:Button +/// Appearance="Primary" +/// Content="WPF UI button with font icon" +/// Icon="{ui:FontIcon '🌈'}" /> +/// +/// +``` + +**C# code examples (e.g., for static managers):** + +```csharp +/// +/// +/// ApplicationThemeManager.Apply( +/// ApplicationTheme.Light +/// ); +/// +/// +``` + +**DependencyProperty documentation** uses a single-line ``: + +```csharp +/// Identifies the dependency property. +public static readonly DependencyProperty IconProperty = DependencyProperty.Register( + nameof(Icon), + typeof(IconElement), + typeof(Button), + new PropertyMetadata(null, null, IconElement.Coerce) +); +``` + +**Internal code**: Avoid comments unless explaining Win32 interop/marshalling. Never add comments that restate what the code does. + +### 2.6 Dependency Properties + +Use `DependencyProperty.Register` with `nameof()`. Always include XML doc with the `Identifies the ... dependency property` pattern. + +```csharp +/// Identifies the dependency property. +public static readonly DependencyProperty IconProperty = DependencyProperty.Register( + nameof(Icon), + typeof(IconElement), + typeof(Button), + new PropertyMetadata(null, null, IconElement.Coerce) +); + +/// +/// Gets or sets displayed . +/// +[Bindable(true)] +[Category("Appearance")] +public IconElement? Icon +{ + get => (IconElement?)GetValue(IconProperty); + set => SetValue(IconProperty, value); +} +``` + +For two-way binding with specific update triggers: + +```csharp +/// Identifies the dependency property. +public static readonly DependencyProperty ValueProperty = DependencyProperty.Register( + nameof(Value), + typeof(double?), + typeof(NumberBox), + new FrameworkPropertyMetadata( + null, + FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, + OnValueChanged, + null, + false, + UpdateSourceTrigger.LostFocus + ) +); +``` + +### 2.7 Control Authoring + +Each control resides in its own subfolder under `Controls/{Name}/` containing at minimum: +- `{Name}.cs` -- C# control class +- `{Name}.xaml` -- XAML ResourceDictionary with styles and templates + +Complex controls are split into partial classes: +- `NavigationView.Base.cs` -- Static constructor, instance constructor +- `NavigationView.Properties.cs` -- DependencyProperty declarations +- `NavigationView.Events.cs` -- RoutedEvent declarations +- `NavigationView.TemplateParts.cs` -- Template part handling +- `NavigationView.Navigation.cs` -- Navigation logic + +**Static constructor pattern** (required for custom-styled controls): + +```csharp +static NavigationView() +{ + DefaultStyleKeyProperty.OverrideMetadata( + typeof(NavigationView), + new FrameworkPropertyMetadata(typeof(NavigationView)) + ); +} +``` + +**Template parts pattern:** + +```csharp +[TemplatePart(Name = PART_ClearButton, Type = typeof(Button))] +[TemplatePart(Name = PART_InlineIncrementButton, Type = typeof(RepeatButton))] +[TemplatePart(Name = PART_InlineDecrementButton, Type = typeof(RepeatButton))] +public partial class NumberBox : Wpf.Ui.Controls.TextBox +{ + private const string PART_ClearButton = nameof(PART_ClearButton); + private const string PART_InlineIncrementButton = nameof(PART_InlineIncrementButton); + private const string PART_InlineDecrementButton = nameof(PART_InlineDecrementButton); +} +``` + +**Base class selection:** + +| Base Class | Use For | +|---|---| +| `System.Windows.Controls.ContentControl` | Card, Badge, InfoBar, Snackbar | +| `System.Windows.Controls.Button` | Button | +| `System.Windows.Controls.Control` | TitleBar, ProgressRing, NavigationView | +| `System.Windows.Controls.Primitives.ToggleButton` | ToggleSwitch | +| `System.Windows.Window` | FluentWindow | + +### 2.8 XAML Styles + +Control styles require `OverridesDefaultStyle=True` and `SnapsToDevicePixels=True`. Use `DynamicResource` for theme-dependent values and `StaticResource` for constants. + +```xml + + + + 11,5,11,6 + 1 + + + +``` + +**Resource key naming conventions:** + +| Type | Pattern | Example | +|---|---|---| +| Brushes | `{Name}Brush` | `ButtonForeground`, `TextFillColorPrimaryBrush` | +| Colors | `{Name}Color` | `AccentColor` | +| Thickness | `{ControlName}{Property}` | `ButtonPadding`, `CardBorderThemeThickness` | +| Doubles | `{ControlName}{Property}` | `ToggleButtonWidth` | + +### 2.9 Service Pattern + +Services follow the `I{Name}Service` / `{Name}Service` naming convention. Both interface and implementation live at the `src/Wpf.Ui/` root level (NOT in a `Services/` subfolder). + +**Interface (`src/Wpf.Ui/ISnackbarService.cs`):** + +```csharp +namespace Wpf.Ui; + +/// +/// Represents a contract with the service that provides global . +/// +public interface ISnackbarService +{ + TimeSpan DefaultTimeOut { get; set; } + void SetSnackbarPresenter(SnackbarPresenter contentPresenter); + SnackbarPresenter? GetSnackbarPresenter(); + void Show(string title, string message, ControlAppearance appearance, IconElement? icon, TimeSpan timeout); +} +``` + +**Implementation (`src/Wpf.Ui/SnackbarService.cs`):** + +```csharp +namespace Wpf.Ui; + +/// +/// A service that provides methods related to displaying the . +/// +public class SnackbarService : ISnackbarService +{ + private SnackbarPresenter? _presenter; + private Snackbar? _snackbar; + + /// + public TimeSpan DefaultTimeOut { get; set; } = TimeSpan.FromSeconds(5); + + /// + public void Show( + string title, + string message, + ControlAppearance appearance, + IconElement? icon, + TimeSpan timeout + ) + { + if (_presenter is null) + { + throw new InvalidOperationException($"The SnackbarPresenter was never set"); + } + + _snackbar ??= new Snackbar(_presenter); + // ... + } +} +``` + +**Available services:** + +| Interface | Implementation | Purpose | +|---|---|---| +| `INavigationService` | `NavigationService` | Page navigation | +| `IContentDialogService` | `ContentDialogService` | Modal content dialogs | +| `ISnackbarService` | `SnackbarService` | Toast-like snackbar notifications | +| `ITaskBarService` | `TaskBarService` | Windows taskbar integration | +| `IThemeService` | `ThemeService` | Theme management | + +### 2.10 Error Handling + +**Win32/Interop code** -- Bare catch blocks swallowing exceptions (intentional for cross-OS compatibility): + +```csharp +catch +{ + // Ignore registry access errors on non-Windows or restricted environments + return null; +} +``` + +**Service code** -- Throw `ArgumentNullException` / `InvalidOperationException` for precondition violations: + +```csharp +if (_presenter is null) +{ + throw new InvalidOperationException($"The SnackbarPresenter was never set"); +} +``` + +```csharp +protected void ThrowIfNavigationControlIsNull() +{ + if (NavigationControl is null) + { + throw new ArgumentNullException(nameof(NavigationControl)); + } +} +``` + +### 2.11 Win32 Interop + +The project uses a three-layer architecture for native Windows API access. + +**Layer 1: CsWin32 auto-generated P/Invoke** via `Microsoft.Windows.CsWin32` NuGet package. Native function names are listed in `src/Wpf.Ui/NativeMethods.txt`. + +**Layer 2: Managed wrappers with validation** (`src/Wpf.Ui/Interop/UnsafeNativeMethods.cs`): + +```csharp +internal static class UnsafeNativeMethods +{ + public static unsafe bool ApplyWindowCornerPreference( + IntPtr handle, + WindowCornerPreference cornerPreference + ) + { + // ALWAYS validate handle before calling Win32 APIs + if (handle == IntPtr.Zero) + { + return false; + } + + if (!PInvoke.IsWindow(new HWND(handle))) + { + return false; + } + + DWM_WINDOW_CORNER_PREFERENCE pvAttribute = UnsafeReflection.Cast(cornerPreference); + + return PInvoke.DwmSetWindowAttribute( + new HWND(handle), + DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE, + &pvAttribute, + sizeof(int) + ) == HRESULT.S_OK; + } +} +``` + +**Layer 3: Utility helpers** (`src/Wpf.Ui/Win32/`): + +```csharp +internal sealed class Utilities +{ + public static bool IsOSWindows11OrNewer => _osVersion.Build >= 22000; +} +``` + +**Critical rule**: Always validate handles (`IntPtr.Zero` check + `PInvoke.IsWindow`) before any native API call. Always search the existing codebase before assuming standard WPF approaches work for TitleBar, window management, system theme detection, or DWM integration. + +### 2.12 Test Naming + +**Unit tests** use `MethodName_ExpectedResult_WhenCondition`: + +```csharp +public class TransitionAnimationProviderTests +{ + [Fact] + public void ApplyTransition_ReturnsFalse_WhenDurationIsLessThan10() + { + UIElement mockedUiElement = Substitute.For(); + + var result = TransitionAnimationProvider.ApplyTransition(mockedUiElement, Transition.FadeIn, -10); + + Assert.False(result); + } + + [Fact] + public void ApplyTransition_ReturnsFalse_WhenElementIsNull() + { + var result = TransitionAnimationProvider.ApplyTransition(null, Transition.FadeIn, 1000); + + Assert.False(result); + } +} +``` + +**Integration tests** use the `UiTest` base class with FlaUI and AwesomeAssertions: + +```csharp +public sealed class TitleBarTests : UiTest +{ + [Fact] + public async Task CloseButton_ShouldCloseWindow_WhenClicked() + { + Button? closeButton = FindFirst("TitleBarCloseButton").AsButton(); + + closeButton.Should().NotBeNull("because CloseButton should be present in the main window title bar"); + closeButton.Click(moveMouse: false); + + await Wait(2); + + Application + ?.HasExited.Should() + .BeTrue("because the main window should be closed after clicking the close button"); + } +} +``` + +### 2.13 MVVM (Gallery / Samples) + +The Gallery app uses CommunityToolkit.Mvvm. ViewModels are `partial` classes inheriting from `ViewModel` (which extends `ObservableObject`). + +**ViewModel (`src/Wpf.Ui.Gallery/ViewModels/Pages/BasicInput/AnchorViewModel.cs`):** + +```csharp +namespace Wpf.Ui.Gallery.ViewModels.Pages.BasicInput; + +public partial class AnchorViewModel : ViewModel +{ + [ObservableProperty] + private bool _isAnchorEnabled = true; + + [RelayCommand] + private void OnAnchorCheckboxChecked(object sender) + { + if (sender is not CheckBox checkbox) + { + return; + } + + IsAnchorEnabled = !(checkbox?.IsChecked ?? false); + } +} +``` + +**Page (`src/Wpf.Ui.Gallery/Views/Pages/BasicInput/AnchorPage.xaml.cs`):** + +```csharp +namespace Wpf.Ui.Gallery.Views.Pages.BasicInput; + +[GalleryPage("Button which opens a link.", SymbolRegular.CubeLink20)] +public partial class AnchorPage : INavigableView +{ + public AnchorViewModel ViewModel { get; init; } + + public AnchorPage(AnchorViewModel viewModel) + { + ViewModel = viewModel; + DataContext = this; + + InitializeComponent(); + } +} +``` + +Key MVVM conventions: +- `[ObservableProperty]` for `_camelCase` backing fields (generates PascalCase properties) +- `[RelayCommand]` for `On`-prefixed private methods (generates `{Name}Command` ICommand) +- Pattern matching with `is not` for type checks in command handlers +- `DataContext = this` set in page constructor (not in XAML) +- `INavigableView` interface for navigation-aware pages + +### 2.14 Central Package Management + +All NuGet package versions are centralized in `Directory.Packages.props`. Individual `.csproj` files reference packages without version attributes. + +**`Directory.Packages.props` (versions defined here):** + +```xml + + + + + + + + + + +``` + +**`.csproj` (NO version attribute):** + +```xml + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + +``` + +### 2.15 Commit Convention + +**Format**: `type(scope): description (#PR_NUMBER)` + +| Type | Use For | +|---|---| +| `feat` | New feature | +| `fix` | Bug fix | +| `chore` | Build/tooling changes | + +| Scope | Use For | +|---|---| +| `controls` | Control-related changes | +| `app` | Gallery app changes | +| `docs` | Documentation changes | + +**Examples from actual commits:** + +``` +fix(controls): Fix TextBlock issues (#1640) +fix(controls): Remove AnimationFactorToValueConverter from CheckBox.xaml (#1649) +fix(controls): NavigationViewItemAutomationPeer followup (#1646) +fix(controls): Update ContentDialog For TitleBar CenterContent (#1642) +feat(app): Add ability to disable automatic apply of system accent color in UiApplication (#1643) +``` + +--- + +## 3. Boundary System + +### 3.1 ALWAYS DO + +These practices are mandatory. Violations will be caught by analyzers, build errors, or code review. + +| # | Rule | Enforcement | +|---|---|---| +| A1 | Use file-scoped namespaces in all C# files | `.editorconfig` `csharp_style_namespace_declarations = file_scoped:warning` | +| A2 | Enable nullable reference types | `Directory.Build.props` `enable` | +| A3 | Add MIT license file header to every .cs and .xaml file | `.editorconfig` `IDE0073 = error` | +| A4 | Add XML docs with `` and `` tags for all public APIs | Code review | +| A5 | Use CSharpier for formatting (110 char, 4-space indent) | `.csharpierrc`, CI | +| A6 | Use Central Package Management (versions in `Directory.Packages.props` only) | `Directory.Build.props` `true` | +| A7 | Validate Win32 handles before any native API call (`IntPtr.Zero` + `PInvoke.IsWindow`) | Code review | +| A8 | Use pattern matching (`is not`) over type checks with casts | `.editorconfig` `csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion` | +| A9 | Use `DependencyProperty.Register` with `nameof()` | Code review, WpfAnalyzers | +| A10 | Include XAML example in XML docs for controls | Code review | +| A11 | Follow commit convention: `type(scope): description (#PR_NUMBER)` | Code review | +| A12 | Search existing codebase before assuming standard WPF approaches work for Win32 | Code review | +| A13 | Use `_camelCase` for private fields | `.editorconfig` naming rules | +| A14 | Set `OverridesDefaultStyle=True` and `SnapsToDevicePixels=True` in control styles | Code review | +| A15 | Use `DynamicResource` for theme-dependent values, `StaticResource` for constants | Code review | +| A16 | Place each control in its own subfolder under `Controls/` | Project convention | + +### 3.2 ASK FIRST + +These actions require discussion with the maintainer before proceeding. + +| # | Action | Reason | +|---|---|---| +| Q1 | Adding a new NuGet package dependency | Impacts all consumers; increases dependency surface area | +| Q2 | Modifying Win32/Interop layer | May affect multiple OS versions; requires testing on Windows 10/11 | +| Q3 | Changing public API surface | Breaking change potential for library consumers | +| Q4 | Modifying TitleBar or window management code | Complex Win32 interaction with WndProc message handling, snap layouts, DWM | +| Q5 | Adding new multi-target framework conditional compilation | Increases maintenance burden across 6 TFMs | +| Q6 | Modifying theme/accent color management | System-wide effects; affects all controls | +| Q7 | Changing NavigationView behavior | Most complex control in the library (27+ DependencyProperties, 7+ RoutedEvents, 6 partial files) | +| Q8 | Modifying assembly signing or packaging configuration | Impacts NuGet distribution and strong-name consumers | + +### 3.3 NEVER DO + +These practices are forbidden. Violations will be rejected in code review. + +| # | Rule | Enforcement | +|---|---|---| +| N1 | Never add package versions directly to `.csproj` files | Central Package Management enabled | +| N2 | Never use `var` for non-apparent types | `.editorconfig` `csharp_style_var_elsewhere = false:warning` | +| N3 | Never use primary constructors | `.editorconfig` `IDE0290 = none` (suppressed) | +| N4 | Never add comments that merely restate what the code does | Code review | +| N5 | Never use emoticons in code or comments | Code review | +| N6 | Never assume library/NuGet availability without checking `Directory.Packages.props` | Code review | +| N7 | Never skip handle validation in Win32 interop code | Code review | +| N8 | Never use expression-bodied members for constructors, methods, or properties | `.editorconfig` `csharp_style_expression_bodied_* = false:silent` | +| N9 | Never modify namespace to match folder structure for Controls | `.editorconfig` `IDE0130 = none` (suppressed); flat `Wpf.Ui.Controls` namespace | +| N10 | Never use `this.` qualification | `.editorconfig` `SA1101 = none`; `dotnet_style_qualification = false:silent` | + +**Concrete examples of NEVER DO violations and their corrections:** + +```csharp +// N1 VIOLATION: version in .csproj + +// CORRECT: no version attribute + + +// N2 VIOLATION: var for non-apparent type +var service = container.Resolve(); +// CORRECT: explicit type +ISnackbarService service = container.Resolve(); + +// N3 VIOLATION: primary constructor +public class MyService(ILogger logger) : IMyService { } +// CORRECT: traditional constructor +public class MyService : IMyService +{ + private readonly ILogger _logger; + + public MyService(ILogger logger) + { + _logger = logger; + } +} + +// N7 VIOLATION: no handle validation +PInvoke.DwmSetWindowAttribute(new HWND(handle), attr, &value, sizeof(int)); +// CORRECT: validate first +if (handle == IntPtr.Zero) return false; +if (!PInvoke.IsWindow(new HWND(handle))) return false; +PInvoke.DwmSetWindowAttribute(new HWND(handle), attr, &value, sizeof(int)); + +// N8 VIOLATION: expression-bodied property +public string Name => _name; +// CORRECT: block body +public string Name +{ + get => _name; +} + +// N9 VIOLATION: folder-matching namespace +namespace Wpf.Ui.Controls.Button; +// CORRECT: flat Controls namespace +// ReSharper disable once CheckNamespace +namespace Wpf.Ui.Controls; + +// N10 VIOLATION: this. qualification +this._name = name; +// CORRECT: no this. +_name = name; +``` + +--- + +## 4. Rules Files Summary + +The project enforces conventions through multiple configuration files working in concert. + +### Configuration Files Overview + +| File | Location | Purpose | +|---|---|---| +| `.editorconfig` | `/` | Encoding, indent rules, naming rules, diagnostic severities | +| `.csharpierrc` | `/` | CSharpier formatter configuration | +| `Directory.Build.props` | `/` | Version, lang version, nullable, unsafe blocks, CPM | +| `Directory.Build.targets` | `/` | SourceLink, assembly signing, analyzers, trimming | +| `Directory.Packages.props` | `/` | Centralized NuGet package versions | +| `.github/copilot-instructions.md` | `/.github/` | GitHub Copilot AI agent instructions | +| `CLAUDE.md` | `/` | Claude Code AI agent guidance | +| `.github/pull_request_template.md` | `/.github/` | PR template with type checkboxes | + +### `.editorconfig` -- Key Rules + +**Encoding and Indentation:** + +| File Type | Indent | Size | +|---|---|---| +| C# (`*.cs`) | Spaces | 4 | +| XML/JSON/YAML (`*.xml`, `*.json`, `*.yml`) | Spaces | 2 | +| MSBuild (`*.csproj`, `*.props`, `*.targets`) | Spaces | 2 | +| Solutions (`*.sln`, `*.slnx`) | Tabs | 4 | +| Markdown (`*.md`) | Spaces | 2 | + +**Enforced Diagnostics (error/warning level):** + +| Diagnostic | Severity | Description | +|---|---|---| +| `IDE0073` | **error** | File header template (MIT license) | +| `csharp_style_namespace_declarations` | warning | File-scoped namespaces | +| `csharp_style_var_elsewhere` | warning | No `var` for non-apparent types | +| `dotnet_style_predefined_type_for_locals_parameters_members` | warning | Language keywords over BCL types | +| `dotnet_style_readonly_field` | warning | Readonly fields where possible | +| `csharp_prefer_static_local_function` | warning | Static local functions | +| `csharp_style_pattern_local_over_anonymous_function` | warning | Local functions over lambdas | +| `csharp_using_directive_placement` | warning | Usings outside namespace | + +**Suppressed Diagnostics:** + +| Diagnostic | Reason | +|---|---| +| `SA1101` | No `this.` qualification required | +| `SA1309` | Allow underscore-prefixed field names | +| `SA1600` | Relaxed XML documentation enforcement (not all members require docs) | +| `CS1591` | Missing XML comment (15000+ existing warnings) | +| `IDE0130` | Namespace not matching folder structure (flat `Wpf.Ui.Controls`) | +| `IDE0290` | Primary constructors not used | +| `CA1510` | `ArgumentNullException.ThrowIfNull` helper not available on older TFMs | +| `WPF0070-0073` | ValueConversion attribute warnings relaxed | + +**Naming Rules:** + +| Symbol | Style | Example | +|---|---|---| +| Constants | PascalCase | `MaxRetryCount` | +| Public/internal/protected fields | PascalCase | `DefaultTimeOut` | +| Public static readonly fields | PascalCase | `IconProperty` | +| Private/protected fields | `_camelCase` | `_presenter` | +| Public methods/properties/events | PascalCase | `Navigate()` | +| Parameters | camelCase | `contentPresenter` | +| Interfaces | `I` + PascalCase | `ISnackbarService` | + +### `.csharpierrc` -- Formatter Configuration + +```json +{ + "printWidth": 110, + "useTabs": false, + "tabWidth": 4, + "preprocessorSymbolSets": [ + "", + "DEBUG", + "DEBUG,CODE_STYLE" + ] +} +``` + +Run formatter: `dotnet csharpier .` + +### `Directory.Build.props` -- Central Build Configuration + +| Property | Value | Effect | +|---|---|---| +| `Version` | `4.2.0` | Assembly and package version | +| `LangVersion` | `14.0` | C# 14 language features | +| `Nullable` | `enable` | Nullable reference types globally enabled | +| `AllowUnsafeBlocks` | `true` | Required for Win32 interop | +| `ManagePackageVersionsCentrally` | `true` | Central Package Management | +| `EnforceCodeStyleInBuild` | `true` | Analyzer warnings fail the build | +| `GenerateDocumentationFile` | `true` | For core projects only | +| `PackageLicenseExpression` | `MIT` | License metadata | + +### `Directory.Build.targets` -- Build Targets + +Key responsibilities: +- **Analyzer packages**: AsyncFixer, IDisposableAnalyzers, StyleCop.Analyzers added to all projects (except `.dcproj`, `.sfproj`, VS template/extension projects) +- **PolySharp**: Added for netstandard2.0/2.1 and net462/472/481 targets (backports modern C# features) +- **SourceLink**: GitHub SourceLink for NuGet packages +- **Assembly signing**: Conditional on `SourceLinkEnabled=true` AND `GeneratePackageOnBuild=true`, using `src/lepo.snk` +- **Trimming/AOT**: Enabled for .NET 6+ targets (`IsTrimmable`, `EnableTrimAnalyzer`, `EnableAotAnalyzer`, `EnableSingleFileAnalyzer`) +- **Commit hash**: Embedded as `AssemblyMetadataAttribute("CommitHash", ...)` via `SourceRevisionId` + +### `Directory.Packages.props` -- Centralized Package Versions + +All NuGet package versions for the entire solution are defined here. Key packages: + +| Package | Version | Purpose | +|---|---|---| +| `CommunityToolkit.Mvvm` | 8.4.0 | MVVM toolkit for Gallery/samples | +| `Microsoft.Windows.CsWin32` | 0.3.242 | P/Invoke source generator | +| `StyleCop.Analyzers` | 1.2.0-beta.556 | Code style analyzer | +| `WpfAnalyzers` | 4.1.1 | WPF-specific analyzers | +| `AsyncFixer` | 1.6.0 | Async/await analyzer | +| `IDisposableAnalyzers` | 4.0.8 | IDisposable pattern analyzer | +| `xunit.v3` | 3.2.0 | Unit test framework | +| `NSubstitute` | 5.3.0 | Mocking framework | +| `AwesomeAssertions` | 9.3.0 | Fluent assertions | +| `FlaUI.Core` / `FlaUI.UIA3` | 5.0.0 | UI automation testing | +| `PolySharp` | 1.15.0 | C# feature polyfills for older TFMs | +| `Microsoft.Extensions.Hosting` | 10.0.0 | .NET Generic Host | + +### `.github/copilot-instructions.md` -- AI Agent Instructions + +Provides GitHub Copilot with: +- Project structure and build commands +- Code conventions (never assume library availability, modern C#) +- XML documentation requirements with concrete examples +- Windows platform guidance (search before assuming WPF approaches work) +- Tone/style: no emoticons, minimize output, no preamble/postamble +- MVVM patterns for Gallery pages +- Testing patterns (unit + integration) + +### `CLAUDE.md` -- Claude Code Guidance + +Provides Claude Code with: +- Build commands for all solution configurations +- Architecture overview (project structure, core library layout) +- Key patterns (Win32 interop, XAML resources, MVVM, CPM) +- Testing frameworks and conventions +- Code conventions summary +- Commit convention format + +### `.github/pull_request_template.md` -- PR Template + +PR type checkboxes: +- Update +- Bugfix +- Feature +- Code style update (formatting, renaming) +- Refactoring (no functional changes, no API changes) +- Build related changes +- Documentation content changes + +Required sections: +- What is the current behavior? (with Issue Number field) +- What is the new behavior? +- Other information (screenshots) + +--- + +## Version Information + +| Field | Value | +|---|---| +| **Library Version** | 4.2.0 | +| **Language Version** | C# 14 (preview) | +| **Target Frameworks** | .NET 10/9/8, .NET Framework 4.8.1/4.7.2/4.6.2 | +| **NuGet Package ID** | WPF-UI | +| **XAML Namespace** | `http://schemas.lepo.co/wpfui/2022/xaml` | +| **XAML Prefix** | `ui` | +| **Constitution Version** | 1.0 (2025-02-10) | diff --git a/docs/architecture/IMPLEMENTATION-GUIDE.md b/docs/architecture/IMPLEMENTATION-GUIDE.md new file mode 100644 index 000000000..f68270eef --- /dev/null +++ b/docs/architecture/IMPLEMENTATION-GUIDE.md @@ -0,0 +1,1315 @@ +# WPF UI Implementation Guide + +Step-by-step guides for common implementation tasks in the WPF UI project. + +## Table of Contents +1. [How to Add a New Control](#1-how-to-add-a-new-control) +2. [How to Add a New Service](#2-how-to-add-a-new-service) +3. [How to Add a Gallery Page](#3-how-to-add-a-gallery-page) +4. [How to Use Win32 Interop](#4-how-to-use-win32-interop) +5. [How to Add Theme Support](#5-how-to-add-theme-support) +6. [Testing a Feature](#6-testing-a-feature) + +--- + +## 1. How to Add a New Control + +This guide walks through creating a new control following the WPF UI conventions. + +### Step 1: Create Control Folder Structure + +``` +src/Wpf.Ui/Controls/ +└── MyControl/ + ├── MyControl.cs # Code-behind class + └── MyControl.xaml # Style and ControlTemplate +``` + +### Step 2: Create the Control Class (MyControl.cs) + +```csharp +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT. +// Copyright (C) Leszek Pomianowski and WPF UI Contributors. +// All Rights Reserved. + +using System.Windows; +using System.Windows.Controls; + +// ReSharper disable once CheckNamespace +namespace Wpf.Ui.Controls; + +/// +/// A custom control that demonstrates the WPF UI control pattern. +/// +/// +/// +/// <ui:MyControl +/// Content="Hello World" +/// Appearance="Primary" +/// Icon="{ui:SymbolIcon Symbol=Heart24}" /> +/// +/// +public class MyControl : ContentControl, IAppearanceControl, IIconControl +{ + /// Identifies the dependency property. + public static readonly DependencyProperty AppearanceProperty = DependencyProperty.Register( + nameof(Appearance), + typeof(ControlAppearance), + typeof(MyControl), + new PropertyMetadata(ControlAppearance.Primary) + ); + + /// Identifies the dependency property. + public static readonly DependencyProperty IconProperty = DependencyProperty.Register( + nameof(Icon), + typeof(IconElement), + typeof(MyControl), + new PropertyMetadata(null, null, IconElement.Coerce) + ); + + /// Identifies the dependency property. + public static readonly DependencyProperty IsCustomProperty = DependencyProperty.Register( + nameof(IsCustom), + typeof(bool), + typeof(MyControl), + new PropertyMetadata(false, OnIsCustomChanged) + ); + + static MyControl() + { + DefaultStyleKeyProperty.OverrideMetadata( + typeof(MyControl), + new FrameworkPropertyMetadata(typeof(MyControl)) + ); + } + + /// + /// Gets or sets the appearance of the control. + /// + [Bindable(true)] + [Category("Appearance")] + public ControlAppearance Appearance + { + get => (ControlAppearance)GetValue(AppearanceProperty); + set => SetValue(AppearanceProperty, value); + } + + /// + /// Gets or sets the icon displayed in the control. + /// + [Bindable(true)] + [Category("Appearance")] + public IconElement? Icon + { + get => (IconElement?)GetValue(IconProperty); + set => SetValue(IconProperty, value); + } + + /// + /// Gets or sets a value indicating whether the control uses custom behavior. + /// + [Bindable(true)] + [Category("Behavior")] + public bool IsCustom + { + get => (bool)GetValue(IsCustomProperty); + set => SetValue(IsCustomProperty, value); + } + + private static void OnIsCustomChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is not MyControl control) + { + return; + } + + control.OnIsCustomChanged((bool)e.NewValue); + } + + protected virtual void OnIsCustomChanged(bool isCustom) + { + // Handle property change + } +} +``` + +### Step 3: Create the XAML Style (MyControl.xaml) + +```xml + + + + + 11,5,11,6 + 1 + 0,0,8,0 + 32 + + + + +``` + +### Step 4: Register in Wpf.Ui.xaml + +Add reference to `src/Wpf.Ui/Resources/Wpf.Ui.xaml`: + +```xml + + + + +``` + +### Step 5: Add Designer Support (Optional) + +Add toolbox icon bitmap to `src/Wpf.Ui/Assets/Toolbox/MyControl.bmp` (16x16 pixels). + +Register in `src/Wpf.Ui/VisualStudioToolsManifest.xml`: + +```xml + + + +``` + +### Step 6: Add to Category Documentation + +Document the control category in architecture notes for future reference. + +--- + +## 2. How to Add a New Service + +This guide shows how to create a new service following the WPF UI service pattern. + +### Step 1: Create the Service Interface (IMyService.cs) + +```csharp +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT. +// Copyright (C) Leszek Pomianowski and WPF UI Contributors. +// All Rights Reserved. + +namespace Wpf.Ui; + +/// +/// Provides functionality for managing custom operations. +/// +public interface IMyService +{ + /// + /// Gets the current state of the service. + /// + bool IsActive { get; } + + /// + /// Initializes the service with the specified parameter. + /// + /// The initialization parameter. + void Initialize(string parameter); + + /// + /// Performs an asynchronous operation. + /// + /// Cancellation token. + /// A task representing the asynchronous operation. + Task PerformOperationAsync(CancellationToken cancellationToken = default); +} +``` + +### Step 2: Create the Service Implementation (MyService.cs) + +```csharp +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT. +// Copyright (C) Leszek Pomianowski and WPF UI Contributors. +// All Rights Reserved. + +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Wpf.Ui; + +/// +/// Implementation of . +/// +public partial class MyService : IMyService +{ + private string? _parameter; + private bool _isActive; + + /// + public bool IsActive => _isActive; + + /// + public void Initialize(string parameter) + { + ArgumentNullException.ThrowIfNull(parameter); + + _parameter = parameter; + _isActive = true; + } + + /// + public async Task PerformOperationAsync(CancellationToken cancellationToken = default) + { + ThrowIfNotInitialized(); + + try + { + // Perform async operation + await Task.Delay(100, cancellationToken); + return true; + } + catch (OperationCanceledException) + { + return false; + } + } + + private void ThrowIfNotInitialized() + { + if (!_isActive || _parameter is null) + { + throw new InvalidOperationException("Service has not been initialized. Call Initialize() first."); + } + } +} +``` + +### Step 3: Add DI Extension Method + +Create `src/Wpf.Ui.DependencyInjection/ServiceCollectionExtensions.MyService.cs`: + +```csharp +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT. +// Copyright (C) Leszek Pomianowski and WPF UI Contributors. +// All Rights Reserved. + +using Microsoft.Extensions.DependencyInjection; +using Wpf.Ui; + +namespace Wpf.Ui.DependencyInjection; + +/// +/// Extension methods for registering WPF UI services. +/// +public static partial class ServiceCollectionExtensions +{ + /// + /// Registers as a singleton. + /// + /// The service collection. + /// The service collection for chaining. + public static IServiceCollection AddMyService(this IServiceCollection services) + { + _ = services.AddSingleton(); + return services; + } +} +``` + +### Step 4: Register in Application + +```csharp +// In your application startup (e.g., App.xaml.cs or Program.cs) +services.AddMyService(); +``` + +### Step 5: Use the Service + +```csharp +public partial class MyViewModel : ViewModel +{ + private readonly IMyService _myService; + + public MyViewModel(IMyService myService) + { + _myService = myService; + _myService.Initialize("parameter-value"); + } + + [RelayCommand] + private async Task OnPerformAction() + { + bool result = await _myService.PerformOperationAsync(); + // Handle result + } +} +``` + +--- + +## 3. How to Add a Gallery Page + +This guide demonstrates adding a new demo page to the WPF UI Gallery application. + +### Step 1: Create ViewModel + +Create `src/Wpf.Ui.Gallery/ViewModels/Pages/MyCategory/MyControlViewModel.cs`: + +```csharp +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT. +// Copyright (C) Leszek Pomianowski and WPF UI Contributors. +// All Rights Reserved. + +using System.Collections.ObjectModel; +using System.Threading.Tasks; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using Wpf.Ui.Controls; + +namespace Wpf.Ui.Gallery.ViewModels.Pages.MyCategory; + +public partial class MyControlViewModel : ViewModel +{ + [ObservableProperty] + private string _textValue = "Hello World"; + + [ObservableProperty] + private bool _isEnabled = true; + + [ObservableProperty] + private ControlAppearance _selectedAppearance = ControlAppearance.Primary; + + [ObservableProperty] + private ObservableCollection _appearances = new() + { + ControlAppearance.Primary, + ControlAppearance.Secondary, + ControlAppearance.Success, + ControlAppearance.Danger + }; + + [RelayCommand] + private void OnToggleEnabled() + { + IsEnabled = !IsEnabled; + } + + [RelayCommand] + private async Task OnPerformAction() + { + await Task.Delay(500); + TextValue = "Action performed!"; + } + + public override Task OnNavigatedToAsync() + { + // Called when page is navigated to + return base.OnNavigatedToAsync(); + } + + public override Task OnNavigatedFromAsync() + { + // Called when navigating away from page + return base.OnNavigatedFromAsync(); + } +} +``` + +### Step 2: Create Page (XAML) + +Create `src/Wpf.Ui.Gallery/Views/Pages/MyCategory/MyControlPage.xaml`: + +```xml + + + + + + + + + + Demonstrates the MyControl with various appearance modes and interactions. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ]]> + + + + + +``` + +### Step 3: Create Page Code-Behind + +Create `src/Wpf.Ui.Gallery/Views/Pages/MyCategory/MyControlPage.xaml.cs`: + +```csharp +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT. +// Copyright (C) Leszek Pomianowski and WPF UI Contributors. +// All Rights Reserved. + +using Wpf.Ui.Controls; +using Wpf.Ui.Gallery.Attributes; +using Wpf.Ui.Gallery.ViewModels.Pages.MyCategory; + +namespace Wpf.Ui.Gallery.Views.Pages.MyCategory; + +[GalleryPage("Custom control demonstration.", SymbolRegular.Heart24)] +public partial class MyControlPage : INavigableView +{ + public MyControlViewModel ViewModel { get; } + + public MyControlPage(MyControlViewModel viewModel) + { + ViewModel = viewModel; + DataContext = this; + InitializeComponent(); + } +} +``` + +### Step 4: Register in DI Container + +Add to `src/Wpf.Ui.Gallery/App.xaml.cs` or DI configuration: + +```csharp +// Register ViewModel +services.AddSingleton(); + +// Register Page +services.AddSingleton(); +``` + +### Step 5: Add Navigation Entry + +If you need manual navigation registration (not using automatic discovery), add to navigation setup: + +```csharp +navigationService.SetNavigationControl(navigationView); + +// Register page type +menuItems.Add(new NavigationViewItem +{ + Content = "MyControl", + Icon = new SymbolIcon { Symbol = SymbolRegular.Heart24 }, + TargetPageType = typeof(MyControlPage) +}); +``` + +--- + +## 4. How to Use Win32 Interop + +This guide demonstrates the three-layer Win32 interop architecture used in WPF UI. + +### Step 1: Add Function to NativeMethods.txt + +Edit `src/Wpf.Ui/NativeMethods.txt`: + +```text +// Existing entries... +DwmSetWindowAttribute +IsWindow + +// Add your new function +GetSystemMetrics +``` + +This triggers CsWin32 to auto-generate P/Invoke declarations in the `Windows.Win32` namespace. + +### Step 2: Create Managed Wrapper (if needed) + +If you need a safe wrapper with handle validation, add to `src/Wpf.Ui/Interop/UnsafeNativeMethods.cs`: + +```csharp +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.UI.WindowsAndMessaging; + +namespace Wpf.Ui.Interop; + +internal static partial class UnsafeNativeMethods +{ + /// + /// Safely retrieves a system metric value with handle validation. + /// + /// Window handle for context (optional). + /// The system metric to retrieve. + /// The metric value, or 0 if the operation fails. + public static int GetSystemMetricSafe(IntPtr handle, SYSTEM_METRICS_INDEX metric) + { + // Validate handle if provided + if (handle != IntPtr.Zero && !PInvoke.IsWindow(new HWND(handle))) + { + return 0; + } + + try + { + return PInvoke.GetSystemMetrics(metric); + } + catch + { + // Swallow exception for OS compatibility (intentional) + return 0; + } + } +} +``` + +### Step 3: Create Utility Helper (if needed) + +If you need higher-level utility functions, add to `src/Wpf.Ui/Win32/Utilities.cs`: + +```csharp +using Wpf.Ui.Interop; +using Windows.Win32.UI.WindowsAndMessaging; + +namespace Wpf.Ui.Win32; + +internal sealed class Utilities +{ + /// + /// Gets the width of the window border. + /// + public static int BorderWidth => UnsafeNativeMethods.GetSystemMetricSafe( + IntPtr.Zero, + SYSTEM_METRICS_INDEX.SM_CXBORDER + ); + + /// + /// Gets the height of the window border. + /// + public static int BorderHeight => UnsafeNativeMethods.GetSystemMetricSafe( + IntPtr.Zero, + SYSTEM_METRICS_INDEX.SM_CYBORDER + ); +} +``` + +### Step 4: Use in Your Code + +```csharp +using Wpf.Ui.Interop; +using Wpf.Ui.Win32; +using Windows.Win32; +using Windows.Win32.Foundation; + +// Option 1: Direct P/Invoke (for simple cases) +int screenWidth = PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CXSCREEN); + +// Option 2: Safe wrapper (validates handle, catches exceptions) +int borderWidth = UnsafeNativeMethods.GetSystemMetricSafe( + windowHandle, + SYSTEM_METRICS_INDEX.SM_CXBORDER +); + +// Option 3: High-level utility +int borderHeight = Utilities.BorderHeight; +``` + +### Step 5: Handle WndProc Messages (if needed) + +To intercept Windows messages: + +```csharp +using System.Windows.Interop; +using Windows.Win32.UI.WindowsAndMessaging; + +public class MyWindow : Window +{ + private HwndSource? _hwndSource; + + protected override void OnSourceInitialized(EventArgs e) + { + base.OnSourceInitialized(e); + + _hwndSource = (HwndSource)PresentationSource.FromVisual(this); + _hwndSource?.AddHook(WndProc); + } + + private IntPtr WndProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) + { + switch ((uint)msg) + { + case PInvoke.WM_DWMCOLORIZATIONCOLORCHANGED: + // Handle theme change + handled = true; + break; + + case PInvoke.WM_THEMECHANGED: + // Handle system theme change + handled = true; + break; + } + + return IntPtr.Zero; + } + + protected override void OnClosed(EventArgs e) + { + _hwndSource?.RemoveHook(WndProc); + base.OnClosed(e); + } +} +``` + +### Important Notes + +1. **Always validate handles** before calling Win32 APIs: + ```csharp + if (handle == IntPtr.Zero || !PInvoke.IsWindow(new HWND(handle))) + return false; + ``` + +2. **Catch exceptions** in interop code for OS compatibility (some APIs may not exist on older Windows versions) + +3. **Use `unsafe` keyword** for pointer operations + +4. **Check OS version** before using new APIs: + ```csharp + if (Wpf.Ui.Win32.Utilities.IsOSWindows11OrNewer) + { + // Use Windows 11-specific API + } + ``` + +--- + +## 5. How to Add Theme Support + +This guide shows how to make your control theme-aware. + +### Step 1: Use DynamicResource in XAML + +```xml + +``` + +### Step 2: Subscribe to Theme Changes (if needed) + +If your control needs to react to theme changes programmatically: + +```csharp +using Wpf.Ui.Appearance; + +public class MyControl : ContentControl +{ + public MyControl() + { + ApplicationThemeManager.Changed += OnThemeChanged; + } + + private void OnThemeChanged(ApplicationTheme currentTheme, Color systemAccent) + { + // React to theme change + UpdateVisuals(currentTheme); + } + + private void UpdateVisuals(ApplicationTheme theme) + { + // Update control state based on theme + if (theme == ApplicationTheme.Dark) + { + // Dark theme-specific logic + } + else + { + // Light theme-specific logic + } + } +} +``` + +### Step 3: Use Theme-Aware Brushes + +Available dynamic brush resources: + +**Background Brushes**: +- `ApplicationBackgroundBrush` +- `ControlFillColorDefaultBrush` +- `ControlFillColorSecondaryBrush` +- `ControlFillColorTertiaryBrush` +- `ControlFillColorDisabledBrush` + +**Foreground Brushes**: +- `TextFillColorPrimaryBrush` +- `TextFillColorSecondaryBrush` +- `TextFillColorTertiaryBrush` +- `TextFillColorDisabledBrush` + +**Accent Brushes**: +- `AccentFillColorDefaultBrush` +- `AccentFillColorSecondaryBrush` +- `AccentFillColorTertiaryBrush` +- `SystemAccentColor` (Color, not Brush) + +**Border Brushes**: +- `ControlElevationBorderBrush` +- `ControlStrokeColorDefaultBrush` +- `ControlStrokeColorSecondaryBrush` + +### Step 4: Implement IThemeControl (if applicable) + +```csharp +public class MyControl : ContentControl, IThemeControl +{ + /// Identifies the dependency property. + public static readonly DependencyProperty ThemeProperty = DependencyProperty.Register( + nameof(Theme), + typeof(ApplicationTheme), + typeof(MyControl), + new PropertyMetadata(ApplicationTheme.Light, OnThemeChanged) + ); + + /// + /// Gets or sets the theme of the control. + /// + public ApplicationTheme Theme + { + get => (ApplicationTheme)GetValue(ThemeProperty); + set => SetValue(ThemeProperty, value); + } + + private static void OnThemeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is MyControl control) + { + control.UpdateTheme((ApplicationTheme)e.NewValue); + } + } + + private void UpdateTheme(ApplicationTheme theme) + { + // Update control based on theme + } +} +``` + +### Step 5: Test Theme Switching + +```csharp +// Apply theme programmatically +ApplicationThemeManager.Apply(ApplicationTheme.Dark); + +// Apply system accent color +ApplicationAccentColorManager.ApplySystemAccent(); + +// Watch for system theme changes +SystemThemeWatcher.Watch(myWindow); +``` + +--- + +## 6. Testing a Feature + +This guide covers both unit testing and integration testing for WPF UI. + +### Unit Test Example + +Create `tests/Wpf.Ui.UnitTests/Controls/MyControlTests.cs`: + +```csharp +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT. +// Copyright (C) Leszek Pomianowski and WPF UI Contributors. +// All Rights Reserved. + +using System.Windows; +using NSubstitute; +using Wpf.Ui.Controls; +using Xunit; + +namespace Wpf.Ui.UnitTests.Controls; + +public class MyControlTests +{ + [Fact] + public void Constructor_ShouldSetDefaultValues() + { + // Arrange & Act + var control = new MyControl(); + + // Assert + Assert.Equal(ControlAppearance.Primary, control.Appearance); + Assert.Null(control.Icon); + Assert.False(control.IsCustom); + } + + [Fact] + public void Appearance_ShouldUpdateWhenSet() + { + // Arrange + var control = new MyControl(); + + // Act + control.Appearance = ControlAppearance.Secondary; + + // Assert + Assert.Equal(ControlAppearance.Secondary, control.Appearance); + } + + [Fact] + public void IsCustomChanged_ShouldInvokeCallback_WhenValueChanges() + { + // Arrange + var control = new MyControl(); + bool callbackInvoked = false; + + // Subscribe to property change if there's a routed event + // Or test via reflection if the method is protected + + // Act + control.IsCustom = true; + + // Assert + Assert.True(control.IsCustom); + } + + [Fact] + public void Icon_ShouldAcceptSymbolIcon() + { + // Arrange + var control = new MyControl(); + var icon = new SymbolIcon { Symbol = SymbolRegular.Heart24 }; + + // Act + control.Icon = icon; + + // Assert + Assert.Equal(icon, control.Icon); + } +} +``` + +### Integration Test Example + +Create `tests/Wpf.Ui.Gallery.IntegrationTests/MyControlTests.cs`: + +```csharp +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT. +// Copyright (C) Leszek Pomianowski and WPF UI Contributors. +// All Rights Reserved. + +using AwesomeAssertions.Autofac; +using FlaUI.Core.AutomationElements; +using Wpf.Ui.Gallery.IntegrationTests.Fixtures; +using Xunit; + +namespace Wpf.Ui.Gallery.IntegrationTests; + +public sealed class MyControlTests : UiTest +{ + [Fact] + public async Task MyControl_ShouldBeVisible_WhenPageLoads() + { + // Arrange + await NavigateToPage("MyControl"); + + // Act + var control = Window.FindFirstDescendant(cf => + cf.ByAutomationId("MyControlExample")); + + // Assert + control.Should().NotBeNull(); + control.IsOffscreen.Should().BeFalse(); + } + + [Fact] + public async Task MyControl_ShouldChangeAppearance_WhenComboBoxChanged() + { + // Arrange + await NavigateToPage("MyControl"); + + var comboBox = Window.FindFirstDescendant(cf => + cf.ByAutomationId("AppearanceComboBox")).AsComboBox(); + + var control = Window.FindFirstDescendant(cf => + cf.ByAutomationId("MyControlExample")); + + // Act + comboBox.Select(1); // Select "Secondary" + await Task.Delay(100); + + // Assert + control.Should().NotBeNull(); + // Add appearance-specific assertions + } + + [Fact] + public async Task MyControl_ShouldDisable_WhenToggleSwitchUnchecked() + { + // Arrange + await NavigateToPage("MyControl"); + + var toggleSwitch = Window.FindFirstDescendant(cf => + cf.ByAutomationId("EnabledToggle")).AsToggleButton(); + + var control = Window.FindFirstDescendant(cf => + cf.ByAutomationId("MyControlExample")); + + // Act + toggleSwitch.Toggle(); + await Task.Delay(100); + + // Assert + control.IsEnabled.Should().BeFalse(); + } + + [Fact] + public async Task MyControl_ShouldExecuteCommand_WhenButtonClicked() + { + // Arrange + await NavigateToPage("MyControl"); + + var button = Window.FindFirstDescendant(cf => + cf.ByAutomationId("ActionButton")).AsButton(); + + var textBlock = Window.FindFirstDescendant(cf => + cf.ByAutomationId("ResultText")).AsLabel(); + + // Act + button.Click(); + await Task.Delay(600); // Wait for async operation + + // Assert + textBlock.Text.Should().Be("Action performed!"); + } + + private async Task NavigateToPage(string pageName) + { + var navigationView = Window.FindFirstDescendant(cf => + cf.ByAutomationId("NavigationView")); + + var menuItem = navigationView.FindFirstDescendant(cf => + cf.ByName(pageName)); + + menuItem.Click(); + await Task.Delay(500); // Wait for navigation + } +} +``` + +### Test Infrastructure + +**Base Test Class** (`tests/Wpf.Ui.Gallery.IntegrationTests/Fixtures/UiTest.cs`): + +```csharp +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using FlaUI.Core; +using FlaUI.Core.AutomationElements; +using FlaUI.UIA3; +using Xunit; + +namespace Wpf.Ui.Gallery.IntegrationTests.Fixtures; + +public abstract class UiTest : IAsyncLifetime +{ + protected Application? Application { get; private set; } + protected Window Window { get; private set; } = null!; + private Process? _process; + + public async Task InitializeAsync() + { + // Launch Gallery application + var appPath = GetApplicationPath(); + _process = Process.Start(appPath); + + await Task.Delay(2000); // Wait for app to start + + // Attach to application + Application = FlaUI.Core.Application.Attach(_process); + + using var automation = new UIA3Automation(); + Window = Application.GetMainWindow(automation); + } + + public async Task DisposeAsync() + { + Application?.Close(); + Application?.Dispose(); + + if (_process != null && !_process.HasExited) + { + _process.Kill(); + _process.Dispose(); + } + + await Task.CompletedTask; + } + + private static string GetApplicationPath() + { + // Path to Gallery executable + return Path.Combine( + AppDomain.CurrentDomain.BaseDirectory, + "..\\..\\..\\..\\..\\src\\Wpf.Ui.Gallery\\bin\\Debug\\net10.0-windows\\Wpf.Ui.Gallery.exe" + ); + } +} +``` + +### Running Tests + +```bash +# Run all tests +dotnet test + +# Run only unit tests +dotnet test tests/Wpf.Ui.UnitTests/Wpf.Ui.UnitTests.csproj + +# Run only integration tests +dotnet test tests/Wpf.Ui.Gallery.IntegrationTests/Wpf.Ui.Gallery.IntegrationTests.csproj + +# Run with code coverage +dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=opencover +``` + +--- + +## Common Pitfalls to Avoid + +1. **Don't add package versions to .csproj** - use `Directory.Packages.props` only + +2. **Don't forget handle validation** in Win32 interop: + ```csharp + if (handle == IntPtr.Zero || !PInvoke.IsWindow(new HWND(handle))) + return false; + ``` + +3. **Don't use `var` for non-apparent types** (causes warning) + +4. **Don't forget MIT license header** (causes error via IDE0073) + +5. **Don't modify control namespace** to match folder structure - use flat `Wpf.Ui.Controls` + +6. **Don't use StaticResource for theme-dependent values** - use DynamicResource + +7. **Don't forget to register pages and ViewModels** in DI container + +8. **Don't use `this.` qualification** (SA1101 suppressed) + +9. **Don't add XML docs that restate code** - explain WHY, not WHAT + +10. **Don't skip the `[GalleryPage]` attribute** on Gallery pages + +--- + +## Quick Reference Checklist + +### Adding a Control +- [ ] Create folder under `Controls/` +- [ ] Create `.cs` file with control class +- [ ] Create `.xaml` file with style +- [ ] Implement interfaces (IAppearanceControl, IIconControl) +- [ ] Register DependencyProperties with XML docs +- [ ] Override DefaultStyleKeyProperty +- [ ] Add to `Wpf.Ui.xaml` MergedDictionaries +- [ ] Add toolbox icon (optional) +- [ ] Add unit tests + +### Adding a Service +- [ ] Create `I{Name}Service.cs` interface +- [ ] Create `{Name}Service.cs` implementation +- [ ] Add DI extension method in `Wpf.Ui.DependencyInjection` +- [ ] Add XML docs with examples +- [ ] Add unit tests +- [ ] Document in CLAUDE.md + +### Adding a Gallery Page +- [ ] Create ViewModel in `ViewModels/Pages/{Category}/` +- [ ] Create Page.xaml in `Views/Pages/{Category}/` +- [ ] Create Page.xaml.cs code-behind +- [ ] Add `[GalleryPage]` attribute +- [ ] Register ViewModel and Page in DI +- [ ] Add navigation entry (if manual) +- [ ] Add code examples with syntax:CodeBlock diff --git a/docs/architecture/MODULE-INTERFACES.md b/docs/architecture/MODULE-INTERFACES.md new file mode 100644 index 000000000..46379c296 --- /dev/null +++ b/docs/architecture/MODULE-INTERFACES.md @@ -0,0 +1,225 @@ +# Module Interfaces + +> WPF UI v4.2.0 | Public and internal API surface per module + +This document defines the public vs internal API surface for each module in the WPF UI solution. It serves as the contract specification for inter-module communication and consumer-facing APIs. + +--- + +## Module Dependency Graph + +```mermaid +graph TD + subgraph "Public API Surface" + Abstractions["Wpf.Ui.Abstractions
Public: 6 types
Internal: none"] + Core["Wpf.Ui
Public: 77+ controls, 6 services,
managers, converters, markup

Internal: Win32, Interop"] + DI["Wpf.Ui.DependencyInjection
Public: 2 types
Internal: none"] + Tray["Wpf.Ui.Tray
Public: 4 types
Internal: 7 types"] + Syntax["Wpf.Ui.SyntaxHighlight
Public: 2 types
Internal: 2 types"] + end + + subgraph "Non-Distributable" + Toast["Wpf.Ui.ToastNotifications
Public: 1 type (STUB)"] + FlaUI["Wpf.Ui.FlaUI
Public: 1 type"] + FontMapper["Wpf.Ui.FontMapper
Build-time tool"] + end + + Core --> Abstractions + DI --> Abstractions + Tray --> Core + Syntax --> Core + + style Abstractions fill:#e1f5ff,stroke:#0277bd + style Core fill:#fff4e1,stroke:#f57f17 + style DI fill:#e8f5e9,stroke:#2e7d32 + style Tray fill:#e8f5e9,stroke:#2e7d32 + style Syntax fill:#e8f5e9,stroke:#2e7d32 + style Toast fill:#fff9c4,stroke:#f9a825 + style FlaUI fill:#f3e5f5,stroke:#6a1b9a + style FontMapper fill:#f3e5f5,stroke:#6a1b9a +``` + +--- + +## Module: Wpf.Ui.Abstractions + +**NuGet:** `WPF-UI.Abstractions` +**TFMs:** `net10.0`, `net9.0`, `net8.0`, `net462`, `netstandard2.1`, `netstandard2.0` +**Dependencies:** None (zero external dependencies) + +### Public Types + +| Type | Kind | Description | +|------|------|-------------| +| `INavigationViewPageProvider` | Interface | Resolves page instances by type for NavigationView | +| `INavigableView` | Interface | Associates a view with its ViewModel type | +| `INavigationAware` | Interface | Lifecycle callbacks for navigation (OnNavigatedTo/From) | +| `NavigationAware` | Abstract class | Base implementation of `INavigationAware` | +| `NavigationException` | Exception | Thrown when navigation operations fail | +| `NavigationViewPageProviderExtensions` | Static class | Extension methods for `INavigationViewPageProvider` | + +### Internal Types + +None. This module is entirely public by design -- it defines the contract surface. + +--- + +## Module: Wpf.Ui (Core) + +**NuGet:** `WPF-UI` +**TFMs:** `net10.0-windows`, `net9.0-windows`, `net8.0-windows`, `net481`, `net472`, `net462` +**Dependencies:** `Wpf.Ui.Abstractions`, `Microsoft.Windows.CsWin32` (build-time), `System.Memory` + +### Public Namespaces + +| Namespace | Contents | +|-----------|----------| +| `Wpf.Ui` | `UiApplication`, service interfaces and implementations | +| `Wpf.Ui.Controls` | 77+ Fluent Design controls | +| `Wpf.Ui.Appearance` | `ApplicationThemeManager`, `ApplicationAccentColorManager`, `SystemThemeWatcher`, `WindowBackgroundManager` | +| `Wpf.Ui.Converters` | 18 `IValueConverter` implementations | +| `Wpf.Ui.Markup` | `ControlsDictionary`, `ThemesDictionary`, `SymbolIconExtension`, `FontIconExtension`, `ImageIconExtension` | +| `Wpf.Ui.Extensions` | 14 extension method classes | +| `Wpf.Ui.Input` | `IRelayCommand`, `IRelayCommand`, `RelayCommand` | + +### Public Service Interfaces + +| Interface | Implementation | Wraps | +|-----------|---------------|-------| +| `INavigationService` | `NavigationService` | `INavigationView` control | +| `IContentDialogService` | `ContentDialogService` | `ContentDialog` control | +| `ISnackbarService` | `SnackbarService` | `Snackbar` control | +| `IThemeService` | `ThemeService` | `ApplicationThemeManager` (static) | +| `ITaskBarService` | `TaskBarService` | COM `ITaskbarList4` | +| `INavigationWindow` | — | Interface for windows hosting NavigationView | + +### Internal / Should-Be-Internal Namespaces + +| Namespace | Status | Notes | +|-----------|--------|-------| +| `Wpf.Ui.Interop` | **Currently public** | Managed Win32 wrappers (`UnsafeNativeMethods`, `PInvoke`). Recommended for internalization (see [RECOMMENDATIONS.md](RECOMMENDATIONS.md)) | +| `Wpf.Ui.Win32` | **Currently public** | OS version utilities. Recommended for internalization | + +> **Note:** `Wpf.Ui.Interop` and `Wpf.Ui.Win32` expose raw P/Invoke declarations that are implementation details. Consumers should not depend on these namespaces. A future major version should mark them `internal`. + +--- + +## Module: Wpf.Ui.DependencyInjection + +**NuGet:** `WPF-UI.DependencyInjection` +**TFMs:** `net10.0`, `net9.0`, `net8.0`, `net462`, `netstandard2.1`, `netstandard2.0` +**Dependencies:** `Wpf.Ui.Abstractions`, `Microsoft.Extensions.DependencyInjection.Abstractions` 3.1.0 + +### Public Types + +| Type | Kind | Description | +|------|------|-------------| +| `ServiceCollectionExtensions` | Static class | `AddNavigationViewPageProvider()` extension method for `IServiceCollection` | +| `DependencyInjectionNavigationViewPageProvider` | Class | `INavigationViewPageProvider` implementation that resolves pages via `IServiceProvider` | + +### Internal Types + +None. + +--- + +## Module: Wpf.Ui.Tray + +**NuGet:** `WPF-UI.Tray` +**TFMs:** `net10.0-windows`, `net9.0-windows`, `net8.0-windows`, `net481`, `net472`, `net462` +**Dependencies:** `Wpf.Ui`, `System.Drawing.Common` + +### Public Types + +| Type | Kind | Description | +|------|------|-------------| +| `INotifyIconService` | Interface | Service interface for tray icon management | +| `NotifyIconService` | Class | `INotifyIconService` implementation | +| `NotifyIcon` | Control | WPF control for declarative tray icon in XAML | +| `RoutedNotifyIconEvent` | Delegate | Event delegate for tray icon interactions | + +### Internal Types + +| Type | Kind | Description | +|------|------|-------------| +| `INotifyIcon` | Interface | Internal abstraction for tray icon operations | +| `TrayHandler` | Class | Shell32 `Shell_NotifyIcon` P/Invoke wrapper | +| `TrayManager` | Class | Tray icon lifecycle management | +| `TrayData` | Struct | Native tray icon data structure | +| `Hicon` | Struct | Icon handle wrapper | +| `NotifyIconEventHandler` | Delegate | Internal event handler | +| `InternalNotifyIconManager` | Class | Theme-aware tray icon management | + +--- + +## Module: Wpf.Ui.SyntaxHighlight + +**NuGet:** `WPF-UI.SyntaxHighlight` +**TFMs:** `net10.0-windows`, `net9.0-windows`, `net8.0-windows`, `net481`, `net472`, `net462` +**Dependencies:** `Wpf.Ui` + +### Public Types + +| Type | Kind | Description | +|------|------|-------------| +| `CodeBlock` | Control | WPF control for syntax-highlighted code display | +| `SyntaxHighlightDictionary` | Markup Extension | XAML resource dictionary for syntax highlighting styles | + +### Internal Types + +| Type | Kind | Description | +|------|------|-------------| +| `Highlighter` | Class | Regex-based syntax highlighting engine | +| `SyntaxLanguage` | Enum | Supported language identifiers | + +--- + +## Module: Wpf.Ui.ToastNotifications + +**NuGet:** `WPF-UI.ToastNotifications` +**TFMs:** `net10.0-windows`, `net9.0-windows`, `net8.0-windows`, `net481`, `net472`, `net462` +**Dependencies:** None (standalone stub) + +### Public Types + +| Type | Kind | Description | +|------|------|-------------| +| `Toast` | Class | **STUB** — all methods throw `NotImplementedException` | + +> **Warning:** This module is a placeholder with no implementation. See [RECOMMENDATIONS.md](RECOMMENDATIONS.md) — recommended to either implement or remove. + +--- + +## Module: Wpf.Ui.FlaUI + +**NuGet:** `WPF-UI.FlaUI` +**TFMs:** `net10.0-windows`, `net9.0-windows`, `net8.0-windows`, `net481` +**Dependencies:** `FlaUI.Core` + +### Public Types + +| Type | Kind | Description | +|------|------|-------------| +| `AutoSuggestBox` | Class | FlaUI automation element wrapper for `Wpf.Ui.Controls.AutoSuggestBox` | + +--- + +## Module: Wpf.Ui.FontMapper + +**Type:** Build-time console tool (not a distributable library) +**TFMs:** `net10.0` +**Dependencies:** None + +### Public Types + +None. This is a build tool that generates `SymbolRegular` and `SymbolFilled` enums from Fluent System Icons font JSON data. It is not referenced by other projects at runtime. + +--- + +## Cross-Module Interface Rules + +1. **Abstractions are the only shared contract.** Modules that need to communicate must do so through `Wpf.Ui.Abstractions` interfaces. +2. **Core library is the integration point.** Only `Wpf.Ui` may depend on Win32 interop and WPF framework internals. +3. **Satellite packages depend downward only.** `Tray` and `SyntaxHighlight` depend on `Core`; they never depend on each other. +4. **DI package depends on Abstractions only.** `Wpf.Ui.DependencyInjection` must never take a dependency on `Wpf.Ui` to keep it lightweight. +5. **Internal namespaces are not API surface.** Types in `Wpf.Ui.Interop` and `Wpf.Ui.Win32` are implementation details even though they are currently `public`. diff --git a/docs/architecture/README.md b/docs/architecture/README.md new file mode 100644 index 000000000..de7e618de --- /dev/null +++ b/docs/architecture/README.md @@ -0,0 +1,195 @@ +# WPF UI - Architecture Documentation + +| Property | Value | +|---|---| +| **Project** | WPF UI (wpfui) | +| **Version** | 4.2.0 | +| **Type** | Open-source WPF UI control library | +| **License** | MIT | +| **Language** | C# 14 / XAML | +| **Platforms** | .NET 10/9/8 + .NET Framework 4.6.2/4.7.2/4.8.1 | + +## Overview + +WPF UI is an open-source library that implements the **Microsoft Fluent Design System** for Windows Presentation Foundation (WPF) applications. It provides **77+ custom controls**, a full theming system with light/dark/high-contrast modes, Win32 interop for modern window chrome effects (Mica, Acrylic, Tabbed backdrops), navigation services, and icon support through the Fluent System Icons font family. + +The library enables thousands of developers to modernize legacy WPF applications with contemporary Windows 11 visual styles without migrating to WinUI 3 or MAUI. + +## Table of Contents + +### Architecture Views + +| Document | Description | +|---|---| +| [C4 Context (Level 1)](views/context.md) | System context diagram showing WPF UI in its environment -- consumer applications, Windows OS, distribution channels | +| [Logical Architecture](views/logical-architecture.md) | Module dependencies, layer diagram, core library internal component structure, detected patterns | + +### Software Design Documents (SDD) + +| Document | Description | +|---|---| +| [CONSTITUTION.md](CONSTITUTION.md) | Project constitution -- coding conventions, boundary system (ALWAYS DO / ASK FIRST / NEVER DO), rules files summary | +| [IMPLEMENTATION-GUIDE.md](IMPLEMENTATION-GUIDE.md) | Step-by-step guides for common tasks -- adding controls, services, gallery pages, Win32 interop, theming, testing | +| [TESTING-SPEC.md](TESTING-SPEC.md) | Testing specification -- frameworks, templates, naming conventions, run commands | +| [MODULE-INTERFACES.md](MODULE-INTERFACES.md) | Module interface contracts -- public vs internal API surface per module, dependency rules | + +### Cross-Cutting Concerns + +| Document | Description | +|---|---| +| [Testing](cross-cutting/testing.md) | Test strategy, frameworks, naming conventions, coverage gaps | +| [Theming and Appearance](cross-cutting/theming-and-appearance.md) | Theme system architecture, accent colors, backdrop effects, theme change flow | +| [Win32 Interop](cross-cutting/win32-interop.md) | Three-layer interop architecture, key APIs, handle validation, WndProc patterns | +| [Navigation](cross-cutting/navigation.md) | Navigation system lifecycle, page caching strategy, DI integration, transition animations | + +### Architecture Decision Records (ADRs) + +| ADR | Decision | +|---|---| +| [ADR-001](decisions/ADR-001-multi-target-framework.md) | Multi-target framework strategy (net10.0 through net462) | +| [ADR-002](decisions/ADR-002-control-library-architecture.md) | WPF control library architecture with Fluent Design | +| [ADR-003](decisions/ADR-003-win32-interop-via-cswin32.md) | Win32 interop via CsWin32 source generator | +| [ADR-004](decisions/ADR-004-static-managers-for-theming.md) | Static singleton managers for theming (implicit) | +| [ADR-005](decisions/ADR-005-feature-folder-controls.md) | Feature-folder organization for controls (implicit) | + +### Other + +| Document | Description | +|---|---| +| [RECOMMENDATIONS.md](RECOMMENDATIONS.md) | Prioritized architectural improvement recommendations | +| [UNCERTAINTIES.md](UNCERTAINTIES.md) | Areas needing further investigation | +| [Diagrams Index](appendix/diagrams/README.md) | Index of all architecture diagrams | +| [LikeC4 Diagrams](appendix/diagrams/wpfui.c4) | Interactive C4 diagrams in LikeC4 DSL format | + +## Quick Architecture Summary + +WPF UI is organized as a set of layered NuGet packages built from a single solution: + +- **Wpf.Ui.Abstractions** -- Zero-dependency contract interfaces (`INavigationViewPageProvider`, `INavigationAware`, `INavigableView`). Targets .NET and .NET Standard 2.0/2.1 for maximum portability. +- **Wpf.Ui** -- Core library with 77+ controls, the theming engine, Win32 interop, services, converters, animations, and icon support. This is the primary NuGet package (`WPF-UI`). +- **Wpf.Ui.DependencyInjection** -- Microsoft.Extensions.DependencyInjection integration for type-based page resolution. +- **Wpf.Ui.Tray** -- System tray icon support via Shell32 P/Invoke. +- **Wpf.Ui.SyntaxHighlight** -- Syntax-highlighted code display control. +- **Wpf.Ui.ToastNotifications** -- Toast notification support (stub, not yet implemented). +- **Wpf.Ui.FlaUI** -- FlaUI automation element wrappers for integration testing. +- **Wpf.Ui.FontMapper** -- Build-time tool that generates `SymbolRegular`/`SymbolFilled` enums from Fluent System Icons font data. +- **Wpf.Ui.Gallery** -- Comprehensive demo application showcasing all controls. +- **Wpf.Ui.Extension** -- Visual Studio 2022 extension with project templates. + +### Key Architectural Concerns + +1. **Control Architecture** -- Folder-per-control pattern with paired `.cs` + `.xaml` files. Controls extend standard WPF base classes and implement capability interfaces (`IAppearanceControl`, `IIconControl`). +2. **Theming System** -- Runtime resource dictionary swapping managed by `ApplicationThemeManager`. Accent colors derived from system settings via WinRT `UISettings` or DWM registry. +3. **Win32 Interop** -- Three-layer architecture: CsWin32 source-generated P/Invoke, managed wrappers (`UnsafeNativeMethods`), and high-level utilities. Enables DWM backdrop effects, custom window chrome, and system theme detection. + +## Technology Stack + +```mermaid +mindmap + root((WPF UI 4.2.0)) + Languages + C# 14 (LangVersion preview) + XAML + Target Frameworks + .NET 10.0-windows + .NET 9.0-windows + .NET 8.0-windows + .NET Framework 4.8.1 + .NET Framework 4.7.2 + .NET Framework 4.6.2 + .NET Standard 2.0/2.1 + Abstractions only + Build & Tooling + MSBuild / dotnet CLI + Central Package Management + Directory.Packages.props + CSharpier formatter + 110 char width + Source generators + CsWin32 P/Invoke + PolySharp polyfills + CommunityToolkit.Mvvm + Analyzers + WpfAnalyzers + StyleCop + AsyncFixer + IDisposableAnalyzers + Strong-name signing + lepo.snk + SourceLink + Microsoft.SourceLink.GitHub + Win32 / Native + DWM APIs + Mica / Acrylic / Tabbed backdrops + Window corner preference + Dark mode attribute + User32 + SetWindowLong / GetWindowLong + Window message handling + Shell32 + System tray icons + Taskbar progress + COM ITaskbarList4 + Testing + XUnit v2 / v3 + NSubstitute mocking + AwesomeAssertions + FlaUI UI automation + Coverlet coverage + CI/CD + GitHub Actions + PR validator + NuGet publish + VS Extension build + DocFX documentation + NuGet.org distribution + GitHub Pages docs + Design System + Microsoft Fluent Design + Fluent System Icons + Regular + Filled TTF fonts + Theme system + Light / Dark / High Contrast + Accent color system + WinRT UISettings integration +``` + +## Repository Structure + +``` +wpfui/ + src/ + Wpf.Ui/ Core library (NuGet: WPF-UI) + Controls/ 77 control subdirectories + Appearance/ Theme management system + Animations/ Transition animations + Converters/ XAML value converters + Extensions/ Extension methods + Hardware/ DPI and rendering detection + Input/ IRelayCommand pattern + Interop/ Win32 managed wrappers + Markup/ XAML markup extensions + Resources/ Theme dictionaries, fonts + Taskbar/ Taskbar progress COM + Win32/ OS version utilities + Wpf.Ui.Abstractions/ Contracts (multi-target) + Wpf.Ui.DependencyInjection/ MS DI integration + Wpf.Ui.Tray/ System tray support + Wpf.Ui.SyntaxHighlight/ Code display control + Wpf.Ui.ToastNotifications/ Toast notifications (stub) + Wpf.Ui.FlaUI/ FlaUI automation helpers + Wpf.Ui.FontMapper/ Icon enum code generator + Wpf.Ui.Gallery/ Demo/showcase application + Wpf.Ui.Extension/ VS 2022 extension + samples/ + Wpf.Ui.Demo.Simple/ Minimal no-DI sample + Wpf.Ui.Demo.Mvvm/ Full MVVM + DI sample + Wpf.Ui.Demo.Console/ .NET Framework 4.7.2 console sample + Wpf.Ui.Demo.Dialogs/ ContentDialog sample + Wpf.Ui.Demo.SetResources.Simple/ Manual resource management sample + tests/ + Wpf.Ui.UnitTests/ XUnit unit tests + Wpf.Ui.Gallery.IntegrationTests/ FlaUI integration tests + docs/ DocFX documentation site + .github/ CI/CD workflows, templates +``` diff --git a/docs/architecture/RECOMMENDATIONS.md b/docs/architecture/RECOMMENDATIONS.md new file mode 100644 index 000000000..e1f24e193 --- /dev/null +++ b/docs/architecture/RECOMMENDATIONS.md @@ -0,0 +1,143 @@ +# WPF UI - Architecture Recommendations + +> Generated: 2026-02-10. These recommendations are based on architectural analysis and should be re-evaluated as the codebase evolves. + +## Recommendations + +### Testing + +| Priority | Recommendation | Rationale | Affected | +|----------|---------------|-----------|----------| +| **Critical** | Add unit tests for core controls, targeting at least NavigationView, TitleBar, FluentWindow, ContentDialog, NumberBox, and ToggleSwitch. | Only 6 unit tests exist for a 77+ control library. Regressions ship undetected. | `src/Wpf.Ui/Controls/`, `tests/Wpf.Ui.UnitTests/` | +| **Critical** | Enable test execution in the PR validator CI workflow. | The current workflow builds the Gallery app but never runs tests. Merging broken code is possible. | `.github/workflows/` | +| **High** | Add integration tests for NavigationView page caching and lifecycle transitions. | Caching behavior is undocumented and untested. Users report inconsistent state across navigation. | `src/Wpf.Ui/Controls/NavigationView/` | +| **Medium** | Add contract tests for `INavigationService`, `IContentDialogService`, and `ISnackbarService`. | Service interfaces are the primary public API surface for DI consumers but lack test coverage. | `src/Wpf.Ui.Abstractions/`, `src/Wpf.Ui/Services/` | + +### Architecture + +| Priority | Recommendation | Rationale | Affected | +|----------|---------------|-----------|----------| +| **High** | Wrap static theme managers (`ApplicationThemeManager`, `ApplicationAccentColorManager`) with injectable service interfaces. | Static managers cannot be mocked or unit tested. `IThemeService` exists but the underlying managers remain untestable. | `src/Wpf.Ui/Appearance/` | +| **High** | Document NavigationView page caching strategy: when pages are created, cached, and disposed. | Caching behavior is implicit in the implementation with no documentation or configuration surface. | `src/Wpf.Ui/Controls/NavigationView/` | +| **Medium** | Add structured logging (e.g., `ILogger`) to Win32 interop error-handling paths. | Catch blocks intentionally swallow Win32 exceptions but produce no diagnostics. Silent failures make debugging difficult for consumers. | `src/Wpf.Ui/Interop/`, `src/Wpf.Ui/Win32/` | + +### Technical Debt + +| Priority | Recommendation | Rationale | Affected | +|----------|---------------|-----------|----------| +| **High** | Either implement `Wpf.Ui.ToastNotifications` or remove it from the solution. | The project is a stub with no implementation. It ships as a package that does nothing. | `src/Wpf.Ui.ToastNotifications/` | +| **Medium** | Audit and reduce public API surface of Win32/Interop namespaces. | Many P/Invoke declarations are public but intended for internal use only. Exposing raw Win32 types couples consumers to implementation details. | `src/Wpf.Ui/Win32/`, `src/Wpf.Ui/Interop/` | +| **Low** | Consolidate resource dictionary loading paths. | `ThemesDictionary` and `ControlsDictionary` have overlapping responsibilities that could confuse consumers. | `src/Wpf.Ui/Resources/` | + +### CI/CD + +| Priority | Recommendation | Rationale | Affected | +|----------|---------------|-----------|----------| +| **Critical** | Add a CI job that runs `dotnet test tests/Wpf.Ui.UnitTests/` on every PR. | No tests run in CI. The existing test suite provides zero protection against regressions. | `.github/workflows/` | +| **High** | Add code coverage reporting (Coverlet is already configured) and set a minimum threshold. | Coverage tooling is present but never executed. Without a baseline, coverage can only decrease. | `tests/Wpf.Ui.UnitTests/`, `.github/workflows/` | +| **Medium** | Add a CI step to run `dotnet csharpier --check .` to enforce formatting. | CSharpier is configured but not enforced in CI. Formatting inconsistencies can slip through review. | `.github/workflows/` | + +### Documentation + +| Priority | Recommendation | Rationale | Affected | +|----------|---------------|-----------|----------| +| **Medium** | Add XML doc `` tags with XAML usage to all public control APIs. | Project conventions require examples, but coverage is inconsistent across the 77+ controls. | `src/Wpf.Ui/Controls/` | +| **Low** | Create a process for keeping architecture docs in sync with code changes. | Architecture documentation (generated 2026-02-10) will drift from the implementation without a manual update process or automation. | `docs/architecture/` | + +--- + +## Task Backlog + +Structured work items derived from the recommendations above. Each item includes acceptance criteria (AC). + +### TB-001: Add unit tests for core controls [Critical] + +**Source:** Testing #1 +**AC:** +- [ ] NavigationView has ≥5 unit tests covering navigation, caching, back stack +- [ ] TitleBar has ≥3 tests covering minimize/maximize/close commands +- [ ] FluentWindow has ≥2 tests covering backdrop type selection +- [ ] ContentDialog has ≥3 tests covering show/hide/result lifecycle +- [ ] NumberBox has ≥3 tests covering min/max/step validation +- [ ] ToggleSwitch has ≥2 tests covering checked/unchecked state +- [ ] All tests follow `MethodName_ExpectedResult_WhenCondition` naming convention +- [ ] All tests use XUnit v3, NSubstitute, AwesomeAssertions + +### TB-002: Enable test execution in CI [Critical] + +**Source:** CI/CD #1, Testing #2 +**AC:** +- [ ] `.github/workflows/` contains a job that runs `dotnet test tests/Wpf.Ui.UnitTests/` on every PR +- [ ] CI job fails the PR if any test fails +- [ ] Test results are visible in the GitHub Actions summary +- [ ] Job runs on `ubuntu-latest` or `windows-latest` as appropriate for WPF tests + +### TB-003: Add code coverage reporting with threshold [High] + +**Source:** CI/CD #2 +**AC:** +- [ ] Coverlet generates coverage report during CI test run +- [ ] Coverage report is uploaded as a CI artifact or displayed in PR summary +- [ ] Minimum coverage threshold is set (≥50% for initial baseline) +- [ ] Build fails if coverage drops below threshold + +### TB-004: Enforce CSharpier formatting in CI [Medium] + +**Source:** CI/CD #3 +**AC:** +- [ ] CI job runs `dotnet csharpier --check .` on every PR +- [ ] PR fails if any file is not formatted +- [ ] Contributing guide documents the `dotnet csharpier .` command + +### TB-005: Wrap static theme managers with injectable interfaces [High] + +**Source:** Architecture #1 +**AC:** +- [ ] `IApplicationThemeManager` interface exists with `Apply()`, `GetAppTheme()`, and `Changed` event +- [ ] `IApplicationAccentColorManager` interface exists with `Apply()` and `GetColorizationColor()` +- [ ] Default implementations delegate to existing static classes +- [ ] Interfaces are registered in DI via `ServiceCollectionExtensions` +- [ ] Existing static API remains functional (non-breaking change) + +### TB-006: Document NavigationView page caching strategy [High] + +**Source:** Architecture #2 +**AC:** +- [ ] XML doc comments on `NavigationCacheMode` enum explain each mode +- [ ] `docs/architecture/cross-cutting/navigation.md` describes caching lifecycle +- [ ] Gallery demo includes a page demonstrating cache mode differences +- [ ] At least one unit test verifies cache behavior per mode + +### TB-007: Implement or remove Wpf.Ui.ToastNotifications [High] + +**Source:** Technical Debt #1 +**AC:** +- [ ] Decision documented: implement with Windows App SDK toast APIs OR remove from solution +- [ ] If implemented: at least one functional toast notification scenario works end-to-end +- [ ] If removed: NuGet package is delisted, project removed from solution, references cleaned up + +### TB-008: Audit and reduce Win32/Interop public API surface [Medium] + +**Source:** Technical Debt #2 +**AC:** +- [ ] All types in `Wpf.Ui.Interop` and `Wpf.Ui.Win32` are marked `internal` or documented as public API +- [ ] `[EditorBrowsable(EditorBrowsableState.Never)]` added to types that must remain public for binary compat +- [ ] No consumer-facing documentation references internal interop types + +### TB-009: Add integration tests for NavigationView caching [High] + +**Source:** Testing #3 +**AC:** +- [ ] FlaUI integration test navigates forward/backward and verifies page instance identity (cached vs new) +- [ ] Test covers `NavigationCacheMode.Enabled`, `Disabled`, and `Required` +- [ ] Test verifies `INavigationAware.OnNavigatedTo`/`OnNavigatedFrom` callback order + +### TB-010: Add contract tests for service interfaces [Medium] + +**Source:** Testing #4 +**AC:** +- [ ] `INavigationService` has ≥3 contract tests (Navigate, GoBack, SetService) +- [ ] `IContentDialogService` has ≥2 contract tests (ShowAsync, SetDialogHost) +- [ ] `ISnackbarService` has ≥2 contract tests (Show, SetPresenter) +- [ ] Tests verify interface contracts, not implementation details +- [ ] Tests use NSubstitute for mock dependencies diff --git a/docs/architecture/TESTING-SPEC.md b/docs/architecture/TESTING-SPEC.md new file mode 100644 index 000000000..109b6cdf7 --- /dev/null +++ b/docs/architecture/TESTING-SPEC.md @@ -0,0 +1,380 @@ +# Testing Specification for AI Agents + +## Overview + +This document provides testing guidelines for AI agents working on the WPF UI project. The project uses separate unit and integration test suites with distinct frameworks and patterns. + +## Test Project Structure + +### Unit Tests +**Project:** `tests/Wpf.Ui.UnitTests/` +**Target Framework:** `net10.0-windows` +**Project Reference:** `Wpf.Ui` + +### Integration Tests +**Project:** `tests/Wpf.Ui.Gallery.IntegrationTests/` +**Target Framework:** `net10.0-windows10.0.26100.0` +**Project References:** `Wpf.Ui.FlaUI`, `Wpf.Ui.Gallery` + +## Testing Frameworks + +### Unit Testing Stack +- **XUnit v2-style** (PackageReference: `xunit`, `xunit.runner.visualstudio`) +- **NSubstitute 5.3.0** - Mocking framework +- **Standard XUnit Assert** - Assertion library +- **Coverlet 6.0.4** - Code coverage collector + +### Integration Testing Stack +- **XUnit v3** (PackageReference: `xunit.v3`, `xunit.runner.visualstudio`) +- **FlaUI.UIA3 5.0.0** - UI automation framework +- **AwesomeAssertions 9.3.0** - Fluent assertion library (FluentAssertions successor) +- **Custom Wpf.Ui.FlaUI** - Custom automation element wrappers + +## Unit Test Template + +### Naming Convention +``` +MethodName_ExpectedResult_WhenCondition +``` + +**Alternative format:** +``` +GivenCondition_MethodName_ExpectedResult +``` + +### Basic Structure +```csharp +using Xunit; +using NSubstitute; +using Wpf.Ui.Animations; // Namespace under test + +namespace Wpf.Ui.UnitTests.Animations; + +public class TransitionAnimationProviderTests +{ + [Fact] + public void ApplyTransition_ReturnsFalse_WhenDurationIsLessThan10() + { + // Arrange + UIElement mockedUiElement = Substitute.For(); + + // Act + var result = TransitionAnimationProvider.ApplyTransition( + mockedUiElement, + Transition.FadeIn, + -10 + ); + + // Assert + Assert.False(result); + } + + [Fact] + public void ApplyTransition_ReturnsFalse_WhenElementIsNull() + { + // Arrange + UIElement? nullElement = null; + + // Act + var result = TransitionAnimationProvider.ApplyTransition( + nullElement, + Transition.FadeIn, + 100 + ); + + // Assert + Assert.False(result); + } +} +``` + +### Mocking with NSubstitute +```csharp +// Create mock +UIElement element = Substitute.For(); + +// Setup return value +INavigationService service = Substitute.For(); +service.Navigate(typeof(DashboardPage)).Returns(true); + +// Verify call +service.Received(1).Navigate(Arg.Any()); +``` + +### Global Usings +Located in `tests/Wpf.Ui.UnitTests/Usings.cs`: +```csharp +global using System; +global using System.Windows; +global using NSubstitute; +global using Xunit; +``` + +## Integration Test Template + +### Naming Convention +``` +Subject_ShouldExpectedBehavior_WhenCondition +``` + +### Base Class Pattern +All integration tests inherit from `UiTest`: + +```csharp +using AwesomeAssertions; +using FlaUI.Core.AutomationElements; +using FlaUI.UIA3.Patterns; + +namespace Wpf.Ui.Gallery.IntegrationTests; + +public sealed class NavigationTests : UiTest +{ + [Fact] + public async Task Settings_ShouldBeAvailable_ThroughAutoSuggestBox() + { + // Arrange + Wpf.Ui.FlaUI.AutoSuggestBox? autoSuggestBox = + FindFirst("NavigationAutoSuggestBox")?.As(); + + autoSuggestBox.Should().NotBeNull( + "because AutoSuggestBox should be present in the navigation bar" + ); + + // Act + autoSuggestBox!.Enter("Settings"); + await Wait(1); + + // Assert + TextBox? pageTitle = FindFirst("PageTitle")?.AsTextBox(); + pageTitle.Should().NotBeNull(); + pageTitle!.Text.Should().Be("Settings"); + } +} +``` + +### UiTest Base Class Features + +**Location:** `tests/Wpf.Ui.Gallery.IntegrationTests/Fixtures/UiTest.cs` + +**Provided Methods:** +```csharp +// Find element by automation ID +protected AutomationElement? FindFirst(string automationId) + +// Find element by condition +protected AutomationElement? FindFirst(Func buildCondition) + +// Wait for specified seconds +protected async Task Wait(int seconds, CancellationToken cancellationToken = default) + +// Type text +protected void Enter(string text) + +// Press key +protected void Press(VirtualKeyShort key) +``` + +**Lifecycle:** +- `IAsyncLifetime` implementation +- Each test gets its own application instance +- Application launched via `TestedApplication` fixture +- Automatic cleanup after test completion + +### AwesomeAssertions Syntax +```csharp +// Null checks +element.Should().NotBeNull("because element must exist"); +element.Should().BeNull(); + +// String assertions +text.Should().Be("Expected"); +text.Should().Contain("substring"); +text.Should().StartWith("prefix"); + +// Boolean assertions +condition.Should().BeTrue("because condition must be met"); +Application?.HasExited.Should().BeTrue(); + +// Collection assertions +items.Should().HaveCount(5); +items.Should().Contain(item); +``` + +### FlaUI Element Access +```csharp +// Find and cast to specific control +Button? button = FindFirst("ButtonId").AsButton(); +TextBox? textBox = FindFirst("TextBoxId")?.AsTextBox(); + +// Custom automation elements +var autoSuggestBox = FindFirst("AutoSuggestBoxId")?.AsAutoSuggestBox(); + +// Interact with controls +button.Click(moveMouse: false); +textBox.Text = "value"; + +// Pattern-based interaction +var invokePattern = element.Patterns.Invoke.Pattern; +invokePattern.Invoke(); +``` + +## Running Tests + +### Unit Tests +```bash +# Run all unit tests +dotnet test tests/Wpf.Ui.UnitTests/ + +# Run specific test class +dotnet test tests/Wpf.Ui.UnitTests/ --filter "FullyQualifiedName~TransitionAnimationProviderTests" + +# Run with coverage +dotnet test tests/Wpf.Ui.UnitTests/ --collect:"XPlat Code Coverage" +``` + +### Integration Tests +```bash +# Run all integration tests +dotnet test tests/Wpf.Ui.Gallery.IntegrationTests/ + +# Run specific test +dotnet test tests/Wpf.Ui.Gallery.IntegrationTests/ --filter "FullyQualifiedName~TitleBarTests" + +# Run with diagnostics +dotnet test tests/Wpf.Ui.Gallery.IntegrationTests/ --logger "console;verbosity=detailed" +``` + +### XUnit Runner Configuration +**Location:** `tests/Wpf.Ui.Gallery.IntegrationTests/xunit.runner.json` + +```json +{ + "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", + "parallelizeTestCollections": false, + "diagnosticMessages": true, + "culture": "invariant" +} +``` + +**Important:** Integration tests do NOT run in parallel due to single Gallery app instance. + +## Test Organization + +### Namespace Mirroring +Test namespaces mirror source namespaces: + +``` +src/Wpf.Ui/Animations/TransitionAnimationProvider.cs +↓ +tests/Wpf.Ui.UnitTests/Animations/TransitionAnimationProviderTests.cs +``` + +### File Naming +``` +{ClassName}Tests.cs +``` + +### Test Coverage Areas + +**Current Unit Test Coverage:** +- Animations: `TransitionAnimationProvider` +- Extensions: `SymbolExtensions.Swap()`, `SymbolExtensions.GetString()` + +**Current Integration Test Coverage:** +- Window title verification +- TitleBar button interaction (Close, Minimize, Maximize) +- Navigation via AutoSuggestBox +- Navigation via NavigationView +- ContentDialog result verification +- ContentDialog keyboard focus isolation + +## Guidelines for AI Agents + +### When Writing Unit Tests + +1. **Mock dependencies** using NSubstitute +2. **Test public API surface** only +3. **Use XUnit Assert methods** for assertions +4. **Follow naming convention** strictly +5. **One assertion per test** (where logical) +6. **Test both success and failure paths** + +### When Writing Integration Tests + +1. **Inherit from UiTest** base class +2. **Use automation IDs** for element discovery +3. **Add delays with Wait()** for UI updates +4. **Use AwesomeAssertions fluent syntax** +5. **Provide clear "because" messages** in assertions +6. **Clean up state** (handled automatically by fixture) + +### Test File Template Locations + +**Unit Test Template:** +``` +tests/Wpf.Ui.UnitTests/Animations/TransitionAnimationProviderTests.cs +tests/Wpf.Ui.UnitTests/Extensions/SymbolExtensionsTests.cs +``` + +**Integration Test Template:** +``` +tests/Wpf.Ui.Gallery.IntegrationTests/TitleBarTests.cs +tests/Wpf.Ui.Gallery.IntegrationTests/NavigationTests.cs +``` + +## Common Patterns + +### Testing Dependency Properties +```csharp +[Fact] +public void PropertyName_DefaultValue_IsExpected() +{ + var control = new MyControl(); + Assert.Equal(expectedDefault, control.PropertyName); +} + +[Fact] +public void PropertyName_CanBeSet_AndRetrieved() +{ + var control = new MyControl(); + var expectedValue = new SomeType(); + + control.PropertyName = expectedValue; + + Assert.Equal(expectedValue, control.PropertyName); +} +``` + +### Testing Services +```csharp +[Fact] +public void Navigate_ReturnsTrue_WhenNavigationSucceeds() +{ + // Arrange + var pageProvider = Substitute.For(); + pageProvider.GetPage(Arg.Any()).Returns(new DashboardPage()); + + var service = new NavigationService(pageProvider); + var navigationView = Substitute.For(); + service.SetNavigationControl(navigationView); + + // Act + bool result = service.Navigate(typeof(DashboardPage)); + + // Assert + Assert.True(result); +} +``` + +## Continuous Integration + +Tests are NOT currently run in CI (PR validator only builds Gallery app). + +**To add test execution to CI**, modify `.github/workflows/wpf-ui-pr-validator.yaml`: +```yaml +- name: Run Unit Tests + run: dotnet test tests/Wpf.Ui.UnitTests/ --no-restore --verbosity normal + +- name: Run Integration Tests + run: dotnet test tests/Wpf.Ui.Gallery.IntegrationTests/ --no-restore --verbosity normal +``` diff --git a/docs/architecture/UNCERTAINTIES.md b/docs/architecture/UNCERTAINTIES.md new file mode 100644 index 000000000..4fd1ced7f --- /dev/null +++ b/docs/architecture/UNCERTAINTIES.md @@ -0,0 +1,154 @@ +# Architecture Uncertainties + +This document collects areas where the architecture documentation is incomplete or uncertain based on the analysis. These items require further investigation or are subject to change. + +## Core Library (Group 1) + +### Test Coverage +**Area:** `tests/` directory (outside analysis scope) + +Cannot determine exact test coverage. Given 77+ controls with complex state management (NavigationView journal, ContentDialog async lifecycle), the testability of static managers (ApplicationThemeManager, SystemThemeWatcher) is limited. Service interfaces (INavigationService, IContentDialogService, etc.) are testable through mock implementations. + +Current unit test coverage is minimal (6 tests covering Animations and Extensions only). + +### WindowBackdrop Implementation +**Area:** `src/Wpf.Ui/Controls/Window/WindowBackdrop.cs` + +WindowBackdrop class is referenced in FluentWindow and WindowBackgroundManager (ApplyBackdrop, RemoveBackdrop, RemoveBackground, RemoveTitlebarBackground) but was not directly examined. Likely located in Controls/Window/ directory and provides Mica/Acrylic/Tabbed backdrop effects via DWM APIs. + +### NavigationView Page Caching +**Area:** `src/Wpf.Ui/Controls/NavigationView/NavigationCache.cs` and `NavigationCacheMode.cs` + +These files exist but were not deeply examined. The caching strategy for navigation pages includes Required, Enabled, and Disabled modes but the exact implementation details need further investigation. + +### PolySharp Configuration +**Area:** Central Package Management in `Directory.Packages.props` + +PolySharp is referenced in core library csproj via `PolySharpExcludeGeneratedTypes` but the actual package reference appears to come from Directory.Packages.props (central package management) which was not fully examined. The exact polyfill configuration and version require verification. + +## Satellite Libraries (Group 2) + +### Toast Notifications Implementation Status +**Module:** `Wpf.Ui.ToastNotifications` + +It is unclear whether this library has any concrete implementation plans or if it is purely a placeholder for future work. The Toast.Show() method throws NotImplementedException. The library has no dependency on Wpf.Ui core despite being a WPF-targeting project. + +### FlaUI Library Scope +**Module:** `Wpf.Ui.FlaUI` + +The FlaUI library has no project reference to Wpf.Ui, yet it exists under the Wpf.Ui namespace. It only depends on FlaUI.Core. Currently contains only an AutoSuggestBox automation element wrapper. It is unclear whether additional automation elements for other WPF UI controls will be added. + +### FontMapper Integration Workflow +**Module:** `Wpf.Ui.FontMapper` + +The FontMapper generates enum files for the core library but has no project reference to it. The generated output path uses a relative 'generated/' directory. The exact integration workflow (how generated files reach the core library) is not evident from the project analysis alone. + +**Additional Issue:** FetchVersion() returns a hardcoded version '1.1.316' instead of actually fetching from GitHub API (the API call code is commented out). + +### SyntaxHighlight Font Dependency +**Module:** `Wpf.Ui.SyntaxHighlight` + +The SyntaxHighlight.xaml references a FontFamily with key 'FiraCode' using pack URI pointing to Wpf.Ui (not Wpf.Ui.SyntaxHighlight), suggesting the font may need to also exist in the core library. + +**Additional Issue:** The CodeBlock.cs is also listed as an EmbeddedResource in the csproj, which is unusual for a source file. + +**WIP Status:** Highlighter class is marked as work in progress. The autodetect language feature defaults to XAML, and C# and XAML use identical regex patterns. + +## Gallery and Tests (Group 3) + +### Gallery MSIX Packaging +**Module:** `Wpf.Ui.Gallery.Package` + +A .wapproj (Windows Application Packaging) project is referenced in Wpf.Ui.Gallery.slnf but not directly analyzed. This likely creates an MSIX package for the Gallery app. The packaging configuration and deployment process need clarification. + +### GalleryAssembly Reference +**Area:** `src/Wpf.Ui.Gallery/ControlsLookup/` + +Referenced as `GalleryAssembly.Asssembly` (note triple 's') in DependencyModel and ControlsLookup. The actual GalleryAssembly class was not directly read. It likely provides the Gallery assembly reference for reflection-based page discovery. + +### ReflectionEventing Usage +**Package:** ReflectionEventing and ReflectionEventing.DependencyInjection + +These packages are referenced in the Gallery csproj but their usage was not traced to specific event handling code within the analyzed files. The purpose and integration point of these packages require investigation. + +### Visual Studio Extension +**Module:** `src/Wpf.Ui.Extension` + +The VS2022 extension project (.vsix) is built for x64 and arm64 platforms but was not analyzed. The extension capabilities and integration points with Visual Studio are unknown. + +## Build and Infrastructure + +### .NET SDK Version Mismatch +**Area:** `build.ps1` + +The build script references .NET SDK 8 for installation (`winget install Microsoft.DotNet.SDK.8`) but the project now targets .NET 10. The build script may be outdated. + +### Dependabot Target Branch +**Area:** `.github/dependabot.yml` + +The Dependabot configuration targets the 'development' branch, not 'main'. The branch strategy and merge workflow from development to main are not documented. + +### CI Test Execution +**Area:** `.github/workflows/wpf-ui-pr-validator.yaml` + +The PR validator workflow only builds the Gallery app and does not run any tests (unit or integration). The rationale for not including test execution in CI is unclear. + +### Strong-Name Signing +**Area:** GitHub Actions secrets and certificate management + +The CD pipeline fetches a strong-name certificate from GitHub secrets (`${{ secrets.WPF_UI_CERTIFICATE_BASE64 }}`). The certificate generation, storage, and rotation procedures are not documented. + +## Documentation and Versioning + +### Version Synchronization +**Area:** `Directory.Build.props` vs actual package versions + +The Version property is set to 4.2.0, but the synchronization mechanism between this central version, individual package versions, and changelog updates is not documented. + +### API Compatibility +**Area:** Deprecated APIs and migration paths + +Several APIs are marked `[Obsolete]` (ContentDialog legacy constructors, WindowBackgroundManager.UpdateBackground with forceBackground parameter). The deprecation timeline and migration strategy for consumers are not documented. + +## Automation Peer Coverage +**Area:** `src/Wpf.Ui/AutomationPeers/` + +Only 4 automation peers exist for 77+ controls: +- CardControlAutomationPeer +- ContentDialogAutomationPeer +- CardActionAutomationPeer +- NavigationViewItemAutomationPeer + +Many interactive controls (Button, NavigationView base, TextBox, etc.) may lack custom automation peer implementations. The strategy for expanding accessibility support is unclear. This potentially affects screen reader compatibility and UI automation testing. + +## Theme Resource Keys +**Area:** Dynamic resource naming and contracts + +20+ accent-related dynamic resources are updated programmatically by ApplicationAccentColorManager: +- SystemAccentColor +- AccentFillColorDefault +- TextOnAccentFillColorPrimary +- (and others) + +The complete list of dynamic resources and their contracts (expected types, update triggers) is not centrally documented. Third-party theme authors may struggle to determine which resources must be provided. + +## Multi-Target Conditional Compilation +**Area:** Cross-framework API compatibility + +Various conditional compilation symbols are used: +- `NET5_0_OR_GREATER` +- `NET6_0_OR_GREATER` +- `NET8_0_OR_GREATER` +- `NET48_OR_GREATER_or_NETCOREAPP3_0_OR_GREATER` + +The decision matrix for when to use each symbol and the feature availability across frameworks is not documented in a single location. + +## System Requirements +**Area:** Minimum Windows version and feature matrix + +The library supports Windows 7 through Windows 11, but feature availability varies: +- Mica/Acrylic backdrops require Windows 11 +- Some DWM features require Windows 10 +- Certain APIs have Fall Creators Update requirements + +A comprehensive feature compatibility matrix by Windows version is not documented. diff --git a/docs/architecture/appendix/diagrams/README.md b/docs/architecture/appendix/diagrams/README.md new file mode 100644 index 000000000..ef0901e32 --- /dev/null +++ b/docs/architecture/appendix/diagrams/README.md @@ -0,0 +1,289 @@ +# Architecture Diagrams Index + +This directory serves as an index of all architecture diagrams used throughout the WPF UI documentation. + +## Architecture Views + +### Context Diagram + +**NuGet Package Distribution Flowchart** +- **Location:** [views/context.md](../../views/context.md#nuget-package-distribution) +- **Type:** Mermaid flowchart +- **Purpose:** Shows the NuGet package distribution flow from GitHub Actions CD through NuGet.org to developer machines +- **Key Components:** GitHub Actions CD, NuGet.org, Developer Machine + +### Logical Architecture + +**Module/Package Dependency Diagram** +- **Location:** [views/logical-architecture.md](../../views/logical-architecture.md#1-modulepackage-dependency-diagram) +- **Type:** Mermaid graph TD +- **Purpose:** Shows dependencies between all 10 solution projects +- **Key Components:** Abstractions, Core, DI, Tray, SyntaxHighlight, Toast, Gallery, FlaUI, FontMapper, Extension + +**Core Library Internal Structure (Layer Diagram)** +- **Location:** [views/logical-architecture.md](../../views/logical-architecture.md#2-core-library-internal-structure-layer-diagram) +- **Type:** Mermaid graph TB +- **Purpose:** Six-layer architecture of the core Wpf.Ui library +- **Layers:** Controls, Appearance/Theming, Services, Infrastructure, Win32 Interop, Resources + +**Control Architecture Class Diagram** +- **Location:** [views/logical-architecture.md](../../views/logical-architecture.md#3-control-architecture-pattern) +- **Type:** Mermaid classDiagram +- **Purpose:** Shows control inheritance and interface implementation patterns +- **Key Components:** WPF base classes, capability interfaces (IAppearanceControl, IIconControl), concrete controls + +**Target Framework Matrix** +- **Location:** [views/logical-architecture.md](../../views/logical-architecture.md#5-target-framework-matrix) +- **Type:** Mermaid gantt chart +- **Purpose:** Visualizes TFM coverage across all modules +- **Key Components:** All solution projects with their target frameworks + +**Service Registration & DI Integration** +- **Location:** [views/logical-architecture.md](../../views/logical-architecture.md#6-service-registration--di-integration) +- **Type:** Mermaid sequenceDiagram +- **Purpose:** Shows Generic Host DI registration flow and runtime page resolution +- **Key Components:** IHostBuilder, IServiceCollection, IServiceProvider, NavigationView, INavigationViewPageProvider + +**Control Lifecycle State Diagram** +- **Location:** [views/logical-architecture.md](../../views/logical-architecture.md#7-control-lifecycle) +- **Type:** Mermaid stateDiagram-v2 +- **Purpose:** Documents the complete lifecycle of WPF UI controls from construction through GC +- **States:** Constructed, Loaded, Template Applied, Themed, Unloaded, GC + +## Cross-Cutting Concerns + +### Navigation + +**Navigation System Lifecycle Sequence Diagram** +- **Location:** [cross-cutting/navigation.md](../../cross-cutting/navigation.md#navigation-lifecycle) +- **Type:** Mermaid sequenceDiagram +- **Purpose:** Complete flow from NavigationService.Navigate() through page resolution, caching, INavigationAware callbacks, and transition animation +- **Key Components:** Consumer Code, NavigationService, NavigationView, INavigationViewPageProvider, Page Cache, Frame, TransitionAnimationProvider + +**Page Cache Mode State Diagram** +- **Location:** [cross-cutting/navigation.md](../../cross-cutting/navigation.md#page-cache-mode) +- **Type:** Mermaid stateDiagram-v2 +- **Purpose:** Illustrates the three caching strategies (Disabled, Enabled, Required) and their decision logic +- **Key States:** PageRequested, CheckCacheMode, CacheDisabled, CacheEnabled, CacheRequired, PageDisplayed + +### Theming and Appearance + +**Theme Change Flow Sequence Diagram** +- **Location:** [cross-cutting/theming-and-appearance.md](../../cross-cutting/theming-and-appearance.md#theme-change-flow) +- **Type:** Mermaid sequence diagram +- **Purpose:** Illustrates the complete flow of a theme change from OS event to UI update +- **Key Components:** User, App, SystemThemeWatcher, WndProc, ApplicationThemeManager, ResourceDictionaryManager, UI + +**Theme System Architecture** +- **Location:** [cross-cutting/theming-and-appearance.md](../../cross-cutting/theming-and-appearance.md#architecture-components) +- **Type:** Component description with code examples +- **Purpose:** Documents the five core manager classes and their responsibilities +- **Key Components:** ApplicationThemeManager, ApplicationAccentColorManager, SystemThemeWatcher, WindowBackgroundManager, ResourceDictionaryManager + +### Win32 Interop + +**Win32 Interop Component Diagram** +- **Location:** [cross-cutting/win32-interop.md](../../cross-cutting/win32-interop.md#component-diagram) +- **Type:** Mermaid graph TB with subgraphs +- **Purpose:** Three-layer component diagram showing all Win32 interop participants from controls through managed wrappers to CsWin32-generated P/Invoke +- **Layers:** High-Level Utilities & Controls, Managed Wrappers (Interop/), CsWin32 Source Generation + +**Three-Layer Architecture Diagram** +- **Location:** [cross-cutting/win32-interop.md](../../cross-cutting/win32-interop.md#three-layer-architecture) +- **Type:** Mermaid graph diagram +- **Purpose:** Shows the layered architecture for Win32 API access +- **Layers:** + - Layer 1: CsWin32 Generated Code (Windows.Win32 namespace) + - Layer 2: Managed Wrappers (UnsafeNativeMethods, Custom PInvoke) + - Layer 3: High-Level Utilities (Win32/Utilities, Appearance Managers) + +**WndProc Message Flow** +- **Location:** [cross-cutting/win32-interop.md](../../cross-cutting/win32-interop.md#wndproc-message-interception) +- **Type:** Code example with explanation +- **Purpose:** Documents window message interception pattern used by SystemThemeWatcher and TitleBar +- **Key Messages:** WM_DWMCOLORIZATIONCOLORCHANGED, WM_THEMECHANGED, WM_SYSCOLORCHANGE, WM_NCHITTEST + +## Architectural Decisions + +### ADR-002: Control Library Architecture + +**Control Folder Structure** +- **Location:** [decisions/ADR-002-control-library-architecture.md](../../decisions/ADR-002-control-library-architecture.md#folder-per-control-structure) +- **Type:** Directory tree visualization +- **Purpose:** Shows the folder-per-control organization pattern +- **Examples:** Button/, NavigationView/, ContentDialog/ + +**Partial Class Decomposition** +- **Location:** [decisions/ADR-002-control-library-architecture.md](../../decisions/ADR-002-control-library-architecture.md#partial-class-decomposition) +- **Type:** Directory structure with annotations +- **Purpose:** Demonstrates how complex controls split into partial classes by concern +- **Example:** NavigationView with 6 partial files (Base, Properties, Events, Navigation, TemplateParts, AttachedProperties) + +### ADR-003: Win32 Interop via CsWin32 + +**Handle Validation Pattern Flowchart** +- **Location:** [decisions/ADR-003-win32-interop-via-cswin32.md](../../decisions/ADR-003-win32-interop-via-cswin32.md#handle-validation-pattern) +- **Type:** Code example with step-by-step comments +- **Purpose:** Documents the mandatory three-step validation pattern for all Win32 calls +- **Steps:** + 1. Check for zero handle + 2. Verify window exists (IsWindow) + 3. Perform operation with exception handling + +### ADR-005: Feature Folder Organization + +**Feature Folder Patterns** +- **Location:** [decisions/ADR-005-feature-folder-controls.md](../../decisions/ADR-005-feature-folder-controls.md#structure-patterns) +- **Type:** Multiple directory tree visualizations +- **Purpose:** Shows three organization patterns: Simple controls, Complex controls with partials, Controls with related types +- **Examples:** + - Simple: Badge/ (2 files) + - Complex: NavigationView/ (7+ files) + - With Related Types: NavigationView/ with NavigationViewItem, NavigationViewItemHeader, etc. + +### Module Interfaces + +**Module Dependency Graph (Public/Internal)** +- **Location:** [MODULE-INTERFACES.md](../../MODULE-INTERFACES.md#module-dependency-graph) +- **Type:** Mermaid graph TD with subgraphs +- **Purpose:** Shows module dependencies with public/internal type counts per module +- **Key Components:** All distributable and non-distributable modules with API surface annotations + +## Data Flow Diagrams + +### Theme Change Flow +**Diagram:** Sequence diagram showing theme change propagation +``` +User → OS Settings → WndProc → SystemThemeWatcher → ApplicationThemeManager → +ResourceDictionaryManager → Application.Resources → UI Update +``` + +### Win32 Call Stack +**Diagram:** Layer diagram showing Win32 interop call flow +``` +High-Level Code → UnsafeNativeMethods (validation) → PInvoke (CsWin32) → Win32 API +``` + +### Navigation Flow +**Diagram:** (Not yet created) Could be added to show: +``` +NavigationService → NavigationView → PageProvider → Page Resolution → +Frame Navigation → INavigationAware Callbacks +``` + +## Component Diagrams + +### Theming System Components +**Components:** +- ApplicationThemeManager (theme selection) +- ApplicationAccentColorManager (accent colors) +- SystemThemeWatcher (OS sync) +- WindowBackgroundManager (window appearance) +- ResourceDictionaryManager (resource swapping) + +**Relationships:** +- SystemThemeWatcher → ApplicationThemeManager (triggers Apply) +- ApplicationThemeManager → ResourceDictionaryManager (delegates swapping) +- ApplicationAccentColorManager → ResourceDictionaryManager (updates dynamic resources) + +### Control Architecture Components +**Components:** +- Control Class (.cs file) +- Implicit Style (.xaml file) +- Dependency Properties +- Routed Events +- Capability Interfaces (IAppearanceControl, IIconControl, IThemeControl) + +**Relationships:** +- Control Class → Dependency Properties (registers) +- Control Class → Capability Interfaces (implements) +- XAML Style → Control Class (targets via TargetType) + +## Future Diagram Additions + +### Recommended Additions +1. **Content Dialog Lifecycle** - Async ShowAsync() flow with cancellation and result handling +2. **Icon System Hierarchy** - IconElement inheritance tree (FontIcon, SymbolIcon, ImageIcon) +3. **Accent Color Derivation** - How 20+ accent resources are computed from system accent +4. **Gallery App Architecture** - MVVM structure with ViewModels, Views, Services, and Models + +### Recently Added (2026-02-10) +- ~~Navigation System Flow~~ — Added in [cross-cutting/navigation.md](../../cross-cutting/navigation.md) +- ~~DI Integration Flow~~ — Added in [views/logical-architecture.md](../../views/logical-architecture.md#6-service-registration--di-integration) +- ~~Service Layer Architecture~~ — Covered by DI Integration and Module Interfaces diagrams + +## Diagram Guidelines + +### Mermaid Syntax +All diagrams use Mermaid for easy maintenance and version control: + +**Sequence Diagram:** +```mermaid +sequenceDiagram + participant A + participant B + A->>B: Message + B-->>A: Response +``` + +**Graph Diagram:** +```mermaid +graph TD + A[Component A] --> B[Component B] + B --> C[Component C] + + style A fill:#e1f5e1 + style B fill:#fff4e1 +``` + +**Class Diagram:** +```mermaid +classDiagram + class Button { + +Icon IconElement + +Appearance ControlAppearance + } + Button --|> IAppearanceControl + Button --|> IIconControl +``` + +### Diagram Best Practices +1. Keep diagrams focused (single concern per diagram) +2. Use consistent styling across related diagrams +3. Include legend when colors/shapes have meaning +4. Maintain diagram source in markdown (not separate image files) +5. Update diagrams when architecture changes + +## Diagram Tools + +### Recommended Tools +- **Mermaid Live Editor** - https://mermaid.live/ +- **VS Code Extensions:** + - Markdown Preview Mermaid Support + - Mermaid Editor + +### Generating PNG/SVG +For presentations or external documentation: +```bash +# Using mmdc (Mermaid CLI) +npm install -g @mermaid-js/mermaid-cli +mmdc -i diagram.mmd -o diagram.png +``` + +## Maintenance + +### Ownership +Architecture diagram maintenance is part of architecture documentation updates. When making architectural changes: + +1. Identify affected diagrams +2. Update diagram source in markdown +3. Verify rendering in preview +4. Update this index if adding new diagrams + +### Review Checklist +- [ ] Diagram accurately reflects current architecture +- [ ] All components labeled clearly +- [ ] Relationships shown with appropriate arrows +- [ ] Color coding explained (if used) +- [ ] Renders correctly in GitHub markdown preview +- [ ] Index updated with new diagram location diff --git a/docs/architecture/appendix/diagrams/wpfui.c4 b/docs/architecture/appendix/diagrams/wpfui.c4 new file mode 100644 index 000000000..0bf33c3d4 --- /dev/null +++ b/docs/architecture/appendix/diagrams/wpfui.c4 @@ -0,0 +1,145 @@ +specification { + element actor { + style { + shape person + } + } + element system + element external { + style { + color muted + } + } + element container + element component +} + +model { + actor developer = 'WPF Developer' { + description 'Builds WPF apps using WPF UI' + } + actor enduser = 'End User' { + description 'Uses WPF desktop applications' + } + + system wpfui = 'WPF UI Library' { + description 'Fluent Design System for WPF. 77+ controls, theming, Win32 interop.' + + container core = 'Wpf.Ui' { + description 'Core library - controls, theming, services, interop' + + component controls = 'Controls' { + description '77+ Fluent Design controls including NavigationView, TitleBar, FluentWindow, ContentDialog, NumberBox, ToggleSwitch' + } + component appearance = 'Appearance' { + description 'Theme and accent color management via ApplicationThemeManager and ApplicationAccentColorManager' + } + component services = 'Services' { + description 'NavigationService, ContentDialogService, SnackbarService, TaskBarService' + } + component interop = 'Win32 Interop' { + description 'DWM, User32, Shell32 wrappers via CsWin32 P/Invoke' + } + component resources = 'Resources' { + description 'ThemesDictionary, ControlsDictionary, embedded Fluent System Icons fonts' + } + component converters = 'Converters' { + description 'XAML value converters for theme-aware data binding' + } + component automationPeers = 'AutomationPeers' { + description 'UI Automation and accessibility support for custom controls' + } + } + + container abstractions = 'Wpf.Ui.Abstractions' { + description 'Contract interfaces for services and navigation. Multi-targets netstandard2.0/2.1.' + } + container di = 'Wpf.Ui.DependencyInjection' { + description 'Microsoft.Extensions.DependencyInjection integration for services and navigation' + } + container tray = 'Wpf.Ui.Tray' { + description 'System tray icon and context menu support' + } + container syntaxHighlight = 'Wpf.Ui.SyntaxHighlight' { + description 'Code syntax highlighting display control' + } + container gallery = 'Wpf.Ui.Gallery' { + description 'Demo application showcasing all controls and patterns' + } + container flaui = 'Wpf.Ui.FlaUI' { + description 'FlaUI-based test automation helpers for integration testing' + } + container fontMapper = 'Wpf.Ui.FontMapper' { + description 'Build-time tool generating icon enum from Fluent System Icons font' + } + } + + external nuget = 'NuGet.org' { + description 'Package distribution for WPF-UI and satellite packages' + } + external windows = 'Windows OS' { + description 'Win32 APIs, Desktop Window Manager, WinRT notifications' + } + external github = 'GitHub' { + description 'Source hosting, CI/CD via GitHub Actions, documentation' + } + external fluentIcons = 'Fluent System Icons' { + description 'Microsoft open-source icon font (Filled + Regular variants)' + } + external wpfFramework = 'WPF Framework' { + description '.NET WPF runtime and base controls' + } + + // Actor relationships + developer -> wpfui.core 'Uses controls, theming, and services via NuGet' + developer -> wpfui.di 'Registers services in DI container' + developer -> nuget 'Installs WPF-UI packages' + enduser -> wpfui.gallery 'Explores demo application' + + // Internal container relationships + wpfui.core -> abstractions 'Implements service interfaces' + wpfui.di -> abstractions 'References contract interfaces' + wpfui.di -> wpfui.core 'Registers concrete service implementations' + wpfui.tray -> wpfui.core 'Extends with system tray functionality' + wpfui.syntaxHighlight -> wpfui.core 'Extends with syntax highlighting control' + wpfui.gallery -> wpfui.core 'Demonstrates all controls and services' + wpfui.gallery -> wpfui.di 'Uses DI for service registration' + wpfui.gallery -> wpfui.tray 'Demonstrates tray integration' + wpfui.gallery -> wpfui.syntaxHighlight 'Demonstrates code display' + wpfui.flaui -> wpfui.core 'Provides automation wrappers for controls' + + // Internal component relationships + wpfui.core.controls -> wpfui.core.appearance 'Reacts to theme changes' + wpfui.core.controls -> wpfui.core.resources 'Consumes styles and templates' + wpfui.core.controls -> wpfui.core.interop 'Uses Win32 for TitleBar, snap layouts' + wpfui.core.controls -> wpfui.core.converters 'Uses value converters in templates' + wpfui.core.controls -> wpfui.core.automationPeers 'Exposes automation support' + wpfui.core.services -> wpfui.core.controls 'Manages NavigationView, ContentDialog, Snackbar' + wpfui.core.appearance -> wpfui.core.interop 'Detects system theme via Win32' + wpfui.core.appearance -> wpfui.core.resources 'Switches theme dictionaries at runtime' + + // External relationships + wpfui.core.interop -> windows 'P/Invoke calls to DWM, User32, Shell32' + wpfui.core -> wpfFramework 'Extends WPF base classes (Control, Window, etc.)' + wpfui.core.resources -> fluentIcons 'Bundles icon font files' + wpfui.core -> nuget 'Published as WPF-UI package' + wpfui.core -> github 'Source hosted, CI/CD pipeline' + wpfui.fontMapper -> fluentIcons 'Parses icon font to generate enums' +} + +views { + view context of wpfui { + title 'WPF UI - System Context (C4 Level 1)' + include *, developer, enduser, nuget, windows, github, fluentIcons, wpfFramework + } + + view containers of wpfui { + title 'WPF UI - Container View (C4 Level 2)' + include wpfui, wpfui.*, developer, windows, nuget, wpfFramework + } + + view components of wpfui.core { + title 'Wpf.Ui Core - Component View (C4 Level 3)' + include wpfui.core, wpfui.core.*, windows, wpfFramework, fluentIcons + } +} diff --git a/docs/architecture/cross-cutting/navigation.md b/docs/architecture/cross-cutting/navigation.md new file mode 100644 index 000000000..38d2d7936 --- /dev/null +++ b/docs/architecture/cross-cutting/navigation.md @@ -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 <> + 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(); +services.AddSingleton(); + +// Pages registered in DI +services.AddTransient(); +services.AddTransient(); +``` + +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. diff --git a/docs/architecture/cross-cutting/testing.md b/docs/architecture/cross-cutting/testing.md new file mode 100644 index 000000000..05114e8e9 --- /dev/null +++ b/docs/architecture/cross-cutting/testing.md @@ -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
net10.0-windows"] + 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
net10.0-windows10.0.26100.0"] + 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 (base class)"] + C2 --> C2b["TestedApplication.cs (app lifecycle)"] + + D["src/Wpf.Ui.FlaUI/"] --> D1["AutoSuggestBox.cs
Custom FlaUI element"] + + 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)` -- 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. diff --git a/docs/architecture/cross-cutting/theming-and-appearance.md b/docs/architecture/cross-cutting/theming-and-appearance.md new file mode 100644 index 000000000..8cbb86751 --- /dev/null +++ b/docs/architecture/cross-cutting/theming-and-appearance.md @@ -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 + + + + + + + + + +``` + +## 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. diff --git a/docs/architecture/cross-cutting/win32-interop.md b/docs/architecture/cross-cutting/win32-interop.md new file mode 100644 index 000000000..3e2833925 --- /dev/null +++ b/docs/architecture/cross-cutting/win32-interop.md @@ -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
Backdrop, corner prefs"] + TitleBar["TitleBar
Custom chrome, snap layouts"] + STW["SystemThemeWatcher
WndProc hooks"] + WBM["WindowBackgroundManager
DWM backdrop effects"] + AACM["ApplicationAccentColorManager
WinRT UISettings"] + TaskBar["TaskBarService
COM ITaskbarList4"] + NotifyIcon["NotifyIcon (Tray)
Shell_NotifyIcon"] + end + + subgraph "Layer 2 — Managed Wrappers (Interop/)" + UNM["UnsafeNativeMethods.cs
Handle validation + safe wrappers"] + PI["PInvoke.cs
Manual DllImport for
SetWindowLongPtr (x86/x64)
"] + UR["UnsafeReflection.cs
Enum/struct unsafe casting"] + end + + subgraph "Layer 1 — CsWin32 Source Generation" + NMT["NativeMethods.txt
35 function/type declarations"] + CsWin32["CsWin32 Generator
Produces Windows.Win32 namespace"] + DWM["DwmSetWindowAttribute
DwmIsCompositionEnabled"] + User32["SetWindowLong
GetWindowLong
GetDpiForWindow"] + Shell32["Shell_NotifyIcon
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. diff --git a/docs/architecture/decisions/ADR-001-multi-target-framework.md b/docs/architecture/decisions/ADR-001-multi-target-framework.md new file mode 100644 index 000000000..b2ebcd98e --- /dev/null +++ b/docs/architecture/decisions/ADR-001-multi-target-framework.md @@ -0,0 +1,208 @@ +# ADR-001: Multi-Target Framework Support + +## Status +Accepted + +## Context +The WPF UI library needs to support a wide range of .NET implementations to maximize compatibility with existing projects while leveraging modern .NET features where available. + +### Supported Frameworks +- **.NET 10, 9, 8** - Modern .NET with Windows-specific APIs +- **.NET Framework 4.8.1, 4.7.2, 4.6.2** - Legacy enterprise applications +- **.NET Standard 2.0, 2.1** - Abstractions library only (maximizes compatibility) + +## Decision + +### Core Library (Wpf.Ui) +Target frameworks: `net10.0-windows;net9.0-windows;net8.0-windows;net481;net472;net462` + +**Rationale:** +- Windows-specific project requires `-windows` TFM suffix for .NET 5+ +- .NET Framework support ensures compatibility with legacy WPF applications +- Multi-version .NET support provides upgrade path for consumers + +### Abstractions Library (Wpf.Ui.Abstractions) +Target frameworks: `net10.0;net9.0;net8.0;net462;netstandard2.1;netstandard2.0` + +**Rationale:** +- No WPF dependencies allows broader compatibility +- .NET Standard 2.0 enables use in class libraries shared between .NET Framework and .NET Core/5+ +- AOT-compatible (aot_compatible: true) for modern deployment scenarios + +### DI Integration (Wpf.Ui.DependencyInjection) +Target frameworks: Same as Abstractions + +**Rationale:** +- Depends only on Microsoft.Extensions.DependencyInjection.Abstractions (version 3.1.0 for broad compatibility) +- No WPF dependencies +- Enables DI integration in non-WPF contexts (e.g., background services) + +## Central Package Management + +All package versions are managed in `Directory.Packages.props`: + +```xml + + + true + + + + + + + + +``` + +Individual projects reference packages without version attributes: +```xml + +``` + +**Benefits:** +- Single source of truth for package versions +- Prevents version conflicts across projects +- Simplifies dependency updates + +## Conditional Compilation + +### Framework Detection +```csharp +#if NET5_0_OR_GREATER + // Modern .NET APIs (Environment.OSVersion) +#else + // .NET Framework fallback (registry) +#endif + +#if NET6_0_OR_GREATER + // DisposeAsync for CancellationTokenRegistration +#endif + +#if NET8_0_OR_GREATER + // Latest .NET 8+ features +#endif +``` + +### Framework-Specific Dependencies +```xml + + + + + +``` + +## PolySharp Integration + +**Package:** PolySharp (build-time source generator) + +Provides polyfills for newer C# features on older target frameworks: +- C# 11+ features on .NET 6/7/8 +- C# 12+ features on older .NET versions + +**Configuration:** +```xml + + + + + + + + System.Runtime.CompilerServices.OverloadResolutionPriorityAttribute; + System.Diagnostics.CodeAnalysis.UnscopedRefAttribute + + +``` + +**Benefits:** +- Use modern C# syntax across all target frameworks +- Init-only properties on .NET Framework +- Required members support +- CallerArgumentExpression on older frameworks + +## Language Version + +Set to C# 14.0 across all projects: +```xml +14.0 +``` + +> **Note:** `Wpf.Ui.csproj` overrides this to `preview` to enable C# preview features. + +**Combined with PolySharp**, this enables: +- Latest C# language features +- Polyfills generated at compile-time for older frameworks +- No runtime dependencies + +## Enforcement + +### MUST Follow + +1. **All package versions in `Directory.Packages.props` only** — Central Package Management is the single source of truth for NuGet versions +2. **Use `-windows` TFM suffix** for projects with WPF dependencies (e.g., `net10.0-windows`, not `net10.0`) +3. **Use `netstandard2.0`/`netstandard2.1`** for abstraction-only packages that have no WPF or Windows dependency +4. **Guard framework-specific code with `#if NET{X}_0_OR_GREATER` directives** — never use runtime version checks for compile-time API differences +5. **Use PolySharp for C# polyfills** — never hand-write polyfill classes for language features (e.g., `IsExternalInit`, `CallerArgumentExpression`) +6. **Set `LangVersion` in `Directory.Build.props`** — override in individual `.csproj` only when justified (currently only `Wpf.Ui.csproj` overrides to `preview`) + +### MUST NOT Do + +1. **Never add `Version` attribute to `PackageReference`** in `.csproj` files — all versions must be in `Directory.Packages.props` +2. **Never add a new TFM without updating all projects** that share the same TFM set — all projects in a TFM group must stay in sync +3. **Never use `#if` with specific patch versions** (e.g., `NET8_0_10`) — only use `_OR_GREATER` suffixed symbols +4. **Never remove a TFM from a shipping package** without a major version bump — removing a TFM is a breaking change for consumers on that framework + +### Verification + +- **CPM enforcement:** `true` in `Directory.Build.props` causes build errors if `Version` is specified in `.csproj` +- **TFM validation:** Build compiles all target frameworks on every `dotnet build` — missing APIs surface as compile errors immediately +- **PolySharp coverage:** PolySharp source generator runs at build time and provides polyfills automatically; manual polyfills would cause duplicate symbol errors + +## Consequences + +### Positive +- **Broad Compatibility:** Supports applications from .NET Framework 4.6.2 through .NET 10 +- **Modern Development:** C# 14 preview features via PolySharp +- **Simplified Maintenance:** Central package management +- **Clear Upgrade Path:** Consumers can upgrade .NET version without changing library +- **AOT Ready:** Abstractions library supports AOT scenarios + +### Negative +- **Increased Build Complexity:** Each commit builds 6+ framework variants +- **Larger Package Size:** Multi-target packages include assemblies for all frameworks +- **Conditional Compilation:** Requires `#if` directives for framework-specific code +- **Testing Burden:** Features should be tested on multiple framework versions + +## Implementation Details + +### Directory.Build.props +Central build properties defined once: +```xml + + 4.2.0 + 14.0 + enable + true + true + true + +``` + +### Conditional Property Groups +```xml + + $(DefineConstants);NET8_0_OR_GREATER + + + + $(DefineConstants);BELOW_NET8 + +``` + +## References +- [.NET Multi-Targeting Documentation](https://docs.microsoft.com/dotnet/standard/frameworks) +- [Central Package Management](https://docs.microsoft.com/nuget/consume-packages/central-package-management) +- [PolySharp GitHub](https://github.com/Sergio0694/PolySharp) +- [TFM Compatibility](https://docs.microsoft.com/dotnet/standard/frameworks#net-5-os-specific-tfms) diff --git a/docs/architecture/decisions/ADR-002-control-library-architecture.md b/docs/architecture/decisions/ADR-002-control-library-architecture.md new file mode 100644 index 000000000..c45d96966 --- /dev/null +++ b/docs/architecture/decisions/ADR-002-control-library-architecture.md @@ -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 + + + + 11,5,11,6 + + + +``` + +### 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 +/// Identifies the dependency property. +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 `` and `` 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) diff --git a/docs/architecture/decisions/ADR-003-win32-interop-via-cswin32.md b/docs/architecture/decisions/ADR-003-win32-interop-via-cswin32.md new file mode 100644 index 000000000..269a24138 --- /dev/null +++ b/docs/architecture/decisions/ADR-003-win32-interop-via-cswin32.md @@ -0,0 +1,399 @@ +# ADR-003: Win32 Interop via CsWin32 + +## Status +Accepted + +## Context +WPF UI requires extensive Win32 API access for features unavailable in standard WPF: +- Desktop Window Manager (DWM) effects (Mica, Acrylic backdrops) +- Window corner rounding (Windows 11) +- Dark mode title bars +- System tray icon management +- System theme detection +- Taskbar progress indicators + +Traditional P/Invoke requires: +- Manual function signature declarations +- COM interface definitions +- Struct layout definitions +- Constant value definitions +- Maintaining cross-architecture compatibility (x86/x64/ARM64) + +## Decision + +### Use CsWin32 Source Generator + +**Package:** Microsoft.Windows.CsWin32 (build-time only, PrivateAssets="all") + +**Declaration File:** `src/Wpf.Ui/NativeMethods.txt` + +CsWin32 generates P/Invoke bindings at compile-time from Win32 metadata. + +### NativeMethods.txt Format + +``` +# DWM Functions +DwmIsCompositionEnabled +DwmSetWindowAttribute +DwmExtendFrameIntoClientArea +S_OK +SetWindowThemeAttribute +DWM_SYSTEMBACKDROP_TYPE +DWM_WINDOW_CORNER_PREFERENCE +DWMWA_COLOR_NONE +WTA_OPTIONS + +# Window Management +GetDpiForWindow +GetForegroundWindow +IsWindowVisible +SetWindowRgn +GetWindowRect +GetSystemMetrics +WINDOW_STYLE + +# COM Interfaces +ITaskbarList4 +TaskbarList + +# Wildcard Patterns +WM_* +HT* +``` + +**CsWin32 automatically generates:** +- Function P/Invoke declarations +- Struct definitions with correct layout +- Enum types +- COM interface wrappers +- Foundation types (HWND, HRESULT, BOOL, etc.) + +### Generated Code Location +**Namespace:** `Windows.Win32` and `Windows.Win32.Foundation` +**Physical Location:** `obj/` directory (not committed to source control) + +**Usage:** +```csharp +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.Graphics.Dwm; + +// Generated type-safe P/Invoke +HRESULT result = PInvoke.DwmSetWindowAttribute( + new HWND(windowHandle), + DWMWINDOWATTRIBUTE.DWMWA_USE_IMMERSIVE_DARK_MODE, + &darkMode, + sizeof(BOOL) +); +``` + +## Three-Layer Architecture + +### Layer 1: CsWin32 Generated Code +**Purpose:** Auto-generated P/Invoke declarations + +**Characteristics:** +- Compile-time generated +- Type-safe API surface +- Cross-architecture compatible +- Not committed to source control + +### Layer 2: Managed Wrappers +**Purpose:** Safe, validated native API access + +**Location:** `src/Wpf.Ui/Interop/` + +#### UnsafeNativeMethods.cs +```csharp +internal static class UnsafeNativeMethods +{ + public static unsafe bool ApplyWindowCornerPreference( + IntPtr handle, + WindowCornerPreference cornerPreference) + { + // Validation layer + if (handle == IntPtr.Zero) + return false; + + if (!PInvoke.IsWindow(new HWND(handle))) + return false; + + // Type conversion + DWM_WINDOW_CORNER_PREFERENCE pvAttribute = + UnsafeReflection.Cast(cornerPreference); + + // Native call with exception handling + try + { + HRESULT hr = PInvoke.DwmSetWindowAttribute( + new HWND(handle), + DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE, + &pvAttribute, + (uint)sizeof(DWM_WINDOW_CORNER_PREFERENCE) + ); + + return hr == HRESULT.S_OK; + } + catch + { + // Graceful degradation for unsupported OS versions + return false; + } + } +} +``` + +**Responsibilities:** +- Handle validation (IntPtr.Zero, IsWindow checks) +- Exception suppression for cross-version compatibility +- HRESULT → bool conversion +- Type-safe enum conversions + +#### Custom PInvoke.cs +**Purpose:** Supplement CsWin32 for missing/incorrect signatures + +```csharp +namespace Windows.Win32; + +internal static partial class PInvoke +{ + // CsWin32 doesn't generate correct SetWindowLongPtr for 32/64-bit + [DllImport("USER32.dll", + ExactSpelling = true, + EntryPoint = "SetWindowLongPtrW", + SetLastError = true)] + internal static extern nint SetWindowLongPtr( + HWND hWnd, + WINDOW_LONG_PTR_INDEX nIndex, + nint dwNewLong + ); +} +``` + +### Layer 3: High-Level Utilities +**Purpose:** Business logic and feature implementation + +**Locations:** +- `src/Wpf.Ui/Win32/Utilities.cs` - OS version detection +- `src/Wpf.Ui/Appearance/` - Theme managers +- `src/Wpf.Ui/Controls/FluentWindow/` - Window chrome +- `src/Wpf.Ui/Tray/` - System tray management + +**Characteristics:** +- Consumes Layer 2 safe wrappers +- OS version feature gating +- Business logic and state management + +## Handle Validation Pattern + +**Critical Requirement:** All native calls MUST validate handles. + +```csharp +public static bool NativeOperation(IntPtr handle) +{ + // Step 1: Null check + if (handle == IntPtr.Zero) + { + return false; + } + + // Step 2: Verify window exists + if (!PInvoke.IsWindow(new HWND(handle))) + { + return false; + } + + // Step 3: Perform operation + HRESULT hr = PInvoke.SomeWin32Function(new HWND(handle), ...); + return hr == HRESULT.S_OK; +} +``` + +**Rationale:** +- Handles can become invalid between retrieval and use +- Window may be destroyed on background thread +- Invalid handles cause native crashes +- IsWindow is inexpensive (single User32 call) + +## Exception Handling Strategy + +**Philosophy:** Native APIs fail silently across Windows versions. Prefer graceful degradation over exceptions. + +```csharp +try +{ + HRESULT hr = PInvoke.DwmSetWindowAttribute(...); + return hr == HRESULT.S_OK; +} +catch (COMException) +{ + // API not available on this Windows version + return false; +} +catch +{ + // Unexpected failure, degrade gracefully + return false; +} +``` + +**Suppressed Exceptions:** +- `COMException` - COM API failures +- `Win32Exception` - Native API errors +- `EntryPointNotFoundException` - API not available on OS version +- `DllNotFoundException` - DLL not present + +## Conditional Compilation + +### Framework-Specific Code +```csharp +#if NET5_0_OR_GREATER + // Modern API available + var version = Environment.OSVersion; +#else + // Fallback for .NET Framework + var version = GetVersionFromRegistry(); +#endif +``` + +### OS Version Feature Gating +```csharp +// Windows 11+ only features +if (Win32.Utilities.IsOSWindows11OrNewer) +{ + UnsafeNativeMethods.ApplyWindowCornerPreference( + handle, + WindowCornerPreference.Round + ); +} + +// DWM composition required +if (Win32.Utilities.IsCompositionEnabled) +{ + UnsafeNativeMethods.ApplyWindowBackdrop( + handle, + WindowBackdropType.Acrylic + ); +} +``` + +## Enforcement + +### MUST Follow + +1. **Add new Win32 APIs to NativeMethods.txt** (never manual P/Invoke unless CsWin32 fails) +2. **Validate handles** before all native calls (IntPtr.Zero + IsWindow) +3. **Return bool** from wrapper methods indicating success +4. **Suppress exceptions** in interop layer for compatibility +5. **Use unsafe keyword** explicitly for pointer operations +6. **Feature-gate by OS version** for version-specific APIs +7. **Use HRESULT == S_OK** pattern for success checking +8. **Keep generated code private** (internal/private visibility) + +### MUST NOT Do + +1. **Never call PInvoke directly** from high-level code (use UnsafeNativeMethods wrappers) +2. **Never skip handle validation** (even if "guaranteed" valid) +3. **Never throw exceptions** from interop wrappers (return false instead) +4. **Never assume API availability** across Windows versions +5. **Never use var** for native types (HRESULT, HWND, etc. - explicit types required) +6. **Never commit obj/ directory** (contains generated code) + +### Verification + +- WpfAnalyzers enforces correct patterns +- Code review checks handle validation +- Multi-version testing (Windows 7, 8.1, 10, 11) + +## Consequences + +### Positive +- **Type Safety:** CsWin32 generates correct signatures from metadata +- **Maintenance:** Win32 metadata updates automatically benefit project +- **Cross-Platform:** ARM64, x64, x86 handled automatically +- **Correctness:** Struct layouts, calling conventions verified by Microsoft +- **Discoverability:** IntelliSense for all Windows APIs +- **Build-Time Only:** Zero runtime dependencies + +### Negative +- **Build-Time Dependency:** Requires CsWin32 NuGet package +- **Opaque Generation:** Generated code in obj/, harder to debug +- **NativeMethods.txt Maintenance:** Must manually add new APIs (currently 34 lines, including wildcard patterns like `WM_*` and `HT*`) +- **Supplements Needed:** Some APIs require manual P/Invoke (SetWindowLongPtr) +- **Learning Curve:** Developers must understand NativeMethods.txt format + +## Performance Considerations + +### CsWin32 Performance +- **Zero overhead:** Generated code identical to hand-written P/Invoke +- **Inlined by JIT:** Same JIT optimization as manual declarations +- **No reflection:** Compile-time code generation + +### Handle Validation Cost +- **IsWindow:** Single User32 API call (~1-2 μs) +- **Negligible:** Compared to DWM/window operations (hundreds of μs) +- **Essential:** Prevents native crashes worth the cost + +## Alternatives Considered + +### Manual P/Invoke +**Rejected:** +- High maintenance burden (200+ Win32 functions across project) +- Error-prone struct layout definitions +- Cross-architecture compatibility issues +- No automatic updates from Windows SDK + +### ComWrappers +**Rejected:** +- Only solves COM interop, not general P/Invoke +- More complex than CsWin32 +- Limited to .NET 5+ + +### PInvoke.net Snippets +**Rejected:** +- Community-maintained, not authoritative +- Copy-paste errors common +- No compile-time verification +- Inconsistent signature styles + +## Migration Guide + +### Adding New Win32 API + +1. **Add to NativeMethods.txt:** + ``` + DwmGetColorizationColor + ``` + +2. **Rebuild project** (CsWin32 generates code) + +3. **Create managed wrapper in UnsafeNativeMethods.cs:** + ```csharp + public static bool GetColorizationColor(out Color color) + { + try + { + HRESULT hr = PInvoke.DwmGetColorizationColor(out uint colorValue, out BOOL opaque); + color = Color.FromArgb(...); + return hr == HRESULT.S_OK; + } + catch + { + color = default; + return false; + } + } + ``` + +4. **Consume from high-level code:** + ```csharp + if (UnsafeNativeMethods.GetColorizationColor(out Color color)) + { + // Use color + } + ``` + +## References +- [CsWin32 GitHub](https://github.com/microsoft/CsWin32) +- [Windows API Documentation](https://docs.microsoft.com/windows/win32/api/) +- [P/Invoke Best Practices](https://docs.microsoft.com/dotnet/standard/native-interop/best-practices) diff --git a/docs/architecture/decisions/ADR-004-static-managers-for-theming.md b/docs/architecture/decisions/ADR-004-static-managers-for-theming.md new file mode 100644 index 000000000..c1b8e7e2a --- /dev/null +++ b/docs/architecture/decisions/ADR-004-static-managers-for-theming.md @@ -0,0 +1,455 @@ +# ADR-004: Static Managers for Theming + +## Status +Accepted + +## Context +The theming system requires global coordination across: +- Application resource dictionary management +- System theme synchronization +- Accent color application +- Window appearance updates + +Multiple approaches exist: +1. **Static classes** (global singleton) +2. **Instance-based services** (registered in DI container) +3. **Ambient context pattern** (ThreadStatic/AsyncLocal) + +## Decision + +Use **static class singleton pattern** for core theme managers: +- `ApplicationThemeManager` +- `ApplicationAccentColorManager` +- `SystemThemeWatcher` +- `WindowBackgroundManager` +- `ResourceDictionaryManager` (internal) + +### Implementation Pattern + +```csharp +public static class ApplicationThemeManager +{ + // Global state + private static ApplicationTheme _currentTheme = ApplicationTheme.Unknown; + + // Global event + public static event ThemeChangedEvent? Changed; + + // Static methods + public static void Apply(ApplicationTheme theme) + { + if (_currentTheme == theme) + return; + + ResourceDictionaryManager manager = new(LibraryNamespace); + manager.UpdateDictionary("theme", GetThemeUri(theme)); + + _currentTheme = theme; + Changed?.Invoke(theme, GetSystemAccent()); + } + + public static ApplicationTheme GetAppTheme() + { + return _currentTheme; + } +} +``` + +### No Constructor, No Instances +```csharp +public static class ApplicationThemeManager +{ + // All members are static + // Cannot be instantiated + // Cannot be inherited + // Cannot be mocked/substituted +} +``` + +## Rationale + +### Simple API Surface +```csharp +// Immediate clarity of global scope +ApplicationThemeManager.Apply(ApplicationTheme.Dark); +var current = ApplicationThemeManager.GetAppTheme(); +``` + +Compared to instance-based: +```csharp +// Requires context about where themeManager comes from +_themeManager.Apply(ApplicationTheme.Dark); +var current = _themeManager.GetAppTheme(); +``` + +### Single Source of Truth +Application theme is fundamentally global state: +- Only one theme can be active +- All windows share the same theme +- Resource dictionaries are process-wide + +Static class enforces singleton semantics at compile-time. + +### No DI Configuration Required +```csharp +// Works immediately without setup +public MainWindow() +{ + InitializeComponent(); + ApplicationThemeManager.Apply(ApplicationTheme.Dark); +} +``` + +Instance-based would require: +```csharp +// App.xaml.cs +services.AddSingleton(); + +// MainWindow.xaml.cs +public MainWindow(IThemeManager themeManager) +{ + _themeManager = themeManager; + InitializeComponent(); + _themeManager.Apply(ApplicationTheme.Dark); +} +``` + +### WPF Application Model Alignment +WPF itself uses static patterns extensively: +- `Application.Current` (static property) +- `Application.Current.Resources` (global resource dictionary) +- `SystemColors` (static class) +- `SystemParameters` (static class) + +## Trade-offs + +### Advantages + +**1. Simplicity** +- No DI configuration required +- No interface abstraction needed +- Clear that state is global +- Immediate usability + +**2. Performance** +- Zero overhead (no interface dispatch) +- No allocation for manager instances +- Direct static method calls + +**3. Discoverability** +- Easy to find with IntelliSense +- Self-documenting via naming (`ApplicationThemeManager` clearly global) +- No need to understand DI container + +**4. Compatibility** +- Works in non-DI scenarios (simple WPF apps) +- Compatible with .NET Framework patterns +- No breaking changes if DI added later + +### Disadvantages + +**1. Limited Testability** +- Cannot mock static classes +- Difficult to isolate in unit tests +- Tests may affect each other through shared state + +**Mitigation:** +- Extract `IThemeService` for consumers who need testability +- Test through integration tests instead of unit tests +- Use `[Collection]` attribute in XUnit to isolate test state + +**2. Hidden Dependencies** +- Static call hides dependency +- Harder to track theme manager usage +- Violates dependency injection principle + +**Mitigation:** +- Theme management is intentionally global +- Not a "hidden" dependency if explicitly documented as global + +**3. Global State** +- Mutable global state +- Concurrent access concerns +- No lifetime management + +**Mitigation:** +- Theme changes are inherently single-threaded (UI thread) +- `Application.Current.Resources` is already global mutable state +- No need for lifetime management (lives entire process lifetime) + +**4. No Polymorphism** +- Cannot substitute alternative implementations +- Cannot extend behavior through inheritance + +**Mitigation:** +- Theme system is not extensible by design +- Alternative implementations not a use case + +## Service Interface for DI + +For consumers requiring testability, `IThemeService` wraps static managers: + +```csharp +public interface IThemeService +{ + ApplicationTheme GetTheme(); + SystemTheme GetNativeSystemTheme(); + ApplicationTheme GetSystemTheme(); + bool SetTheme(ApplicationTheme applicationTheme); + bool SetSystemAccent(); + bool SetAccent(Color accentColor); + bool SetAccent(SolidColorBrush accentSolidBrush); +} + +public partial class ThemeService : IThemeService +{ + public ApplicationTheme GetTheme() + => ApplicationThemeManager.GetAppTheme(); + + public SystemTheme GetNativeSystemTheme() + => ApplicationThemeManager.GetSystemTheme(); + + public ApplicationTheme GetSystemTheme() + => ApplicationThemeManager.GetSystemTheme() switch { ... }; + + public bool SetTheme(ApplicationTheme applicationTheme) + { + ApplicationThemeManager.Apply(applicationTheme); + return true; + } + + public bool SetSystemAccent() + { + ApplicationAccentColorManager.ApplySystemAccent(); + return true; + } + + public bool SetAccent(Color accentColor) + { + ApplicationAccentColorManager.Apply(accentColor); + return true; + } + + public bool SetAccent(SolidColorBrush accentSolidBrush) + { + ApplicationAccentColorManager.Apply(accentSolidBrush.Color); + return true; + } +} +``` + +**Registration:** +```csharp +services.AddSingleton(); +``` + +**Usage in testable code:** +```csharp +public class SettingsViewModel +{ + private readonly IThemeService _themeService; + + public SettingsViewModel(IThemeService themeService) + { + _themeService = themeService; + } + + public void ApplyDarkMode() + { + _themeService.SetTheme(ApplicationTheme.Dark); + } +} +``` + +**Testing with mock:** +```csharp +[Fact] +public void ApplyDarkMode_CallsThemeService() +{ + // Arrange + var mockThemeService = Substitute.For(); + var viewModel = new SettingsViewModel(mockThemeService); + + // Act + viewModel.ApplyDarkMode(); + + // Assert + mockThemeService.Received(1).SetTheme(ApplicationTheme.Dark); +} +``` + +## UiApplication Pattern + +`UiApplication` uses `[ThreadStatic]` for thread-local singleton: + +```csharp +public class UiApplication +{ + [ThreadStatic] + private static UiApplication? _uiApplication; + + public static UiApplication? Current => _uiApplication; + + public UiApplication(Application application) + { + // Stores the application reference and sets _uiApplication + } + + // Instance methods operate on the wrapped Application + public ResourceDictionary Resources => Application.Current.Resources; +} +``` + +**Rationale:** +- Non-static class instantiated with an `Application` parameter +- Uses `[ThreadStatic]` backing field `_uiApplication` for thread-local singleton +- Each UI thread gets its own `UiApplication` instance +- Safely wraps `Application.Current` (which is also thread-local) +- Allows future expansion with per-thread state + +## Enforcement + +### MUST Follow + +1. **Use static managers directly** for simple scenarios: + ```csharp + ApplicationThemeManager.Apply(ApplicationTheme.Dark); + ``` + +2. **Use IThemeService** in testable/DI-dependent code: + ```csharp + public ViewModel(IThemeService themeService) { } + ``` + +3. **Document global nature** in XML docs: + ```csharp + /// + /// Global theme manager. Applies themes application-wide. + /// + ``` + +4. **Never create wrapper instances** of static managers: + ```csharp + // BAD: Don't do this + public class ThemeManagerWrapper + { + private ApplicationTheme _cachedTheme; + + public void Apply(ApplicationTheme theme) + { + _cachedTheme = theme; + ApplicationThemeManager.Apply(theme); + } + } + ``` + +### MUST NOT Do + +1. **Never attempt to mock static classes** in unit tests + - Use `IThemeService` instead if testing is needed + +2. **Never cache theme state** in instance fields + - Always query `ApplicationThemeManager.GetAppTheme()` for current state + +3. **Never create parallel theme systems** + - Static managers are the single source of truth + +### Verification + +- Code review enforces proper usage +- Documentation clearly marks classes as static global managers +- `IThemeService` provides tested alternative where needed + +## Testing Strategy + +### Integration Tests +Theme system is tested through integration tests that exercise full stack: + +```csharp +[Fact] +public async Task ThemeChange_UpdatesWindowAppearance() +{ + // Arrange + var window = new FluentWindow(); + window.Show(); + + // Act + ApplicationThemeManager.Apply(ApplicationTheme.Dark); + await Task.Delay(500); // Allow visual update + + // Assert + var theme = ApplicationThemeManager.GetAppTheme(); + Assert.Equal(ApplicationTheme.Dark, theme); + + // Cleanup + window.Close(); +} +``` + +### Unit Tests for Consumer Code +Consumer code uses `IThemeService` for testability: + +```csharp +[Fact] +public void ApplyDarkTheme_UpdatesCurrentTheme() +{ + var themeService = Substitute.For(); + var viewModel = new SettingsViewModel(themeService); + + viewModel.ApplyDarkTheme(); + + themeService.Received().SetTheme(ApplicationTheme.Dark); +} +``` + +## Documentation Requirements + +All static manager classes include XML doc warning: + +```csharp +/// +/// Global static manager for application theming. +/// +/// +/// +/// This is a static class managing process-wide theme state. +/// Theme changes affect all windows in the application. +/// +/// +/// For testable code, use instead. +/// +/// +public static class ApplicationThemeManager +{ + // ... +} +``` + +## Future Considerations + +### Potential Migration to Instances +If future requirements demand it, can migrate while maintaining compatibility: + +```csharp +// New instance-based implementation +public sealed class ThemeManager : IThemeManager +{ + // Instance implementation +} + +// Static facade maintains compatibility +public static class ApplicationThemeManager +{ + private static readonly IThemeManager _instance = new ThemeManager(); + + public static void Apply(ApplicationTheme theme) + => _instance.Apply(theme); +} +``` + +This preserves existing code while enabling DI-based usage. + +## References +- WPF Static Classes: `Application.Current`, `SystemColors`, `SystemParameters` +- Gang of Four Singleton Pattern +- [Dependency Injection Anti-Pattern: Service Locator](https://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/) diff --git a/docs/architecture/decisions/ADR-005-feature-folder-controls.md b/docs/architecture/decisions/ADR-005-feature-folder-controls.md new file mode 100644 index 000000000..e97a08e6b --- /dev/null +++ b/docs/architecture/decisions/ADR-005-feature-folder-controls.md @@ -0,0 +1,395 @@ +# ADR-005: Feature Folder Organization for Controls + +## Status +Accepted + +## Context +With 77+ controls in the library, code organization becomes critical for maintainability. The structure must support: +- Clear isolation between controls +- Easy location of control-related files +- Logical grouping of complex control components +- Scalability as new controls are added + +## Decision + +### Feature Folder per Control + +Each control resides in `Controls/{ControlName}/` directory: + +``` +Controls/ +├── Button/ +│ ├── Button.cs +│ └── Button.xaml +├── Card/ +│ ├── Card.cs +│ └── Card.xaml +├── NavigationView/ +│ ├── NavigationView.Base.cs +│ ├── NavigationView.Properties.cs +│ ├── NavigationView.Events.cs +│ ├── NavigationView.Navigation.cs +│ ├── NavigationView.TemplateParts.cs +│ ├── NavigationView.AttachedProperties.cs +│ ├── NavigationView.xaml +│ ├── NavigationViewItem.cs +│ ├── NavigationViewItemHeader.cs +│ └── NavigationViewItemSeparator.cs +``` + +**One control = one folder** with all related files. + +## Structure Patterns + +### Simple Controls +**Pattern:** Single .cs + .xaml pair + +``` +Badge/ +├── Badge.cs # Control implementation +└── Badge.xaml # Implicit style +``` + +**Applies to:** Button, Badge, Card, InfoBar, TextBox, etc. (50+ controls) + +### Complex Controls with Partial Classes +**Pattern:** Multiple partial class files organized by concern + +``` +NavigationView/ +├── NavigationView.Base.cs # Core control logic +├── NavigationView.Properties.cs # 27 dependency properties +├── NavigationView.Events.cs # 7 routed events +├── NavigationView.Navigation.cs # Page navigation logic +├── NavigationView.TemplateParts.cs # Template part fields/binding +├── NavigationView.AttachedProperties.cs # Attached property definitions +└── NavigationView.xaml # Implicit style + template +``` + +**Partial class naming:** `{ControlName}.{Concern}.cs` + +**Applies to:** NavigationView, ContentDialog, TitleBar + +### Controls with Related Types +**Pattern:** Additional related controls in same folder + +``` +NavigationView/ +├── NavigationView*.cs # Main control (6 files) +├── NavigationView.xaml +├── NavigationViewItem.cs # Item container +├── NavigationViewItemHeader.cs # Header item +├── NavigationViewItemSeparator.cs # Separator +├── NavigationViewContentPresenter.cs # Content host +├── INavigationView.cs # Interface +└── INavigationViewItem.cs # Item interface +``` + +**Rationale:** Tightly coupled types that are only used together. + +### Controls with Supporting Subdirectories +**Pattern:** Subfolder for supporting types + +``` +ContentDialog/ +├── ContentDialog.cs +├── ContentDialog.FocusBehavior.cs +├── ContentDialog.xaml +├── ContentDialogHost.cs +├── ContentDialogHostBehavior.cs +└── EventArgs/ + ├── ContentDialogButtonClickEventArgs.cs + ├── ContentDialogClosingEventArgs.cs + └── ContentDialogClosedEventArgs.cs +``` + +**When to use subfolder:** +- 3+ related types (EventArgs, Converters, Enums) +- Clear sub-component (e.g., EventArgs) + +## Flat Namespace Strategy + +**All controls use:** `Wpf.Ui.Controls` namespace (regardless of folder depth) + +```csharp +// File: Controls/NavigationView/NavigationView.cs +namespace Wpf.Ui.Controls; // Flat namespace, not Wpf.Ui.Controls.NavigationView +// ReSharper disable once CheckNamespace +``` + +**Trade-off:** Folder structure does not match namespace. + +**IDE Configuration Required:** +```ini +# .editorconfig +dotnet_diagnostic.IDE0130.severity = none # Suppress namespace/folder mismatch +``` + +## Partial Class Decomposition Strategies + +### By Concern +NavigationView splits by logical concern: +- **Base.cs** - Control infrastructure (template application, initialization) +- **Properties.cs** - Dependency property definitions +- **Events.cs** - Routed event definitions +- **Navigation.cs** - Page navigation logic +- **TemplateParts.cs** - Template part fields and OnApplyTemplate logic +- **AttachedProperties.cs** - Attached property definitions + +### By Feature +ContentDialog splits by feature: +- **ContentDialog.cs** - Main implementation (async ShowAsync, dialog lifecycle) +- **ContentDialog.FocusBehavior.cs** - Keyboard focus management (isolated behavior) + +### Guidelines for Splitting + +**When to split:** +- File exceeds 500 lines +- Clear separation of concerns exists (properties vs. logic) +- Feature is self-contained and isolatable + +**How to name:** +- **Base.cs** - Core control logic (template, initialization) +- **Properties.cs** - All dependency properties +- **Events.cs** - All routed events +- **{Feature}.cs** - Specific feature (FocusBehavior, Animation, etc.) + +**Don't split:** +- Controls under 300 lines +- No clear separation of concerns +- When references would create circular dependencies + +## File Naming Conventions + +### Control Classes +``` +{ControlName}.cs # Simple control +{ControlName}.{Concern}.cs # Partial class with concern +``` + +### XAML Styles +``` +{ControlName}.xaml # Implicit style ResourceDictionary +``` + +### Supporting Types +``` +{TypePurpose}{ControlName}.cs # e.g., NavigationViewItem +{ControlName}{Purpose}.cs # e.g., ContentDialogHost +I{ControlName}.cs # Interface +``` + +### Event Arguments +``` +{ControlName}{EventName}EventArgs.cs +``` + +Examples: +- `ContentDialogButtonClickEventArgs.cs` +- `NavigatedEventArgs.cs` + +## Enforcement + +### MUST Follow + +1. **One control = one folder** under `Controls/` +2. **Paired .cs + .xaml** with matching names +3. **Flat namespace** `Wpf.Ui.Controls` for all controls +4. **Partial class naming** `{ControlName}.{Concern}.cs` +5. **ReSharper suppress comment** when namespace doesn't match folder: + ```csharp + namespace Wpf.Ui.Controls; + // ReSharper disable once CheckNamespace + ``` + +6. **Keep related types together** in same folder when tightly coupled + +### MUST NOT Do + +1. **Never nest control folders** (keep flat under Controls/) + ``` + ❌ Controls/Buttons/Button/ + ✅ Controls/Button/ + ``` + +2. **Never use category-specific namespace** + ```csharp + ❌ namespace Wpf.Ui.Controls.Buttons; + ✅ namespace Wpf.Ui.Controls; + ``` + +3. **Never split files arbitrarily** (must have clear separation of concerns) + +4. **Never create more than 2 directory levels** under Controls/ + ``` + ✅ Controls/ContentDialog/EventArgs/ + ❌ Controls/ContentDialog/EventArgs/Closing/ + ``` + +### Verification + +1. **IDE0130 suppressed** in .editorconfig for namespace/folder mismatch +2. **Code review** checks folder organization +3. **Naming conventions** enforced during PR review + +## Benefits + +### Developer Experience + +**Easy to Find:** +```bash +# Looking for Button control? +Controls/Button/Button.cs # Immediately obvious location +``` + +**Clear Boundaries:** +- All Button-related code in `Controls/Button/` +- No cross-control dependencies (enforced by folder isolation) + +**Scalable:** +- Adding new control = create new folder +- Doesn't affect existing controls + +### Maintainability + +**Isolation:** +- Changes to Button don't affect Card +- Merge conflicts localized to single control + +**Discoverability:** +- New developers find controls by folder name +- Related types co-located (NavigationViewItem with NavigationView) + +**Refactoring:** +- Easy to split large files (add `.Properties.cs`) +- Clear when to split (file size, concern separation) + +### Build Performance + +**Partial Classes:** +- Compiler can parallelize partial class compilation +- Changes to Properties.cs don't trigger recompilation of Navigation.cs + +## Trade-offs + +### Advantages +- ✅ Clear physical organization +- ✅ Easy to locate control files +- ✅ Enforces encapsulation (hard to reference across control folders) +- ✅ Scales to 100+ controls +- ✅ Supports complex controls with many files + +### Disadvantages +- ❌ Folder/namespace mismatch requires suppression +- ❌ 77+ folders in single directory (large directory) +- ❌ No physical categorization (all controls appear equal) +- ❌ Related controls may be far apart alphabetically (Button vs. ToggleButton) + +## Alternatives Considered + +### Category-Based Folders +``` +Controls/ +├── Buttons/ +│ ├── Button/ +│ └── ToggleButton/ +└── Navigation/ + └── NavigationView/ +``` + +**Rejected:** +- Requires category-specific namespaces or deeper mismatch +- Category classification is subjective (is ToggleSwitch a Button or Input?) +- Doesn't align with flat namespace strategy + +### Single File Per Control +``` +Controls/ +├── Button.cs +├── Button.xaml +├── NavigationView.cs +└── NavigationView.xaml +``` + +**Rejected:** +- Complex controls (NavigationView 1000+ lines) unmanageable +- Supporting types have unclear location +- 150+ files in single directory + +### Hybrid Categorization +``` +Controls/ +├── Button/ +├── Input/ +│ ├── TextBox/ +│ └── NumberBox/ +└── Navigation/ + └── NavigationView/ +``` + +**Rejected:** +- Inconsistent structure (some categories, some not) +- Unclear where new controls go +- Complicates namespace strategy + +## Migration Path + +### Adding New Simple Control + +1. Create folder `Controls/{NewControl}/` +2. Add `{NewControl}.cs` with control class +3. Add `{NewControl}.xaml` with implicit style +4. Add ReSharper suppress comment to .cs file + +### Splitting Existing Control + +Example: Card becomes too large + +**Before:** +``` +Card/ +├── Card.cs (500 lines) +└── Card.xaml +``` + +**After:** +``` +Card/ +├── Card.Base.cs (200 lines - core logic) +├── Card.Properties.cs (100 lines - dependency properties) +├── Card.Animation.cs (100 lines - animation logic) +└── Card.xaml +``` + +**Refactoring steps:** +1. Extract dependency properties to `Card.Properties.cs` +2. Extract animation logic to `Card.Animation.cs` +3. Keep core logic in `Card.Base.cs` +4. All files use `partial class Card` + +## Documentation + +### Control Folder README +Each complex control folder includes README.md: + +```markdown +# NavigationView + +Complex navigation container with 6 partial class files: + +- **Base.cs** - Core control logic, template application +- **Properties.cs** - 27 dependency properties +- **Events.cs** - 7 routed events +- **Navigation.cs** - Page navigation, journal, back/forward +- **TemplateParts.cs** - Template part bindings +- **AttachedProperties.cs** - HeaderContent attached property + +Related types: +- NavigationViewItem - Selectable item container +- NavigationViewItemHeader - Non-selectable header +- INavigationView - Public control interface +``` + +## References +- [Feature Folders in ASP.NET](https://docs.microsoft.com/archive/msdn-magazine/2016/september/asp-net-core-feature-slices-for-asp-net-core-mvc) (similar pattern) +- [Vertical Slice Architecture](https://jimmybogard.com/vertical-slice-architecture/) diff --git a/docs/architecture/views/context.md b/docs/architecture/views/context.md new file mode 100644 index 000000000..9dd3e1cc3 --- /dev/null +++ b/docs/architecture/views/context.md @@ -0,0 +1,190 @@ +# C4 Context Diagram (Level 1) + +This document describes the system context of WPF UI -- the external actors, systems, and data flows that surround the library. + +## Context Diagram + +```plantuml +@startuml +!include + +title System Context Diagram - WPF UI Library (v4.2.0) + +Person(dev, "Library Consumer\n(WPF Developer)", "Develops WPF desktop applications\nusing WPF UI controls, theming,\nand navigation services") + +Person(enduser, "End User", "Uses WPF desktop applications\nbuilt with WPF UI controls") + +System(wpfui, "WPF UI Library", "Open-source WPF control library\nimplementing Microsoft Fluent Design.\n77+ controls, theming, Win32 interop,\nnavigation services.\n\nC# 14 / XAML\nNuGet: WPF-UI v4.2.0") + +System_Ext(nuget, "NuGet.org", "Package registry.\nDistributes WPF-UI, WPF-UI.Abstractions,\nWPF-UI.DependencyInjection, WPF-UI.Tray\nNuGet packages") + +System_Ext(vsmarket, "VS Marketplace", "Distributes Wpf.Ui.Extension VSIX\nwith project templates for\nVisual Studio 2022") + +System_Ext(consumerapp, "Consumer WPF Application", "Desktop application built by the\nLibrary Consumer that references\nWPF UI NuGet packages and uses\nFluentWindow, NavigationView,\nthemed controls, etc.") + +System_Ext(windows, "Windows OS", "Provides Win32 APIs:\n- DWM (Mica/Acrylic backdrops)\n- User32 (window management)\n- Shell32 (system tray, taskbar)\n- WinRT UISettings (accent colors)\n- System theme registry values") + +System_Ext(github, "GitHub", "Source code hosting,\nGitHub Actions CI/CD,\nGitHub Pages documentation site") + +System_Ext(fluenticons, "Fluent System Icons\n(Microsoft)", "Open-source icon font providing\nthousands of Regular and Filled\nglyph icons bundled as TTF fonts") + +Rel(dev, wpfui, "References via NuGet,\nuses controls in XAML,\ncalls services in C#") +Rel(dev, nuget, "Installs packages\nvia dotnet add / NuGet UI") +Rel(dev, vsmarket, "Installs VS extension\nfor project templates") +Rel(dev, consumerapp, "Builds and maintains") + +Rel(enduser, consumerapp, "Uses desktop\napplication") + +Rel(wpfui, windows, "P/Invoke calls:\nDwmSetWindowAttribute,\nSetWindowLong,\nShell_NotifyIcon,\nIUISettings3 COM,\nWM_ message handling") + +Rel(wpfui, fluenticons, "Bundles Regular + Filled\nTTF font files as\nembedded resources") + +Rel_Back(nuget, wpfui, "Publishes packages\nvia GitHub Actions CD") +Rel_Back(github, wpfui, "Hosts source,\nruns CI/CD,\nhosts docs") + +@enduml +``` + +## External Actors + +### Library Consumers (WPF Developers) + +The primary users of WPF UI are C#/WPF developers who want to apply Microsoft Fluent Design System styling to their desktop applications. They interact with the library by: + +1. **Installing NuGet packages** -- `WPF-UI` (core), optionally `WPF-UI.DependencyInjection`, `WPF-UI.Tray`, `WPF-UI.Abstractions` +2. **Adding XAML resource dictionaries** -- `` and `` in `App.xaml` +3. **Using controls in XAML** -- Via the `xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"` namespace prefix +4. **Calling services in C#** -- `INavigationService`, `IContentDialogService`, `ISnackbarService`, `ApplicationThemeManager` + +### End Users + +End users interact with consumer WPF applications built using WPF UI. They experience Fluent Design visuals (rounded corners, Mica backdrop, accent colors) and interact with the 77+ controls. End users do not directly interact with the WPF UI library. + +### Windows OS + +WPF UI makes extensive use of the Windows platform through several channels: + +| API Surface | Usage | Key Functions | +|---|---|---| +| **DWM (Desktop Window Manager)** | Backdrop effects (Mica, Acrylic, Tabbed), dark mode, window 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 Messages** | Theme change detection, title bar hit testing | `WM_THEMECHANGED`, `WM_DWMCOLORIZATIONCOLORCHANGED`, `WM_NCHITTEST` | + +### NuGet.org + +The primary distribution channel for WPF UI. The GitHub Actions CD pipeline (`wpf-ui-cd-nuget.yaml`) publishes the following packages on every version bump (triggered by `Directory.Build.props` changes on the `main` branch): + +| Package ID | Project | Description | +|---|---|---| +| `WPF-UI` | `Wpf.Ui` | Core library with controls, theming, services | +| `WPF-UI.Abstractions` | `Wpf.Ui.Abstractions` | Interface contracts for navigation | +| `WPF-UI.DependencyInjection` | `Wpf.Ui.DependencyInjection` | MS DI integration | +| `WPF-UI.Tray` | `Wpf.Ui.Tray` | System tray icon support | + +### Visual Studio Marketplace + +Distributes the `Wpf.Ui.Extension` VSIX for Visual Studio 2022 (x64 and arm64). Built by the `wpf-ui-cd-extension.yaml` workflow. Provides project templates for creating new WPF UI applications. + +### GitHub + +Hosts the source repository, runs CI/CD via GitHub Actions (7 workflows), and serves the DocFX documentation site via GitHub Pages. + +## Data Flows + +### NuGet Package Distribution + +```mermaid +flowchart LR + Dev["Developer Machine"] + NuGet["NuGet.org"] + GH["GitHub Actions CD"] + + GH -->|"nuget push *.nupkg
Strong-name signed
SourceLink embedded"| NuGet + NuGet -->|"dotnet add package WPF-UI
Downloads .nupkg + .snupkg"| Dev + + style Dev fill:#e1f5ff,stroke:#0277bd + style NuGet fill:#fff4e1,stroke:#f57f17 + style GH fill:#e8f5e9,stroke:#2e7d32 +``` + +### Win32 API Calls (Runtime) + +``` +Consumer WPF App + | + | Uses FluentWindow / TitleBar / SystemThemeWatcher + | + v + Wpf.Ui Core Library + | + |-- UnsafeNativeMethods (handle validation layer) + | | + | |-- CsWin32 PInvoke (source-generated declarations) + | | + | v + | Windows.Win32 namespace + | + v + Windows OS Kernel / DWM / Shell32 / User32 +``` + +### XAML Resource Loading + +``` +Consumer App.xaml + | + | + | + | + v + ThemesDictionary (Markup Extension) + | Resolves pack URI: + | pack://application:,,,/Wpf.Ui;component/Resources/Theme/Dark.xaml + | + v + ControlsDictionary (Markup Extension) + | Resolves pack URI: + | pack://application:,,,/Wpf.Ui;component/Resources/Wpf.Ui.xaml + | --> Merges all 77 control .xaml ResourceDictionaries + | --> Loads font resources (FluentSystemIcons) + | --> Loads palette, typography, variables + | + v + WPF Resource System + | Controls resolve styles via DefaultStyleKeyProperty + | Theme brushes resolved via DynamicResource + | Accent colors updated programmatically by ApplicationAccentColorManager +``` + +### Theme Change Flow + +``` +Windows OS + | + | WM_DWMCOLORIZATIONCOLORCHANGED + | WM_THEMECHANGED + | WM_SYSCOLORCHANGE + | + v + SystemThemeWatcher (HwndSource.AddHook) + | + | Detects OS theme change + | + v + ApplicationThemeManager.ApplySystemTheme() + | + |-- ResourceDictionaryManager.UpdateDictionary("theme", newThemeUri) + | Swaps Light.xaml <-> Dark.xaml in MergedDictionaries + | + |-- ApplicationAccentColorManager.Apply(accentColor, theme) + | Updates 20+ dynamic color resources + | + |-- WindowBackgroundManager.UpdateBackground(window) + | Applies DWM dark mode attribute + backdrop effect + | + |-- Fires ApplicationThemeManager.Changed event + | --> Subscribers (CodeBlock, InternalNotifyIconManager, consumer code) +``` diff --git a/docs/architecture/views/logical-architecture.md b/docs/architecture/views/logical-architecture.md new file mode 100644 index 000000000..be7410624 --- /dev/null +++ b/docs/architecture/views/logical-architecture.md @@ -0,0 +1,655 @@ +# Logical Architecture + +This document describes the logical architecture of WPF UI v4.2.0, a C# WPF control library implementing Microsoft Fluent Design System. It covers module dependencies, internal layer organization, control authoring patterns, cross-cutting concerns, and the multi-targeting strategy. + +--- + +## 1. Module/Package Dependency Diagram + +The WPF UI solution consists of ten projects organized around a core library (`Wpf.Ui`) with satellite packages for optional functionality. + +```mermaid +graph TD + Abstractions["Wpf.Ui.Abstractions
NuGet: WPF-UI.Abstractions
Zero-dependency interfaces:
INavigationViewPageProvider,
INavigationAware, INavigableView<T>
Targets: netstandard2.0/2.1,
net462, net8.0, net9.0, net10.0
"] + + Core["Wpf.Ui
NuGet: WPF-UI
77+ controls, theming, services,
Win32 interop
Targets: net10.0-windows,
net9.0/8.0-windows,
net481, net472, net462
"] + + DI["Wpf.Ui.DependencyInjection
NuGet: WPF-UI.DependencyInjection
MS DI integration
Targets: netstandard2.0/2.1,
net462, net8.0, net9.0, net10.0
"] + + Tray["Wpf.Ui.Tray
NuGet: WPF-UI.Tray
System tray icons via Shell32
Targets: net10.0/9.0/8.0-windows,
net481, net472, net462
"] + + Syntax["Wpf.Ui.SyntaxHighlight
NuGet: WPF-UI.SyntaxHighlight
Code syntax highlighting (WIP)
Targets: net10.0/9.0/8.0-windows,
net481, net472, net462
"] + + Toast["Wpf.Ui.ToastNotifications
NuGet: WPF-UI.ToastNotifications
Toast notifications (STUB,
all throw NotImplementedException)
Targets: net10.0/9.0/8.0-windows,
net481, net472, net462
"] + + Gallery["Wpf.Ui.Gallery
Demo/showcase app
MVVM with CommunityToolkit.Mvvm
Target: net10.0-windows10.0.26100.0"] + + FlaUI["Wpf.Ui.FlaUI
NuGet: WPF-UI.FlaUI
Test automation helpers
Targets: net10.0/9.0/8.0-windows,
net481
"] + + FontMapper["Wpf.Ui.FontMapper
Build tool: icon enum generation
from FluentSystemIcons JSON
Target: net10.0"] + + Extension["Wpf.Ui.Extension
VS 2022 VSIX extension
Project templates (Blank, Compact, Fluent)
Target: net481 (VSIX SDK)"] + + %% Dependency arrows (arrow points from dependent to dependency) + Core --> Abstractions + DI --> Abstractions + DI -.->|"Microsoft.Extensions.
DependencyInjection.Abstractions 3.1.0"| ExtDI["Microsoft.Extensions.
DependencyInjection"] + Tray --> Core + Syntax --> Core + Gallery --> Core + Gallery --> DI + Gallery --> Tray + Gallery --> Syntax + Gallery --> Toast + + style Abstractions fill:#e1f5ff,stroke:#0277bd + style Core fill:#fff4e1,stroke:#f57f17 + style DI fill:#e8f5e9,stroke:#2e7d32 + style Tray fill:#e8f5e9,stroke:#2e7d32 + style Syntax fill:#e8f5e9,stroke:#2e7d32 + style Toast fill:#fff9c4,stroke:#f9a825 + style Gallery fill:#fce4ec,stroke:#c62828 + style FlaUI fill:#f3e5f5,stroke:#6a1b9a + style FontMapper fill:#f3e5f5,stroke:#6a1b9a + style Extension fill:#f3e5f5,stroke:#6a1b9a + style ExtDI fill:#e0e0e0,stroke:#616161 +``` + +**Dependency rules:** + +| Package | Direct dependencies | +|---------|-------------------| +| `Wpf.Ui.Abstractions` | None (zero external dependencies) | +| `Wpf.Ui` | `Wpf.Ui.Abstractions`, Microsoft.Windows.CsWin32 (build-time), System.Memory | +| `Wpf.Ui.DependencyInjection` | `Wpf.Ui.Abstractions`, Microsoft.Extensions.DependencyInjection.Abstractions 3.1.0 | +| `Wpf.Ui.Tray` | `Wpf.Ui`, System.Drawing.Common | +| `Wpf.Ui.SyntaxHighlight` | `Wpf.Ui` | +| `Wpf.Ui.ToastNotifications` | None (standalone stub) | +| `Wpf.Ui.Gallery` | `Wpf.Ui`, `Wpf.Ui.DependencyInjection`, `Wpf.Ui.Tray`, `Wpf.Ui.SyntaxHighlight`, `Wpf.Ui.ToastNotifications`, CommunityToolkit.Mvvm, Microsoft.Extensions.Hosting | +| `Wpf.Ui.FlaUI` | FlaUI.Core | +| `Wpf.Ui.FontMapper` | None | +| `Wpf.Ui.Extension` | VSIX SDK (VS 2022), template projects (Blank, Compact, Fluent) | + +--- + +## 2. Core Library Internal Structure (Layer Diagram) + +The `src/Wpf.Ui/` project is organized into six logical layers, from user-facing controls down to Win32 primitives. + +```mermaid +graph TB + subgraph "Layer 1 - Controls (77 control folders)" + direction LR + C1["FluentWindow"] + C2["NavigationView"] + C3["TitleBar"] + C4["ContentDialog"] + C5["NumberBox"] + C6["AutoSuggestBox"] + C7["ToggleSwitch"] + C8["Snackbar"] + C9["...70 more"] + end + + subgraph "Layer 2 - Appearance / Theming" + direction LR + T1["ApplicationThemeManager
(static)"] + T2["ApplicationAccentColorManager
(static)"] + T3["SystemThemeWatcher
(static)"] + T4["WindowBackgroundManager
(static)"] + T5["ResourceDictionaryManager
(internal)"] + end + + subgraph "Layer 3 - Services" + direction LR + S1["NavigationService"] + S2["ContentDialogService"] + S3["SnackbarService"] + S4["ThemeService"] + S5["TaskBarService"] + end + + subgraph "Layer 4 - Infrastructure" + direction LR + I1["Converters/
18 IValueConverters"] + I2["Extensions/
14 extension classes"] + I3["Markup/
ControlsDictionary,
ThemesDictionary,
SymbolIconExtension,
FontIconExtension"] + I4["Animations/
TransitionAnimationProvider"] + I5["Input/
IRelayCommand,
RelayCommand<T>"] + I6["Hardware/
DPI, rendering tier"] + I7["AutomationPeers/
Accessibility"] + end + + subgraph "Layer 5 - Win32 Interop" + direction LR + W1["CsWin32
(NativeMethods.txt
-> Windows.Win32)"] + W2["Interop/
UnsafeNativeMethods.cs
PInvoke.cs"] + W3["Win32/
Utilities.cs
OS version detection"] + end + + subgraph "Layer 6 - Resources" + direction LR + R1["Theme/
Light.xaml, Dark.xaml,
HC1, HC2, HCBlack,
HCWhite"] + R2["Fonts/
FluentSystemIcons-Filled.ttf
FluentSystemIcons-Regular.ttf"] + R3["Root resources:
Accent.xaml, Palette.xaml,
StaticColors.xaml,
Typography.xaml,
Variables.xaml"] + R4["Wpf.Ui.xaml
(master dictionary,
merges all 77 control styles)"] + end + + C1 & C2 & C3 & C4 & C5 & C6 & C7 & C8 --> T1 & T2 + C1 & C2 & C3 --> W2 + T1 & T2 & T3 & T4 --> T5 + T3 --> W2 + T4 --> W2 + S1 --> C2 + S2 --> C4 + S3 --> C8 + S5 --> W2 + I4 --> I6 + W2 --> W1 + W2 --> W3 + T5 --> R1 + + style C1 fill:#fff4e1,stroke:#f57f17 + style C2 fill:#fff4e1,stroke:#f57f17 + style C3 fill:#fff4e1,stroke:#f57f17 + style C4 fill:#fff4e1,stroke:#f57f17 + style C5 fill:#fff4e1,stroke:#f57f17 + style C6 fill:#fff4e1,stroke:#f57f17 + style C7 fill:#fff4e1,stroke:#f57f17 + style C8 fill:#fff4e1,stroke:#f57f17 + style C9 fill:#fff4e1,stroke:#f57f17 + style T1 fill:#e8f5e9,stroke:#2e7d32 + style T2 fill:#e8f5e9,stroke:#2e7d32 + style T3 fill:#e8f5e9,stroke:#2e7d32 + style T4 fill:#e8f5e9,stroke:#2e7d32 + style T5 fill:#e8f5e9,stroke:#2e7d32 + style S1 fill:#e1f5ff,stroke:#0277bd + style S2 fill:#e1f5ff,stroke:#0277bd + style S3 fill:#e1f5ff,stroke:#0277bd + style S4 fill:#e1f5ff,stroke:#0277bd + style S5 fill:#e1f5ff,stroke:#0277bd + style W1 fill:#ffebee,stroke:#c62828 + style W2 fill:#ffebee,stroke:#c62828 + style W3 fill:#ffebee,stroke:#c62828 + style R1 fill:#f3e5f5,stroke:#6a1b9a + style R2 fill:#f3e5f5,stroke:#6a1b9a + style R3 fill:#f3e5f5,stroke:#6a1b9a + style R4 fill:#f3e5f5,stroke:#6a1b9a +``` + +### Layer Details + +#### Layer 1 -- Controls (77 control folders) + +Each control follows a folder-per-control pattern: `Controls/{Name}/{Name}.cs` + `Controls/{Name}/{Name}.xaml`. + +Controls extend WPF base classes (`ContentControl`, `Control`, `Button`, `ToggleButton`, `Window`, etc.) and optionally implement WPF UI interfaces: `IAppearanceControl` (accent/appearance support), `IIconControl` (icon support), `IThemeControl` (theme awareness), `IDpiAwareControl` (DPI awareness). + +Complex controls are split into partial classes by concern. For example, `NavigationView` spans six partial files: + +| File | Responsibility | +|------|---------------| +| `NavigationView.Base.cs` | Core logic, constructor, static constructor | +| `NavigationView.Properties.cs` | DependencyProperty registrations | +| `NavigationView.Events.cs` | RoutedEvent registrations | +| `NavigationView.Navigation.cs` | Page navigation logic | +| `NavigationView.TemplateParts.cs` | Template part bindings (OnApplyTemplate) | +| `NavigationView.AttachedProperties.cs` | Attached property definitions | + +Key controls: `FluentWindow`, `NavigationView`, `TitleBar`, `ContentDialog`, `NumberBox`, `AutoSuggestBox`, `ToggleSwitch`, `Snackbar`, `BreadcrumbBar`, `InfoBar`, `RatingControl`, `ProgressRing`. + +#### Layer 2 -- Appearance/Theming + +| Class | Pattern | Responsibility | +|-------|---------|---------------| +| `ApplicationThemeManager` | Static | Runtime theme switching via ResourceDictionary swap. Fires `ThemeChangedEvent`. | +| `ApplicationAccentColorManager` | Static | Updates 20+ dynamic color resources (SystemAccentColor, AccentFillColorDefault, etc.) from WinRT UISettings or registry fallback. | +| `SystemThemeWatcher` | Static | Hooks WndProc for `WM_THEMECHANGED`, `WM_DWMCOLORIZATIONCOLORCHANGED`, `WM_SYSCOLORCHANGE`. Auto-syncs app theme with OS. | +| `WindowBackgroundManager` | Static | Applies DWM backdrop effects (Mica, Acrylic, Tabbed) via `DwmSetWindowAttribute`. | +| `ResourceDictionaryManager` | Internal | URI-based dictionary search and swap within `Application.Resources.MergedDictionaries`. | + +Six theme files: `Light.xaml`, `Dark.xaml`, `HC1.xaml`, `HC2.xaml`, `HCBlack.xaml`, `HCWhite.xaml`. + +#### Layer 3 -- Services + +Service implementations are defined at the `src/Wpf.Ui/` root level alongside their interface files. + +| Service | Interface | Wraps | +|---------|-----------|-------| +| `NavigationService` | `INavigationService` | `INavigationView` control | +| `ContentDialogService` | `IContentDialogService` | `ContentDialog` control | +| `SnackbarService` | `ISnackbarService` | `Snackbar` control | +| `ThemeService` | `IThemeService` | `ApplicationThemeManager` static class | +| `TaskBarService` | `ITaskBarService` | COM `ITaskbarList4` via Win32 interop | + +#### Layer 4 -- Infrastructure + +| Component | Contents | +|-----------|----------| +| **Converters/** | 18 `IValueConverter` implementations: `BoolToVisibilityConverter`, `BrushToColorConverter`, `EnumToBoolConverter`, `IconSourceElementConverter`, `CornerRadiusSplitConverter`, `ProgressThicknessConverter`, etc. | +| **Extensions/** | 14 extension method classes: `ColorExtensions`, `FrameExtensions`, `NavigationServiceExtensions`, `SnackbarServiceExtensions`, `SymbolExtensions`, `UiElementExtensions`, etc. | +| **Markup/** | XAML markup extensions: `ControlsDictionary`, `ThemesDictionary`, `SymbolIconExtension`, `FontIconExtension`, `ImageIconExtension`, `ThemeResourceExtension`, `Design`. | +| **Animations/** | `TransitionAnimationProvider` (applies `FadeIn`, `SlideBottom`, `SlideRight`, etc.), `Transition` enum, `AnimationProperties`. Checks `HardwareAcceleration.RenderingTier` before animating. | +| **Input/** | `IRelayCommand`, `IRelayCommand`, `RelayCommand` -- lightweight command implementations. | +| **Hardware/** | `DpiHelper`, `DisplayDpi`, `HardwareAcceleration`, `RenderingTier` -- DPI detection and rendering tier evaluation. | +| **AutomationPeers/** | `CardControlAutomationPeer`, `ContentDialogAutomationPeer`. Controls with automation peers override `OnCreateAutomationPeer()`. | +| **Taskbar/** | `TaskbarProgress`, `TaskbarProgressState` -- Windows taskbar progress bar manipulation. | + +#### Layer 5 -- Win32 Interop + +A three-layer architecture for native Windows API access: + +1. **CsWin32 auto-generation** (`NativeMethods.txt` lists 35 Win32 functions/types). The `Microsoft.Windows.CsWin32` source generator produces P/Invoke signatures in the `Windows.Win32` namespace. Covers DWM (DwmSetWindowAttribute, DwmIsCompositionEnabled), User32 (SetWindowLong, GetDpiForWindow, GetForegroundWindow), Shell32 (ITaskbarList4), and associated structs/enums. + +2. **Managed wrappers** (`Interop/`): + - `UnsafeNativeMethods.cs` -- safe wrappers with handle validation (`IntPtr.Zero` check + `PInvoke.IsWindow`) before calling CsWin32-generated methods. Methods like `ApplyWindowCornerPreference`, `ApplyBorderColor`, `RemoveWindowTitlebarContents`. + - `PInvoke.cs` -- manual `[DllImport]` for `SetWindowLongPtr` (not generated by CsWin32 for all overloads). + - `UnsafeReflection.cs` -- type casting helpers for enum-to-Win32-struct conversion. + +3. **OS utilities** (`Win32/`): + - `Utilities.cs` -- OS version detection (Vista, Windows 7, 8, 10, 11 build checks), DWM composition availability, system theme detection via `IUISettings3` COM interface. + +#### Layer 6 -- Resources + +| Path | Contents | +|------|----------| +| `Resources/Theme/` | 6 XAML theme dictionaries (Light, Dark, HC1, HC2, HCBlack, HCWhite) | +| `Resources/Fonts/` | `FluentSystemIcons-Filled.ttf`, `FluentSystemIcons-Regular.ttf` (embedded resources) | +| `Resources/Accent.xaml` | Dynamic accent color resources | +| `Resources/Palette.xaml` | Fluent Design color palette | +| `Resources/StaticColors.xaml` | Non-theme-dependent color constants | +| `Resources/Typography.xaml` | Font family, size, and weight resources | +| `Resources/Variables.xaml` | Corner radius, spacing, sizing tokens | +| `Resources/Wpf.Ui.xaml` | Master ResourceDictionary that merges all 77+ control style dictionaries | +| `Resources/DefaultContextMenu.xaml` | Styled context menu for TextBox-like controls | +| `Resources/DefaultFocusVisualStyle.xaml` | Fluent focus visual style | + +--- + +## 3. Control Architecture Pattern + +The following class diagram shows how WPF UI controls extend WPF base classes, implement marker interfaces, and register DependencyProperties. + +```mermaid +classDiagram + direction TB + + namespace System.Windows.Controls { + class WpfControl["Control"] + class WpfContentControl["ContentControl"] + class WpfButton["Button"] + class WpfToggleButton["ToggleButton"] + class WpfWindow["Window"] + } + + namespace Wpf.Ui.Controls { + class IAppearanceControl { + <> + +ControlAppearance Appearance + } + class IIconControl { + <> + +IconElement? Icon + } + class IThemeControl { + <> + +ApplicationTheme ApplicationTheme + } + class IDpiAwareControl { + <> + +DisplayDpi CurrentWindowDisplayDpi + } + + class Button { + +DependencyProperty IconProperty$ + +DependencyProperty AppearanceProperty$ + +DependencyProperty MouseOverBackgroundProperty$ + +DependencyProperty PressedBackgroundProperty$ + +DependencyProperty CornerRadiusProperty$ + +IconElement? Icon + +ControlAppearance Appearance + +CornerRadius CornerRadius + } + + class FluentWindow { + +DependencyProperty WindowCornerPreferenceProperty$ + +DependencyProperty WindowBackdropTypeProperty$ + +DependencyProperty ExtendsContentIntoTitleBarProperty$ + +WindowCornerPreference WindowCornerPreference + +WindowBackdropType WindowBackdropType + +bool ExtendsContentIntoTitleBar + -static FluentWindow() DefaultStyleKeyProperty.OverrideMetadata() + } + + class ToggleSwitch { + +DependencyProperty OffContentProperty$ + +DependencyProperty OnContentProperty$ + +object? OffContent + +object? OnContent + -static ToggleSwitch() DefaultStyleKeyProperty.OverrideMetadata() + } + + class NavigationView { + <> + .Base.cs: Core logic + .Properties.cs: DependencyProperty defs + .Events.cs: RoutedEvent defs + .Navigation.cs: Page navigation + .TemplateParts.cs: Template bindings + .AttachedProperties.cs: Attached props + } + + class ContentDialog { + +ShowAsync() Task~ContentDialogResult~ + +Hide() void + } + } + + WpfButton <|-- Button + WpfWindow <|-- FluentWindow + WpfToggleButton <|-- ToggleSwitch + WpfControl <|-- NavigationView + WpfContentControl <|-- ContentDialog + + Button ..|> IAppearanceControl + Button ..|> IIconControl +``` + +### Control Authoring Recipe + +Every WPF UI control follows a consistent authoring pattern: + +**1. C# class file** (`Controls/{Name}/{Name}.cs`): + +```csharp +// All controls use the flat namespace Wpf.Ui.Controls +// regardless of their folder location (ReSharper CheckNamespace suppressed) +namespace Wpf.Ui.Controls; + +public class MyControl : System.Windows.Controls.ContentControl, IAppearanceControl, IIconControl +{ + // DependencyProperty registration via static readonly fields + public static readonly DependencyProperty IconProperty = DependencyProperty.Register( + nameof(Icon), typeof(IconElement), typeof(MyControl), + new PropertyMetadata(null, null, IconElement.Coerce)); + + // CLR property wrapper with [Bindable] and [Category] attributes + [Bindable(true)] + [Category("Appearance")] + public IconElement? Icon + { + get => (IconElement?)GetValue(IconProperty); + set => SetValue(IconProperty, value); + } + + // Static constructor: override DefaultStyleKey for implicit style resolution + static MyControl() + { + DefaultStyleKeyProperty.OverrideMetadata( + typeof(MyControl), + new FrameworkPropertyMetadata(typeof(MyControl))); + } +} +``` + +**2. XAML style file** (`Controls/{Name}/{Name}.xaml`): + +```xml + + + +``` + +**3. Registration in master dictionary** (`Resources/Wpf.Ui.xaml`): + +```xml + + + + +``` + +### Key invariants: + +- `OverridesDefaultStyle="True"` and `SnapsToDevicePixels="True"` are set on every control style. +- Theme-dependent brushes always use `DynamicResource` (not `StaticResource`) so they update when themes switch at runtime. +- The static constructor calling `DefaultStyleKeyProperty.OverrideMetadata` ensures WPF resolves the implicit style from the control's assembly rather than the application. +- Complex controls split into partial classes by concern (properties, events, navigation logic, template parts). + +--- + +## 4. Key Cross-Cutting Patterns + +### Flat Namespace + +All controls reside in the single namespace `Wpf.Ui.Controls` despite being organized into individual subfolders under `Controls/`. This is enforced via `// ReSharper disable once CheckNamespace` pragmas in each file. The project does not suppress IDE0130 (namespace does not match folder structure) at the project level; instead, the ReSharper-specific pragma handles it. + +**Rationale**: Consumers use a single `xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"` namespace in XAML. A flat C# namespace mirrors the flat XAML namespace. + +### Static Singleton Managers for Theming + +The four core theming classes (`ApplicationThemeManager`, `ApplicationAccentColorManager`, `SystemThemeWatcher`, `WindowBackgroundManager`) are all `static` classes. They operate on `Application.Current.Resources` directly. + +**Trade-off**: This sacrifices testability and multi-window isolation in favor of a simple, discoverable API. Consumers call `ApplicationThemeManager.Apply(ApplicationTheme.Dark)` without needing DI or service resolution. The `ThemeService` wraps these statics behind `IThemeService` for consumers who prefer DI. + +### CommunityToolkit.Mvvm in Gallery and Samples + +The Gallery demo app and sample applications use CommunityToolkit.Mvvm source generators: + +- `[ObservableProperty]` for bindable properties +- `[RelayCommand]` for ICommand implementations +- ViewModels extend `ObservableObject` and implement `INavigationAware` +- Pages implement `INavigableView` for view-model association + +This is a consumption pattern only; the core `Wpf.Ui` library has no dependency on CommunityToolkit.Mvvm. + +### Central Package Management + +All NuGet package versions are declared in `Directory.Packages.props` at the repository root. Individual `.csproj` files reference packages without version numbers. This ensures consistent versions across all projects in the solution. + +### Service Interface Inversion + +Service interfaces (`INavigationService`, `IContentDialogService`, etc.) are defined at the `Wpf.Ui` assembly root level, alongside their implementations. This allows: + +1. Direct instantiation for simple apps: `var service = new NavigationService();` +2. DI registration for hosted apps: `services.AddSingleton();` +3. The `ControlsServices.Initialize(IServiceProvider)` static method enables control-level service resolution. + +### Async Dialog Pattern + +`ContentDialog.ShowAsync()` returns `Task` using `TaskCompletionSource` to bridge the WPF event model to async/await. Supports `CancellationToken` and closing cancellation via `ContentDialogClosingEventArgs.Cancel = true`. + +### Win32 Interop Layering + +Native Windows API calls follow a strict three-layer pipeline: + +``` +NativeMethods.txt --> CsWin32 source generator --> Windows.Win32 namespace (auto-generated) + | + Interop/UnsafeNativeMethods.cs (handle validation) + | + Win32/Utilities.cs (OS version guards) +``` + +Manual `[DllImport]` in `Interop/PInvoke.cs` supplements CsWin32 for signatures it cannot generate (e.g., `SetWindowLongPtr` with `nint` parameters). + +--- + +## 5. Target Framework Matrix + +```mermaid +gantt + title Target Framework Coverage by Module + dateFormat X + axisFormat %s + + section Wpf.Ui.Abstractions + netstandard2.0 :a1, 0, 1 + netstandard2.1 :a2, 1, 2 + net462 :a3, 2, 3 + net8.0 :a4, 3, 4 + net9.0 :a5, 4, 5 + net10.0 :a6, 5, 6 + + section Wpf.Ui + net462 :b1, 0, 1 + net472 :b2, 1, 2 + net481 :b3, 2, 3 + net8.0-windows :b4, 3, 4 + net9.0-windows :b5, 4, 5 + net10.0-windows :b6, 5, 6 + + section Wpf.Ui.DependencyInjection + netstandard2.0 :c1, 0, 1 + netstandard2.1 :c2, 1, 2 + net462 :c3, 2, 3 + net8.0 :c4, 3, 4 + net9.0 :c5, 4, 5 + net10.0 :c6, 5, 6 + + section Wpf.Ui.Tray + net462 :d1, 0, 1 + net472 :d2, 1, 2 + net481 :d3, 2, 3 + net8.0-windows :d4, 3, 4 + net9.0-windows :d5, 4, 5 + net10.0-windows :d6, 5, 6 + + section Wpf.Ui.Gallery + net10.0-windows10.0.26100.0 :e1, 5, 6 +``` + +| Module | Target Frameworks | Windows TFM | Rationale | +|--------|------------------|-------------|-----------| +| **Wpf.Ui.Abstractions** | `netstandard2.0`, `netstandard2.1`, `net462`, `net8.0`, `net9.0`, `net10.0` | No | Maximum compatibility; no WPF or Windows dependency. Consumable by any .NET project. | +| **Wpf.Ui** | `net10.0-windows`, `net9.0-windows`, `net8.0-windows`, `net481`, `net472`, `net462` | Yes | Requires WPF (`true`) and Win32 P/Invoke. Supports .NET Framework 4.6.2+ for legacy app modernization. | +| **Wpf.Ui.DependencyInjection** | `netstandard2.0`, `netstandard2.1`, `net462`, `net8.0`, `net9.0`, `net10.0` | No | Only depends on abstractions and MS DI interfaces. No WPF dependency. | +| **Wpf.Ui.Tray** | `net10.0-windows`, `net9.0-windows`, `net8.0-windows`, `net481`, `net472`, `net462` | Yes | Requires WPF + Win32 Shell32 interop for tray icons. | +| **Wpf.Ui.SyntaxHighlight** | `net10.0-windows`, `net9.0-windows`, `net8.0-windows`, `net481`, `net472`, `net462` | Yes | Requires WPF for custom control rendering. | +| **Wpf.Ui.ToastNotifications** | `net10.0-windows`, `net9.0-windows`, `net8.0-windows`, `net481`, `net472`, `net462` | Yes | Same targeting as core library (currently stub). | +| **Wpf.Ui.FlaUI** | `net10.0-windows`, `net9.0-windows`, `net8.0-windows`, `net481` | Yes | Test automation; constrained by FlaUI.Core compatibility. | +| **Wpf.Ui.FontMapper** | `net10.0` | No | Console build tool; targets latest runtime only. | +| **Wpf.Ui.Gallery** | `net10.0-windows10.0.26100.0` | Yes | Demo app targets latest .NET + latest Windows SDK for full feature demonstration. | +| **Wpf.Ui.Extension** | `net481` (via VSIX SDK) | N/A | VS 2022 VSIX extension; targets .NET Framework 4.8.1 per VSIX SDK requirements. | + +--- + +## 6. Service Registration & DI Integration + +The following sequence diagram shows how WPF UI integrates with `Microsoft.Extensions.DependencyInjection` via the Generic Host pattern used in the Gallery and sample applications. + +```mermaid +sequenceDiagram + participant App as Application Startup + participant Host as IHostBuilder + participant SC as IServiceCollection + participant Ext as ServiceCollectionExtensions + participant SP as IServiceProvider + participant CS as ControlsServices + participant NavView as NavigationView + participant Provider as INavigationViewPageProvider + + App->>Host: Host.CreateDefaultBuilder() + App->>Host: ConfigureServices(services => ...) + + Host->>SC: services.AddSingleton() + Host->>SC: services.AddSingleton() + Host->>SC: services.AddSingleton() + Host->>Ext: services.AddNavigationViewPageProvider() + Ext->>SC: Register INavigationViewPageProvider + + Host->>SC: services.AddTransient() + Host->>SC: services.AddTransient() + + App->>Host: Build() + Host->>SP: Create IServiceProvider + + App->>CS: ControlsServices.Initialize(serviceProvider) + CS->>CS: Store IServiceProvider for control-level resolution + + Note over NavView,Provider: At runtime, when NavigationView needs a page: + NavView->>Provider: GetPage(typeof(DashboardPage)) + Provider->>SP: GetRequiredService(typeof(DashboardPage)) + SP-->>Provider: DashboardPage instance + Provider-->>NavView: Resolved page +``` + +--- + +## 7. Control Lifecycle + +WPF UI controls follow the standard WPF element lifecycle with additional steps for Fluent Design theming and Win32 interop integration. + +```mermaid +stateDiagram-v2 + [*] --> Constructed: new MyControl() + Constructed --> Constructed: Static ctor: DefaultStyleKeyProperty.OverrideMetadata() + + Constructed --> Loaded: Added to visual tree + Loaded --> TemplateApplied: OnApplyTemplate() + TemplateApplied --> TemplateApplied: GetTemplateChild() binds named parts + + TemplateApplied --> Themed: DynamicResource brushes resolved + Themed --> Themed: ApplicationThemeManager.Changed → re-resolve brushes + + Themed --> Unloaded: Removed from visual tree + Unloaded --> Loaded: Re-added to visual tree + + Unloaded --> GarbageCollected: No references remain + GarbageCollected --> [*] + + note right of Constructed + DependencyProperties registered + via static readonly fields + end note + + note right of TemplateApplied + Controls bind template parts + (e.g., PART_CloseButton) + and hook event handlers + end note + + note right of Themed + Theme changes trigger + DynamicResource updates + without re-creating controls + end note +``` + +### Lifecycle Details + +| Phase | Trigger | WPF UI Actions | +|-------|---------|----------------| +| **Constructed** | `new` / XAML parser | Static constructor registers `DefaultStyleKey`; DependencyProperties are static | +| **Loaded** | Added to visual tree | Control subscribes to theme events if needed | +| **Template Applied** | `OnApplyTemplate()` | Named template parts (`PART_*`) resolved via `GetTemplateChild()` | +| **Themed** | Resource resolution | `DynamicResource` brushes resolve from current theme dictionary | +| **Unloaded** | Removed from tree | Event handlers and hooks should be cleaned up | +| **GC** | No remaining references | Standard .NET garbage collection | + +--- + +### Conditional Compilation + +The codebase uses `#if` directives to handle API differences across target frameworks: + +| Directive | Usage | +|-----------|-------| +| `NET5_0_OR_GREATER` | `Environment.OSVersion.Version` vs. registry-based fallback for OS detection | +| `NET6_0_OR_GREATER` | `DisposeAsync` for `CancellationTokenRegistration` | +| `NET48_OR_GREATER` or `NETCOREAPP3_0_OR_GREATER` | `IServiceProvider` support in controls | +| `NET8_0_OR_GREATER` | Newer framework API usage | + +`PolySharp` provides polyfills (e.g., `IsExternalInit`, `CallerArgumentExpression`, nullable attributes) so that C# 14 language features can be used across all target frameworks. diff --git a/docs/docfx.json b/docs/docfx.json index 462d9f49d..1ca987d0b 100644 --- a/docs/docfx.json +++ b/docs/docfx.json @@ -4,20 +4,22 @@ "src": [ { "files": [ - "src/Wpf.Ui/*.csproj", - "src/Wpf.Ui.Tray/*.csproj", - "src/Wpf.Ui.Abstractions/*.csproj", - "src/Wpf.Ui.DependencyInjection/*.csproj" + "Wpf.Ui/*.csproj", + "Wpf.Ui.Tray/*.csproj", + "Wpf.Ui.Abstractions/*.csproj", + "Wpf.Ui.DependencyInjection/*.csproj" ], - "src": "../" + "src": "../src" } ], "dest": "api", - "properties": { - "TargetFramework": "net472" - }, + "includePrivateMembers": false, "disableGitFeatures": false, - "disableDefaultFilter": false + "disableDefaultFilter": false, + "noRestore": false, + "namespaceLayout": "flattened", + "memberLayout": "samePage", + "allowCompilationErrors": false } ], "build": { @@ -28,7 +30,8 @@ ], "exclude": [ "_site/**", - "obj/**" + "obj/**", + "architecture/**" ] } ], @@ -46,6 +49,7 @@ ] } ], + "xref": [], "xrefService": [ "https://xref.docs.microsoft.com/query?uid={uid}" ], @@ -57,7 +61,7 @@ "_appName": "WPF UI", "_appFaviconPath": "images/favicon.ico", "_appLogoPath": "images/wpfui.png", - "_appFooter": "Made with docfx, ChatGPT and DeepL | Copyright © 2025 lepo.co" + "_appFooter": "Made with docfx, ChatGPT | Copyright © 2026 lepo.co" }, "dest": "_site", "template": [ diff --git a/docs/index.md b/docs/index.md index 42ccf40e9..ce4808e75 100644 --- a/docs/index.md +++ b/docs/index.md @@ -86,28 +86,36 @@
-
+
- 650KDownloads + 850KDownloads
-
+
9KGitHub Stars
-
+
900Forks
-
-
- 2Sponsors -
-
+## Support + +To ensure you receive the expert guidance you need, we offer a variety of support plans designed to meet the diverse needs of our community. Whether you are looking to modernize your WPF applications or need assistance with our other libraries, our tailored support solutions are here to help. From priority email support to 24/7 dedicated assistance, we provide flexible plans to suit your project requirements. + + + ![Demo App Sample](https://user-images.githubusercontent.com/13592821/166259110-0fb98120-fe34-4e6d-ab92-9f72ad7113c3.png) ![Monaco Editor](https://user-images.githubusercontent.com/13592821/258610583-7d71f69d-45b3-4be6-bcb8-8cf6cd60a2ff.png) @@ -117,6 +125,6 @@ [Created with ❤ in Poland by lepo.co](https://lepo.co/) A simple way to make your application written in WPF keep up with modern design trends. Library changes the base elements like `Page`, `ToggleButton` or `List`, and also includes additional controls like `Navigation`, `NumberBox`, `Dialog` or `Snackbar`. -[![Discord](https://img.shields.io/discord/1071051348348514375?label=discord)](https://discord.gg/AR9ywDUwGq) [![GitHub license](https://img.shields.io/github/license/lepoco/wpfui)](https://github.com/lepoco/wpfui/blob/master/LICENSE) [![Nuget](https://img.shields.io/nuget/v/WPF-UI)](https://www.nuget.org/packages/WPF-UI/) [![Nuget](https://img.shields.io/nuget/dt/WPF-UI?label=nuget)](https://www.nuget.org/packages/WPF-UI/) [![VS 2022 Downloads](https://img.shields.io/visual-studio-marketplace/i/lepo.WPF-UI?label=vs-2022)](https://marketplace.visualstudio.com/items?itemName=lepo.WPF-UI) [![Sponsors](https://img.shields.io/github/sponsors/lepoco)](https://github.com/sponsors/lepoco) +[![Discord](https://img.shields.io/discord/1071051348348514375?label=discord)](https://discord.gg/AR9ywDUwGq) [![GitHub license](https://img.shields.io/github/license/lepoco/wpfui)](https://github.com/lepoco/wpfui/blob/master/LICENSE) [![Nuget](https://img.shields.io/nuget/v/WPF-UI)](https://www.nuget.org/packages/WPF-UI/) [![Nuget](https://img.shields.io/nuget/dt/WPF-UI?label=nuget)](https://www.nuget.org/packages/WPF-UI/) [Getting Started](/documentation/getting-started.html) diff --git a/docs/migration/index.md b/docs/migration/index.md new file mode 100644 index 000000000..08651f5bb --- /dev/null +++ b/docs/migration/index.md @@ -0,0 +1,8 @@ + +# Migration Guides + +Guides for migrating between major versions of Wpf.Ui. + +- [v2 Migration](v2-migration.md) +- [v3 Migration](v3-migration.md) +- [v4 Migration](v4-migration.md) diff --git a/docs/templates/wpfui/layout/_master.tmpl b/docs/templates/wpfui/layout/_master.tmpl index 14ed8c5a8..fa18d5f75 100644 --- a/docs/templates/wpfui/layout/_master.tmpl +++ b/docs/templates/wpfui/layout/_master.tmpl @@ -134,7 +134,7 @@ diff --git a/docs/templates/wpfui/src/nav.ts b/docs/templates/wpfui/src/nav.ts index 40e55068f..20243cb73 100644 --- a/docs/templates/wpfui/src/nav.ts +++ b/docs/templates/wpfui/src/nav.ts @@ -1,8 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { render, html, TemplateResult } from 'lit-html' -import { breakWordLit, meta, isExternalHref } from './helper' +import { html, render, TemplateResult } from 'lit-html' +import { breakWordLit, isExternalHref, meta } from './helper' import { themePicker } from './theme' import { TocNode } from './toc' @@ -55,8 +55,9 @@ export async function renderNavbar(): Promise { const icons = html`
${window.docfx.iconLinks?.map(i => html``)} + ${themePicker(renderCore)} - Sponsor + Sponsor
` render(html`${menu} ${icons}`, navbar) diff --git a/docs/toc.yml b/docs/toc.yml index 3cc694620..22f24c3e0 100644 --- a/docs/toc.yml +++ b/docs/toc.yml @@ -2,40 +2,40 @@ items: - name: Home href: / - name: Documentation - href: /documentation + href: /documentation/ items: - - name: Getting started - href: documentation/getting-started.md - - name: NuGet Package - href: documentation/nuget.md - - name: Visual Studio Extension - href: documentation/extension.md - - name: Themes - href: documentation/themes.md - - name: Icons - href: documentation/icons.md - - name: Theme Watcher - href: documentation/system-theme-watcher.md - - name: Theme Accent - href: documentation/accent.md - - name: Navigation - href: documentation/navigation-view.md - - name: Menu - href: documentation/menu.md - - name: Gallery - href: documentation/gallery.md - - name: Releases - href: documentation/releases.md + - name: Getting started + href: documentation/getting-started.md + - name: NuGet Package + href: documentation/nuget.md + - name: Visual Studio Extension + href: documentation/extension.md + - name: Themes + href: documentation/themes.md + - name: Icons + href: documentation/icons.md + - name: Theme Watcher + href: documentation/system-theme-watcher.md + - name: Theme Accent + href: documentation/accent.md + - name: Navigation + href: documentation/navigation-view.md + - name: Menu + href: documentation/menu.md + - name: Gallery + href: documentation/gallery.md + - name: Releases + href: documentation/releases.md - name: API v4.0 href: api/ - name: Migration - href: /migration + href: /migration/ items: - - name: Key changes in v4 - href: migration/v4-migration.md - - name: Key changes in v3 - href: migration/v3-migration.md - - name: Key changes in v2 - href: migration/v2-migration.md + - name: Key changes in v4 + href: migration/v4-migration.md + - name: Key changes in v3 + href: migration/v3-migration.md + - name: Key changes in v2 + href: migration/v2-migration.md - name: Support plans href: https://lepo.co/support diff --git a/src/Wpf.Ui.Abstractions/README.md b/src/Wpf.Ui.Abstractions/README.md new file mode 100644 index 000000000..78fb4eb88 --- /dev/null +++ b/src/Wpf.Ui.Abstractions/README.md @@ -0,0 +1,15 @@ +# Wpf.Ui.Abstractions + +Common interfaces and contracts for Wpf.Ui. Used to decouple core library features and enable extensibility. + +## Included types + +```csharp +Wpf.Ui.Abstractions.INavigationViewPageProvider +Wpf.Ui.Abstractions.NavigationViewPageProviderExtensions +Wpf.Ui.Abstractions.Controls.INavigableView +Wpf.Ui.Abstractions.Controls.INavigationAware +Wpf.Ui.Abstractions.Controls.NavigationAware +``` + +Documentation can be found at . We also have a [tutorial](https://wpfui.lepo.co/documentation/getting-started) over there for newcomers. diff --git a/src/Wpf.Ui.Abstractions/Wpf.Ui.Abstractions.csproj b/src/Wpf.Ui.Abstractions/Wpf.Ui.Abstractions.csproj index 8b578d989..213ce3c9d 100644 --- a/src/Wpf.Ui.Abstractions/Wpf.Ui.Abstractions.csproj +++ b/src/Wpf.Ui.Abstractions/Wpf.Ui.Abstractions.csproj @@ -2,11 +2,15 @@ WPF-UI.Abstractions - net10.0;net9.0;net8.0;net462;netstandard2.1;netstandard2.0 + net10.0;net9.0;net8.0;net481;net472;net462;netstandard2.1;netstandard2.0 true Abstractions for the WPF UI. $(CommonTags);abstractions;standard <_SilenceIsAotCompatibleUnsupportedWarning>true + + + + diff --git a/src/Wpf.Ui.DependencyInjection/README.md b/src/Wpf.Ui.DependencyInjection/README.md new file mode 100644 index 000000000..4d98149e3 --- /dev/null +++ b/src/Wpf.Ui.DependencyInjection/README.md @@ -0,0 +1,11 @@ +# Wpf.Ui.DependencyInjection + +DI implementation for Wpf.Ui. Used to decouple core library features and enable extensibility. + +## Included types + +```csharp +Wpf.Ui.DependencyInjection.DependencyInjectionNavigationViewPageProvider +``` + +Documentation can be found at . We also have a [tutorial](https://wpfui.lepo.co/documentation/getting-started) over there for newcomers. diff --git a/src/Wpf.Ui.DependencyInjection/Wpf.Ui.DependencyInjection.csproj b/src/Wpf.Ui.DependencyInjection/Wpf.Ui.DependencyInjection.csproj index 1629d6223..77a30bf40 100644 --- a/src/Wpf.Ui.DependencyInjection/Wpf.Ui.DependencyInjection.csproj +++ b/src/Wpf.Ui.DependencyInjection/Wpf.Ui.DependencyInjection.csproj @@ -2,7 +2,7 @@ WPF-UI.DependencyInjection - net10.0;net9.0;net8.0;net462;netstandard2.1;netstandard2.0 + net10.0;net9.0;net8.0;net481;net472;net462;netstandard2.1;netstandard2.0 true Dependency injection for the WPF UI. $(CommonTags);dependency;injection;abstractions;standard @@ -17,4 +17,8 @@ + + + + diff --git a/src/Wpf.Ui.FlaUI/README.md b/src/Wpf.Ui.FlaUI/README.md new file mode 100644 index 000000000..09e9ce5d2 --- /dev/null +++ b/src/Wpf.Ui.FlaUI/README.md @@ -0,0 +1,11 @@ +# Wpf.Ui.FlaUI + +FlaUI automation helpers for Wpf.Ui. Provides test utilities for interacting with WPF controls in automated UI tests. + +## Included types + +```csharp +Wpf.Ui.FlaUI.AutoSuggestBox +``` + +Docs: diff --git a/src/Wpf.Ui.FlaUI/Wpf.Ui.FlaUI.csproj b/src/Wpf.Ui.FlaUI/Wpf.Ui.FlaUI.csproj index ccb9cc53a..c1bcc8c1d 100644 --- a/src/Wpf.Ui.FlaUI/Wpf.Ui.FlaUI.csproj +++ b/src/Wpf.Ui.FlaUI/Wpf.Ui.FlaUI.csproj @@ -19,4 +19,8 @@ + + + + diff --git a/src/Wpf.Ui.Gallery/Views/Pages/DashboardPage.xaml b/src/Wpf.Ui.Gallery/Views/Pages/DashboardPage.xaml index fe3db4866..b2a5a9d6d 100644 --- a/src/Wpf.Ui.Gallery/Views/Pages/DashboardPage.xaml +++ b/src/Wpf.Ui.Gallery/Views/Pages/DashboardPage.xaml @@ -26,7 +26,7 @@ - - - - - - - - - - - - + Text="© 2026 lepo.co | Leszek Pomianowski & WPF UI Contributors" /> + + + + diff --git a/src/Wpf.Ui.ToastNotifications/README.md b/src/Wpf.Ui.ToastNotifications/README.md new file mode 100644 index 000000000..f80ca7a8d --- /dev/null +++ b/src/Wpf.Ui.ToastNotifications/README.md @@ -0,0 +1,11 @@ +# Wpf.Ui.ToastNotifications + +Toast notification support for Wpf.Ui. Provides a simple API for displaying toast messages in WPF applications. + +## Included types + +```csharp +Wpf.Ui.ToastNotifications.Toast +``` + +Docs: diff --git a/src/Wpf.Ui.ToastNotifications/Wpf.Ui.ToastNotifications.csproj b/src/Wpf.Ui.ToastNotifications/Wpf.Ui.ToastNotifications.csproj index 3e7975bd0..904ff1b4b 100644 --- a/src/Wpf.Ui.ToastNotifications/Wpf.Ui.ToastNotifications.csproj +++ b/src/Wpf.Ui.ToastNotifications/Wpf.Ui.ToastNotifications.csproj @@ -25,4 +25,8 @@ + + + + diff --git a/src/Wpf.Ui.Tray/README.md b/src/Wpf.Ui.Tray/README.md new file mode 100644 index 000000000..42d79e62b --- /dev/null +++ b/src/Wpf.Ui.Tray/README.md @@ -0,0 +1,20 @@ +# Wpf.Ui.Tray + +System tray integration for Wpf.Ui. Provides services and controls for tray icons in WPF applications. + +## Included types + +```csharp +Wpf.Ui.Tray.NotifyIconService +Wpf.Ui.Tray.INotifyIconService +Wpf.Ui.Tray.Controls.NotifyIcon +Wpf.Ui.Tray.TrayManager +Wpf.Ui.Tray.TrayHandler +Wpf.Ui.Tray.TrayData +Wpf.Ui.Tray.RoutedNotifyIconEvent +Wpf.Ui.Tray.INotifyIcon +Wpf.Ui.Tray.Hicon +Wpf.Ui.Tray.NotifyIconEventHandler +``` + +Docs: diff --git a/src/Wpf.Ui.Tray/Wpf.Ui.Tray.csproj b/src/Wpf.Ui.Tray/Wpf.Ui.Tray.csproj index a33fde3a9..d18c646ed 100644 --- a/src/Wpf.Ui.Tray/Wpf.Ui.Tray.csproj +++ b/src/Wpf.Ui.Tray/Wpf.Ui.Tray.csproj @@ -41,4 +41,8 @@ + + + + diff --git a/src/Wpf.Ui/Appearance/SystemThemeWatcher.cs b/src/Wpf.Ui/Appearance/SystemThemeWatcher.cs index 5faa164b4..81fc79afb 100644 --- a/src/Wpf.Ui/Appearance/SystemThemeWatcher.cs +++ b/src/Wpf.Ui/Appearance/SystemThemeWatcher.cs @@ -156,9 +156,15 @@ private static IntPtr WndProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam msg == (int)PInvoke.WM_DWMCOLORIZATIONCOLORCHANGED || msg == (int)PInvoke.WM_THEMECHANGED || msg == (int)PInvoke.WM_SYSCOLORCHANGE - || (msg == (int)PInvoke.WM_SETTINGCHANGE && - lParam != IntPtr.Zero && - string.Equals(Marshal.PtrToStringUni(lParam), "ImmersiveColorSet", StringComparison.Ordinal)) + || ( + msg == (int)PInvoke.WM_SETTINGCHANGE + && lParam != IntPtr.Zero + && string.Equals( + Marshal.PtrToStringUni(lParam), + "ImmersiveColorSet", + StringComparison.Ordinal + ) + ) ) { UpdateObservedWindow(hWnd); diff --git a/src/Wpf.Ui/AutomationPeers/CardControlAutomationPeer.cs b/src/Wpf.Ui/AutomationPeers/CardControlAutomationPeer.cs index 54c97a87c..7158eea43 100644 --- a/src/Wpf.Ui/AutomationPeers/CardControlAutomationPeer.cs +++ b/src/Wpf.Ui/AutomationPeers/CardControlAutomationPeer.cs @@ -14,8 +14,7 @@ namespace Wpf.Ui.AutomationPeers; internal class CardControlAutomationPeer : FrameworkElementAutomationPeer { public CardControlAutomationPeer(CardControl owner) - : base(owner) - { } + : base(owner) { } protected override string GetClassNameCore() { diff --git a/src/Wpf.Ui/Controls/CardAction/CardActionAutomationPeer.cs b/src/Wpf.Ui/Controls/CardAction/CardActionAutomationPeer.cs index b774a936a..aa5ddab58 100644 --- a/src/Wpf.Ui/Controls/CardAction/CardActionAutomationPeer.cs +++ b/src/Wpf.Ui/Controls/CardAction/CardActionAutomationPeer.cs @@ -13,8 +13,7 @@ namespace Wpf.Ui.Controls; internal class CardActionAutomationPeer : FrameworkElementAutomationPeer, IInvokeProvider { public CardActionAutomationPeer(CardAction owner) - : base(owner) - { } + : base(owner) { } protected override string GetClassNameCore() { @@ -45,10 +44,14 @@ void IInvokeProvider.Invoke() // Async call of click event // In ClickHandler opens a dialog and suspend the execution we don't want to block this thread - Dispatcher.BeginInvoke(DispatcherPriority.Input, new DispatcherOperationCallback(_ => - { - ((CardAction)Owner).AutomationClick(); - return null; - }), null); + Dispatcher.BeginInvoke( + DispatcherPriority.Input, + new DispatcherOperationCallback(_ => + { + ((CardAction)Owner).AutomationClick(); + return null; + }), + null + ); } } diff --git a/src/Wpf.Ui/Controls/FluentWindow/FluentWindow.cs b/src/Wpf.Ui/Controls/FluentWindow/FluentWindow.cs index 2ee2f5428..155e2d688 100644 --- a/src/Wpf.Ui/Controls/FluentWindow/FluentWindow.cs +++ b/src/Wpf.Ui/Controls/FluentWindow/FluentWindow.cs @@ -107,7 +107,10 @@ static FluentWindow() ); } - private void OnApplicationThemeManagerChanged(ApplicationTheme currentApplicationTheme, Color systemAccent) + private void OnApplicationThemeManagerChanged( + ApplicationTheme currentApplicationTheme, + Color systemAccent + ) { if (IsActive && ApplicationAccentColorManager.IsAccentColorOnTitleBarsEnabled) { diff --git a/src/Wpf.Ui/Controls/NavigationView/NavigationViewItemAutomationPeer.cs b/src/Wpf.Ui/Controls/NavigationView/NavigationViewItemAutomationPeer.cs index fcbe933cc..ea3cd2775 100644 --- a/src/Wpf.Ui/Controls/NavigationView/NavigationViewItemAutomationPeer.cs +++ b/src/Wpf.Ui/Controls/NavigationView/NavigationViewItemAutomationPeer.cs @@ -9,7 +9,10 @@ namespace Wpf.Ui.Controls; -internal class NavigationViewItemAutomationPeer : FrameworkElementAutomationPeer, IExpandCollapseProvider, ISelectionItemProvider +internal class NavigationViewItemAutomationPeer + : FrameworkElementAutomationPeer, + IExpandCollapseProvider, + ISelectionItemProvider { private readonly NavigationViewItem _owner; @@ -32,7 +35,10 @@ protected override AutomationControlType GetAutomationControlTypeCore() public override object GetPattern(PatternInterface patternInterface) { // Only provide expand collapse pattern if we have children! https://github.com/microsoft/microsoft-ui-xaml/blob/50177b54e88e923e24440df679bdf984b0048ab4/src/controls/dev/NavigationView/NavigationViewItemAutomationPeer.cpp#L52 - if (patternInterface == PatternInterface.SelectionItem || (patternInterface == PatternInterface.ExpandCollapse && _owner is { HasMenuItems: true })) + if ( + patternInterface == PatternInterface.SelectionItem + || (patternInterface == PatternInterface.ExpandCollapse && _owner is { HasMenuItems: true }) + ) { return this; } diff --git a/src/Wpf.Ui/Controls/TitleBar/TitleBar.cs b/src/Wpf.Ui/Controls/TitleBar/TitleBar.cs index f65a33aa7..e52900913 100644 --- a/src/Wpf.Ui/Controls/TitleBar/TitleBar.cs +++ b/src/Wpf.Ui/Controls/TitleBar/TitleBar.cs @@ -711,7 +711,12 @@ or PInvoke.WM_NCLBUTTONUP UIElement? headerCenterUIElement = CenterContent as UIElement; UIElement? headerRightUiElement = TrailingContent as UIElement; - isMouseOverHeaderContent = (headerLeftUIElement is not null && headerLeftUIElement != _titleBlock && headerLeftUIElement.IsMouseOverElement(lParam)) + isMouseOverHeaderContent = + ( + headerLeftUIElement is not null + && headerLeftUIElement != _titleBlock + && headerLeftUIElement.IsMouseOverElement(lParam) + ) || (headerCenterUIElement?.IsMouseOverElement(lParam) ?? false) || (headerRightUiElement?.IsMouseOverElement(lParam) ?? false); } diff --git a/src/Wpf.Ui/README.md b/src/Wpf.Ui/README.md new file mode 100644 index 000000000..2a7d78386 --- /dev/null +++ b/src/Wpf.Ui/README.md @@ -0,0 +1,32 @@ +# WPF UI + +WPF UI provides the Fluent experience in your known and loved WPF framework. Intuitive design, themes, navigation and new immersive controls. All natively and effortlessly. Library changes the base elements like `Page`, `ToggleButton` or `List`, and also includes additional controls like `Navigation`, `NumberBox`, `Dialog` or `Snackbar`. + +Documentation can be found at . We also have a [tutorial](https://wpfui.lepo.co/documentation/getting-started) over there for newcomers. + +**WPF UI Gallery** is a free application available in the _Microsoft Store_, with which you can test all functionalities. + + +```powershell +winget install 'WPF UI' +``` + +**Visual Studio** +The plugin for **Visual Studio 2022** let you easily create new projects using **WPF UI**. + + +## Support plans + +To ensure you receive the expert guidance you need, we offer a variety of support plans designed to meet the diverse needs of our community. Whether you are looking to modernize your WPF applications or need assistance with our other libraries, our tailored support solutions are here to help. From priority email support to 24/7 dedicated assistance, we provide flexible plans to suit your project requirements. + +[Take a look at the lepo.co support plans](https://lepo.co/support) + +## Help us keep working on this project + +Support the development of WPF UI and other innovative projects by becoming a sponsor on GitHub! Your monthly or one-time contributions help us continue to deliver high-quality, open-source solutions that empower developers worldwide. + +[Sponsor WPF UI on GitHub](https://github.com/sponsors/pomianowski) + +![Demo App Sample](https://user-images.githubusercontent.com/13592821/166259110-0fb98120-fe34-4e6d-ab92-9f72ad7113c3.png) + +![Monaco Editor](https://user-images.githubusercontent.com/13592821/258610583-7d71f69d-45b3-4be6-bcb8-8cf6cd60a2ff.png) diff --git a/src/Wpf.Ui/UiApplication.cs b/src/Wpf.Ui/UiApplication.cs index 36ebb1c6c..7ac290a50 100644 --- a/src/Wpf.Ui/UiApplication.cs +++ b/src/Wpf.Ui/UiApplication.cs @@ -111,7 +111,6 @@ public ResourceDictionary Resources return _application?.Resources ?? _resources; } - set { _application?.Resources = value; @@ -137,7 +136,12 @@ public void Shutdown() private static bool ApplicationHasResources(Application application) { - return application.Resources.MergedDictionaries.Any(e => e.Source?.ToString() - .Contains(Appearance.ApplicationThemeManager.LibraryNamespace, StringComparison.OrdinalIgnoreCase) == true); + return application.Resources.MergedDictionaries.Any(e => + e.Source?.ToString() + .Contains( + Appearance.ApplicationThemeManager.LibraryNamespace, + StringComparison.OrdinalIgnoreCase + ) == true + ); } } diff --git a/src/Wpf.Ui/Wpf.Ui.csproj b/src/Wpf.Ui/Wpf.Ui.csproj index 1b322d324..32b23765b 100644 --- a/src/Wpf.Ui/Wpf.Ui.csproj +++ b/src/Wpf.Ui/Wpf.Ui.csproj @@ -53,6 +53,10 @@ + + + + all diff --git a/tests/Wpf.Ui.Gallery.IntegrationTests/Wpf.Ui.Gallery.IntegrationTests.csproj b/tests/Wpf.Ui.Gallery.IntegrationTests/Wpf.Ui.Gallery.IntegrationTests.csproj index 7956d330c..0c911cc1b 100644 --- a/tests/Wpf.Ui.Gallery.IntegrationTests/Wpf.Ui.Gallery.IntegrationTests.csproj +++ b/tests/Wpf.Ui.Gallery.IntegrationTests/Wpf.Ui.Gallery.IntegrationTests.csproj @@ -4,31 +4,20 @@ net10.0-windows10.0.26100.0 enable Exe - + false + true + true - - - - + - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - @@ -36,4 +25,10 @@ + + + + + + diff --git a/tests/Wpf.Ui.UnitTests/Usings.cs b/tests/Wpf.Ui.UnitTests/GlobalUsings.cs similarity index 100% rename from tests/Wpf.Ui.UnitTests/Usings.cs rename to tests/Wpf.Ui.UnitTests/GlobalUsings.cs diff --git a/tests/Wpf.Ui.UnitTests/Wpf.Ui.UnitTests.csproj b/tests/Wpf.Ui.UnitTests/Wpf.Ui.UnitTests.csproj index efa518b76..27d793d5e 100644 --- a/tests/Wpf.Ui.UnitTests/Wpf.Ui.UnitTests.csproj +++ b/tests/Wpf.Ui.UnitTests/Wpf.Ui.UnitTests.csproj @@ -1,26 +1,32 @@ - + net10.0-windows + enable + Exe false + true + true - + + + + + - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - + + + + + + + diff --git a/tests/Wpf.Ui.UnitTests/xunit.runner.json b/tests/Wpf.Ui.UnitTests/xunit.runner.json new file mode 100644 index 000000000..2b64621b3 --- /dev/null +++ b/tests/Wpf.Ui.UnitTests/xunit.runner.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", + "culture": "invariant", + "parallelizeTestCollections": false, + "diagnosticMessages": true +}