Technical Documentation: lepoco/wpfui
ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference
Overview (`README.md`)
WPF UI
Created with ❤ in Poland by Leszek Pomianowski and wonderful open-source community.
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.
Deliver humanitarian aid directly to Ukraine
https://bank.gov.ua/en/about/humanitarian-aid-to-ukraine
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
Sponsor WPF UI on GitHub
Getting started
For a starter guide see our documentation.
WPF UI Gallery is a free application available in the Microsoft Store, with which you can test all functionalities.
https://apps.microsoft.com/store/detail/wpf-ui/9N9LKV8R9VGM?cid=windows-lp-hero
winget install 'WPF UI'WPF UI is delivered via NuGet package manager. You can find the package here:
https://www.nuget.org/packages/wpf-ui/
Visual Studio
The plugin for Visual Studio 2022 let you easily create new projects using WPF UI.
https://marketplace.visualstudio.com/items?itemName=lepo.wpf-ui
Screenshots
Works with Visual Studio Designer
Custom Tray icon and menu in pure WPF
Custom Windows 11 SnapLayout available for TitleBar
Development
If you want to propose a new functionality or submit a bugfix, create a Pull Request for the branch main.
How to use?
First, your application needs to load custom styles, add in the MyApp\App.xaml file:
<Application
...
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ui:ThemesDictionary Theme="Light" />
<ui:ControlsDictionary />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>If your application does not have MyApp\App.xaml file, use ApplicationThemeManager.Apply(frameworkElement) to apply/update the theme resource in the frameworkElement.
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
ApplicationThemeManager.Apply(this);
}
}Now you can create fantastic apps, e.g. with one button:
<ui:FluentWindow
...
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml">
<StackPanel>
<ui:TitleBar Title="WPF UI"/>
<ui:Card Margin="8">
<ui:Button Content="Hello World" Icon="{ui:SymbolIcon Fluent24}" />
</ui:Card>
</StackPanel>
</ui:FluentWindow>Microsoft Property
Design of the interface, choice of colors and the appearance of the controls were inspired by projects made by Microsoft for Windows 11.
The Wpf.Ui.Gallery app includes icons from Microsoft WinUI 3 Gallery app. They are used here as an example of creating tools for Microsoft systems.
Segoe Fluent Icons
WPF UI uses Fluent System Icons. Although this font was also created by Microsoft, it does not contain all the icons for Windows 11. If you need the missing icons, add Segoe Fluent Icons to your application.
According to the EULA of Segoe Fluent Icons we cannot ship a copy of it with this dll. Segoe Fluent Icons is installed by default on Windows 11, but if you want these icons in an application for Windows 10 and below, you must manually add the font to your application's resources.
https://docs.microsoft.com/en-us/windows/apps/design/style/segoe-fluent-icons-font
https://docs.microsoft.com/en-us/windows/apps/design/downloads/#fonts
In the app dictionaries, you can add an alternate path to the font
<FontFamily x:Key="SegoeFluentIcons">pack://application:,,,/;component/Fonts/#Segoe Fluent Icons</FontFamily>Code of Conduct
This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community.
License
WPF UI is free and open source software licensed under MIT License. You can use it in private and commercial projects.
Keep in mind that you must include a copy of the license in your project.
Overview (`docs/architecture/README.md`)
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) | System context diagram showing WPF UI in its environment -- consumer applications, Windows OS, distribution channels |
| Logical Architecture | Module dependencies, layer diagram, core library internal component structure, detected patterns |
Software Design Documents (SDD)
| Document | Description |
|---|---|
| CONSTITUTION.md | Project constitution -- coding conventions, boundary system (ALWAYS DO / ASK FIRST / NEVER DO), rules files summary |
| IMPLEMENTATION-GUIDE.md | Step-by-step guides for common tasks -- adding controls, services, gallery pages, Win32 interop, theming, testing |
| TESTING-SPEC.md | Testing specification -- frameworks, templates, naming conventions, run commands |
| MODULE-INTERFACES.md | Module interface contracts -- public vs internal API surface per module, dependency rules |
Cross-Cutting Concerns
| Document | Description |
|---|---|
| Testing | Test strategy, frameworks, naming conventions, coverage gaps |
| Theming and Appearance | Theme system architecture, accent colors, backdrop effects, theme change flow |
| Win32 Interop | Three-layer interop architecture, key APIs, handle validation, WndProc patterns |
| Navigation | Navigation system lifecycle, page caching strategy, DI integration, transition animations |
Architecture Decision Records (ADRs)
| ADR | Decision |
|---|---|
| ADR-001 | Multi-target framework strategy (net10.0 through net462) |
| ADR-002 | WPF control library architecture with Fluent Design |
| ADR-003 | Win32 interop via CsWin32 source generator |
| ADR-004 | Static singleton managers for theming (implicit) |
| ADR-005 | Feature-folder organization for controls (implicit) |
Other
| Document | Description |
|---|---|
| RECOMMENDATIONS.md | Prioritized architectural improvement recommendations |
| UNCERTAINTIES.md | Areas needing further investigation |
| Diagrams Index | Index of all architecture diagrams |
| LikeC4 Diagrams | 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<T>). 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/SymbolFilledenums 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
- Control Architecture -- Folder-per-control pattern with paired
.cs+.xamlfiles. Controls extend standard WPF base classes and implement capability interfaces (IAppearanceControl,IIconControl). - Theming System -- Runtime resource dictionary swapping managed by
ApplicationThemeManager. Accent colors derived from system settings via WinRTUISettingsor DWM registry. - 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
/* Detailed source-code truncated for AI context efficiency. */Repository Structure
/* Detailed source-code truncated for AI context efficiency. */Overview (`docs/templates/README.md`)
This folder contains the source code for website themes used by docfx.exe.
Overview (`src/Wpf.Ui.Abstractions/README.md`)
Wpf.Ui.Abstractions
Common interfaces and contracts for Wpf.Ui. Used to decouple core library features and enable extensibility.
Included types
Wpf.Ui.Abstractions.INavigationViewPageProvider
Wpf.Ui.Abstractions.NavigationViewPageProviderExtensions
Wpf.Ui.Abstractions.Controls.INavigableView
Wpf.Ui.Abstractions.Controls.INavigationAware
Wpf.Ui.Abstractions.Controls.NavigationAwareDocumentation can be found at https://wpfui.lepo.co/. We also have a tutorial over there for newcomers.
Overview (`src/Wpf.Ui.DependencyInjection/README.md`)
Wpf.Ui.DependencyInjection
DI implementation for Wpf.Ui. Used to decouple core library features and enable extensibility.
Included types
Wpf.Ui.DependencyInjection.DependencyInjectionNavigationViewPageProviderDocumentation can be found at https://wpfui.lepo.co/. We also have a tutorial over there for newcomers.
Overview (`src/Wpf.Ui.FlaUI/README.md`)
Wpf.Ui.FlaUI
FlaUI automation helpers for Wpf.Ui. Provides test utilities for interacting with WPF controls in automated UI tests.
Included types
Wpf.Ui.FlaUI.AutoSuggestBoxDocs: https://wpfui.lepo.co/
Overview (`src/Wpf.Ui.SyntaxHighlight/README.md`)
Wpf.Ui.SyntaxHighlight
Syntax highlighting controls and utilities for Wpf.Ui. Enables code formatting and display in WPF applications.
Included types
Wpf.Ui.SyntaxHighlight.Highlighter
Wpf.Ui.SyntaxHighlight.Controls.CodeBlock
Wpf.Ui.SyntaxHighlight.Markup.SyntaxHighlightDictionaryDocs: https://wpfui.lepo.co/
Overview (`src/Wpf.Ui.ToastNotifications/README.md`)
Wpf.Ui.ToastNotifications
Toast notification support for Wpf.Ui. Provides a simple API for displaying toast messages in WPF applications.
Included types
Wpf.Ui.ToastNotifications.ToastDocs: https://wpfui.lepo.co/
Overview (`src/Wpf.Ui.Tray/README.md`)
Wpf.Ui.Tray
System tray integration for Wpf.Ui. Provides services and controls for tray icons in WPF applications.
Included types
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.NotifyIconEventHandlerDocs: https://wpfui.lepo.co/
Overview (`src/Wpf.Ui/README.md`)
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 https://wpfui.lepo.co/. We also have a tutorial over there for newcomers.
WPF UI Gallery is a free application available in the Microsoft Store, with which you can test all functionalities.
https://apps.microsoft.com/store/detail/wpf-ui/9N9LKV8R9VGM?cid=windows-lp-hero
winget install 'WPF UI'Visual Studio
The plugin for Visual Studio 2022 let you easily create new projects using WPF UI.
https://marketplace.visualstudio.com/items?itemName=lepo.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
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.
Overview (`docs/architecture/appendix/diagrams/README.md`)
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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- Type: Code example with step-by-step comments
- Purpose: Documents the mandatory three-step validation pattern for all Win32 calls
- Steps:
- Check for zero handle
- Verify window exists (IsWindow)
- Perform operation with exception handling
ADR-005: Feature Folder Organization
Feature Folder Patterns
- Location: decisions/ADR-005-feature-folder-controls.md
- 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
- 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 UpdateWin32 Call Stack
Diagram: Layer diagram showing Win32 interop call flow
High-Level Code → UnsafeNativeMethods (validation) → PInvoke (CsWin32) → Win32 APINavigation Flow
Diagram: (Not yet created) Could be added to show:
NavigationService → NavigationView → PageProvider → Page Resolution →
Frame Navigation → INavigationAware CallbacksComponent 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
- Content Dialog Lifecycle - Async ShowAsync() flow with cancellation and result handling
- Icon System Hierarchy - IconElement inheritance tree (FontIcon, SymbolIcon, ImageIcon)
- Accent Color Derivation - How 20+ accent resources are computed from system accent
- Gallery App Architecture - MVVM structure with ViewModels, Views, Services, and Models
Recently Added (2026-02-10)
Navigation System Flow— Added in cross-cutting/navigation.mdDI Integration Flow— Added in views/logical-architecture.mdService 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:
sequenceDiagram
participant A
participant B
A->>B: Message
B-->>A: ResponseGraph Diagram:
graph TD
A[Component A] --> B[Component B]
B --> C[Component C]
style A fill:#e1f5e1
style B fill:#fff4e1Class Diagram:
classDiagram
class Button {
+Icon IconElement
+Appearance ControlAppearance
}
Button --|> IAppearanceControl
Button --|> IIconControlDiagram Best Practices
- Keep diagrams focused (single concern per diagram)
- Use consistent styling across related diagrams
- Include legend when colors/shapes have meaning
- Maintain diagram source in markdown (not separate image files)
- 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:
# Using mmdc (Mermaid CLI)
npm install @mermaid-js/mermaid-cli
mmdc -i diagram.mmd -o diagram.pngMaintenance
Ownership
Architecture diagram maintenance is part of architecture documentation updates. When making architectural changes:
- Identify affected diagrams
- Update diagram source in markdown
- Verify rendering in preview
- 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
Index (`docs/index.md`)
<h1 class="display-1">WPF UI</h1>
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.<a class="btn btn-outline-primary mr-05"
href="/documentation/getting-started.html">Start tutorial</a><a class="btn btn-outline-light"
href="/documentation">Read the docs</a>
<h4>Check out the Gallery</h4>
Check out all the controls and functionality with the help of the free **WPF UI Gallery ** app.
<a href="https://apps.microsoft.com/store/detail/wpf-ui/9N9LKV8R9VGM?cid=windows-lp-hero" target="_blank" rel="noopener" class="btn btn-ms-store">
</a>
<h2 class="display-4">Fluent UI in your favorite environment</h2>
**Open source**
WPF UI is free and open source. You can check the source code or
change it to your liking.
**Easy to install**
WPF UI is delivered via NuGet package manager. Just find it in
the search and add to your project.
**Compatible**
Most styles and controls work right away in Visual Studio
Designer.
850KDownloads
9KGitHub Stars
900ForksSupport
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.
<a class="btn btn-block btn-outline-primary mr-05" href="https://lepo.co/support">Take a look at the lepo.co support plans</a>
<a class="btn btn-block btn-outline-light" href="https://github.com/sponsors/pomianowski">Sponsor WPF UI on GitHub</a>Created with ❤ in Poland by 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.
Documentation Getting Started (`docs/documentation/getting-started.md`)
Getting started
Adding dictionaries
XAML, and hence WPF, operate on resource dictionaries. These are HTML-like files that describe the appearance and various aspects of the controls. WPF UI adds its own sets of these files to tell the application how the controls should look.
There should be a file called App.xaml in your new application. Add new dictionaries to it using WPF UI ControlsDictionary and ThemesDictionary classes:
<Application
...
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ui:ThemesDictionary Theme="Dark" />
<ui:ControlsDictionary />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>Notice that the ThemeDictionary lets you choose a color theme, Light or Dark.
The main window
There should be a MainWindow.xaml file in your newly created application. It contains the arrangement of the controls used and their parameters.
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
</Grid>
</Window>Add the WPF UI library namespace to this window to tell the XAML compiler that you will be using controls from the library.
<Window
...
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml" />Adding controls
To add a new control from the WPF UI library, just enter its class name, prefixing it with the ui: prefix:
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<ui:SymbolIcon Symbol="Fluent24"/>
</Grid>
</Window>Well...
That's it when it comes to the basics, information about individual controls can be found in documentation. Rules for building a WPF application can be found in the official Microsoft documentation. You can check out how to build MVVM applications here.
If you think this documentation needs improvement, please help improve it here.
Documentation Index (`docs/documentation/index.md`)
WPF UI Docs
WPF UI is a library built for Windows Presentation Foundation (WPF) and the C# language.
To be able to work with them comfortably, you will need:
- Visual Studio 2022 Community Edition
- .NET desktop development
(Additional workload in Visual Studio)
[!NOTE]
Visual Studio 2022 and Visual Studio Code are two different programs. If you want to create WPF apps, it's possible to compile them in Visual Studio Code, however for comfortable work we recommend Visual Studio 2022 or JetBrains Rider.
Installation
You can install WPF UI, the library for the Windows Presentation Foundation framework, in several ways.
- Directly specify the
Wpf.Ui.dllfile in your application's project file (.csproj). - Copy the library source code into your application codebase.
- Use the NuGet package manager.
We recommend using the NuGet package manager, it allows you to easily install and update your application dependencies.
More information on how to install WPF UI using NuGet can be found here.
Extension for Visual Studio
Creators of WPF UI have prepared a special plugin that will automatically create a project based on WPF UI, Dependency Injection and MVVM, thanks to which you will quickly and easily start a new apps.
Learn more about the WPF UI plug-in for Visual Studio 2022
Getting started
Once you have chosen how to install WPF UI, you can move on to creating your first app, more on this in Getting Started.
Migration Index (`docs/migration/index.md`)
Migration Guides
Guides for migrating between major versions of Wpf.Ui.
Docfx.Json (`docs/docfx.json`)
{
"metadata": [
{
"src": [
{
"files": [
"Wpf.Ui/.csproj",
"Wpf.Ui.Tray/.csproj",
"Wpf.Ui.Abstractions/.csproj",
"Wpf.Ui.DependencyInjection/.csproj"
],
"src": "../src"
}
],
"dest": "api",
"includePrivateMembers": false,
"disableGitFeatures": false,
"disableDefaultFilter": false,
"noRestore": false,
"namespaceLayout": "flattened",
"memberLayout": "samePage",
"allowCompilationErrors": false
}
],
"build": {
"content": [
{
"files": [
"/*.{md,yml}"
],
"exclude": [
"_site/",
"obj/",
"architecture/"
]
}
],
"resource": [
{
"files": [
"/images/",
"codesnippet/",
"manifest.webmanifest",
"robots.txt"
],
"exclude": [
"_site/",
"obj/**"
]
}
],
"xref": [],
"xrefService": [
"https://xref.docs.microsoft.com/query?uid={uid}"
],
"postProcessors": [
"ExtractSearchIndex"
],
"globalMetadata": {
"_appTitle": "WPF UI",
"_appName": "WPF UI",
"_appFaviconPath": "images/favicon.ico",
"_appLogoPath": "images/wpfui.png",
"_appFooter": "Made with <a href="https://dotnet.github.io/docfx\" rel="noreferrer">docfx, <a href="https://chat.openai.com/\" rel="noreferrer">ChatGPT | Copyright © 2026 <a href="https://lepo.co/\">lepo.co"
},
"dest": "_site",
"template": [
"default",
"templates/wpfui"
],
"globalMetadataFiles": [],
"fileMetadataFiles": [],
"markdownEngineName": "markdig",
"noLangKeyword": false,
"keepFileLink": false,
"cleanupCacheHistory": false,
"disableGitFeatures": false
},
"sitemap": {
"baseUrl": "https://wpfui.lepo.co",
"priority": 0.1,
"changefreq": "monthly"
}
}
Toc.Yml (`docs/toc.yml`)
items:
- name: Home
href: / - name: 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
- name: API v4.0
href: api/ - name: 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
- name: Support plans
href: https://lepo.co/support
Architecture CONSTITUTION (`docs/architecture/CONSTITUTION.md`)
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
| 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.
// ReSharper disable once CheckNamespace
namespace Wpf.Ui.Controls;
public class Button : System.Windows.Controls.Button, IAppearanceControl, IIconControl
{
// ...
}Services use the Wpf.Ui namespace:
namespace Wpf.Ui;
public class SnackbarService : ISnackbarService
{
// ...
}Gallery app follows folder-based namespaces:
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.
// 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.
private bool _valueUpdating;
private SnackbarPresenter? _presenter;
private Snackbar? _snackbar;In Gallery ViewModels with CommunityToolkit.Mvvm, the same convention applies:
[ObservableProperty]
private bool _isAnchorEnabled = true;2.4 File Headers
C# files -- MIT license header enforced as a build error via IDE0073:
// 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:
2.5 XML Documentation
Public APIs require `` and <example> tags. Examples show XAML usage with <code lang="xml"> and HTML-escaped angle brackets.
Control class documentation:
/// <summary>
/// Creates a hyperlink to web pages, files, email addresses, locations in the same page,
/// or anything else a URL can address.
/// </summary>
/// <example>
/// <code lang="xml">
/// <ui:Anchor
/// NavigateUri="https://lepo.co/" />
/// </code>
/// </example>
public class Anchor : Wpf.Ui.Controls.HyperlinkButton { }Control with multiple XAML examples:
/// <summary>
/// Inherited from the <see cref="System.Windows.Controls.Button"/>,
/// adding <see cref="SymbolRegular"/>.
/// </summary>
/// <example>
/// <code lang="xml">
/// <ui:Button
/// Appearance="Primary"
/// Content="WPF UI button with font icon"
/// Icon="{ui:SymbolIcon Symbol=Fluent24}" />
/// </code>
/// <code lang="xml">
/// <ui:Button
/// Appearance="Primary"
/// Content="WPF UI button with font icon"
/// Icon="{ui:FontIcon '🌈'}" />
/// </code>
/// </example>C# code examples (e.g., for static managers):
/// <example>
/// <code lang="csharp">
/// ApplicationThemeManager.Apply(
/// ApplicationTheme.Light
/// );
/// </code>
/// </example>DependencyProperty documentation uses a single-line ``:
/// <summary>Identifies the <see cref="Icon"/> dependency property.</summary>
public static readonly DependencyProperty IconProperty = DependencyProperty.Register(
nameof(Icon),
typeof(IconElement),
typeof(Button),
new PropertyMetadata(null, 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.
/// <summary>Identifies the <see cref="Icon"/> dependency property.</summary>
public static readonly DependencyProperty IconProperty = DependencyProperty.Register(
nameof(Icon),
typeof(IconElement),
typeof(Button),
new PropertyMetadata(null, null, IconElement.Coerce)
);
/// <summary>
/// Gets or sets displayed <see cref="IconElement"/>.
/// </summary>
[Bindable(true)]
[Category("Appearance")]
public IconElement? Icon
{
get => (IconElement?)GetValue(IconProperty);
set => SetValue(IconProperty, value);
}For two-way binding with specific update triggers:
/// <summary>Identifies the <see cref="Value"/> dependency property.</summary>
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 constructorNavigationView.Properties.cs-- DependencyProperty declarationsNavigationView.Events.cs-- RoutedEvent declarationsNavigationView.TemplateParts.cs-- Template part handlingNavigationView.Navigation.cs-- Navigation logic
Static constructor pattern (required for custom-styled controls):
static NavigationView()
{
DefaultStyleKeyProperty.OverrideMetadata(
typeof(NavigationView),
new FrameworkPropertyMetadata(typeof(NavigationView))
);
}Template parts pattern:
[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.
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:Wpf.Ui.Controls">
<Thickness x:Key="ButtonPadding">11,5,11,6</Thickness>
<Thickness x:Key="ButtonBorderThemeThickness">1</Thickness>
<Style x:Key="DefaultRepeatButtonStyle" TargetType="{x:Type RepeatButton}">
<Setter Property="FocusVisualStyle" Value="{DynamicResource DefaultControlFocusVisualStyle}" />
<Setter Property="Background" Value="{DynamicResource ButtonBackground}" />
<Setter Property="Foreground" Value="{DynamicResource ButtonForeground}" />
<Setter Property="BorderBrush" Value="{DynamicResource ControlElevationBorderBrush}" />
<Setter Property="BorderThickness" Value="{StaticResource ButtonBorderThemeThickness}" />
<Setter Property="Padding" Value="{StaticResource ButtonPadding}" />
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="OverridesDefaultStyle" Value="True" />
</Style>
</ResourceDictionary>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):
namespace Wpf.Ui;
/// <summary>
/// Represents a contract with the service that provides global <see cref="Snackbar"/>.
/// </summary>
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):
namespace Wpf.Ui;
/// <summary>
/// A service that provides methods related to displaying the <see cref="Snackbar"/>.
/// </summary>
public class SnackbarService : ISnackbarService
{
private SnackbarPresenter? _presenter;
private Snackbar? _snackbar;
/// <inheritdoc />
public TimeSpan DefaultTimeOut { get; set; } = TimeSpan.FromSeconds(5);
/// <inheritdoc />
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):
catch
{
// Ignore registry access errors on non-Windows or restricted environments
return null;
}Service code -- Throw ArgumentNullException / InvalidOperationException for precondition violations:
if (_presenter is null)
{
throw new InvalidOperationException($"The SnackbarPresenter was never set");
}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):
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/):
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:
public class TransitionAnimationProviderTests
{
[Fact]
public void ApplyTransition_ReturnsFalse_WhenDurationIsLessThan10()
{
UIElement mockedUiElement = Substitute.For<UIElement>();
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:
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):
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):
namespace Wpf.Ui.Gallery.Views.Pages.BasicInput;
[GalleryPage("Button which opens a link.", SymbolRegular.CubeLink20)]
public partial class AnchorPage : INavigableView<AnchorViewModel>
{
public AnchorViewModel ViewModel { get; init; }
public AnchorPage(AnchorViewModel viewModel)
{
ViewModel = viewModel;
DataContext = this;
InitializeComponent();
}
}Key MVVM conventions:
[ObservableProperty]for_camelCasebacking fields (generates PascalCase properties)[RelayCommand]forOn-prefixed private methods (generates{Name}CommandICommand)- Pattern matching with
is notfor type checks in command handlers DataContext = thisset in page constructor (not in XAML)INavigableView<TViewModel>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):
<Project>
<ItemGroup>
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageVersion Include="Microsoft.Windows.CsWin32" Version="0.3.242" />
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
<PackageVersion Include="WpfAnalyzers" Version="4.1.1" />
<PackageVersion Include="xunit.v3" Version="3.2.0" />
</ItemGroup>
</Project>.csproj (NO version attribute):
<PackageReference Include="Microsoft.Windows.CsWin32">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>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 <Nullable>enable</Nullable> |
| A3 | Add MIT license file header to every .cs and .xaml file | .editorconfig IDE0073 = error |
| A4 | Add XML docs with `` and <example> 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 <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally> |
| 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:
// N1 VIOLATION: version in .csproj
<PackageReference Include="NSubstitute" Version="5.3.0" />
// CORRECT: no version attribute
<PackageReference Include="NSubstitute" />
// N2 VIOLATION: var for non-apparent type
var service = container.Resolve<ISnackbarService>();
// CORRECT: explicit type
ISnackbarService service = container.Resolve<ISnackbarService>();
// 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
{
"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=trueANDGeneratePackageOnBuild=true, usingsrc/lepo.snk - Trimming/AOT: Enabled for .NET 6+ targets (
IsTrimmable,EnableTrimAnalyzer,EnableAotAnalyzer,EnableSingleFileAnalyzer) - Commit hash: Embedded as
AssemblyMetadataAttribute("CommitHash", ...)viaSourceRevisionId
`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) |
Architecture IMPLEMENTATION GUIDE (`docs/architecture/IMPLEMENTATION-GUIDE.md`)
WPF UI Implementation Guide
Step-by-step guides for common implementation tasks in the WPF UI project.
Table of Contents
- How to Add a New Control
- How to Add a New Service
- How to Add a Gallery Page
- How to Use Win32 Interop
- How to Add Theme Support
- 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 ControlTemplateStep 2: Create the Control Class (MyControl.cs)
/* Detailed source-code truncated for AI context efficiency. */Step 3: Create the XAML Style (MyControl.xaml)
/* Detailed source-code truncated for AI context efficiency. */Step 4: Register in Wpf.Ui.xaml
Add reference to src/Wpf.Ui/Resources/Wpf.Ui.xaml:
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Wpf.Ui;component/Controls/MyControl/MyControl.xaml" />
</ResourceDictionary.MergedDictionaries>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:
<ToolboxItems UIFramework="WPF" VSCategory="WPF UI" BlendCategory="WPF UI">
<Item Type="Wpf.Ui.Controls.MyControl" />
</ToolboxItems>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)
// 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;
/// <summary>
/// Provides functionality for managing custom operations.
/// </summary>
public interface IMyService
{
/// <summary>
/// Gets the current state of the service.
/// </summary>
bool IsActive { get; }
/// <summary>
/// Initializes the service with the specified parameter.
/// </summary>
/// <param name="parameter">The initialization parameter.</param>
void Initialize(string parameter);
/// <summary>
/// Performs an asynchronous operation.
/// </summary>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task representing the asynchronous operation.</returns>
Task<bool> PerformOperationAsync(CancellationToken cancellationToken = default);
}Step 2: Create the Service Implementation (MyService.cs)
// 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;
/// <summary>
/// Implementation of <see cref="IMyService"/>.
/// </summary>
public partial class MyService : IMyService
{
private string? _parameter;
private bool _isActive;
/// <inheritdoc />
public bool IsActive => _isActive;
/// <inheritdoc />
public void Initialize(string parameter)
{
ArgumentNullException.ThrowIfNull(parameter);
_parameter = parameter;
_isActive = true;
}
/// <inheritdoc />
public async Task<bool> 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:
// 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;
/// <summary>
/// Extension methods for registering WPF UI services.
/// </summary>
public static partial class ServiceCollectionExtensions
{
/// <summary>
/// Registers <see cref="IMyService"/> as a singleton.
/// </summary>
/// <param name="services">The service collection.</param>
/// <returns>The service collection for chaining.</returns>
public static IServiceCollection AddMyService(this IServiceCollection services)
{
_ = services.AddSingleton<IMyService, MyService>();
return services;
}
}Step 4: Register in Application
// In your application startup (e.g., App.xaml.cs or Program.cs)
services.AddMyService();Step 5: Use the Service
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:
/* Detailed source-code truncated for AI context efficiency. */Step 2: Create Page (XAML)
Create src/Wpf.Ui.Gallery/Views/Pages/MyCategory/MyControlPage.xaml:
/* Detailed source-code truncated for AI context efficiency. */Step 3: Create Page Code-Behind
Create src/Wpf.Ui.Gallery/Views/Pages/MyCategory/MyControlPage.xaml.cs:
// 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<MyControlViewModel>
{
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:
// Register ViewModel
services.AddSingleton<MyControlViewModel>();
// Register Page
services.AddSingleton<MyControlPage>();Step 5: Add Navigation Entry
If you need manual navigation registration (not using automatic discovery), add to navigation setup:
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:
// Existing entries...
DwmSetWindowAttribute
IsWindow
// Add your new function
GetSystemMetricsThis 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:
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.UI.WindowsAndMessaging;
namespace Wpf.Ui.Interop;
internal static partial class UnsafeNativeMethods
{
/// <summary>
/// Safely retrieves a system metric value with handle validation.
/// </summary>
/// <param name="handle">Window handle for context (optional).</param>
/// <param name="metric">The system metric to retrieve.</param>
/// <returns>The metric value, or 0 if the operation fails.</returns>
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:
using Wpf.Ui.Interop;
using Windows.Win32.UI.WindowsAndMessaging;
namespace Wpf.Ui.Win32;
internal sealed class Utilities
{
/// <summary>
/// Gets the width of the window border.
/// </summary>
public static int BorderWidth => UnsafeNativeMethods.GetSystemMetricSafe(
IntPtr.Zero,
SYSTEM_METRICS_INDEX.SM_CXBORDER
);
/// <summary>
/// Gets the height of the window border.
/// </summary>
public static int BorderHeight => UnsafeNativeMethods.GetSystemMetricSafe(
IntPtr.Zero,
SYSTEM_METRICS_INDEX.SM_CYBORDER
);
}Step 4: Use in Your Code
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:
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
Always validate handles before calling Win32 APIs:
csharpif (handle == IntPtr.Zero || !PInvoke.IsWindow(new HWND(handle))) return false;Catch exceptions in interop code for OS compatibility (some APIs may not exist on older Windows versions)
Use
unsafekeyword for pointer operationsCheck OS version before using new APIs:
csharpif (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
<Style TargetType="{x:Type controls:MyControl}">
<Setter Property="Background" Value="{DynamicResource ControlFillColorDefaultBrush}" />
<Setter Property="Foreground" Value="{DynamicResource TextFillColorPrimaryBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource ControlElevationBorderBrush}" />
<Setter Property="Padding" Value="{StaticResource MyControlPadding}" />
</Style>Step 2: Subscribe to Theme Changes (if needed)
If your control needs to react to theme changes programmatically:
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:
ApplicationBackgroundBrushControlFillColorDefaultBrushControlFillColorSecondaryBrushControlFillColorTertiaryBrushControlFillColorDisabledBrush
Foreground Brushes:
TextFillColorPrimaryBrushTextFillColorSecondaryBrushTextFillColorTertiaryBrushTextFillColorDisabledBrush
Accent Brushes:
AccentFillColorDefaultBrushAccentFillColorSecondaryBrushAccentFillColorTertiaryBrushSystemAccentColor(Color, not Brush)
Border Brushes:
ControlElevationBorderBrushControlStrokeColorDefaultBrushControlStrokeColorSecondaryBrush
Step 4: Implement IThemeControl (if applicable)
public class MyControl : ContentControl, IThemeControl
{
/// <summary>Identifies the <see cref="Theme"/> dependency property.</summary>
public static readonly DependencyProperty ThemeProperty = DependencyProperty.Register(
nameof(Theme),
typeof(ApplicationTheme),
typeof(MyControl),
new PropertyMetadata(ApplicationTheme.Light, OnThemeChanged)
);
/// <summary>
/// Gets or sets the theme of the control.
/// </summary>
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
// 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:
/* Detailed source-code truncated for AI context efficiency. */Integration Test Example
Create tests/Wpf.Ui.Gallery.IntegrationTests/MyControlTests.cs:
/* Detailed source-code truncated for AI context efficiency. */Test Infrastructure
Base Test Class (tests/Wpf.Ui.Gallery.IntegrationTests/Fixtures/UiTest.cs):
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
# 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=opencoverCommon Pitfalls to Avoid
Don't add package versions to .csproj - use
Directory.Packages.propsonlyDon't forget handle validation in Win32 interop:
csharpif (handle == IntPtr.Zero || !PInvoke.IsWindow(new HWND(handle))) return false;Don't use
varfor non-apparent types (causes warning)Don't forget MIT license header (causes error via IDE0073)
Don't modify control namespace to match folder structure - use flat
Wpf.Ui.ControlsDon't use StaticResource for theme-dependent values - use DynamicResource
Don't forget to register pages and ViewModels in DI container
Don't use
this.qualification (SA1101 suppressed)Don't add XML docs that restate code - explain WHY, not WHAT
Don't skip the
[GalleryPage]attribute on Gallery pages
Quick Reference Checklist
Adding a Control
- Create folder under
Controls/ - Create
.csfile with control class - Create
.xamlfile with style - Implement interfaces (IAppearanceControl, IIconControl)
- Register DependencyProperties with XML docs
- Override DefaultStyleKeyProperty
- Add to
Wpf.Ui.xamlMergedDictionaries - Add toolbox icon (optional)
- Add unit tests
Adding a Service
- Create
I{Name}Service.csinterface - Create
{Name}Service.csimplementation - 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
Architecture MODULE INTERFACES (`docs/architecture/MODULE-INTERFACES.md`)
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
graph TD
subgraph "Public API Surface"
Abstractions["<b>Wpf.Ui.Abstractions</b><br/><i>Public: 6 types</i><br/><i>Internal: none</i>"]
Core["<b>Wpf.Ui</b><br/><i>Public: 77+ controls, 6 services,<br/>managers, converters, markup</i><br/><i>Internal: Win32, Interop</i>"]
DI["<b>Wpf.Ui.DependencyInjection</b><br/><i>Public: 2 types</i><br/><i>Internal: none</i>"]
Tray["<b>Wpf.Ui.Tray</b><br/><i>Public: 4 types</i><br/><i>Internal: 7 types</i>"]
Syntax["<b>Wpf.Ui.SyntaxHighlight</b><br/><i>Public: 2 types</i><br/><i>Internal: 2 types</i>"]
end
subgraph "Non-Distributable"
Toast["<b>Wpf.Ui.ToastNotifications</b><br/><i>Public: 1 type (STUB)</i>"]
FlaUI["<b>Wpf.Ui.FlaUI</b><br/><i>Public: 1 type</i>"]
FontMapper["<b>Wpf.Ui.FontMapper</b><br/><i>Build-time tool</i>"]
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:#6a1b9aModule: 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<T> |
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<T>, RelayCommand<T> |
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) |
Wpf.Ui.Win32 |
Currently public | OS version utilities. Recommended for internalization |
Note:
Wpf.Ui.InteropandWpf.Ui.Win32expose raw P/Invoke declarations that are implementation details. Consumers should not depend on these namespaces. A future major version should mark theminternal.
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<T>() 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 — 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
- Abstractions are the only shared contract. Modules that need to communicate must do so through
Wpf.Ui.Abstractionsinterfaces. - Core library is the integration point. Only
Wpf.Uimay depend on Win32 interop and WPF framework internals. - Satellite packages depend downward only.
TrayandSyntaxHighlightdepend onCore; they never depend on each other. - DI package depends on Abstractions only.
Wpf.Ui.DependencyInjectionmust never take a dependency onWpf.Uito keep it lightweight. - Internal namespaces are not API surface. Types in
Wpf.Ui.InteropandWpf.Ui.Win32are implementation details even though they are currentlypublic.
Architecture RECOMMENDATIONS (`docs/architecture/RECOMMENDATIONS.md`)
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 <example> 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_WhenConditionnaming 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 runsdotnet 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-latestorwindows-latestas 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:
-
IApplicationThemeManagerinterface exists withApply(),GetAppTheme(), andChangedevent -
IApplicationAccentColorManagerinterface exists withApply()andGetColorizationColor() - 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
NavigationCacheModeenum explain each mode -
docs/architecture/cross-cutting/navigation.mddescribes 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.InteropandWpf.Ui.Win32are markedinternalor 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, andRequired - Test verifies
INavigationAware.OnNavigatedTo/OnNavigatedFromcallback order
TB-010: Add contract tests for service interfaces [Medium]
Source: Testing #4
AC:
-
INavigationServicehas ≥3 contract tests (Navigate, GoBack, SetService) -
IContentDialogServicehas ≥2 contract tests (ShowAsync, SetDialogHost) -
ISnackbarServicehas ≥2 contract tests (Show, SetPresenter) - Tests verify interface contracts, not implementation details
- Tests use NSubstitute for mock dependencies
Architecture TESTING SPEC (`docs/architecture/TESTING-SPEC.md`)
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_WhenConditionAlternative format:
GivenCondition_MethodName_ExpectedResultBasic Structure
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<UIElement>();
// 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
// Create mock
UIElement element = Substitute.For<UIElement>();
// Setup return value
INavigationService service = Substitute.For<INavigationService>();
service.Navigate(typeof(DashboardPage)).Returns(true);
// Verify call
service.Received(1).Navigate(Arg.Any<Type>());Global Usings
Located in tests/Wpf.Ui.UnitTests/Usings.cs:
global using System;
global using System.Windows;
global using NSubstitute;
global using Xunit;Integration Test Template
Naming Convention
Subject_ShouldExpectedBehavior_WhenConditionBase Class Pattern
All integration tests inherit from UiTest:
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>();
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:
// Find element by automation ID
protected AutomationElement? FindFirst(string automationId)
// Find element by condition
protected AutomationElement? FindFirst(Func<ConditionFactory, ConditionBase> 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:
IAsyncLifetimeimplementation- Each test gets its own application instance
- Application launched via
TestedApplicationfixture - Automatic cleanup after test completion
AwesomeAssertions Syntax
// 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
// 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
# 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
# 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
{
"$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.csFile Naming
{ClassName}Tests.csTest 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
- Mock dependencies using NSubstitute
- Test public API surface only
- Use XUnit Assert methods for assertions
- Follow naming convention strictly
- One assertion per test (where logical)
- Test both success and failure paths
When Writing Integration Tests
- Inherit from UiTest base class
- Use automation IDs for element discovery
- Add delays with Wait() for UI updates
- Use AwesomeAssertions fluent syntax
- Provide clear "because" messages in assertions
- 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.csIntegration Test Template:
tests/Wpf.Ui.Gallery.IntegrationTests/TitleBarTests.cs
tests/Wpf.Ui.Gallery.IntegrationTests/NavigationTests.csCommon Patterns
Testing Dependency Properties
[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
[Fact]
public void Navigate_ReturnsTrue_WhenNavigationSucceeds()
{
// Arrange
var pageProvider = Substitute.For<INavigationViewPageProvider>();
pageProvider.GetPage(Arg.Any<Type>()).Returns(new DashboardPage());
var service = new NavigationService(pageProvider);
var navigationView = Substitute.For<INavigationView>();
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:
- 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 normalArchitecture UNCERTAINTIES (`docs/architecture/UNCERTAINTIES.md`)
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_GREATERNET6_0_OR_GREATERNET8_0_OR_GREATERNET48_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.
Documentation About Wpf (`docs/documentation/about-wpf.md`)
What is WPF
WPF (Windows Presentation Foundation) is a resolution-independent UI framework for building Windows desktop applications. It provides vector-based graphics, advanced layout, data binding, multimedia, animation, and extensive styling and templating capabilities.
.NET Framework vs Modern .NET
WPF has two implementations:
Modern .NET (.NET 6+): Open-source implementation hosted on GitHub. Provides improved performance, new APIs, side-by-side deployment, and modern tooling. Despite .NET being cross-platform, WPF only runs on Windows.
.NET Framework 4: Legacy Windows-only implementation. Maintained for compatibility with existing applications but receives minimal new features.
New WPF applications should target modern .NET (.NET 6 or later) to benefit from:
- Better performance and reduced memory usage
- Regular updates and new features
- Modern C# language versions
- Improved debugging and diagnostics
- Side-by-side deployment without machine-wide installations
Documentation Accent (`docs/documentation/accent.md`)
Accent Colors
Accent colors provide visual emphasis and brand identity in WPF UI applications. The library manages accent color resources that automatically adapt to light and dark themes.
[!TIP]
UseSystemThemeWatcher.Watch(this)in your main window constructor to automatically sync accent colors with Windows personalization settings.
Apply System Accent
Use the system's personalization accent color:
using Wpf.Ui.Appearance;
ApplicationAccentColorManager.ApplySystemAccent();Apply Theme with Accent
Apply theme and accent together:
using Wpf.Ui.Appearance;
using Wpf.Ui.Controls;
ApplicationThemeManager.Apply(
ApplicationTheme.Dark,
WindowBackdropType.Mica,
updateAccent: true // Automatically applies system accent
);Available backdrop types: None, Auto, Mica, Acrylic, Tabbed.
[!IMPORTANT]
Always useDynamicResource(notStaticResource) for accent color bindings to receive runtime updates when accent changes.
Apply Custom Accent
Set a custom accent color:
ApplicationAccentColorManager.Apply(
Color.FromArgb(0xFF, 0xEE, 0x00, 0xBB),
ApplicationTheme.Dark
);Retrieve the Windows colorization color programmatically:
Color colorizationColor = ApplicationAccentColorManager.GetColorizationColor();
ApplicationAccentColorManager.Apply(colorizationColor, ApplicationTheme.Dark);Accent Color Resources
WPF UI provides these accent color resources:
System Accent Colors
Base accent colors that update when you call ApplicationAccentColorManager.Apply():
SystemAccentColor- Primary system accentSystemAccentColorPrimary- Lighter/darker variant for light/dark themesSystemAccentColorSecondary- More prominent variant (most commonly used)SystemAccentColorTertiary- Strongest variant
<Border Background="{DynamicResource SystemAccentColorSecondaryBrush}" />[!TIP]
SystemAccentColorSecondaryis the most commonly used variant for interactive elements and provides optimal contrast in both light and dark themes.
Accent Text Colors
For text and interactive elements like links:
AccentTextFillColorPrimaryBrush- Primary accent text (rest/hover state)AccentTextFillColorSecondaryBrush- Secondary accent textAccentTextFillColorTertiaryBrush- Tertiary accent text (pressed state)AccentTextFillColorDisabledBrush- Disabled accent text
<ui:Anchor
Content="WPF UI Documentation"
NavigateUri="https://wpfui.lepo.co/"
Foreground="{DynamicResource AccentTextFillColorPrimaryBrush}" />Accent Fill Colors
For button backgrounds and filled surfaces:
AccentFillColorDefaultBrush- Default accent fillAccentFillColorSecondaryBrush- Secondary fill (90% opacity)AccentFillColorTertiaryBrush- Tertiary fill (80% opacity)AccentFillColorDisabledBrush- Disabled state fill
<Button Background="{DynamicResource AccentFillColorDefaultBrush}" />Text on Accent Colors
For text displayed on accent-colored backgrounds. These resources automatically adjust to black or white based on accent brightness:
TextOnAccentFillColorPrimary- Primary text color on accent backgroundsTextOnAccentFillColorSecondary- Secondary text on accent backgroundsTextOnAccentFillColorDisabled- Disabled text on accent backgrounds
[!NOTE]
Text colors automatically switch between black and white when accent brightness exceeds 80% HSV to maintain readability.
Theme-Specific Behavior
Accent variants adjust automatically based on the application theme:
Dark Theme:
- Primary: Base color + 17 brightness, -30% saturation
- Secondary: Base color + 17 brightness, -45% saturation
- Tertiary: Base color + 17 brightness, -65% saturation
Light Theme:
- Primary: Base color - 10 brightness
- Secondary: Base color - 25 brightness
- Tertiary: Base color - 40 brightness
[!NOTE]
Brightness adjustments are calculated in HSV color space. Negative values darken the color, positive values lighten it.
Advanced: Custom Accent Variants
Specify all accent variants manually:
ApplicationAccentColorManager.Apply(
systemAccent: Color.FromArgb(0xFF, 0x00, 0x78, 0xD4),
primaryAccent: Color.FromArgb(0xFF, 0x00, 0x67, 0xC0),
secondaryAccent: Color.FromArgb(0xFF, 0x00, 0x3E, 0x92),
tertiaryAccent: Color.FromArgb(0xFF, 0x00, 0x1A, 0x68)
);[!CAUTION]
Manually specified accent variants won't automatically adjust when theme changes. Consider using automatic variant generation unless you need precise color control.
Accessing Current Accent
Read current accent colors from ApplicationAccentColorManager:
Color currentSystemAccent = ApplicationAccentColorManager.SystemAccent;
Color currentPrimaryAccent = ApplicationAccentColorManager.PrimaryAccent;
Color currentSecondaryAccent = ApplicationAccentColorManager.SecondaryAccent;
Color currentTertiaryAccent = ApplicationAccentColorManager.TertiaryAccent;
Brush accentBrush = ApplicationAccentColorManager.SystemAccentBrush;Theme Changed Event
Monitor accent changes when theme is applied:
ApplicationThemeManager.Changed += (theme, accent) =>
{
Debug.WriteLine($"Theme changed to {theme}, accent: {accent}");
};Automatic System Theme Tracking
Use SystemThemeWatcher to automatically update theme and accent when Windows settings change:
using Wpf.Ui.Appearance;
public partial class MainWindow : Window
{
public MainWindow()
{
// Watch system theme changes with Mica backdrop and accent updates
SystemThemeWatcher.Watch(this);
InitializeComponent();
}
}With custom backdrop and accent settings:
SystemThemeWatcher.Watch(
this,
WindowBackdropType.Acrylic,
updateAccents: true
);Stop watching for theme changes:
SystemThemeWatcher.UnWatch(this);[!NOTE]
SystemThemeWatchermonitorsWM_WININICHANGEmessages and automatically applies system theme and accent when Windows personalization settings change.
Documentation Extension (`docs/documentation/extension.md`)
Visual Studio 2022 Extension for WPF UI
Visual Studio allows you to add extensions that can be installed in several ways:
- Build them locally and then install the
.vsixpackage. - Download the extension from the internet and install the
.vsixfile. - Install the extension using the search in Visual Studio.
In this tutorial, we'll cover the last way, if you want to know more, check out Manage extensions for Visual Studio.
In any case, if you want to download a plugin and install it manually, or leave your review, you can find it in the Visual Studio Marketplace:
https://marketplace.visualstudio.com/items?itemName=lepo.wpf-ui
[!NOTE]
The source code for WPF UI Visual Studio 2022 extension is public and you can check it out here.
How to?
Install Visual Studio 2022 from Visual Studio 2022 downloads.
After installation, open Visual Studio
Expand the Extensions tab in the menu and then click Manage Extensions
In the Online tab, use the search engine to enter WPF-UI in it, then click Download
After downloading, restart Visual Studio
After restarting, you will see a window asking you to confirm the installation.
Once installed, you can restart Visual Studio and click Create new project
In the top right corner, you can select the project type
Voila, you have just installed the WPF UI extension.
Done!
After creating a project, you can familiarize yourself with its structure and proceed to further steps.
Documentation Fonticon (`docs/documentation/fonticon.md`)
FontIcon
FontIcon is a control responsible for rendering icons based on the provided font.
Implementation
class Wpf.Ui.Controls.FontIconExposes
// Gets or sets displayed glyph
FontIcon.Glyph = '\uE00B';// Gets or sets used font family
FontIcon.FontFamily = "Segoe Fluent Icons";// Icon foreground
FontIcon.Foreground = Brushes.White;// Icon size
FontIcon.FontSize = 16;How to use
<ui:FontIcon
Glyph=""
FontFamily="{DynamicResource SegoeFluentIcons}"
FontSize="16"
Foreground="White"/>Documentation Gallery (`docs/documentation/gallery.md`)
WPF UI Gallery
WPF UI Gallery is a free application available in the Microsoft Store, with which you can test all functionalities.
https://apps.microsoft.com/store/detail/wpf-ui/9N9LKV8R9VGM
$ winget install 'WPF UI'Documentation Icons (`docs/documentation/icons.md`)
Fluent System Icons
Fluent System Icons is a set of icons that is designed to be used with Microsoft's Fluent Design System. It is a collection of over 1,500 icons that are designed to be modern, consistent, and scalable, and can be used in a variety of applications and platforms, including web and mobile applications.
The Fluent System Icons set includes a range of icons, such as those for basic navigation, media playback, communication, and more. The icons are available in various sizes, from 16x16 to 512x512 pixels, and are provided in vector format, allowing for easy scaling and customization.
Fluent System Icons is available for free and can be downloaded from the official Microsoft website. It is also open source, meaning that developers can contribute to the icon set or create their own custom icons based on the existing ones.
Fluent UI System Icons
WPF UI uses Fluent UI System Icons in most of the graphical controls.
Getting started
Icons are displayed by using the font that comes with the library. All glyphs are mapped to the SymbolRegular and SymbolFilled enums.
Icon controls and fonts will be automatically added to your application if you add ControlsDictionary in the App.xaml file:
<Application
...
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml">
<Application.Resources>
<ui:ThemesDictionary Theme="Dark" />
<ui:ControlsDictionary />
</Application.Resources>
</Application>[!NOTE]
You can find out how the Control Dictionary works here
Segoe Fluent Icons
Not all icons available in WinUi 3 are in Fluent UI System Icons. Some of them require the Segoe Fluent Icons font.
According to the EULA of Segoe Fluent Icons we cannot ship a copy of it with this dll. Segoe Fluent Icons is installed by default on Windows 11, but if you want these icons in an application for Windows 10 and below, you must manually add the font to your application's resources.
https://docs.microsoft.com/en-us/windows/apps/design/style/segoe-fluent-icons-font
https://docs.microsoft.com/en-us/windows/apps/design/downloads/#fonts
In the App.xaml dictionaries, you can add an alternate path to the font
<Application
...
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml">
<Application.Resources>
<ui:ThemesDictionary Theme="Dark" />
<ui:ControlsDictionary />
<FontFamily x:Key="SegoeFluentIcons">pack://application:,,,/;component/Fonts/#Segoe Fluent Icons</FontFamily>
</Application.Resources>
</Application>Documentation Navigation View (`docs/documentation/navigation-view.md`)
NavigationView
NavigationView is a top-level navigation control that provides a collapsible navigation pane (the "hamburger menu") and a content area. It is the primary way to implement top-level navigation in your app.
[!TIP]
For a complete implementation example, see the WPF UI Gallery application.
Anatomy
The NavigationView control has several key areas:
- Pane: The area on the left or top that contains navigation items.
- Header: An area at the top of the content area, often used for a page title or a
BreadcrumbBar. - Content Area: The main area of the control where page content is displayed.
- AutoSuggestBox: An optional search box integrated into the navigation pane.
- MenuItems: The primary list of navigation items.
- FooterMenuItems: A secondary list of navigation items, typically for settings or about pages.
Basic Usage
Define NavigationView in your XAML and add NavigationViewItem objects to the MenuItems and FooterMenuItems collections.
<ui:NavigationView
xmlns:pages="clr-namespace:YourApp.Views.Pages"
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml">
<ui:NavigationView.MenuItems>
<ui:NavigationViewItem
Content="Home"
Icon="{ui:SymbolIcon Home24}"
TargetPageType="{x:Type pages:DashboardPage}" />
<ui:NavigationViewItem
Content="Data"
Icon="{ui:SymbolIcon DataHistogram24}"
TargetPageType="{x:Type pages:DataPage}" />
</ui:NavigationView.MenuItems>
<ui:NavigationView.FooterMenuItems>
<ui:NavigationViewItem
Content="Settings"
Icon="{ui:SymbolIcon Settings24}"
TargetPageType="{x:Type pages:SettingsPage}" />
</ui:NavigationView.FooterMenuItems>
</ui:NavigationView>[!NOTE]
TargetPageTypeis a required property onNavigationViewItemthat specifies the page to navigate to when the item is selected. The value must be aSystem.Type.
Programmatic Navigation
You can navigate programmatically by calling the Navigate method with either the Type of the page or its PageTag.
// Navigate by Type
MyNavigationView.Navigate(typeof(SettingsPage));
// Navigate by Tag
MyNavigationView.Navigate("settings");To use tags, you must define a PageTag on the NavigationViewItem. If not defined, a tag is automatically generated from the Content property (e.g., "Settings Page" becomes "settingspage").
<ui:NavigationViewItem
Content="Settings"
PageTag="settings"
TargetPageType="{x:Type pages:SettingsPage}" />Back Navigation
NavigationView automatically handles back navigation. The back button is shown when CanGoBack is true. You can also call GoBack() programmatically.
if (MyNavigationView.CanGoBack)
{
MyNavigationView.GoBack();
}Pane Display Mode
Control the visibility and behavior of the navigation pane with the PaneDisplayMode property.
Left: The pane is always open on the left.Top: The pane is shown as a horizontal bar at the top.LeftCompact: The pane is collapsed to show only icons, and expands on hover or when the hamburger button is clicked.LeftMinimal: The pane is hidden and can be opened as an overlay.
<ui:NavigationView PaneDisplayMode="Top" />You can also control the pane's open state with the IsPaneOpen property.
[!TIP]
To create a responsive layout that changesPaneDisplayModebased on window width, bindPaneDisplayModeto a property in your ViewModel and update it in theWindow.SizeChangedevent.
Header
The Header property provides a content area above the navigation frame. It is commonly used with a BreadcrumbBar to show the user's location.
<ui:NavigationView>
<ui:NavigationView.Header>
<ui:BreadcrumbBar />
</ui:NavigationView.Header>
</ui:NavigationView>The BreadcrumbBar will automatically sync with the NavigationView's navigation history.
MVVM Integration
For MVVM applications, it is recommended to use INavigationService and IPageService for navigation and page resolution.
1. Service Configuration
First, register the required services and your pages/ViewModels with your dependency injection container.
// Using Microsoft.Extensions.DependencyInjection
Host.CreateDefaultBuilder()
.ConfigureServices((context, services) =>
{
// Main window
services.AddScoped<IWindow, MainWindow>();
services.AddScoped<MainWindowViewModel>();
// Services
services.AddSingleton<INavigationService, NavigationService>();
services.AddSingleton<IPageService, PageService>();
// Pages and ViewModels
services.AddScoped<DashboardPage>();
services.AddScoped<DashboardViewModel>();
services.AddScoped<SettingsPage>();
services.AddScoped<SettingsViewModel>();
}).Build();2. ViewModel Setup
In your MainWindowViewModel, define collections for your navigation items and bind them to the NavigationView.
public partial class MainWindowViewModel : ObservableObject
{
[ObservableProperty]
private ICollection<object> _menuItems = new ObservableCollection<object>();
[ObservableProperty]
private ICollection<object> _footerMenuItems = new ObservableCollection<object>();
public MainWindowViewModel()
{
MenuItems = new ObservableCollection<object>
{
new NavigationViewItem("Home", SymbolRegular.Home24, typeof(DashboardPage)),
new NavigationViewItem("Data", SymbolRegular.DataHistogram24, typeof(DataPage))
};
FooterMenuItems = new ObservableCollection<object>
{
new NavigationViewItem("Settings", SymbolRegular.Settings24, typeof(SettingsPage))
};
}
}3. View Setup
In your MainWindow.xaml, bind the MenuItemsSource and FooterMenuItemsSource properties to the collections in your ViewModel. Then, attach the INavigationService.
<ui:NavigationView
x:Name="RootNavigationView"
MenuItemsSource="{Binding MenuItems}"
FooterMenuItemsSource="{Binding FooterMenuItems}" />public partial class MainWindow : IWindow
{
public MainWindow(
MainWindowViewModel viewModel,
INavigationService navigationService,
IPageService pageService
)
{
ViewModel = viewModel;
DataContext = this;
InitializeComponent();
// Attach the service to the NavigationView
navigationService.SetNavigationControl(RootNavigationView);
// You can also set the page service, which is required for some functionalities
RootNavigationView.SetPageService(pageService);
}
public MainWindowViewModel ViewModel { get; }
}4. Navigating from a ViewModel
Inject INavigationService into any ViewModel and use it to navigate.
public partial class DashboardViewModel : ObservableObject
{
private readonly INavigationService _navigationService;
public DashboardViewModel(INavigationService navigationService)
{
_navigationService = navigationService;
}
[RelayCommand]
private void OnGoToSettings()
{
_navigationService.Navigate(typeof(SettingsPage));
}
}Navigation Events
NavigationView provides several events to hook into the navigation lifecycle:
Navigating: Occurs before navigation starts. Can be cancelled.Navigated: Occurs after navigation is complete.SelectionChanged: Occurs when aNavigationViewItemis selected.
private void OnNavigating(NavigationView sender, NavigatingCancelEventArgs args)
{
// Don't navigate to settings if the user is not an admin
if (args.PageType == typeof(SettingsPage) && !_isAdmin)
{
args.Cancel = true;
}
}Navigation-Aware Pages
Implement INavigationAware on your page's code-behind or INavigableView<T> on your ViewModel to receive navigation events directly.
INavigationAware
This interface is ideal for code-behind scenarios.
public partial class MyPage : INavigationAware
{
public void OnNavigatedTo()
{
// Page was navigated to
}
public void OnNavigatedFrom()
{
// Page was navigated away from
}
}`INavigableView`
This interface is designed for MVVM. Your page must inherit from INavigableView<T> where T is its ViewModel. The ViewModel will then receive the navigation calls.
Page:
[GalleryPage("My Page", SymbolRegular.Page24)]
public partial class MyPage : INavigableView<MyViewModel>
{
public MyViewModel ViewModel { get; }
public MyPage(MyViewModel viewModel)
{
ViewModel = viewModel;
DataContext = this;
InitializeComponent();
}
}ViewModel:
public partial class MyViewModel : ObservableObject, INavigationAware
{
public void OnNavigatedTo()
{
// Page was navigated to
}
public void OnNavigatedFrom()
{
// Page was navigated away from
}
}[!IMPORTANT]
ForINavigableView<T>to work, your page must have a publicViewModelproperty that returns an instance of the ViewModel.
History and Caching
NavigationView maintains a navigation history.
History: A collection ofPageinstances that have been visited.CacheHistory: The number of pages to keep in memory. The default is0. Set to a value greater than 0 to cache pages. When a cached page is navigated to, its previous state is preserved.
<ui:NavigationView CacheHistory="5" />[!CAUTION]
Caching pages increases memory consumption. Use it only for pages that are expensive to create or where preserving state is critical. Avoid caching pages that display frequently changing data.
Documentation Nuget (`docs/documentation/nuget.md`)
NuGet package for WPF UI
What's NuGet?
NuGet is a free, open-source package management system for the Microsoft .NET platform. It simplifies the process of finding, installing, and managing third-party libraries and tools in .NET projects. NuGet allows developers to easily add functionality to their projects without having to manually download and reference external libraries. It also provides a way for developers to publish and share their own packages with the .NET community. Overall, NuGet makes it easier to manage dependencies in .NET projects and helps to improve productivity for developers.
- Read more here
- How to install NuGet packages
- Install and manage packages in Visual Studio using the NuGet Package Manager
How to install **WPF UI** in Visual Studio using the NuGet Package Manager?
1. Create new WPF project
2. Open _Manage NuGet Packages_ window via solution explorer
3. In the _Browse_ tab, enter "**WPF-UI**"
4. Install **WPF-UI** package
Done!
Package installed, to learn more, go to the Getting started page.
Documentation Releases (`docs/documentation/releases.md`)
WPF UI Releases
| Version | Is supported |
|---|---|
| 3.0.0 | Yes |
Documentation Symbolicon (`docs/documentation/symbolicon.md`)
SymbolIcon
SymbolIcon is a control responsible for rendering icons.
Implementation
class Wpf.Ui.Controls.SymbolIconExposes
// Gets or sets displayed symbol
SymbolIcon.Symbol = SymbolRegular.Empty;// Defines whether or not we should use the SymbolFilled
SymbolIcon.Filled = false;// Icon foreground
SymbolIcon.Foreground = Brushes.White;// Icon size
SymbolIcon.FontSize = 16;How to use
<ui:SymbolIcon
Symbol="Fluent24"
Filled="False"
FontSize="16"
Foreground="White"/>Documentation System Theme Watcher (`docs/documentation/system-theme-watcher.md`)
SystemThemeWatcher
SystemThemeWatcher automatically synchronizes the application's theme, accent color, and window backdrop with the current Windows theme settings. It listens for system-level changes and applies them to your application in real-time.
[!TIP]
The simplest way to enable system theme tracking is to callSystemThemeWatcher.Watch(this);in your main window's constructor.
How It Works
SystemThemeWatcher attaches a hook to a window's message procedure (WndProc) and listens for the WM_WININICHANGE system message. When Windows broadcasts this message (e.g., when the user changes from light to dark mode), the watcher automatically calls ApplicationThemeManager.ApplySystemTheme() to update your application's appearance.
Basic Usage
Enable theme watching in your window's constructor. This will use the default Mica backdrop and update accent colors.
using Wpf.Ui.Appearance;
public partial class MainWindow : System.Windows.Window
{
public MainWindow()
{
// This will apply the system theme, accent, and default backdrop.
SystemThemeWatcher.Watch(this);
InitializeComponent();
}
}Customization
You can customize the backdrop effect and control whether the accent color is updated.
Window Backdrop
Specify the WindowBackdropType to apply when the theme changes.
SystemThemeWatcher.Watch(
this,
WindowBackdropType.Acrylic // Use Acrylic backdrop
);Available backdrop types:
None: No backdrop effect.Auto: Automatically selects the appropriate backdrop.Mica: The default Windows 11 Mica effect.Acrylic: The semi-transparent Acrylic effect.Tabbed: A blurred wallpaper effect available in recent Windows 11 versions.
Accent Color Updates
You can prevent the watcher from changing the application's accent color.
SystemThemeWatcher.Watch(
this,
updateAccents: false // Theme will change, but accent color will not
);Stop Watching
To stop a window from responding to system theme changes, use the UnWatch method.
SystemThemeWatcher.UnWatch(this);[!IMPORTANT]
Do not callUnWatchon a window that has not been loaded, as it will throw anInvalidOperationException. It is safe to callWatchon a window that is not yet loaded.
Dependency Injection Usage
If you are resolving your main window from a dependency injection container, you can start the watcher after the host is built.
var host = Host.CreateDefaultBuilder()
.ConfigureServices(services =>
{
services.AddHostedService<ApplicationHostService>();
services.AddSingleton<MainWindow>();
// ... other services
}).Build();
await host.StartAsync();
var mainWindow = host.Services.GetRequiredService<MainWindow>();
// Watch the window after it's been created
SystemThemeWatcher.Watch(mainWindow);[!NOTE]
SystemThemeWatcherworks on a static, global basis. While you canWatchmultiple windows, the theme and accent settings applied will be the same for all of them based on the parameters of the lastWatchcall that triggered an update.
Documentation Themes (`docs/documentation/themes.md`)
Application Themes
WPF UI provides a robust theming system that allows you to control your application's appearance, including support for light, dark, and high contrast modes. The ApplicationThemeManager class is the primary tool for managing themes at runtime.
[!IMPORTANT]
For theme changes to apply correctly, your colors and brushes should be referenced asDynamicResource.
Setting the Initial Theme
The easiest way to set the initial theme is by using the ThemesDictionary in your App.xaml. This ensures that the correct theme resources are loaded at startup.
<Application
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ui:ThemesDictionary Theme="Light" />
<ui:ControlsDictionary />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>The Theme property on ThemesDictionary can be set to Light or Dark.
Changing the Theme at Runtime
Use the ApplicationThemeManager.Apply() method to change the theme while the application is running.
using Wpf.Ui.Appearance;
using Wpf.Ui.Controls;
// Apply the Light theme with a Mica backdrop
ApplicationThemeManager.Apply(
ApplicationTheme.Light,
WindowBackdropType.Mica
);`ApplicationTheme` Enum
This enum specifies the theme to apply:
Light: The standard light theme.Dark: The standard dark theme.HighContrast: Automatically selects the appropriate Windows High Contrast theme.
System Theme Integration
You can synchronize your application's theme with the current Windows theme settings.
One-Time Sync
To apply the current system theme once, use ApplySystemTheme().
// Apply the current Windows theme (light or dark)
ApplicationThemeManager.ApplySystemTheme();Automatic Tracking
For continuous synchronization, use the SystemThemeWatcher. It automatically updates your app's theme and accent color when the user changes their Windows settings. See the SystemThemeWatcher documentation for more details.
using Wpf.Ui.Appearance;
public partial class MainWindow : System.Windows.Window
{
public MainWindow()
{
// Watch for system theme changes
SystemThemeWatcher.Watch(this);
InitializeComponent();
}
}Reading the Current Theme
You can get the current application and system themes at any time.
ApplicationThemeManager.GetAppTheme(): Returns the currentApplicationTheme(Light, Dark, or HighContrast).ApplicationThemeManager.GetSystemTheme(): Returns the currentSystemTheme.
ApplicationTheme currentAppTheme = ApplicationThemeManager.GetAppTheme();
SystemTheme currentSystemTheme = ApplicationThemeManager.GetSystemTheme();
if (currentAppTheme == ApplicationTheme.Dark)
{
// ...
}`SystemTheme` Enum
This enum represents the actual theme reported by Windows, including decorative themes like Glow, CapturedMotion, and Sunrise. ApplicationThemeManager maps these to either Light or Dark.
High Contrast Themes
WPF UI automatically handles Windows High Contrast themes. When a high contrast mode is detected, ApplicationThemeManager loads the appropriate high contrast resource dictionary (HC1, HC2, HCBlack, or HCWhite).
ApplicationThemeManager.IsHighContrast(): Checks if the application is currently in a high contrast theme.ApplicationThemeManager.IsSystemHighContrast(): Checks if Windows is currently in a high contrast mode.
Theme Changed Event
The ApplicationThemeManager.Changed event is triggered whenever the application's theme is successfully changed.
ApplicationThemeManager.Changed += (currentTheme, currentAccent) =>
{
Debug.WriteLine($"Theme changed to {currentTheme} with accent {currentAccent}");
};This event is useful for applying custom logic after a theme change, such as updating graphics or non-WPF UI elements.
[!TIP]
TheChangedevent is fired by both manualApply()calls and automatic updates fromSystemThemeWatcher, providing a single place to react to any theme change.
Migration V2 Migration (`docs/migration/v2-migration.md`)
Migration plan
This page outlines key changes and important details to consider when migrating. It highlights what’s new, what’s changed, and any steps you need to take to ensure a smooth transition. This isn’t a full step-by-step guide but a quick reference to help you navigate the most critical parts of the migration process.
Key changes in v2
Global namespace change
In version 2.0 the namespace has been changed from WPF-UI to the more .NET compatible Wpf.Ui. The package name has not been changed to maintain branding and consistency.
Navigation
The navigation control has been rewritten yet again, making it almost completely incompatible.
All navigation controls inherit from Wpf.Ui.Controls.Navigation.NavigationBase base class.
<ui:NavigationStore
Frame="{Binding ElementName=RootFrame}"
Precache="False"
SelectedPageIndex="-1"
TransitionDuration="200"
TransitionType="FadeInWithSlide">
<ui:NavigationStore.Items>
<ui:NavigationItem
Cache="True"
Content="Home"
Icon="Home24"
PageTag="dashboard"
PageType="{x:Type pages:Dashboard}" />
<ui:NavigationSeparator />
</ui:NavigationStore.Items>
<ui:NavigationStore.Footer>
<ui:NavigationItem
Click="NavigationButtonTheme_OnClick"
Content="Theme"
Icon="DarkTheme24" />
</ui:NavigationStore.Footer>
</ui:NavigationStore><ui:NavigationFluent
Frame="{Binding ElementName=RootFrame}"
Precache="False"
SelectedPageIndex="-1"
TransitionDuration="200"
TransitionType="FadeInWithSlide">
<ui:NavigationFluent.Items>
<ui:NavigationItem
Cache="True"
Content="Home"
Icon="Home24"
PageTag="dashboard"
PageType="{x:Type pages:Dashboard}" />
<ui:NavigationSeparator />
</ui:NavigationFluent.Items>
<ui:NavigationFluent.Footer>
<ui:NavigationItem
Click="NavigationButtonTheme_OnClick"
Content="Theme"
Icon="DarkTheme24" />
</ui:NavigationFluent.Footer>
</ui:NavigationFluent><ui:NavigationCompact
Frame="{Binding ElementName=RootFrame}"
Precache="False"
SelectedPageIndex="-1"
TransitionDuration="200"
TransitionType="FadeInWithSlide">
<ui:NavigationCompact.Items>
<ui:NavigationItem
Cache="True"
Content="Home"
Icon="Home24"
PageTag="dashboard"
PageType="{x:Type pages:Dashboard}" />
<ui:NavigationSeparator />
</ui:NavigationCompact.Items>
<ui:NavigationCompact.Footer>
<ui:NavigationItem
Click="NavigationButtonTheme_OnClick"
Content="Theme"
Icon="DarkTheme24" />
</ui:NavigationCompact.Footer>
</ui:NavigationCompact>Titlebar
The titlebar control has been rewritten yet again, making it almost completely incompatible.
Migration V3 Migration (`docs/migration/v3-migration.md`)
Migration plan
This page outlines key changes and important details to consider when migrating. It highlights what’s new, what’s changed, and any steps you need to take to ensure a smooth transition. This isn’t a full step-by-step guide but a quick reference to help you navigate the most critical parts of the migration process.
Navigation
All navigation controls have been merged into one NavigationView, inspired by Win UI.
Icons
All icons are based on the new IconElement control. They replaced icons in TitleBar, NavigationView, Button and other controls.
Control Gallery
Inspired by Win UI Controls Gallery, a new application for testing and browsing controls was created -** WPF UI Gallery**. It replaced the Demo app.
Dialogs
ContentDialog, MessageBox and Snackbar have been modified, their interfaces are not fully compatible.
Migration V4 Migration (`docs/migration/v4-migration.md`)
Migration plan
This page outlines key changes and important details to consider when migrating. It highlights what’s new, what’s changed, and any steps you need to take to ensure a smooth transition. This isn’t a full step-by-step guide but a quick reference to help you navigate the most critical parts of the migration process.
Abstractions package
Some WPF UI interfaces have been moved to a standalone package WPF-UI.Abstractions. You don't need to reference it, it will always be added automatically with WPF-UI NuGet package.
Navigation interfaces
Navigation interfaces have been moved to a standalone WPF-UI.Abstractions package. This way, if you have models, views or other business services in another project, not related to WPF, you can develop them together for multiple applications.
New namespaces
INavigationAware and INavigableView have beed moved to Wpf.Ui.Abstractions.Controls namespace.
Dependency injection based page creation
IPageService have been renamed to INavigationViewPageProvider.
Its default implementation is in the new Wpf.Ui.DependencyInjection package. You just need to use the services.AddNavigationViewPageProvider() extension and then indicate in the navigation that you want to use this interface. Then NavigationView will use DI container for pages creation.
Program.cs
var builder = Host.CreateDefaultBuilder();
builder.Services.AddNavigationViewPageProvider();MyWindow.xaml.cs
var pageProvider = serviceProvider.GetRequiredService<INavigationViewPageProvider>();
// NavigationControl is x:Name of our NavigationView defined in XAML.
NavigationControl.SetPageProviderService(pageProvider)Navigation service
The INavigationService defined in the main package (WPF-UI) makes navigation management easy. You can use it for convenient injection between view models. We HIGHLY recommend it to be Singleton.
var builder = Host.CreateDefaultBuilder();
builder.Services.AddNavigationViewPageProvider();
builder.Services.AddSingleton<INavigationService, NavigationService>();Templates .Stylelintrc.Json (`docs/templates/.stylelintrc.json`)
{
"extends": "stylelint-config-standard-scss",
"ignoreFiles": [
"**/*.ts"
],
"rules": {
"selector-class-pattern": null,
"font-family-no-missing-generic-family-keyword": [ true, {
"ignoreFontFamilies": [
"bootstrap-icons"
]
}]
}
}
Templates Package.Json (`docs/templates/package.json`)
{
"name": "@docfx/template",
"version": "1.0.0",
"description": "Docfx static website templates",
"keywords": [
"docfx",
"template"
],
"author": "docfx",
"license": "MIT",
"browserslist": [
"defaults"
],
"scripts": {
"build": "node build.js",
"lint": "eslint wpfui/src && stylelint wpfui/src"
},
"dependencies": {
"@default/anchor-js": "npm:[email protected]",
"@default/bootstrap": "npm:[email protected]",
"@default/highlight.js": "npm:[email protected]",
"@default/lunr": "npm:[email protected]",
"@default/mark.js": "npm:[email protected]",
"@default/twbs-pagination": "josecebe/twbs-pagination#1.3.1",
"@default/url": "npm:@websanova/[email protected]",
"@websanova/url": "^2.6.3",
"anchor-js": "^5.0.0",
"bootstrap": "^5.3.1",
"bootstrap-icons": "^1.10.5",
"highlight.js": "^11.8.0",
"jquery": "3.7.0",
"lit-html": "^2.8.0",
"lunr": "2.3.9",
"mathjax": "^3.2.2",
"mermaid": "^10.3.0"
},
"devDependencies": {
"@types/jest": "^29.5.3",
"@typescript-eslint/eslint-plugin": "^6.2.1",
"@typescript-eslint/parser": "^6.2.1",
"browser-sync": "^2.29.3",
"esbuild": "~0.18.17",
"esbuild-sass-plugin": "~2.10.0",
"eslint": "^8.46.0",
"eslint-config-standard": "^17.1.0",
"jest": "^29.6.2",
"stylelint": "^15.10.2",
"stylelint-config-standard-scss": "^10.0.0",
"ts-jest": "^29.1.1",
"typescript": "^5.1.6",
"yargs": "^17.7.2"
}
}
Templates Tsconfig.Json (`docs/templates/tsconfig.json`)
{
"compilerOptions": {
"removeComments": true,
"resolveJsonModule": true,
"esModuleInterop": true
}
}
Architecture Cross Cutting Navigation (`docs/architecture/cross-cutting/navigation.md`)
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.
/* Detailed source-code truncated for AI context efficiency. */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.
stateDiagram-v2
[*] --> PageRequested: Navigate(pageType)
state PageRequested {
[*] --> CheckCacheMode
state CheckCacheMode <<choice>>
CheckCacheMode --> CacheDisabled: Disabled
CheckCacheMode --> CacheEnabled: Enabled
CheckCacheMode --> CacheRequired: Required
state CacheDisabled {
[*] --> CreateNew_D: Always create new instance
CreateNew_D --> ReturnPage_D: Return new page
}
state CacheEnabled {
[*] --> LookupCache_E: Check cache
LookupCache_E --> ReturnCached_E: Cache hit
LookupCache_E --> CreateAndCache_E: Cache miss
CreateAndCache_E --> ReturnPage_E: Store and return
ReturnCached_E --> ReturnPage_E: Return cached
}
state CacheRequired {
[*] --> LookupCache_R: Check cache
LookupCache_R --> ReturnCached_R: Cache hit (guaranteed after first)
LookupCache_R --> CreateAndCache_R: First request only
CreateAndCache_R --> ReturnPage_R: Store and return
ReturnCached_R --> ReturnPage_R: Return cached
}
}
PageRequested --> PageDisplayed: Page resolved
PageDisplayed --> [*]Cache Mode Comparison
| Mode | First Visit | Subsequent Visits | Page State | Use Case |
|---|---|---|---|---|
| Disabled | New instance | New instance | Lost on navigate away | Forms, transient views |
| Enabled | New instance | Cached instance (if available) | Preserved while cached | Dashboard, lists |
| Required | New instance | Always cached instance | Always preserved | Settings, stateful views |
Key Components
NavigationService
Thin wrapper around INavigationView that provides a service-oriented API for navigation. Registered in DI as INavigationService.
Key methods:
Navigate(Type pageType)— Navigate to a page by typeNavigate(string pageTag)— Navigate to a page by tagGoBack()— Navigate to the previous page in the back stackSetNavigationControl(INavigationView)— Bind to a NavigationView instance
INavigationViewPageProvider
Abstraction for page instance resolution. Two implementations:
DependencyInjectionNavigationViewPageProvider(fromWpf.Ui.DependencyInjection) — resolves pages viaIServiceProvider- 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 viewOnNavigatedFrom()— 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:
// In Program.cs or Startup
services.AddNavigationViewPageProvider<DependencyInjectionNavigationViewPageProvider>();
services.AddSingleton<INavigationService, NavigationService>();
// Pages registered in DI
services.AddTransient<DashboardPage>();
services.AddTransient<SettingsPage>();The DependencyInjectionNavigationViewPageProvider resolves pages from the DI container, respecting their registered lifetime (Transient, Scoped, Singleton).
Back Stack
NavigationView maintains an internal back stack of previously visited page types. The GoBack() operation pops the most recent entry and navigates to it. The back stack is cleared when navigating to a page that is already in the stack (cycle prevention).
Design Considerations
- Static vs DI navigation: Simple apps can use
NavigationServicedirectly; 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.
NavigationServicedoes not marshal calls.
Architecture Cross Cutting Testing (`docs/architecture/cross-cutting/testing.md`)
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
[Fact]
public void ApplyTransition_ReturnsFalse_WhenDurationIsLessThan10()Alternative pattern: GivenX_Method_ExpectedResult
[Fact]
public void GivenAllRegularSymbols_Swap_ReturnsValidFilledSymbol()Integration Tests
Pattern: Subject_ShouldExpectedBehavior_WhenCondition
[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
graph TD
A["tests/"] --> B["Wpf.Ui.UnitTests/"]
A --> C["Wpf.Ui.Gallery.IntegrationTests/"]
B --> B1["Wpf.Ui.UnitTests.csproj<br/><i>net10.0-windows</i>"]
B --> B2["Animations/"]
B --> B3["Extensions/"]
B --> B4["GlobalUsings.cs"]
B2 --> B2a["TransitionAnimationProviderTests.cs"]
B3 --> B3a["SymbolExtensionsTests.cs"]
C --> C1["Wpf.Ui.Gallery.IntegrationTests.csproj<br/><i>net10.0-windows10.0.26100.0</i>"]
C --> C2["Fixtures/"]
C --> C3["WindowTests.cs"]
C --> C4["TitleBarTests.cs"]
C --> C5["NavigationTests.cs"]
C --> C6["ContentDialogAutomationTests.cs"]
C --> C7["xunit.runner.json"]
C2 --> C2a["UiTest.cs <i>(base class)</i>"]
C2 --> C2b["TestedApplication.cs <i>(app lifecycle)</i>"]
D["src/Wpf.Ui.FlaUI/"] --> D1["AutoSuggestBox.cs<br/><i>Custom FlaUI element</i>"]
style A fill:#f5f5f5,stroke:#333
style B fill:#e3f2fd,stroke:#1565c0
style C fill:#e8f5e9,stroke:#2e7d32
style D fill:#fff3e0,stroke:#e65100Test 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, XunitSource: tests/Wpf.Ui.UnitTests/GlobalUsings.cs
Integration Tests
Integration tests use a custom infrastructure built on FlaUI:
TestedApplication(IAsyncLifetime): Launches and manages the Gallery.exeprocess. Finds the executable in the test output directory. UsesUIA3Automationfor UI element discovery.UiTest(abstract base class,IAsyncLifetime): Provides helper methods for all UI tests:FindFirst(string automationId)-- finds UI elements by automation IDFindFirst(Func<ConditionFactory, ConditionBase>)-- finds by conditionWait(int seconds)-- async delay for UI settlingEnter(string value)-- simulates keyboard text inputPress(VirtualKeyShort)-- simulates a key press
Integration Test Runner Configuration
Tests run sequentially (no parallel test collections) with invariant culture:
{
"parallelizeTestCollections": false,
"diagnosticMessages": true,
"culture": "invariant"
}Source: tests/Wpf.Ui.Gallery.IntegrationTests/xunit.runner.json
Run Commands
# 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.
Architecture Cross Cutting Theming And Appearance (`docs/architecture/cross-cutting/theming-and-appearance.md`)
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_DWMCOLORIZATIONCOLORCHANGEDWM_THEMECHANGEDWM_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/:
- Light.xaml - Light theme color scheme
- Dark.xaml - Dark theme color scheme
- HC1.xaml - High contrast theme variant 1
- HC2.xaml - High contrast theme variant 2
- HCBlack.xaml - High contrast black theme
- 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
// 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
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 AppearanceUsage Examples
Basic Theme Application
// Apply dark theme
ApplicationThemeManager.Apply(ApplicationTheme.Dark);
// Get current theme
ApplicationTheme current = ApplicationThemeManager.GetAppTheme();Automatic OS Synchronization
public MainWindow()
{
InitializeComponent();
// Enable automatic theme synchronization
Appearance.SystemThemeWatcher.Watch(this);
}Custom Accent Color
// Apply custom accent color
Color myAccent = Color.FromRgb(0, 120, 215);
ApplicationAccentColorManager.Apply(
myAccent,
ApplicationTheme.Dark,
systemGlassColor: false,
systemAccentColor: true
);XAML Theme Selection
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ui:ThemesDictionary Theme="Dark" />
<ui:ControlsDictionary />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>Design Considerations
Static Singleton Pattern
The theme managers use static class design for simple, globally-accessible APIs. This pattern trades testability for API simplicity and ensures single-instance theme state across the application.
Runtime Resource Swapping
Theme changes occur via runtime resource dictionary replacement rather than restart-required configuration. This enables live theme switching without application restart.
OS Integration
Deep integration with Windows theme system via WndProc message hooks and WinRT UISettings ensures automatic synchronization with user preferences.
Multi-Version Support
Theme system gracefully degrades on older Windows versions, falling back to registry-based accent color detection when WinRT APIs are unavailable.
Architecture Cross Cutting Win32 Interop (`docs/architecture/cross-cutting/win32-interop.md`)
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
/* Detailed source-code truncated for AI context efficiency. */Three-Layer Architecture
Layer 1: CsWin32 Source Generation
The project uses Microsoft's CsWin32 source generator to produce type-safe P/Invoke bindings at compile time.
- Configuration:
NativeMethods.txtlists the Win32 functions and types needed by the library. - Generated namespace:
Windows.Win32 - Foundation types:
HWND,HRESULT,BOOLfromWindows.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:
- Check that the handle is not
IntPtr.Zero - Verify the handle via
PInvoke.IsWindow() - Call the native API
- Catch any exception and return
falseornull
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.
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:
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.
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.
Handle validation is mandatory. Every wrapper method must validate that the
HWNDis non-zero and represents a valid window before calling any native API. This prevents access violations from stale or recycled handles.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:
SetWindowLongPtrdoes not exist as a distinct entry point on 32-bit Windows. The customPInvoke.cshandles this by routing toSetWindowLongon x86 andSetWindowLongPtrWon x64. - Windows 10 vs 11: Many DWM attributes (e.g.,
DWMWA_SYSTEMBACKDROP_TYPE) are only available on Windows 11 (build 22000+). TheUtilitiesclass gates these calls. - COM activation:
ITaskbarList4andIUISettings3require COM activation. These are wrapped to handleCOMExceptiongracefully.
Architecture Decisions ADR 001 Multi Target Framework (`docs/architecture/decisions/ADR-001-multi-target-framework.md`)
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
-windowsTFM 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:
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Microsoft.Windows.CsWin32" Version="0.3.242" />
<PackageVersion Include="System.Memory" Version="4.6.3" />
</ItemGroup>
</Project>Individual projects reference packages without version attributes:
<PackageReference Include="Microsoft.Windows.CsWin32" />Benefits:
- Single source of truth for package versions
- Prevents version conflicts across projects
- Simplifies dependency updates
Conditional Compilation
Framework Detection
#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
#endifFramework-Specific Dependencies
<PackageReference Include="System.ValueTuple" Condition="'$(TargetFramework)' == 'net462'" />
<PackageReference Include="System.Drawing.Common" Condition="'$(TargetFramework)' != 'net462'" />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:
<ItemGroup>
<CompilerVisibleProperty Include="PolySharpExcludeGeneratedTypes" />
</ItemGroup>
<PropertyGroup>
<PolySharpExcludeGeneratedTypes>
System.Runtime.CompilerServices.OverloadResolutionPriorityAttribute;
System.Diagnostics.CodeAnalysis.UnscopedRefAttribute
</PolySharpExcludeGeneratedTypes>
</PropertyGroup>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:
<LangVersion>14.0</LangVersion>Note:
Wpf.Ui.csprojoverrides this to<LangVersion>preview</LangVersion>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
- All package versions in
Directory.Packages.propsonly — Central Package Management is the single source of truth for NuGet versions - Use
-windowsTFM suffix for projects with WPF dependencies (e.g.,net10.0-windows, notnet10.0) - Use
netstandard2.0/netstandard2.1for abstraction-only packages that have no WPF or Windows dependency - Guard framework-specific code with
#if NET{X}_0_OR_GREATERdirectives — never use runtime version checks for compile-time API differences - Use PolySharp for C# polyfills — never hand-write polyfill classes for language features (e.g.,
IsExternalInit,CallerArgumentExpression) - Set
LangVersioninDirectory.Build.props— override in individual.csprojonly when justified (currently onlyWpf.Ui.csprojoverrides topreview)
MUST NOT Do
- Never add
Versionattribute toPackageReferencein.csprojfiles — all versions must be inDirectory.Packages.props - 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
- Never use
#ifwith specific patch versions (e.g.,NET8_0_10) — only use_OR_GREATERsuffixed symbols - 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:
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>inDirectory.Build.propscauses build errors ifVersionis 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
#ifdirectives for framework-specific code - Testing Burden: Features should be tested on multiple framework versions
Implementation Details
Directory.Build.props
Central build properties defined once:
<PropertyGroup>
<Version>4.2.0</Version>
<LangVersion>14.0</LangVersion>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>Conditional Property Groups
<PropertyGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">
<DefineConstants>$(DefineConstants);NET8_0_OR_GREATER</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">
<DefineConstants>$(DefineConstants);BELOW_NET8</DefineConstants>
</PropertyGroup>References
Architecture Decisions ADR 002 Control Library Architecture (`docs/architecture/decisions/ADR-002-control-library-architecture.md`)
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.xamlBenefits:
- 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:
- {ControlName}.cs - Control class with code-behind
- {ControlName}.xaml - ResourceDictionary with implicit style
Control Class Pattern:
// Controls/Button/Button.cs
namespace Wpf.Ui.Controls; // Flat namespace
// ReSharper disable once CheckNamespace
public class Button : System.Windows.Controls.Button, IAppearanceControl, IIconControl
{
static Button()
{
DefaultStyleKeyProperty.OverrideMetadata(
typeof(Button),
new FrameworkPropertyMetadata(typeof(Button))
);
}
// Dependency properties
public static readonly DependencyProperty IconProperty =
DependencyProperty.Register(nameof(Icon), ...);
}XAML Style Pattern:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:Wpf.Ui.Controls">
<Thickness x:Key="ButtonPadding">11,5,11,6</Thickness>
<Style TargetType="{x:Type controls:Button}">
<Setter Property="OverridesDefaultStyle" Value="True" />
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:Button}">
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>Flat Namespace Strategy
All controls use a single namespace: Wpf.Ui.Controls
// Physical path: Controls/Button/Button.cs
namespace Wpf.Ui.Controls; // NOT Wpf.Ui.Controls.Button
// ReSharper disable once CheckNamespace // Suppress warningRationale:
- 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:
/// <summary>Identifies the <see cref="Icon"/> dependency property.</summary>
public static readonly DependencyProperty IconProperty = DependencyProperty.Register(
nameof(Icon),
typeof(IconElement),
typeof(Button),
new PropertyMetadata(null, OnIconChanged, IconElement.Coerce)
);CLR Wrapper:
[Bindable(true)]
[Category("Appearance")]
public IconElement? Icon
{
get => (IconElement?)GetValue(IconProperty);
set => SetValue(IconProperty, value);
}Callback Pattern:
private static void OnIconChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is Button button)
{
button.UpdateIconVisibility();
}
}Capability Interfaces
Controls implement interfaces for cross-cutting capabilities:
IAppearanceControl
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
public interface IIconControl
{
IconElement? Icon { get; set; }
}Used by: Button, NavigationViewItem, AutoSuggestBox
IThemeControl
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
- Each control in its own subfolder under
Controls/{ControlName}/ - Paired .cs + .xaml files with matching names
- Flat namespace
Wpf.Ui.Controlsfor all controls - Static constructor with
DefaultStyleKeyProperty.OverrideMetadata - Implicit style in XAML with
TargetType(nox:Key) OverridesDefaultStyle=Truein all control stylesSnapsToDevicePixels=Truein all control styles- XML documentation with `` and
<example>for public API
MUST NOT Do
- Never nest controls in subdirectories (keep flat under Controls/)
- Never use different namespace from
Wpf.Ui.Controls - Never create keyed styles as primary style (use implicit TargetType)
- 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
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.Controlsnamespace - WinUI 3 Control Architecture: Flat namespace strategy
- WPF Control Authoring
Architecture Decisions ADR 003 Win32 Interop Via Cswin32 (`docs/architecture/decisions/ADR-003-win32-interop-via-cswin32.md`)
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:
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
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
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 detectionsrc/Wpf.Ui/Appearance/- Theme managerssrc/Wpf.Ui/Controls/FluentWindow/- Window chromesrc/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.
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.
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 failuresWin32Exception- Native API errorsEntryPointNotFoundException- API not available on OS versionDllNotFoundException- DLL not present
Conditional Compilation
Framework-Specific Code
#if NET5_0_OR_GREATER
// Modern API available
var version = Environment.OSVersion;
#else
// Fallback for .NET Framework
var version = GetVersionFromRegistry();
#endifOS Version Feature Gating
// 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
- Add new Win32 APIs to NativeMethods.txt (never manual P/Invoke unless CsWin32 fails)
- Validate handles before all native calls (IntPtr.Zero + IsWindow)
- Return bool from wrapper methods indicating success
- Suppress exceptions in interop layer for compatibility
- Use unsafe keyword explicitly for pointer operations
- Feature-gate by OS version for version-specific APIs
- Use HRESULT == S_OK pattern for success checking
- Keep generated code private (internal/private visibility)
MUST NOT Do
- Never call PInvoke directly from high-level code (use UnsafeNativeMethods wrappers)
- Never skip handle validation (even if "guaranteed" valid)
- Never throw exceptions from interop wrappers (return false instead)
- Never assume API availability across Windows versions
- Never use var for native types (HRESULT, HWND, etc. - explicit types required)
- 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_*andHT*) - 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
Add to NativeMethods.txt:
textDwmGetColorizationColorRebuild project (CsWin32 generates code)
Create managed wrapper in UnsafeNativeMethods.cs:
csharppublic 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; } }Consume from high-level code:
csharpif (UnsafeNativeMethods.GetColorizationColor(out Color color)) { // Use color }
References
Architecture Decisions ADR 004 Static Managers For Theming (`docs/architecture/decisions/ADR-004-static-managers-for-theming.md`)
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:
- Static classes (global singleton)
- Instance-based services (registered in DI container)
- Ambient context pattern (ThreadStatic/AsyncLocal)
Decision
Use static class singleton pattern for core theme managers:
ApplicationThemeManagerApplicationAccentColorManagerSystemThemeWatcherWindowBackgroundManagerResourceDictionaryManager(internal)
Implementation Pattern
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
public static class ApplicationThemeManager
{
// All members are static
// Cannot be instantiated
// Cannot be inherited
// Cannot be mocked/substituted
}Rationale
Simple API Surface
// Immediate clarity of global scope
ApplicationThemeManager.Apply(ApplicationTheme.Dark);
var current = ApplicationThemeManager.GetAppTheme();Compared to instance-based:
// 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
// Works immediately without setup
public MainWindow()
{
InitializeComponent();
ApplicationThemeManager.Apply(ApplicationTheme.Dark);
}Instance-based would require:
// App.xaml.cs
services.AddSingleton<IThemeManager, ThemeManager>();
// 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 (
ApplicationThemeManagerclearly 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
IThemeServicefor 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.Resourcesis 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:
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:
services.AddSingleton<IThemeService, ThemeService>();Usage in testable code:
public class SettingsViewModel
{
private readonly IThemeService _themeService;
public SettingsViewModel(IThemeService themeService)
{
_themeService = themeService;
}
public void ApplyDarkMode()
{
_themeService.SetTheme(ApplicationTheme.Dark);
}
}Testing with mock:
[Fact]
public void ApplyDarkMode_CallsThemeService()
{
// Arrange
var mockThemeService = Substitute.For<IThemeService>();
var viewModel = new SettingsViewModel(mockThemeService);
// Act
viewModel.ApplyDarkMode();
// Assert
mockThemeService.Received(1).SetTheme(ApplicationTheme.Dark);
}UiApplication Pattern
UiApplication uses [ThreadStatic] for thread-local singleton:
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
Applicationparameter - Uses
[ThreadStatic]backing field_uiApplicationfor thread-local singleton - Each UI thread gets its own
UiApplicationinstance - Safely wraps
Application.Current(which is also thread-local) - Allows future expansion with per-thread state
Enforcement
MUST Follow
Use static managers directly for simple scenarios:
csharpApplicationThemeManager.Apply(ApplicationTheme.Dark);Use IThemeService in testable/DI-dependent code:
csharppublic ViewModel(IThemeService themeService) { }Document global nature in XML docs:
csharp/// <summary> /// Global theme manager. Applies themes application-wide. /// </summary>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
Never attempt to mock static classes in unit tests
- Use
IThemeServiceinstead if testing is needed
- Use
Never cache theme state in instance fields
- Always query
ApplicationThemeManager.GetAppTheme()for current state
- Always query
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
IThemeServiceprovides tested alternative where needed
Testing Strategy
Integration Tests
Theme system is tested through integration tests that exercise full stack:
[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:
[Fact]
public void ApplyDarkTheme_UpdatesCurrentTheme()
{
var themeService = Substitute.For<IThemeService>();
var viewModel = new SettingsViewModel(themeService);
viewModel.ApplyDarkTheme();
themeService.Received().SetTheme(ApplicationTheme.Dark);
}Documentation Requirements
All static manager classes include XML doc warning:
/// <summary>
/// Global static manager for application theming.
/// </summary>
/// <remarks>
/// <para>
/// This is a static class managing process-wide theme state.
/// Theme changes affect all windows in the application.
/// </para>
/// <para>
/// For testable code, use <see cref="IThemeService"/> instead.
/// </para>
/// </remarks>
public static class ApplicationThemeManager
{
// ...
}Future Considerations
Potential Migration to Instances
If future requirements demand it, can migrate while maintaining compatibility:
// 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
Architecture Decisions ADR 005 Feature Folder Controls (`docs/architecture/decisions/ADR-005-feature-folder-controls.md`)
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.csOne control = one folder with all related files.
Structure Patterns
Simple Controls
Pattern: Single .cs + .xaml pair
Badge/
├── Badge.cs # Control implementation
└── Badge.xaml # Implicit styleApplies 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 + templatePartial 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 interfaceRationale: 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.csWhen 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)
// File: Controls/NavigationView/NavigationView.cs
namespace Wpf.Ui.Controls; // Flat namespace, not Wpf.Ui.Controls.NavigationView
// ReSharper disable once CheckNamespaceTrade-off: Folder structure does not match namespace.
IDE Configuration Required:
# .editorconfig
dotnet_diagnostic.IDE0130.severity = none # Suppress namespace/folder mismatchPartial 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 concernXAML Styles
{ControlName}.xaml # Implicit style ResourceDictionarySupporting Types
{TypePurpose}{ControlName}.cs # e.g., NavigationViewItem
{ControlName}{Purpose}.cs # e.g., ContentDialogHost
I{ControlName}.cs # InterfaceEvent Arguments
{ControlName}{EventName}EventArgs.csExamples:
ContentDialogButtonClickEventArgs.csNavigatedEventArgs.cs
Enforcement
MUST Follow
One control = one folder under
Controls/Paired .cs + .xaml with matching names
Flat namespace
Wpf.Ui.Controlsfor all controlsPartial class naming
{ControlName}.{Concern}.csReSharper suppress comment when namespace doesn't match folder:
csharpnamespace Wpf.Ui.Controls; // ReSharper disable once CheckNamespaceKeep related types together in same folder when tightly coupled
MUST NOT Do
Never nest control folders (keep flat under Controls/)
text❌ Controls/Buttons/Button/ ✅ Controls/Button/Never use category-specific namespace
csharp❌ namespace Wpf.Ui.Controls.Buttons; ✅ namespace Wpf.Ui.Controls;Never split files arbitrarily (must have clear separation of concerns)
Never create more than 2 directory levels under Controls/
text✅ Controls/ContentDialog/EventArgs/ ❌ Controls/ContentDialog/EventArgs/Closing/
Verification
- IDE0130 suppressed in .editorconfig for namespace/folder mismatch
- Code review checks folder organization
- Naming conventions enforced during PR review
Benefits
Developer Experience
Easy to Find:
# Looking for Button control?
Controls/Button/Button.cs # Immediately obvious locationClear 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.xamlRejected:
- 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
- Create folder
Controls/{NewControl}/ - Add
{NewControl}.cswith control class - Add
{NewControl}.xamlwith implicit style - Add ReSharper suppress comment to .cs file
Splitting Existing Control
Example: Card becomes too large
Before:
Card/
├── Card.cs (500 lines)
└── Card.xamlAfter:
Card/
├── Card.Base.cs (200 lines - core logic)
├── Card.Properties.cs (100 lines - dependency properties)
├── Card.Animation.cs (100 lines - animation logic)
└── Card.xamlRefactoring steps:
- Extract dependency properties to
Card.Properties.cs - Extract animation logic to
Card.Animation.cs - Keep core logic in
Card.Base.cs - All files use
partial class Card
Documentation
Control Folder README
Each complex control folder includes README.md:
# 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 interfaceReferences
- Feature Folders in ASP.NET (similar pattern)
- Vertical Slice Architecture
Architecture Views Context (`docs/architecture/views/context.md`)
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
/* Detailed source-code truncated for AI context efficiency. */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:
- Installing NuGet packages --
WPF-UI(core), optionallyWPF-UI.DependencyInjection,WPF-UI.Tray,WPF-UI.Abstractions - Adding XAML resource dictionaries --
andinApp.xaml - Using controls in XAML -- Via the
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"namespace prefix - 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
flowchart LR
Dev["Developer Machine"]
NuGet["NuGet.org"]
GH["GitHub Actions CD"]
GH -->|"nuget push *.nupkg<br/>Strong-name signed<br/>SourceLink embedded"| NuGet
NuGet -->|"dotnet add package WPF-UI<br/>Downloads .nupkg + .snupkg"| Dev
style Dev fill:#e1f5ff,stroke:#0277bd
style NuGet fill:#fff4e1,stroke:#f57f17
style GH fill:#e8f5e9,stroke:#2e7d32Win32 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 / User32XAML Resource Loading
Consumer App.xaml
|
| <ui:ThemesDictionary Theme="Dark"/>
| <ui:ControlsDictionary/>
|
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 ApplicationAccentColorManagerTheme 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)Architecture Views Logical Architecture (`docs/architecture/views/logical-architecture.md`)
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.
/* Detailed source-code truncated for AI context efficiency. */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.
/* Detailed source-code truncated for AI context efficiency. */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<T>, RelayCommand<T> -- 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:
CsWin32 auto-generation (
NativeMethods.txtlists 35 Win32 functions/types). TheMicrosoft.Windows.CsWin32source generator produces P/Invoke signatures in theWindows.Win32namespace. Covers DWM (DwmSetWindowAttribute, DwmIsCompositionEnabled), User32 (SetWindowLong, GetDpiForWindow, GetForegroundWindow), Shell32 (ITaskbarList4), and associated structs/enums.Managed wrappers (
Interop/):UnsafeNativeMethods.cs-- safe wrappers with handle validation (IntPtr.Zerocheck +PInvoke.IsWindow) before calling CsWin32-generated methods. Methods likeApplyWindowCornerPreference,ApplyBorderColor,RemoveWindowTitlebarContents.PInvoke.cs-- manual[DllImport]forSetWindowLongPtr(not generated by CsWin32 for all overloads).UnsafeReflection.cs-- type casting helpers for enum-to-Win32-struct conversion.
OS utilities (
Win32/):Utilities.cs-- OS version detection (Vista, Windows 7, 8, 10, 11 build checks), DWM composition availability, system theme detection viaIUISettings3COM 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.
/* Detailed source-code truncated for AI context efficiency. */Control Authoring Recipe
Every WPF UI control follows a consistent authoring pattern:
1. C# class file (Controls/{Name}/{Name}.cs):
// 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):
<ResourceDictionary ...>
<Style TargetType="{x:Type controls:MyControl}">
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="OverridesDefaultStyle" Value="True" />
<Setter Property="Background" Value="{DynamicResource ControlFillColorDefaultBrush}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:MyControl}">
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>3. Registration in master dictionary (Resources/Wpf.Ui.xaml):
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Wpf.Ui;component/Controls/MyControl/MyControl.xaml" />
</ResourceDictionary.MergedDictionaries>Key invariants:
OverridesDefaultStyle="True"andSnapsToDevicePixels="True"are set on every control style.- Theme-dependent brushes always use
DynamicResource(notStaticResource) so they update when themes switch at runtime. - The static constructor calling
DefaultStyleKeyProperty.OverrideMetadataensures 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
ObservableObjectand implementINavigationAware - Pages implement
INavigableView<TViewModel>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:
- Direct instantiation for simple apps:
var service = new NavigationService(); - DI registration for hosted apps:
services.AddSingleton(); - The
ControlsServices.Initialize(IServiceProvider)static method enables control-level service resolution.
Async Dialog Pattern
ContentDialog.ShowAsync() returns Task<ContentDialogResult> 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
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.
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<INavigationService, NavigationService>()
Host->>SC: services.AddSingleton<IContentDialogService, ContentDialogService>()
Host->>SC: services.AddSingleton<ISnackbarService, SnackbarService>()
Host->>Ext: services.AddNavigationViewPageProvider<DI_PageProvider>()
Ext->>SC: Register INavigationViewPageProvider
Host->>SC: services.AddTransient<DashboardPage>()
Host->>SC: services.AddTransient<SettingsPage>()
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 page7. Control Lifecycle
WPF UI controls follow the standard WPF element lifecycle with additional steps for Fluent Design theming and Win32 interop integration.
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 noteLifecycle 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.
--- METRICS ---
- Files Extracted: 52
- Estimated Token Budget: ~69409 tokens
- Canonical Reference: https://codewiki.google/github.com/lepoco/wpfui