はじめに
WPFコントロールの中でよく使われるものとしてTabControlがあります。
普通にそのまま使うことが多いのですが、MahAppsを入れて作ったところ、ヘッダーが文字ベースでしかも大きいという、なかなかに使い勝手の難しそうな描画になりました。以下のみたいな状態です。(見た目が分かりやすいように色付けしてます)
// MahApps
<TabControl Background="Moccasin">
<TabItem Header="Tab1"/>
<TabItem Header="Tab2"/>
<TabItem Header="Tab3"/>
<TabItem Header="Tab4"/>
</TabControl>
2年位前に、初めてMahAppsを試ししてみた時があったのですが、そのときは、このTabControlの描画を見て使うのをやめたという経緯がありました。
Webアプリとかスマホアプリだったら、文字ベースやアイコンベースにして使うことが多いので、むしろこのようなTabControlのほうが使いやすいと思いますが、自分が作っていたのは、Windowsの業務アプリで、どちらかといえばWindows Fromsの見た目のほうがお客さんにもわかりやすいのです。
じゃあ、MahAppsとか使わなければいいじゃんということになりますが、WPFのみでのアプリはすでに完成していて、今回そのアプリをMahApps、MaterialDesignを使って見た目や操作性を上げたいという思いがあり、取り組み始めたというのがきっかけです。
とりあえず、MahAppsはおいておいて、標準コントロールのヘッダーの変更方法など、いろいろと調べ、試してみることにしました。
標準コントロールの描画
表示する必要もないほどですが、比べ安いように標準コントロールも書いておきます
<TabControl>
<TabItem Header="Tab1">
<TextBlock Text="Tab1" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</TabItem>
<TabItem Header="Tab2">
<TextBlock Text="Tab2" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</TabItem>
<TabItem Header="Tab3">
<TextBlock Text="Tab3" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</TabItem>
<TabItem Header="Tab4">
<TextBlock Text="Tab4" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</TabItem>
</TabControl>
ヘッダーの一部を丸くしてみた
ググるとよく出てくるサンプルがヘッダーの形を変更したいというもの。
シンプルにまとめてみました
<Style x:Key="SampleItem1" TargetType="{x:Type TabItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Border x:Name="border"
BorderThickness="1,1,1,0"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
CornerRadius="0,10,0,0">
<ContentPresenter x:Name="ContentSite"
VerticalAlignment="Center"
HorizontalAlignment="Center"
ContentSource="Header"
Margin="10,0"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="border"
Property="Background"
Value="white"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<TabControl >
<TabItem Style="{StaticResource SampleItem1}" Header="Tab1">
<TextBlock Text="Tab1" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</TabItem>
<TabItem Style="{StaticResource SampleItem1}" Header="Tab2">
<TextBlock Text="Tab2" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</TabItem>
<TabItem Style="{StaticResource SampleItem1}" Header="Tab3">
<TextBlock Text="Tab3" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</TabItem>
<TabItem Style="{StaticResource SampleItem1}" Header="Tab4">
<TextBlock Text="Tab4" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</TabItem>
</TabControl>
ヘッダーの右上の部分が丸くなるようにStyleでCornerRadius="0,10,0,0"と設定しています。
ところが、ここで問題が発生しました。
タブが選択されたても、図の蛍光ペンのところのように、Borderが見えたままになっています。
標準コントロールでは消えているのですが、これがうまくいきませんでした。
これはどういうこと?と再びググり続けること数日、結構たくさんの方々が、同じ問題にぶつかり、質問をあげていました。
大体の回答の傾向として、BorderThickness="1"になっているので、Marginを"-1"にしましょうというものです。
Marginの値を設定して、下の枠線が消えるようにしてみた
さきほどのサンプルと異なるのは、内のセッターでMarginを"0,0,0,-1"と設定しているところです。
<Style x:Key="SampleItem2" TargetType="{x:Type TabItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Border x:Name="border"
BorderThickness="1,1,1,0"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
CornerRadius="0,10,0,0">
<ContentPresenter x:Name="ContentSite"
VerticalAlignment="Center"
HorizontalAlignment="Center"
ContentSource="Header"
Margin="10,0"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="border"
Property="Margin"
Value="0,0,0,-1" />
<Setter TargetName="border"
Property="Background"
Value="white"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
これで下の枠線が消えて、解決と思ったのですが、使っているとやや気になる点がでてきました。
タブをクリックすると、Margin-1分だけ、ヘッダーの文字が下に下がります。
文字がその分動くので、クリックしたなという感じになるのですが、文字が動くというのは、気になるといえば気になるのです。
そこで改めて標準コントロールはどうなっているのかと確認してみると・・・文字とヘッダーが上に引き出されたみたいに、少し上方向に移動しています。
もう少し正確に表現すると、「選択したタブが前方に出てきた」みたいに見える描画になっていました。
どんな仕組みなんだろうかと、標準コントロールのTemplateを見てみました。すると・・・
ヘッダーが二つのBorderで作られていました
さきほどまではControlTemplate内でBorderを設定していました。
ところが標準コントロールはこのBorderの中にさらにBorderをセットしていました。
その名も mainBorderとinnerBorder そのまんまですね。
これを参考に、必要な部分だけでシンプルにしてみました
<Style x:Key="SampleItem3" TargetType="{x:Type TabItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Grid SnapsToDevicePixels="true">
<Border x:Name="mainBorder"
BorderThickness="1,1,1,0"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
CornerRadius="0,10,0,0"
Margin="0">
<Border x:Name="innerBorder"
Background="#FFFFFF"
BorderThickness="1,1,1,0"
BorderBrush="#ACACAC"
CornerRadius="0,10,0,0"
Margin="-1"
Opacity="0"/>
</Border>
<ContentPresenter x:Name="contentPresenter"
VerticalAlignment="Center"
HorizontalAlignment="Center"
ContentSource="Header"
Margin="10,0" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Margin" Value="-2,-2,-2,0"/>
<Setter Property="Opacity" TargetName="innerBorder" Value="1"/>
<Setter Property="BorderThickness" TargetName="innerBorder" Value="1,1,1,0"/>
<Setter Property="BorderThickness" TargetName="mainBorder" Value="1,1,1,0"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
innerBorderを作り、Opacity=0として、最初は隠しておきます。
選択されたときに(IsSelected=True)、Opacity=1にして表示されるようにしていました。
さらに、細かいところで、「前方に出てきているような表示」は、Margin=-2,-2,-2,0という設定値で表現していました。
このinnerBorderという作り方は、ググッても僕は見つけられなかったので、発見でした。
一つの部品でうまくいかないなら、別の部品で補完してしまえばいいという発想ですね。
なるほどーと思いつつ、その昔、VB6でアプリを作っていたころ、そういえば、表コントロールとかで、セルが選択されたときに、そのセルに重ねるようにテキストボックスを表示させて、入力が終わったら、セルに値を設定するなんということをしていたことを思い出しました(共感してもらえる人いるかな・・・)。
というわけで、記事はここまで。
自分としてはこれをMahAppsを使用した場合でも使えるように微調整していきたいと思っています。