徒然電脳

日々のプログラミング(とその他)忘備録 このサイトは独自研究のみに基づきます マサカリ歓迎しますというかよろしくお願いします

WPFの簡単なまとめ Template編

1回:WPFの簡単なまとめ Resorce編 - 徒然電脳
2回:WPFの簡単なまとめ Style編 - 徒然電脳

はい、3記事目です。

外観を根幹から変えること

今まではリソースやスタイルを使って、プロパティの取りうる範囲で外観を変更してきました。
今回は、その壁を超えて「コントロール」ごと外観を変えてしまいましょう。

Template?

とりあえず、コードを見ましょうか。

MainWindow.xaml

<Window x:Class="Template.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <StackPanel>
        <!-- ControlTemplateの中のコントロールの外観を持つようになる -->
        <Button Content="Button" Click="Button_Click">
            <Button.Template>
                <ControlTemplate TargetType="Button">
                    <Label>実はボタンです</Label>
                </ControlTemplate>
            </Button.Template>
        </Button>
        
        <!-- ControlTemplate内にコンテナを置くといろんなことができる -->
        <Button Content="Button" Click="Button_Click">
            <Button.Template>
                <ControlTemplate>
                    <Grid>
                        <Rectangle Fill="NavajoWhite" Height="20"/>
                        <Label HorizontalAlignment="Center">これも実はボタン</Label>
                    </Grid>
                </ControlTemplate>
            </Button.Template>
        </Button>

    </StackPanel>
</Window>

実行してみましょう。
f:id:TempProg:20150704211348j:plain
もちろん、どちらもクリックできて・・・
f:id:TempProg:20150704211404j:plain
(Button_Clickの実装は省略します)

要素のTemplateプロパティでテンプレートを指定可能です。TemplateプロパティにはControlTemplateのインスタンスを入れてやります。ControlTemplate内には1つの要素をとることができ、最初のボタンの例ではlabelを入れてあります。
実行結果を見てやれば一目瞭然ですが、外観としてはボタンの影も形もありません。ただラベルがあるだけです。しかし、「ボタンとしての機能を有します」。クリックすれば分かる通り、ボタンのクリックイベントが発生します。2つ目のボタンのように、ControlTemplate内にコンテナ(GridとかStackPanelなど)を置くことでいろんな外観を設定できるようになります。

StyleでTemplateを指定

StyleとしてTenplateを指定することも可能です。

MainWindow.xaml

<Window x:Class="Template.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <StackPanel>
        <!-- TemplateはsetterのpropertyをTemplateにしてしまえばスタイルとしても使える不思議 -->
        <StackPanel.Resources>
            <Style TargetType="Button">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="Button">
                            <Grid>
                                <Rectangle Fill="NavajoWhite" Height="20"/>
                                <Label HorizontalAlignment="Center">ボタン</Label>
                            </Grid>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </StackPanel.Resources>

        <Button Click="Button_Click"/>
        <Button Click="Button_Click"/>

    </StackPanel>
</Window>

このようにすれば、すべてのボタンがSetter.Value内のコントロールの外観を持つようになります。もちろん、Styleですからx:Keyの指定等によって特定の要素のみに適応させたりすることも可能です。

消えたContentプロパティ

上までの例ではButtonのContentプロパティが消えてしまっています。これでは困ります。そこで、Template内でContetnPresenterさんを呼び出しましょう。

MainWindow.xaml

<Window x:Class="Template.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <StackPanel>
        <!-- TemplateはsetterのpropertyをTemplateにしてしまえばスタイルとしても使える不思議 -->
        <StackPanel.Resources>
            <Style TargetType="Button">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="Button">
                            <Grid>
                                <Rectangle Fill="SkyBlue" Height="40"/>
                                <!-- ContentPresenter -->
                                <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
                            </Grid>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </StackPanel.Resources>
        
        <Button Click="Button_Click" Content="ボタンA"/>
        <Button Click="Button_Click" Content="ボタンB"/>

    </StackPanel>
</Window>

このように、ContentPresenter要素をおいてやることでButton要素のContentプロパティの値をそこに表示させることが可能です。

なお、TemplateBindingを用いることでもできます。

MainWindow.xaml

<Window x:Class="Template.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <StackPanel>
        <StackPanel.Resources>
            <Style TargetType="Button">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="Button">
                            <Grid>
                                <Rectangle Fill="SkyBlue" Height="40"/>
                                <!-- TemplateBinding -->
                                <Label Content="{TemplateBinding Content}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
                            </Grid>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </StackPanel.Resources>

        <Button Click="Button_Click" Content="ボタンA"/>
        <Button Click="Button_Click" Content="ボタンB"/>

    </StackPanel>
</Window>

TemplateBindingマークアップ拡張でテンプレートの適応先のプロパティを持ってくる(バインディングする)ことができます。正直なところ、こちらのほうがわかりやすいようなきがします。今のところContentPresenterの存在する意味は謎です。どなたかご存知でしたらご連絡お願いします。

トリガー

スタイル同様、ControlTemplateにもTriggersプロパティがあります。ただし、SetterはControlTemplate内の要素に対して設定することになります。ソースを見てみましょう。

MainWindow.xaml

<Window x:Class="Template.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <StackPanel>
        <StackPanel.Resources>
            <Style TargetType="Button">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="Button">
                            <Grid>
                                <Rectangle Fill="SkyBlue" Height="40"/>
                                <Label Name="contentLabel" Content="{TemplateBinding Content}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
                            </Grid>
                            
                            <!-- Triggers -->
                            <ControlTemplate.Triggers>
                                <Trigger Property="IsMouseOver" Value="True">
                                    <Setter TargetName="contentLabel" Property="FontSize" Value="20" />
                                </Trigger>
                            </ControlTemplate.Triggers>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </StackPanel.Resources>
        
        <Button Click="Button_Click" Content="ボタンA"/>
        <Button Click="Button_Click" Content="ボタンB"/>

    </StackPanel>
</Window>

こうすれば、要素にマウスオーバーした時にフォントサイズが大きくなります。

Setterには何の要素に対してプロパティを設定すればいいか指定してやる必要があります。そこでlabelにNameをつけてやり、その値をSetterのTargetNameプロパティで指定してやります。こうすれば、どの要素のフォントサイズを大きくすればいいかわかりますね。もちろん、StyleレベルでTriggersを指定することも可能ですが、この時はTemplateを1から書かなきゃいけません。


とりあえずテンプレートも書きたいことは書いた・・・
間違い等ありましたら、指摘してくださるとありがたいです。