業務のためのC#・C言語・C++学習

主にC#の文法やWPF周りのアウトプットに利用してます。

【C#-WPF】XAMLの記述に便利なResourcesとStyleについて

WPFの大きな特長は表示とロジックの分離が可能にするMVVMパターンで設計できることである。
設計者は、開発者が C# をプログラミング ロジックの作業を行っているのと同時に、XAML のみを使用してアプリの外観に関する作業を行うことが可能になる。

そして、WPFの柔軟性を象徴する機能がリソース、スタイル、コントロールテンプレート、データテンプレートで、この機能を用いることでコントロールの外観を自由自在にカスタマイズ可能となる。Windowsフォームなどの既存のGUI作成フレームワークでは、コントロールに対して背景色やフォント・サイズの変更など、限定的なカスタマイズしかできなかった。これに対して、WPFのコントロール・テンプレートは、外観をほぼ無制限にカスタマイズできるポテンシャルを秘めている。 https://atmarkit.itmedia.co.jp/ait/articles/1009/07/news096.html

画面遷移などの高度な外観実装をするためにはリソース・スタイル・テンプレートが必須知識となる。 ここでは、XAMLの記述を効率化させるために便利なResources(リソース)とStyle(スタイル)について解説する。

リソース

WPF xamlでリソースとはUI要素を1つの変数で一括管理する仕組みである。
例としてリソースに背景色を指定するためのブラシを設定し2つのTextBox要素に反映させる。

<Window x:Class="DataTemplate.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:DataTemplate"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <SolidColorBrush x:Key="MyColor" Color="Red"/>
    </Window.Resources>
    <Grid>
        <TextBox Text="aaaaa" Background="{StaticResource MyColor}" Margin="0,45,-0.4,298"/>
        <TextBox Text="bbbb" Background="{StaticResource MyColor}" Margin="0,226,-0.4,133"/>
    </Grid>
</Window>

<Window.Resources>にMyColorをKeyとするインスタンスを宣言し、利用者である2つのTextBoxにBackground="{StaticResource MyColor}"を設定する。これにより背景色を反映させることができる。
これだけではリソースのメリットを感じないかもしれないが、仮にTextBoxが5つある場合はどうすればいいだろうか。5つのTextBox毎に背景色の定義をすることもできるがこれは手間である。リソースを定義しておけば1つのインスタンスを一元管理し5つのTextBox全てに同じ背景色を設定できる。

<Window x:Class="DataTemplate.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:DataTemplate"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <TextBox Text="aaaaa" Background="Red" Margin="0,0,-0.4,343"/>
        <TextBox Text="bbbb" Background="Red" Margin="0,94,-0.4,265"/>
        <TextBox Text="ccc" Background="Red" Margin="0,192,-0.4,167"/>
        <TextBox Text="ddd" Background="Red" Margin="0,268,-0.4,91"/>
        <TextBox Text="eeee" Background="Red" Margin="0,359,-0.4,0"/>

    </Grid>
</Window>
[f:id:gaishiengineer:20220918132547p:plain]

共通の背景色を持つ5つTextBoxにそれぞれBackground="Red"と背景色を指定している。しかし、色の変更があった場合はそれぞれのTextBoxのBackgroundを全て変更しないといけなくなった。これでは保守性が低いのでXMALにリソースを追加しそのリソースを変更するだけで全てのTextBoxに変更を反映させた方が便利である。

リソースの宣言箇所

リソースの宣言箇所を<Window.Resources>で行ったが他にも方法がある。
例えばGridのリソースで宣言する方法がある。

<Window x:Class="DataTemplate.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:DataTemplate"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <SolidColorBrush x:Key="MyColor" Color="Red"/>
    </Window.Resources>
    <Grid>
        <Grid.Resources>
            <SolidColorBrush x:Key="MyColor" Color="BlueViolet"/>
        </Grid.Resources>
        <TextBox Text="aaaaa" Background="{StaticResource MyColor}" Margin="0,0,-0.4,343"/>
        <TextBox Text="bbbb" Background="{StaticResource MyColor}" Margin="0,94,-0.4,265"/>
        <TextBox Text="ccc" Background="{StaticResource MyColor}" Margin="0,192,-0.4,167"/>
        <TextBox Text="ddd" Background="{StaticResource MyColor}" Margin="0,268,-0.4,91"/>
        <TextBox Text="eeee" Background="{StaticResource MyColor}" Margin="0,359,-0.4,0"/>
    </Grid>
</Window>

