Resources & Resource Dictionaries
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production; see the section bibliography for the reference material consulted while preparing these pages. |
A XAML resource is any object, usually a brush, a color, a style, a template or a converter, that is declared once under a key and then reused from many places. Resources are how a WPF app gets one visual identity instead of hundreds of copy-pasted attribute values, and they are the mechanism behind light and dark themes. This page was written against .NET 10 LTS (latest patch 10.0.13) with C# 14, Visual Studio 2026 18.11.1, Windows App SDK 2.5.1 and CommunityToolkit.Mvvm 8.4.2 on Windows 10 and Windows 11. .NET 11 is at release candidate 2 (11.0.0-rc.2) and general availability is expected at .NET Conf, 10 to 13 November 2026; the .NET 11 WPF release notes list bug fixes only at the time of writing, so nothing on this page changes for it. WPF is the stack in focus; WinUI 3 and Windows Forms are compared in a table further down.
The running scenario is Bookshelf Desktop, with a BooksViewModel, an IBookRepository and an IDialogService
registered in the Generic Host. Here those pieces are used to show where colors and styles live, how the app switches
between a light and a dark theme at run time, and how a shared control library contributes a dictionary to the app.
The XAML language itself (elements, attributes, type converters) is covered on the page titled XAML Language Fundamentals, and the markup extensions as syntax (including {Binding} and {x:Static}) on Markup Extensions & Namescopes; this page concentrates on what a resource is and how it is found. Styles, templates and triggers are the
most common resources and have their own pages later in the section.
What a resource is
Every FrameworkElement and FrameworkContentElement has a Resources property of type ResourceDictionary, and so
does Application. A ResourceDictionary is a dictionary from a key to an object that can be filled in XAML (elements
with an x:Key) or in code. A resource is looked up when a markup extension such as {StaticResource Key} or
{DynamicResource Key} asks for it, or when code calls FindResource. Because the dictionary can hold any .NET
object, the same mechanism serves very different purposes:
| Kind of resource | Typical key | Why it is a resource |
|---|---|---|
Colors and brushes |
|
One definition drives every window, and a theme change is one swap. |
Styles |
|
Sets of property values shared by many controls. |
Data templates |
a |
Tells WPF how to draw a view model type, for example a |
Converters and other helpers |
|
Instances created once and reused by bindings. |
Geometry, fonts, thicknesses, doubles |
|
Design tokens, so spacing and sizes are consistent. |
Images and URIs |
|
A single place for assets that several windows use. |
Data sources, |
|
Declarative sorting and grouping next to the markup that uses it. |
The rule that makes resources valuable is that a key is resolved at the point of use, walking outward from the element. That walk is described next.
Resource lookup order
When a reference is resolved, WPF searches for the key in a fixed order and stops at the first match:
-
the
Resourcesof the element that has the property being set; -
the
Resourcesof its logical parent, then of that parent’s parent, and so on up to the root element (aWindowor aPage); -
the
Resourcesof theApplication(Application.Current.Resources), which includes everything merged into it; -
the theme resource dictionaries (the control library theme files such as
Themes/Generic.xamland the OS-theme-specific dictionaries WPF ships); -
the system resources:
SystemColors,SystemFontsandSystemParameters.
If nothing matches, a static reference fails when the XAML loads, and a dynamic reference leaves the property at its default value.
Three consequences follow, and the examples below rely on them:
-
Nearest wins. A resource defined on a
Gridshadows a resource with the same key on theWindowor inApp.xaml, but only for the elements inside thatGrid. This is the supported way to override a theme for one region. -
The logical tree is walked, not the visual tree. Content inside a
ContextMenu,ToolTiporPopupis not a visual child of the element that opened it; WPF compensates for the common cases, but a resource that works in a window and not in a popup is a sign that the lookup is not reaching the dictionary you expect. Declare shared resources at application level to avoid the question. -
A dictionary’s own entries beat its merged dictionaries, and among merged dictionaries the last one listed wins. That rule is the basis of theming by swapping a merged dictionary (see below).
Source: XAML resources overview
StaticResource and DynamicResource
Both markup extensions return the resource stored under a key. The difference is when.
| Aspect | {StaticResource Key} |
{DynamicResource Key} |
|---|---|---|
When it is resolved |
Once, while the XAML is loaded. |
At run time, and again whenever the resource found under the key is replaced. |
Forward references |
Not allowed. The key must be defined earlier in the file (or in an outer scope), so order matters inside a dictionary. |
Allowed. The resource may be defined later or may not exist yet. |
Reacts to a changed resource |
No. The element keeps the object it got at load time. |
Yes. Replacing the entry in the dictionary, or swapping a merged dictionary, updates the element. |
Where it can be used |
As the value of any property or of a resource entry. |
Only as the value of a dependency property (or of a |
Cost |
Cheapest: a lookup at load, nothing afterwards. |
A reference object is kept per use and re-evaluated on change, which adds memory and work when used thousands of times. |
Missing key |
|
No exception; the property keeps its default value. |
Typical use in Bookshelf |
Fixed design tokens, converters, and |
Anything a theme can change: surface, text and border brushes, and system colors. |
The practical rule is: use StaticResource by default, and DynamicResource for values that must follow a theme, a
system setting (such as high contrast) or a runtime change. A StaticResource placed in a style can still point at a
resource that a theme replaces later, but it will keep pointing at the old object, which is the most common reason a
"half-themed" window appears after a theme swap.
Both extensions accept any resource key, not only strings. The keys WPF itself uses are listed here:
| Key form | Example and meaning |
|---|---|
String |
|
Type |
|
Data type |
A |
Static member |
|
Component key |
|
Resource options
A few attributes and rules apply to the entries themselves:
| Setting | Behavior |
|---|---|
|
Required for every entry in a |
|
|
Freezing |
Brushes, geometries and other |
|
A URI to a XAML file that is merged or loaded as the dictionary. To combine a file with local entries, use
|
Order of definition |
For |
Source: XAML resources overview
Merged dictionaries
Putting every resource in App.xaml does not scale. A merged dictionary lets a dictionary include other
dictionaries from separate XAML files. The files can then be organized by purpose, edited independently and reused by
other projects.
In the Bookshelf app the resources are split into four files with a clear dependency order:
-
Resources/Colors.xaml: the fixed palette (accent, danger, success), asColorvalues; -
Resources/Brushes.xaml: fixed brushes built from the palette; -
Resources/Styles.xaml: implicit and keyed styles, which use both fixed brushes and theme brushes; -
Themes/Light.xamlandThemes/Dark.xaml: the theme-dependent brushes, introduced in the theme section.
The three base files are shown first. Colors has no dependencies:
<!-- Resources/Colors.xaml -->
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Color x:Key="Bookshelf.Color.Accent">#0F6CBD</Color>
<Color x:Key="Bookshelf.Color.OnAccent">#FFFFFF</Color>
<Color x:Key="Bookshelf.Color.Danger">#C42B1C</Color>
<Color x:Key="Bookshelf.Color.Success">#107C10</Color>
</ResourceDictionary>
Source: XAML resources overview
Brushes uses the colors. A merged dictionary is loaded in isolation, so a StaticResource inside Brushes.xaml can only
see keys defined earlier in the same file or in the dictionaries merged into that file. It does not see a sibling
that App.xaml merges. The safe pattern is to merge the dependency inside the file that needs it:
<!-- Resources/Brushes.xaml -->
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Colors.xaml" />
</ResourceDictionary.MergedDictionaries>
<SolidColorBrush x:Key="Bookshelf.Brush.Accent" Color="{StaticResource Bookshelf.Color.Accent}" />
<SolidColorBrush x:Key="Bookshelf.Brush.OnAccent" Color="{StaticResource Bookshelf.Color.OnAccent}" />
<SolidColorBrush x:Key="Bookshelf.Brush.Danger" Color="{StaticResource Bookshelf.Color.Danger}" />
<SolidColorBrush x:Key="Bookshelf.Brush.Success" Color="{StaticResource Bookshelf.Color.Success}" />
</ResourceDictionary>
Source: Merged resource dictionaries
Styles refers to brushes the theme will supply later, so it uses DynamicResource for those and StaticResource for
the fixed ones:
<!-- Resources/Styles.xaml -->
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Brushes.xaml" />
</ResourceDictionary.MergedDictionaries>
<!-- Implicit style: applies to every TextBlock below this dictionary -->
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="{DynamicResource Bookshelf.Brush.OnSurface}" />
<Setter Property="TextTrimming" Value="CharacterEllipsis" />
</Style>
<Style x:Key="Bookshelf.Style.Heading" TargetType="TextBlock" BasedOn="{StaticResource {x:Type TextBlock}}">
<Setter Property="FontSize" Value="20" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="Margin" Value="0,0,0,8" />
</Style>
<Style TargetType="Button">
<Setter Property="Padding" Value="12,6" />
<Setter Property="Background" Value="{DynamicResource Bookshelf.Brush.Surface}" />
<Setter Property="Foreground" Value="{DynamicResource Bookshelf.Brush.OnSurface}" />
<Setter Property="BorderBrush" Value="{DynamicResource Bookshelf.Brush.Border}" />
</Style>
<Style x:Key="Bookshelf.Style.PrimaryButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Background" Value="{StaticResource Bookshelf.Brush.Accent}" />
<Setter Property="Foreground" Value="{StaticResource Bookshelf.Brush.OnAccent}" />
<Setter Property="BorderBrush" Value="{StaticResource Bookshelf.Brush.Accent}" />
</Style>
</ResourceDictionary>
Source: Styles and templates
App.xaml merges the files, and the order again matters, because the last dictionary wins on a duplicate key:
<!-- App.xaml -->
<Application x:Class="Bookshelf.Desktop.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- No StartupUri: the main window is created by the Generic Host in OnStartup. -->
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!-- Slot 0: the theme. Replaced at run time by ThemeService. -->
<ResourceDictionary Source="Themes/Light.xaml" />
<ResourceDictionary Source="Resources/Brushes.xaml" />
<ResourceDictionary Source="Resources/Styles.xaml" />
</ResourceDictionary.MergedDictionaries>
<!-- Entries here beat everything merged above. -->
<Thickness x:Key="Bookshelf.Spacing.Medium">12</Thickness>
</ResourceDictionary>
</Application.Resources>
</Application>
Source: Merged resource dictionaries
Two details are worth stating plainly. First, each reference to a merged dictionary creates its own instance of that
dictionary. In the example Brushes.xaml is loaded once for App.xaml and once more through Styles.xaml, so the
brushes exist twice, as two different objects. That is harmless here, but a large dictionary merged from fifty
UserControl files is parsed fifty times; merge heavy dictionaries once at application level and let the controls find
them by lookup. Second, the relative Source="Colors.xaml" in these files is resolved against the file that contains
it, which works inside one assembly; across assemblies a pack URI is required (below).
The ResourceDictionary API
| Member | Use |
|---|---|
|
Loads the dictionary from a XAML file URI (relative, or a pack URI). |
|
A |
Indexer, |
Dictionary access by key; in code, the key is any object (a string, a |
|
Enumerate the entries of this dictionary only; merged entries are not included. |
|
Indicates whether the dictionary rejects changes. |
Light and dark themes with DynamicResource
The theme-dependent brushes live in two dictionaries that define the same keys with different values. At any moment
exactly one of them is merged into the application, in slot 0. Every style and control that refers to these keys
through DynamicResource repaints when the dictionary is replaced.
<!-- Themes/Light.xaml -->
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<SolidColorBrush x:Key="Bookshelf.Brush.Surface" Color="#FFFFFF" />
<SolidColorBrush x:Key="Bookshelf.Brush.SurfaceAlt" Color="#F3F5F7" />
<SolidColorBrush x:Key="Bookshelf.Brush.OnSurface" Color="#1F2328" />
<SolidColorBrush x:Key="Bookshelf.Brush.Muted" Color="#57606A" />
<SolidColorBrush x:Key="Bookshelf.Brush.Border" Color="#D0D7DE" />
</ResourceDictionary>
Source: DynamicResource markup extension
<!-- Themes/Dark.xaml : same keys, different values -->
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<SolidColorBrush x:Key="Bookshelf.Brush.Surface" Color="#1F2328" />
<SolidColorBrush x:Key="Bookshelf.Brush.SurfaceAlt" Color="#2B3138" />
<SolidColorBrush x:Key="Bookshelf.Brush.OnSurface" Color="#E6EDF3" />
<SolidColorBrush x:Key="Bookshelf.Brush.Muted" Color="#9DA7B3" />
<SolidColorBrush x:Key="Bookshelf.Brush.Border" Color="#3D444D" />
</ResourceDictionary>
Source: DynamicResource markup extension
The main window uses only DynamicResource for theme brushes, and StaticResource for the fixed ones. The data context is
a BooksViewModel supplied by the host, so bindings use {Binding} (WPF has no compiled x:Bind):
<!-- MainWindow.xaml -->
<Window x:Class="Bookshelf.Desktop.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Bookshelf Desktop" Width="900" Height="560"
Background="{DynamicResource Bookshelf.Brush.Surface}">
<Grid Margin="{StaticResource Bookshelf.Spacing.Medium}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<TextBlock Style="{StaticResource Bookshelf.Style.Heading}" Text="Books" />
<TextBox Width="220" Margin="16,0,0,8" Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}"
Background="{DynamicResource Bookshelf.Brush.SurfaceAlt}"
Foreground="{DynamicResource Bookshelf.Brush.OnSurface}"
BorderBrush="{DynamicResource Bookshelf.Brush.Border}" />
<Button Margin="8,0,0,8" Content="Add book" Command="{Binding AddBookCommand}"
Style="{StaticResource Bookshelf.Style.PrimaryButton}" />
<Button Margin="8,0,0,8" Content="Toggle theme" Command="{Binding ToggleThemeCommand}" />
</StackPanel>
<DataGrid Grid.Row="1" ItemsSource="{Binding Books}" AutoGenerateColumns="False" IsReadOnly="True"
Background="{DynamicResource Bookshelf.Brush.SurfaceAlt}"
Foreground="{DynamicResource Bookshelf.Brush.OnSurface}"
BorderBrush="{DynamicResource Bookshelf.Brush.Border}">
<DataGrid.Columns>
<DataGridTextColumn Header="Title" Binding="{Binding Title}" Width="*" />
<DataGridTextColumn Header="Author" Binding="{Binding Author}" Width="200" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
Source: DynamicResource markup extension
The swapping itself is ordinary C#. The ThemeService replaces the dictionary in slot 0 by index, so the precedence
of the merged list is unchanged, and it can read the Windows app-mode preference for the initial value. It is a user
pattern, not an API: Microsoft Learn documents merged dictionaries but has no page that recommends a runtime theme
switching recipe for WPF.
using System.Windows;
using Microsoft.Win32;
namespace Bookshelf.Desktop.Services;
public enum AppTheme { Light, Dark }
public interface IThemeService
{
AppTheme Current { get; }
void Apply(AppTheme theme);
AppTheme ReadSystemPreference();
}
public sealed class ThemeService : IThemeService
{
private const int ThemeSlot = 0;
private static readonly Uri LightUri = new("Themes/Light.xaml", UriKind.Relative);
private static readonly Uri DarkUri = new("Themes/Dark.xaml", UriKind.Relative);
public AppTheme Current { get; private set; } = AppTheme.Light;
public void Apply(AppTheme theme)
{
var merged = Application.Current.Resources.MergedDictionaries;
var next = new ResourceDictionary { Source = theme == AppTheme.Dark ? DarkUri : LightUri };
if (merged.Count > ThemeSlot)
{
merged[ThemeSlot] = next; // replace in place: precedence order is preserved
}
else
{
merged.Insert(ThemeSlot, next);
}
Current = theme;
}
public AppTheme ReadSystemPreference()
{
// 0 means the user chose dark mode for apps; a missing value means light.
const string key = @"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize";
using var personalize = Registry.CurrentUser.OpenSubKey(key);
return personalize?.GetValue("AppsUseLightTheme") is 0 ? AppTheme.Dark : AppTheme.Light;
}
}
Source: Merged resource dictionaries
The view model drives it with an MVVM Toolkit command. BooksViewModel depends on the interfaces, never on
Application, so it can be unit tested with fakes:
using System.Collections.ObjectModel;
using Bookshelf.Core;
using Bookshelf.Desktop.Services;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
namespace Bookshelf.Desktop.ViewModels;
public sealed partial class BooksViewModel : ObservableObject
{
private readonly IBookRepository _books;
private readonly IDialogService _dialogs;
private readonly IThemeService _theme;
public BooksViewModel(IBookRepository books, IDialogService dialogs, IThemeService theme)
{
_books = books;
_dialogs = dialogs;
_theme = theme;
_theme.Apply(_theme.ReadSystemPreference());
}
public ObservableCollection<BookViewModel> Books { get; } = [];
[ObservableProperty]
public partial string? SearchText { get; set; }
[RelayCommand]
private void ToggleTheme() =>
_theme.Apply(_theme.Current == AppTheme.Light ? AppTheme.Dark : AppTheme.Light);
[RelayCommand]
private async Task AddBookAsync()
{
var created = await _dialogs.EditBookAsync(null);
if (created is not null)
{
var saved = await _books.AddAsync(created, CancellationToken.None);
Books.Add(new BookViewModel(saved));
}
}
}
Source: RelayCommand attribute
The service is registered with the other services; the full host setup belongs to the page titled WPF Architecture & Application Model:
services.AddSingleton<IThemeService, ThemeService>();
services.AddSingleton<BooksViewModel>();
services.AddSingleton<MainWindow>();
Source: Merged resource dictionaries
Things this pattern requires to work:
-
every themed value is reached through
DynamicResource. A singleStaticResourceon a theme brush keeps the old color after the swap; -
both theme files define exactly the same keys. A key missing from one file leaves the previous brush behind;
-
the whole dictionary is replaced, which produces a resource-changed notification for every key it contains;
-
the high contrast mode of Windows has its own palette; check
SystemParameters.HighContrastand use theSystemColorsbrushes in that case instead of the custom theme.
Built-in theme support in .NET 9 and 10
Since .NET 9, WPF also ships a Fluent theme (the PresentationFramework.Fluent assembly) and a ThemeMode
property on Window and Application with the values None, System, Light and Dark. The feature was introduced
as experimental in .NET 9, which means it is guarded by the WPF0001 diagnostic and the API surface can still change,
so check the What’s new in WPF page of the version you target before depending on it. The Bookshelf ThemeService
above works on every supported version and is independent of it. Microsoft Learn describes ThemeMode in the API
reference and the .NET 9 and 10 what’s-new pages, but has no Fluent theming deep dive; the custom brushes of this page
are not themed by ThemeMode.
Sources: ThemeMode and What’s new in WPF for .NET 9
Pack URIs
A resource that lives in a different file, or a different assembly, is identified by a pack URI. WPF supports the
pack scheme with two authorities:
| Authority | Meaning | Example |
|---|---|---|
|
Files that are part of the application, compiled into it or shipped with it. |
|
|
A loose file at the location the application was started from (or, for a downloaded app, the site it came from). The file is not part of the assembly. |
|
Within the application authority, three path forms exist:
| Form | Meaning |
|---|---|
|
A resource in the local assembly. In XAML attributes such as |
|
A resource in a referenced assembly ( |
|
The same, restricted to a version of the referenced assembly (a public key token can follow the version for strong-named assemblies). |
The commas in pack://application:,,,/ are the URI-escaped form of the slashes of application:///. A pack URI in code
must be created as an absolute URI, new Uri("pack://application:,,,/Resources/Styles.xaml", UriKind.Absolute), because
the pack scheme has to be registered first; touching Application.Current (or constructing any WPF Application)
does that. View-model tests should not depend on pack URIs, because they need the WPF runtime.
Source: Pack URIs in WPF
Build actions: Resource, Content, Embedded resource
How a file is built decides how it is reached at run time. In SDK-style WPF projects (Microsoft.NET.Sdk with
<UseWPF>true</UseWPF>), .xaml files are included automatically, so most XAML needs no configuration; images and
data files do.
| Build action | What happens | How it is reached | Typical use |
|---|---|---|---|
|
The XAML is compiled to BAML and stored in the assembly resources. It is the default for |
A relative or |
Windows, user controls, resource dictionaries. |
|
The file is embedded in the assembly (in the |
A pack URI such as |
Images, fonts, small assets that must travel with the assembly. |
|
The file is not embedded; it is copied beside the executable (set |
The same pack URI form as |
Large or replaceable files (sample data, templates, documents). |
|
The file is embedded in the assembly as a manifest resource, but not in the WPF resource stream. |
Not reachable with a pack URI. Use |
Localized strings, binary blobs read by code. |
|
The file is ignored by the build. |
Not available at run time. |
Documentation, design sources. |
In the project file:
<!-- Bookshelf.Desktop.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net10.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>14</LangVersion>
</PropertyGroup>
<ItemGroup>
<!-- XAML files are Page / ApplicationDefinition by default: no entry needed. -->
<Resource Include="Assets\Images\*.png" /> <!-- compiled into the assembly -->
<Content Include="Assets\Samples\books.json"> <!-- copied next to the exe -->
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<EmbeddedResource Include="Assets\Licenses\THIRD-PARTY.txt" /> <!-- manifest resource -->
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Bookshelf.Controls\Bookshelf.Controls.csproj" />
</ItemGroup>
</Project>
Source: Pack URIs in WPF
Choosing between them is mostly about trust and updates. Resource files are signed and versioned with the assembly and
cannot be tampered with without breaking it, but they enlarge the assembly and are loaded into memory. Content files
can be replaced after deployment, which is useful for sample data and unwanted for anything the app trusts. A
siteoforigin file is outside the assembly and the package entirely, so treat its content as untrusted input.
Resources in code
Anything that XAML does with markup extensions has a code equivalent, found on FrameworkElement and Application:
| API | Behavior |
|---|---|
|
Runs the full lookup order from |
|
The same lookup, returning |
|
Binds a dependency property to a resource key dynamically, exactly like |
|
Reads or writes only that element dictionary; does not walk the tree and does not find merged or application resources of parents. |
|
The application dictionary, including everything merged into it. Use it for global access and for swapping merged dictionaries. |
|
Lookup starting at the application level (application, theme, system). |
|
Load a compiled XAML component, or open a stream for a |
A small control from the Bookshelf details pane shows the options. A status badge picks its brush from the application resources in code, registers a dynamic reference for the border, and loads a placeholder cover by pack URI:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace Bookshelf.Desktop.Views;
public sealed class BookStatusBadge : Border
{
private static readonly Uri PlaceholderCover =
new("pack://application:,,,/Assets/Images/cover-placeholder.png", UriKind.Absolute);
public BookStatusBadge(bool isOverdue)
{
Padding = new Thickness(8, 2, 8, 2);
CornerRadius = new CornerRadius(4);
// 1. A one-time lookup: the badge keeps this brush even if the theme changes later.
// TryFindResource avoids an exception when a resource dictionary is missing a key.
var key = isOverdue ? "Bookshelf.Brush.Danger" : "Bookshelf.Brush.Success";
Background = TryFindResource(key) as Brush ?? Brushes.Gray;
// 2. A dynamic reference: equivalent to BorderBrush="{DynamicResource Bookshelf.Brush.Border}"
SetResourceReference(BorderBrushProperty, "Bookshelf.Brush.Border");
Child = new Image
{
Width = 16,
Height = 16,
Source = new BitmapImage(PlaceholderCover), // build action: Resource
};
}
// Reading the global dictionary, for example to show a design token in a dialog.
public static Thickness MediumSpacing =>
Application.Current.Resources["Bookshelf.Spacing.Medium"] is Thickness t ? t : new Thickness(8);
}
Two code-side pitfalls matter in practice. FindResource and TryFindResource need the element to be connected
to the logical tree (or at least to have its resources reachable); calling them in a constructor before the element is
loaded only sees the element own dictionary and the application, which is why this example uses application-level
keys. And a value read once, as in the first case above, is not dynamic: use SetResourceReference (or set the
property in XAML with DynamicResource) whenever the value must follow a theme.
Source: XAML resources overview
Sharing dictionaries across assemblies
Shared design resources belong in a class library, so that Bookshelf Desktop, a future admin tool and the tests can all
use them. The library, Bookshelf.Controls, is a WPF class library whose dictionaries are compiled as Page items and
therefore stored in its assembly.
<!-- Bookshelf.Controls/Bookshelf.Controls.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
<!-- Bookshelf.Controls/Resources/Shared.xaml -->
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style x:Key="Bookshelf.Style.Card" TargetType="Border">
<Setter Property="Padding" Value="12" />
<Setter Property="CornerRadius" Value="8" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="BorderBrush" Value="{DynamicResource Bookshelf.Brush.Border}" />
<Setter Property="Background" Value="{DynamicResource Bookshelf.Brush.SurfaceAlt}" />
</Style>
</ResourceDictionary>
Source: Styles and templates
The application references the project and merges the dictionary with the AssemblyName;component form. The library
dictionary refers to theme brushes with DynamicResource on purpose: it does not define them, and the host application
supplies them (a StaticResource there would fail to load, because the keys are not in the library):
<!-- Bookshelf.Desktop/App.xaml : merged section only -->
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Themes/Light.xaml" />
<ResourceDictionary Source="Resources/Brushes.xaml" />
<ResourceDictionary Source="Resources/Styles.xaml" />
<ResourceDictionary Source="/Bookshelf.Controls;component/Resources/Shared.xaml" />
</ResourceDictionary.MergedDictionaries>
Source: Pack URIs in WPF
<!-- BookDetailsView.xaml uses the shared style -->
<Border Style="{StaticResource Bookshelf.Style.Card}">
<StackPanel>
<TextBlock Style="{StaticResource Bookshelf.Style.Heading}" Text="{Binding Title}" />
<TextBlock Text="{Binding Author}" />
</StackPanel>
</Border>
Source: Pack URIs in WPF
The same file can be loaded from code by its absolute pack URI, which is how a plug-in or a theme editor adds a dictionary at run time:
var shared = new ResourceDictionary
{
Source = new Uri("pack://application:,,,/Bookshelf.Controls;component/Resources/Shared.xaml", UriKind.Absolute),
};
Application.Current.Resources.MergedDictionaries.Add(shared);
Source: Merged resource dictionaries
Library authors have a second, different mechanism: a custom control library puts its default styles in
Themes/Generic.xaml, declares [assembly: ThemeInfo(ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)]
and WPF finds them at the "theme" step of the lookup, with no merge in the app. A shared dictionary like the one above is
for styles the application applies explicitly or implicitly to standard controls; theme files are for the default look
of controls the library defines. The choice between a UserControl and a templated custom control is on the page titled
User Controls & Custom Controls.
Resources across the Windows UI stacks
The concepts transfer to the other stacks, but the details differ. WinUI 3 uses the same idea with its own markup extensions, and Windows Forms has no XAML resources at all.
| Aspect | WPF | WinUI 3 (Windows App SDK) | Windows Forms |
|---|---|---|---|
Dictionary type |
|
|
None. |
Static reference |
|
|
Not applicable ( |
Reacts to a change |
|
|
Set properties again in code, or use |
Theme variants in one dictionary |
No. Use two dictionaries and swap them, as above. |
Yes: |
Not applicable. |
Merge another file |
|
|
Not applicable. |
Built-in control styles |
Aero2 or Fluent ( |
|
System renderer. |
Implicit styles |
Yes, by type. |
Yes, by type. |
No; subclasses or code. |
Application dictionary |
|
|
|
Build action for images |
|
|
|
Windows Forms supports a dark mode through Application.SetColorMode: experimental (diagnostic WFO5001) in .NET 9
and stable in .NET 10, where it no longer needs a NoWarn. It is documented in the Windows Forms what’s-new pages; Microsoft Learn has no equivalent of a resource dictionary there. Third-party
frameworks such as Avalonia (labelled third-party) follow the WPF model with their own resource dictionary classes and
URI schemes; this site does not cover them. The WinUI XAML model is introduced on the page titled WinUI 3 & the Windows App SDK, and the differences in markup between the XAML dialects on XAML Dialects: WPF, WinUI & MAUI.
From .NET Framework to .NET 8, 9 and 10
Resource dictionaries, pack URIs and the lookup rules are unchanged between .NET Framework 4.x and modern .NET, so existing XAML moves as it is. What changes is around them:
| Area | Change |
|---|---|
Project format |
Old projects list every file; SDK-style projects include |
Assembly references |
|
.NET 8 and 9 |
Both leave support on 2026-11-10. .NET 9 added the experimental Fluent theme and |
.NET 10 |
LTS until 2028-11-14. Resource handling is unchanged. See the .NET 10 WPF what’s-new page for incremental improvements. |
.NET 11 |
Release candidate 2; the WPF what’s-new page lists bug fixes only, no resource changes. |
Trimming and Native AOT |
Not supported for WPF and Windows Forms. This matters here because pack URIs and BAML loading depend on reflection and on resource names, which trimmers cannot analyze. |
Pitfalls, performance and security
-
A
StaticResourcethat is defined after its use fails at load. The error message isCannot find resource namedand the XAML designer shows an empty window. Move the definition earlier, or switch toDynamicResource. -
Sibling merged dictionaries do not see each other. Keep each file self-sufficient by merging its own dependencies, or reference by
DynamicResource. Do not rely on the order inApp.xaml. -
Implicit styles do not apply to derived types. A style for
TargetType="Window"does not styleMainWindow; use an explicitBasedOnstyle, or a style keyed by name. -
Typos in
DynamicResourceare silent. UnlikeStaticResource, a wrong key raises nothing. Search the Output window for resource-not-found traces while debugging, and keep key names in a naming convention. -
Do not mutate a shared resource. Changing a brush Color that many elements share changes it everywhere; if you need a per-element variant, clone it (
brush.Clone()) or usex:Shared="False". -
Replace, do not edit, to retheme. Swapping a whole dictionary notifies every
DynamicResource, whereas editing frozen objects in place throws. -
Merging the same dictionary many times costs startup time and memory. Merge once at application level; for controls shipped as a library, prefer lookups over per-control merges.
-
DynamicResourceeverywhere is not free. Thousands of dynamic references in data templates add allocation and update work. UseStaticResourcefor tokens that never change, and consider freezing brushes. -
Pack URI mistakes are runtime errors. A wrong relative path or a missing
;componentproduces anIOExceptionor aXamlParseExceptiononly when the dictionary loads. Check the project file build action and assembly name. Add a smoke test that opens the main window. -
Loose files are not trusted.
siteoforiginandContentfiles can be replaced by a user or by malware with write access to the install folder. Never load XAML withXamlReader.Loadfrom an untrusted source: XAML can instantiate arbitrary types and is an injection vector. -
Accessibility. A custom theme must keep a contrast ratio that is readable; respect high contrast through
SystemParameters.HighContrastand theSystemColorskeys. -
Localization is not resources of this kind. Strings belong in
.resx(anEmbeddedResource) or in a dictionary per language; see the page titled Globalization & Localization.
What Microsoft Learn does not provide
-
The WPF documentation of resources has not been rewritten for modern .NET; the conceptual pages above are the same as for .NET Framework. They do not discuss .NET 10 specifics because there are none.
-
There is no official runtime theme-switching recipe for WPF and no guidance for swapping merged dictionaries. The
ThemeServicein this page is a user pattern that relies only on documented behavior (MergedDictionaries,DynamicResource). -
ThemeModeand the Fluent theme are experimental; Learn documents the API but not a design walkthrough. -
There is no official guidance on structuring dictionaries for large apps, or on key naming. The
Bookshelf.Brush.*prefix is only a convention used in this section. -
Performance advice about merged dictionaries and
DynamicResourceis community knowledge and should be measured on your own app.
References
-
Namespaces, Assemblies and Projects for assemblies and project references in C#