TabControl(C# WPF)

C#

いわゆるタブで表示するコントロールの紹介です。
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です。

TabControl Styles and Templates

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にしています。

コメント