<Window.Resources>と<Grid.Resources>で同じブラシのインスタンスを宣言している。<Window.Resources>では背景色がRedに、<Grid.Resources>では背景色が BlueVioletに設定している。実行結果から分かる通りGrid内では<Grid.Resources>で宣言したインスタンスが優先的に反映される。

スタイル

リソースの宣言ではx:Key属性で変数を指定しStaticResource で利用していた。一方、スタイルを利用すればさらに簡潔なXAMLが書ける。
例としてリソースでx:Key属性を用いた宣言とスタイルを用いた宣言を挙げる。

<Window x:Class="Style.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Style"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <SolidColorBrush x:Key="MyColor" Color="Red"/>
        
        <Style TargetType="TextBox">
            <Setter Property="Background" Value="Blue"/>
        </Style>
    </Window.Resources>
    <Grid>
        <TextBox Text="aaaaa" Background="{StaticResource MyColor}" Margin="0,0,-0.4,343"/>
        <TextBox Text="bbbb" Background="{StaticResource MyColor}" Margin="0,94,-0.4,265"/>
        <TextBox Text="ccc" Background="{StaticResource MyColor}" Margin="0,192,-0.4,167"/>
        <TextBox Text="ddd"  Margin="0,268,-0.4,91"/>
        <TextBox Text="eeee"  Margin="0,359,-0.4,0"/>
    </Grid>
</Window>

5つのTextBoxのうち3つをBackground="{StaticResource MyColor}"で背景を指定、残り2つをリソースのStyleで指定している。
StyleはTargetTypeというプロパティを持っており、このプロパティにはスタイルの対象となる型を設定しておく必要がある。
Styleを用いることで、TargetTypeプロパティで指定した型のUI要素を利用すると自動的にスタイルが適用される。

スタイルの適用方法の違い

3つのスタイル適用方法を例として示す。

<Window x:Class="Style2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Style2"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <SolidColorBrush x:Key="MyColor" Color="Red"/>

        <Style TargetType="TextBox">
            <Setter Property="Background" Value="Blue"/>
        </Style>

        <Style x:Key="MyTextBox" TargetType="TextBox">
            <Setter Property="Background" Value="Green"/>
        </Style>

    </Window.Resources>
    <Grid>
        <TextBox Text="aaaaa" Background="{StaticResource MyColor}" Margin="0,0,-0.4,343"/>
        <TextBox Text="bbbb" Background="{StaticResource MyColor}" Margin="0,94,-0.4,265"/>
        <TextBox Text="ccc" Style="{StaticResource MyTextBox}" Margin="0,192,-0.4,167"/>
        <TextBox Text="ddd"  Margin="0,268,-0.4,91"/>
        <TextBox Text="eeee"  Margin="0,359,-0.4,0"/>
    </Grid>
</Window>

リソース内でTextBoxの背景色指定方法の1つは、x:Key属性を用いSolidColorBrushで宣言、使用者はStaticResourceで利用。Redが反映。
2つ目はx:Key属性を用いないスタイル利用. blueが反映
3つ目はx:Key属性を用いたスタイル利用。Greenが反映

スタイルの継承

BsedOnプロパティを利用することで同じUI要素のスタイルを継承できる。
例としてTextBoxのスタイル継承を示す。BsedONプロパティでTextBoxの背景色を継承しつつ追加でフォントサイズを大きくする。

<Window x:Class="Style3.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Style3"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <SolidColorBrush x:Key="MyColor" Color="Red"/>

        <Style TargetType="TextBox">
            <Setter Property="Background" Value="Blue"/>
        </Style>

        <Style x:Key="MyTextBox" TargetType="TextBox"
               BasedOn="{StaticResource {x:Type TextBox}}">
            <Setter Property="FontSize" Value="20"/>
        </Style>

    </Window.Resources>
    <Grid>
        <TextBox Text="aaaaa" Background="{StaticResource MyColor}" Margin="0,0,-0.4,343"/>
        <TextBox Text="bbbbb" Background="{StaticResource MyColor}" Margin="0,82,-0.4,261"/>
        <TextBox Text="ccccc"  Margin="0,164,-0.4,195"/>
        <TextBox Text="ddddd"  Margin="0,230,-0.4,129"/>
        <TextBox Text="eeeee" Style="{StaticResource MyTextBox}" Margin="0,291,-0.4,68"/>
        <TextBox Text="fffff" Style="{StaticResource MyTextBox}" Margin="0,357,-0.4,2"/>
    </Grid>
</Window>