Markup.XamlParseExceptionについて

Markup.XamlParseExceptionについて

はじめに

今回は WPF で Prism フレームワークを使用して、コントロールのスタイルをカスタマイズしているときに遭遇したエラーの解決方法についてまとめていきます。

使用している Prism のバージョンは8.1.97です。

また、アイコンのライブラリとしてMahApps.Metro.IconPacks.Materialを使用しています。

App.xaml にスタイルを定義

TextBox のレイアウトを以下のように整えるコードをApp.xamlに書いていました。

textbox-style.png

"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 が表示されていましたが、ビルドすると以下のエラーが発生しました。

static-resource-exception.png

ググっていると色々な情報が出てくるのですが、ビルドされる際に指定したリソースが解析・ロードされる際の順序が関係しているようです。

これは WPF で起こる奇妙な問題のひとつです。完全にランダムで、Prism を使用していないときにも発生する可能性があります。 ウェブで検索すると、さまざまな理由で動作しない例が大量に見つかります。これは、リソースのロード方法とロード順序の複雑な性質に関係しています。

Application.Resources not loaded in App #1575 より引用

不思議なことに 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というリソースディクショナリを作成します。

add-resource-dictonary.png

App.xamlに記述していた TextBox のスタイルを移動します。

"Resources\TextBoxSearch.xaml"
<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から読み込みます。

"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タグ内に追記していきます。

参考記事