33 KiB
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:
<!--
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.
Based on Microsoft XAML for Win UI
Copyright (c) Microsoft Corporation. All Rights Reserved.
-->
2.5 XML Documentation
Public APIs require <summary> 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>:
/// <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">
<!-- Constants use StaticResource -->
<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 <summary> 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) |