はじめに
今回は WPF で Prism フレームワークを使用して、コントロールのスタイルをカスタマイズしているときに遭遇したエラーの解決方法についてまとめていきます。
使用している Prism のバージョンは8.1.97
です。
また、アイコンのライブラリとしてMahApps.Metro.IconPacks.Material
を使用しています。
App.xaml にスタイルを定義
TextBox のレイアウトを以下のように整えるコードをApp.xaml
に書いていました。
<prism:PrismApplication x:Class="MVVM.DataGridSearch.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Icon="http://metro.mahapps.com/winfx/xaml/iconpacks"
xmlns:local="clr-namespace:MVVM.DataGridSearch"
xmlns:prism="http://prismlibrary.com/">
<Application.Resources>
<Style x:Key="textBoxSearch" TargetType="TextBox">
<Setter Property="Background" Value="#ffffff" />
<Setter Property="Background" Value="#ffffff" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="FontSize" Value="12" />
<Setter Property="Padding" Value="15,10" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Margin" Value="0,10" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TextBoxBase">
<Border x:Name="border"
Background="{TemplateBinding Background}"
BorderBrush="#e0e6ed"
BorderThickness="1"
CornerRadius="20"
SnapsToDevicePixels="True">
<ScrollViewer x:Name="PART_ContentHost"
Focusable="False"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="border" Property="BorderBrush" Value="#d9d9d9" />
</Trigger>
<Trigger Property="IsKeyboardFocused" Value="True">
<Setter TargetName="border" Property="BorderBrush" Value="#d9d9d9" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Application.Resources>
</prism:PrismApplication>
MainWindow.xaml
では以下のようにStaticResource
でスタイルを適応しています。
<Window x:Class="MVVM.DataGridSearch.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Icon="http://metro.mahapps.com/winfx/xaml/iconpacks"
xmlns:prism="http://prismlibrary.com/"
Title="{Binding Title}"
Width="1080"
Height="720"
prism:ViewModelLocator.AutoWireViewModel="True"
AllowsTransparency="True"
Background="Transparent"
WindowStartupLocation="CenterScreen"
WindowStyle="None">
<Border Background="#eff2f7"
CornerRadius="20"
MouseDown="Border_MouseDown"
MouseLeftButtonDown="Border_MouseLeftButtonDown">
<Grid Margin="30,20,20,30">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- Search TextBox -->
<Grid Grid.Row="0"
Width="300"
HorizontalAlignment="Left">
<TextBlock Margin="20,0"
VerticalAlignment="Center"
Panel.ZIndex="1"
Foreground="#b0b9c6"
IsHitTestVisible="False"
Text="検索する"
Visibility="Visible" />
<TextBox x:Name="txtSearch" Style="{StaticResource textBoxSearch}" />
<Icon:PackIconMaterial Width="15"
Height="15"
Margin="0,0,15,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Panel.ZIndex="1"
Foreground="#b0b9c6"
Kind="Magnify" />
</Grid>
<!-- 後に追加する予定のコントロール -->
<!-- <DataGrid Grid.Row="1" /> -->
</Grid>
</Border>
</Window>
この時点で XAML デザイナー側ではエラーは出ておらず、スタイルが適応されていた状態の TextBox が表示されていましたが、ビルドすると以下のエラーが発生しました。
ググっていると色々な情報が出てくるのですが、ビルドされる際に指定したリソースが解析・ロードされる際の順序が関係しているようです。
これは WPF で起こる奇妙な問題のひとつです。完全にランダムで、Prism を使用していないときにも発生する可能性があります。 ウェブで検索すると、さまざまな理由で動作しない例が大量に見つかります。これは、リソースのロード方法とロード順序の複雑な性質に関係しています。
不思議なことに Prism を使用していない WPF プロジェクトを作成して同様のコードを実装したところ、エラー無く実行できました。
フレームワークを使用することによってソースのロード方法や順序が異なるのかもしれませんが、ここは深追いせずに上記 issues に記載されている実装方法に従うことにします。
解決方法
App.xaml
に複数のスタイルを記述するか、ResourceDictionary
を 使用する方法で解決することが出来ます。
一番簡単な方法であるApp.xaml
に適当なスタイルを追加してみます。
<!-- 省略 -->
</Setter.Value>
</Setter>
</Style>
<!-- 適当なスタイルを追加 -->
+ <SolidColorBrush x:Key="MyBackground" Color="Blue" />
</Application.Resources>
</prism:PrismApplication>
これでビルドするとエラーが解消されました。
ResourceDictionary を使用した方法
ResourceDictionary
を使用する方法も見ていきます。
今回はResources
というフォルダを作成しTextBoxSearch.xaml
というリソースディクショナリを作成します。
App.xaml
に記述していた TextBox のスタイルを移動します。
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style x:Key="textBoxSearch" TargetType="TextBox">
<Setter Property="Background" Value="#ffffff" />
<Setter Property="Background" Value="#ffffff" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="FontSize" Value="12" />
<Setter Property="Padding" Value="15,10" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Margin" Value="0,10" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TextBoxBase">
<Border x:Name="border"
Background="{TemplateBinding Background}"
BorderBrush="#e0e6ed"
BorderThickness="1"
CornerRadius="20"
SnapsToDevicePixels="True">
<ScrollViewer x:Name="PART_ContentHost"
Focusable="False"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="border" Property="BorderBrush" Value="#d9d9d9" />
</Trigger>
<Trigger Property="IsKeyboardFocused" Value="True">
<Setter TargetName="border" Property="BorderBrush" Value="#d9d9d9" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
このリソースをApp.xaml
から読み込みます。
<prism:PrismApplication x:Class="MVVM.DataGridSearch.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Icon="http://metro.mahapps.com/winfx/xaml/iconpacks"
xmlns:local="clr-namespace:MVVM.DataGridSearch"
xmlns:prism="http://prismlibrary.com/">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources\TextBoxSearch.xaml" />
<!-- 複数リソースを適応したい場合は追記していく -->
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</prism:PrismApplication>
この方法でもエラーは解消されます。
複数のリソースがある場合はResourceDictionary.MergedDictionaries
タグ内に追記していきます。