概要
WPF のリソース参照には StaticResource と DynamicResource の2種類がある。
StaticResource で定義したリソースをコードから変更しても画面が更新されない場合、その原因はリソースを評価するタイミングの違いにある。
本記事では、両者の内部動作の違いを解説し、用途に応じた選択基準を整理する。
前提・対象環境
- フレームワーク: .NET 6 以降 / WPF
- 言語: C#
- アーキテクチャ: MVVM・コードビハインドのいずれにも適用可能
- 検証環境: .NET 10 / Windows 11
本記事の図は、上記の環境で同じリソースを StaticResource と DynamicResource の両方から参照した画面を実際に表示し、実行中にリソースを差し替えて得たものである。
この環境で確認しているのは次の点である。
- 差し替える前は、両者とも同じ値になる。
- 差し替えると、
StaticResourceで参照した側の値は変わらない。 - 差し替えると、
DynamicResourceで参照した側の値は追随する。
問題
アプリ実行中に ResourceDictionary のエントリを変更したにもかかわらず、コントロールの外観が変わらないケースがある。
たとえば、以下のように Window.Resources に定義した SolidColorBrush をコードから差し替えても、ボタンの背景色は変化しない。
<Window.Resources>
<SolidColorBrush x:Key="ThemeColor" Color="SkyBlue" />
</Window.Resources>
<Button Background="{StaticResource ThemeColor}" Content="ボタン" />
// 実行中に色を変更しようとする
Resources["ThemeColor"] = new SolidColorBrush(Colors.OrangeRed);
上記のコードを実行しても、ボタンの背景色は SkyBlue のまま変わらない。
ThemeColor を参照する 2 つのボタンに対し、実行中にリソースを OrangeRed へ差し替えた直後の状態。StaticResource 側は読み込み時に確定した SkyBlue のまま変わらない。原因・背景
StaticResource と DynamicResource の決定的な違いは、リソースを検索・確定するタイミングにある。
StaticResource の動作
StaticResource は XAML が解析(ロード)される瞬間に一度だけリソースを検索し、見つかった値をコントロールのプロパティに直接セットする。
値のセット後は参照関係が存在しないため、リソースの内容を変更しても WPF はそれを検知せず、プロパティは変化しない。
また、XAML は上から順番に解析されるため、StaticResource で参照するリソースは参照元より前の行で定義されている必要がある。
定義順が守られていない場合、XamlParseException が発生する。
DynamicResource の動作
DynamicResource は、画面ロード時にプロパティとリソースキーの紐付け(ディクショナリキーの保持)だけを行う。
アプリ実行中にそのキーに対応するリソースが変更されると、WPF はそれを検知し、対象プロパティを自動的に再評価して画面を更新する。
| 比較項目 | StaticResource | DynamicResource |
|---|---|---|
| 評価タイミング | XAML ロード時(一度のみ) | ロード時 + 変更検知のたびに再評価 |
| 実行時の変更 | 反映されない | 即座に反映される |
| リソースの定義順 | 参照元より前に定義が必要 | 前後どちらでも可 |
| パフォーマンス | 高速 | 監視オーバーヘッドあり |
この違いは、実行中にリソースを差し替えてプロパティを読めば確かめられる。
PanelBrush の値を実行中に白から赤へ差し替え、その前後で Border.Background を読んだ結果。参照の書き方以外の条件は同一である。差し替え前は両者とも同じ値である。差が出るのは差し替えた後だけであり、StaticResource 側は白のまま変わらない。
解決方法
実行時にリソースの変更を画面に反映させるには、StaticResource を DynamicResource に置き換える。
実装例
DynamicResource による動的テーマ切り替え
以下の例では、ボタンのクリック時にリソースディクショナリのブラシを差し替え、画面全体の配色を切り替える。
<Window.Resources>
<SolidColorBrush x:Key="ThemeColor" Color="SkyBlue" />
</Window.Resources>
<StackPanel>
<Button Background="{DynamicResource ThemeColor}" Content="対象ボタン" />
<Button Content="テーマ切り替え" Click="OnThemeToggleClick" />
</StackPanel>
private bool _isDark = false;
private void OnThemeToggleClick(object sender, RoutedEventArgs e)
{
_isDark = !_isDark;
Resources["ThemeColor"] = _isDark
? new SolidColorBrush(Colors.DarkSlateGray)
: new SolidColorBrush(Colors.SkyBlue);
}
DynamicResource を使用することで、Resources["ThemeColor"] への代入が即座に Background プロパティへ反映される。
ResourceDictionary の丸ごと差し替え
アプリ全体のテーマをライト/ダーク間で切り替える場合は、テーマ定義ファイルを分けておき、MergedDictionaries を差し替える方法が一般的である。
テーマファイルの例(Themes/Light.xaml):
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<SolidColorBrush x:Key="Background" Color="White" />
<SolidColorBrush x:Key="Foreground" Color="Black" />
</ResourceDictionary>
テーマファイルの例(Themes/Dark.xaml):
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<SolidColorBrush x:Key="Background" Color="#1E1E1E" />
<SolidColorBrush x:Key="Foreground" Color="White" />
</ResourceDictionary>
差し替えコード:
private void SwitchTheme(string themeName)
{
var uri = new Uri($"Themes/{themeName}.xaml", UriKind.Relative);
var dict = new ResourceDictionary { Source = uri };
Application.Current.Resources.MergedDictionaries.Clear();
Application.Current.Resources.MergedDictionaries.Add(dict);
}
テーマ対象のすべてのリソースを DynamicResource で参照しておくことで、上記の差し替え操作だけで画面全体の外観が切り替わる。
注意点
- 本記事のテーマ切り替えで
Freezeが問題になるのは、既存Freezableインスタンスを変更する場合である: たとえば((SolidColorBrush)Resources["ThemeColor"]).Color = ...のように既存のSolidColorBrushを直接変更する場合、Freeze()済みなら変更できない。
一方、今回のようにResources["ThemeColor"]に新しいSolidColorBrushを代入して差し替えるだけなら、通常はIsFrozenの確認は不要である。
なおFreezeにはこのほかにも、スレッドをまたいだアクセスが可能になる、アニメーションの対象にすると複製されるといった影響がある。 DynamicResourceの乱用はパフォーマンスに影響する:DynamicResourceは値を実行時に検索し、リソースが変わるたびに再評価する遅延式として動作するため、すべてのリソース参照をDynamicResourceにすると描画速度やメモリ使用量に影響が出る場合がある。
変更が不要なリソースにはStaticResourceを維持する。- リソースが解決されていても外観が変わらないことがある: 対象プロパティにローカル値が設定されていると、スタイルのトリガーが指定した値は実効値にならない。
この場合の原因は評価タイミングではなく依存関係プロパティの値優先順位にある(WPF で Style の Trigger・DataTrigger が効かない原因と依存関係プロパティの値優先順位)。 DynamicResourceを書ける場所は値の種類によって決まる:StyleやControlTemplateのトリガーであっても、SetterのValue(依存関係プロパティへ与える値)にはDynamicResourceを使用できる。
使用できないのは、依存関係プロパティでない値、Trigger/DataTriggerのValue(比較値)、およびStoryboardやアニメーションのプロパティ値である。
コンパイル時ではなく実行時にエラーが発生することがあるため、事前に動作確認が必要である。
代替案・比較
| 方法 | メリット | デメリット | 適するケース |
|---|---|---|---|
DynamicResource への切り替え |
実装がシンプル・変更が即反映 | 監視コストがある | テーマ切り替えやシステムカラー連動 |
MergedDictionaries の差し替え |
テーマを一括管理できる | ファイル分割の設計が必要 | ライト/ダークモードなどの全体テーマ切り替え |
INotifyPropertyChanged + バインディング |
ViewModel の値変更で UI が更新される | リソースディクショナリを使わない設計になる | 単一コントロールの動的スタイルではなく、データ駆動の表示切り替え |
まとめ
StaticResource は XAML ロード時に値を確定するため、実行後のリソース変更は画面に反映されない。
実行中に変更を反映させる必要がある場合は DynamicResource を使用する。
選択の基準は次のとおりである。
- 変更が不要なリソース(固定カラー、固定フォントサイズなど):
StaticResourceを使用する。
パフォーマンスへの影響が少ない。
ただし定義順を満たしていても、キー名が一致しない場合や参照元から到達できないスコープに定義した場合は解決に失敗するため、定義順・キー名・スコープの 3 点を確認する。 - 実行中に変更が必要なリソース(テーマカラー、OS 設定連動など):
DynamicResourceを使用する。
SystemColorsやSystemParametersなどの OS 設定と連動させる場合もDynamicResourceが必要となる。
原則として StaticResource を基本とし、動的な変更が求められる箇所にのみ DynamicResource を適用する設計が、保守性とパフォーマンスのバランスとして適切である。