いわゆるタブで表示するコントロールの紹介です。
WPFでは標準でTabControlというものがあり、結構便利です。
<TabControl Background="Green">
<TabItem Header="tab1" >
<StackPanel Background="YellowGreen">
<TextBlock Text="111" />
</StackPanel>
</TabItem>
<TabItem Header="tab2" />
<TabItem Header="tab3" />
<TabItem Header="tab4" />
</TabControl>
こんな感じでTabItemに好きなようにコントロールを配置するだけで、タブ切替が実装できます。
とても簡単です。
不要な装飾を削除(Border, Padding)
しかし、これを作り込んでいこうとすると、微妙なpaddingやmarginが邪魔して一筋縄ではいきません。苦労するポイントを見ていきます。
見にくいので色を変えて拡大しました。
まず目に付くのが赤色の部分ですが、ここは面倒なので後で説明します。
黄色の部分がタブで選択されたContentが表示される領域なのですが、緑の縁取りがあります。
これがTabControl本体にPaddingが設定されています。TabItemではなく、TabControlのプロパティです!
さらにいうと 緑の縁取り の外側にうっすらグレーの縁取りがあります。
これもTabControlにBorderが設定されているので、取っ払います。
<TabControl Background="Green" Padding="0" BorderThickness="0">
<TabItem Header="tab1" >
<StackPanel Background="Yellow">
<TextBlock Text="111" />
</StackPanel>
</TabItem>
</TabControl>
大分見た目がスッキリしました。
Headerの隙間をなんとかしたい!
ココからが本題の赤い所です。この赤は何かというと、TabControlの親のBackgroundです。
<Window Background="Red">
<TabControl Background="Green" Padding="0" BorderThickness="0">
<TabItem Header="tab1" >
<StackPanel Background="Yellow">
<TextBlock Text="111" />
</StackPanel>
</TabItem>
</TabControl>
</Window>
こうなっているわけです。
私が気になったのがtab1の左や上に出来ている空間です。
タブが選択されると、TabItemのHeaderが僅かに拡大して、この赤いスペースを埋めるわけですが、選択されていないと隙間として残ります。上下のコントロール間で幅に差が出てしまうと、統一感がない気がして好きじゃありません。
Header部分の色が変われば選択中の目印としては十分ですので、サイズ変化も合わせて消します。
TabControlの構造
弄る前にTabControlがザックリどういった構造となっているかを見てみます。
キーワードはTabControl、TabPanel、TabItemです。
Exampleを見てみてください。
コントロールが入り組んで複雑です。こんなに複雑じゃなくていいんですよ。
もっとシンプルで単純な奴が欲しいので、これを極限までスリムにします。
元のコントロールを流用しようとすると苦労するのでバッサリ切り捨てます。
かなり乱暴にいうと、TabControlはHeader領域であるTabPanelと、Content領域であるContentPresenterで成り立っています。なのでTabControlをまず削ります。
以下くらいのTemplateで十分です。
<Grid KeyboardNavigation.TabNavigation="Local">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TabPanel x:Name="HeaderPanel" Grid.Row="0" Margin="0" Panel.ZIndex="1" Background="Transparent" IsItemsHost="True" KeyboardNavigation.TabIndex="1" />
<ContentPresenter x:Name="PART_SelectedContentHost" Grid.Row="1" Margin="0" ContentSource="SelectedContent" />
</Grid>
続いて、TabPanelというのがHeader領域なので、このTemplateを弄っていきます。
実際に弄るのはTabItemになります。
<Border x:Name="templateRoot" Background="{TemplateBinding Background}" BorderThickness="1" BorderBrush="Gray">
<ContentPresenter x:Name="contentPresenter" Margin="0" ContentSource="Header" Focusable="True" RecognizesAccessKey="True" />
</Border>
これも最低限のBorderとContentPresenterで十分です。
TabPanel内に表示されるのはTabItemであり、TabItemこそがいわゆるタブの見た目の部分です。
下のように書いた場合、TabItem以下の要素はContentとして、Content領域に表示されます。
<TabItem Header="hoge">
<StackPanel Background="Bisque">
<TextBlock Text="333" />
</StackPanel>
</TabItem>
なのでタブの部分にStyleを当てたい場合は、TabItemを編集するわけです。
SimpleなTabControl例
<Style TargetType="{x:Type TabControl}">
<Setter Property="OverridesDefaultStyle" Value="True" />
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabControl}">
<Grid KeyboardNavigation.TabNavigation="Local">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TabPanel x:Name="HeaderPanel" Grid.Row="0" Margin="0" Panel.ZIndex="1" Background="Transparent" IsItemsHost="True" KeyboardNavigation.TabIndex="1" />
<ContentPresenter x:Name="PART_SelectedContentHost" Grid.Row="1" Margin="0" ContentSource="SelectedContent" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="TabItemStyle" TargetType="{x:Type TabItem}">
<!--<Setter Property="OverridesDefaultStyle" Value="True" />-->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Border x:Name="templateRoot" Background="{TemplateBinding Background}" BorderThickness="1" BorderBrush="Gray" Margin="1,0">
<ContentPresenter x:Name="contentPresenter" Margin="0" ContentSource="Header" Focusable="True" RecognizesAccessKey="True" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
これで変なPaddingやMarginのないControlができました。
ここまでシンプルなら構造も弄りやすいです。
TabItemはIsSelectedプロパティが使えるので、これをトリガーにすれば選択時の見た目も変更できます。
ちなみに上記でコメントアウトしているOverridesDefaultStyleに関してですが、Trueにすると左のようになります。TabItem内にStackPanelとTextboxを置いて、StackPanelのBackgroundを肌色にしています。高さや幅が全幅に広がらなくなります。
この辺りは必要に応じて使い分ける感じになりそうです。
私は全幅でBackgroundを設定したかったので、Falseにしています。
コメント