TabControl のタブ位置を固定する

WPF の TabControl は

TabItem が増えて横一列に入りきれなくなると多段表示になります。この状態で上段のタブをクリックすると、タブの位置が入れ替わってしまいます。

私はこの動作が大嫌い

タブブラウザみたいにタブの位置はそのままに、内容だけ切り替わってほしい。でも TabControl の標準の動作は前述の通りですし、かといって自分で一から作成するのも面倒。お金を払って商用コントロールを買うなんてもってのほか!

気に入らないなら…

Template を書き変えちゃえばいいのさ!

XAML を記述します

<Window x:Class="TabControlSample.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:classic="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Classic"
    Title="Window1" Height="300" Width="300">
    <Window.Resources>
        <!--TabControl のタブ位置を固定するスタイル-->
        <Style TargetType="{x:Type TabControl}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type TabControl}">
                        <Grid>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto"/>
                                <RowDefinition Height="*"/>
                            </Grid.RowDefinitions>
                            <!--TabStrip に WrapPanel を使って位置が変わらないようにする-->
                            <WrapPanel x:Name="HeaderPanel"
                                       Margin="0,-2,0,0"
                                       Height="Auto"
                                       IsItemsHost="True"
                                       Grid.Row="0"/>
                            <Grid x:Name="ContentPanel"
                                  Grid.Row="1">
                                <!--HeaderPanel と ContentPanel の境界線を引く-->
                                <classic:ClassicBorderDecorator Background="{TemplateBinding Background}"
                                                                BorderBrush="{TemplateBinding BorderBrush}"
                                                                BorderThickness="0,1,0,0"
                                                                BorderStyle="Raised">
                                    <!--選択されたタブの内容を表示-->
                                    <ContentPresenter x:Name="PART_SelectedContentHost"
                                                      ContentSource="SelectedContent"/>
                                </classic:ClassicBorderDecorator>
                            </Grid>
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
        
        <!--タブの幅と高さを固定にする-->
        <Style TargetType="{x:Type TabItem}">
            <Setter Property="Height" Value="28"/>
            <Setter Property="Width" Value="90"/>
        </Style>
    </Window.Resources>
    <Grid>
        <TabControl>
            <TabItem Header="Google">
                <Label Content="Google"/>
            </TabItem>
            <TabItem Header="Yahoo!">
                <Label Content="Yahoo!"/>
            </TabItem>
            <TabItem Header="Livedoor">
                <Label Content="Livedoor"/>
            </TabItem>
            <TabItem Header="Infoseek">
                <Label Content="Infoseek"/>
            </TabItem>
            <TabItem Header="Microsoft">
                <Label Content="Microsoft"/>
            </TabItem>
            <TabItem Header="Apple">
                <Label Content="Apple"/>
            </TabItem>
        </TabControl>
    </Grid>
</Window>

タブ位置固定だけでなく、幅と高さも固定にしてみました。

確認してみます

下段のタブが選択されているところ。
f:id:griefworker:20090421174737p:image
ここで上段の Google タブを選択します。
f:id:griefworker:20090421174758p:image
ページが切り替わったけど、タブの位置は変わっていません。
やった!

まとめ

Template をちょっと書き変えるだけで、タブ位置固定が実現できてしまいました。WPF スゴイ。WinForms ではこうはいきませんね。
TabControl の TabStripPlacement プロパティの値によって、WrapPanel の位置を変更すればもっと良くなりますね。面倒なので今回はしませんが。