WPF で StaticResource を変更しても画面が更新されない原因と解決方法

StaticResource はXAMLロード時に値を確定するため、実行時の変更は反映されない。動的な変更が必要な場合は DynamicResource を使用する。両者の仕組みと使い分けの判断基準を解説する。

概要

WPF のリソース参照には StaticResourceDynamicResource の2種類がある。 StaticResource で定義したリソースをコードから変更しても画面が更新されない場合、その原因はリソースを評価するタイミングの違いにある。 本記事では、両者の内部動作の違いを解説し、用途に応じた選択基準を整理する。


前提・対象環境


問題

アプリ実行中に 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 のまま変わらない。


原因・背景

StaticResourceDynamicResource の決定的な違いは、リソースを検索・確定するタイミングにある。

StaticResource の動作

StaticResource は XAML が解析(ロード)される瞬間に一度だけリソースを検索し、見つかった値をコントロールのプロパティに直接セットする。 値のセット後は参照関係が存在しないため、リソースの内容を変更しても WPF はそれを検知せず、プロパティは変化しない。

また、XAML は上から順番に解析されるため、StaticResource で参照するリソースは参照元よりの行で定義されている必要がある。 定義順が守られていない場合、XamlParseException が発生する。

DynamicResource の動作

DynamicResource は、画面ロード時にプロパティとリソースキーの紐付け(ディクショナリキーの保持)だけを行う。 アプリ実行中にそのキーに対応するリソースが変更されると、WPF はそれを検知し、対象プロパティを自動的に再評価して画面を更新する。

比較項目 StaticResource DynamicResource
評価タイミング XAML ロード時(一度のみ) ロード時 + 変更検知のたびに再評価
実行時の変更 反映されない 即座に反映される
リソースの定義順 参照元より前に定義が必要 前後どちらでも可
パフォーマンス 高速 監視オーバーヘッドあり

解決方法

実行時にリソースの変更を画面に反映させるには、StaticResourceDynamicResource に置き換える。


実装例

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 で参照しておくことで、上記の差し替え操作だけで画面全体の外観が切り替わる。


注意点


代替案・比較

方法 メリット デメリット 適するケース
DynamicResource への切り替え 実装がシンプル・変更が即反映 監視コストがある テーマ切り替えやシステムカラー連動
MergedDictionaries の差し替え テーマを一括管理できる ファイル分割の設計が必要 ライト/ダークモードなどの全体テーマ切り替え
INotifyPropertyChanged + バインディング ViewModel の値変更で UI が更新される リソースディクショナリを使わない設計になる 単一コントロールの動的スタイルではなく、データ駆動の表示切り替え

まとめ

StaticResource は XAML ロード時に値を確定するため、実行後のリソース変更は画面に反映されない。 実行中に変更を反映させる必要がある場合は DynamicResource を使用する。

選択の基準は次のとおりである。

原則として StaticResource を基本とし、動的な変更が求められる箇所にのみ DynamicResource を適用する設計が、保守性とパフォーマンスのバランスとして適切である。