WPF 標準コントロールデモアプリ › TabItem
TabItem Layout
TabItem は TabControl の 1 ページです。タブの帯に表示するヘッダーと、タブを選んでいる間に表示する内容からなります。
どのタブが選ばれたまま残るか
TabItem は HeaderedContentControl を継承しており、タブの帯に出す Header と、ページ本体の Content を持ちます。親の TabControl で同時に選択できるタブは最大 1 つです。ある TabItem の IsSelected を True にすると、SelectedIndex がそのタブに移り、他のタブの IsSelected は外れます。3 つのうち 3 つ目のタブに IsSelected="True" を設定すると、SelectedIndex は 2 になり、他のタブの選択は外れました。
IsSelected は既定で TwoWay にバインドされます。SelectedIndex を 1 に変えると、バインドした 1 つ目のソースは False、2 つ目は True になりました。実際のマウスで 2 つ目のタブの見出しをクリックしても同じでした。
選択中にできるのは 1 つだけで、どれが残るかは値の届き方で変わります。コードから 2 つのタブを順に True にすると、後に設定したタブが選ばれました。XAML で 2 つのタブに IsSelected="True" を書くと、先に書いたタブが選ばれました。選択は 1 か所で管理し、すべてのタブの IsSelected ではなく、TabControl の SelectedIndex か SelectedItem をバインドします。
無効なタブもコードからは選べる
IsEnabled="False" のタブは、利用者の操作では選べません。支援技術が使う UI オートメーション(ISelectionItemProvider.Select)で選ぼうとすると ElementNotEnabledException が発生し、選択は動きませんでした。実際のマウスで見出しをクリックしても選ばれませんでした。
一方、コードからは選べます。SelectedIndex = 1 で無効なタブが選ばれ、その内容が表示されました。前の手順が終わるまで後のタブを無効にしておく場合は、選択を変えるコードの側でも、開かせたくないタブを選ばないようにします。
| 条件 | 計測値 |
|---|---|
| 基底クラス | HeaderedContentControl |
| IsSelected: BindsTwoWayByDefault | True |
| TabItem の TabStripPlacement: 読み取り専用か | True |
| TabControl.TabStripPlacement=Left: 両方のタブの TabItem.TabStripPlacement | Left / Left |
| Tab3.IsSelected = true: SelectedIndex / Tab1..3 の IsSelected | 2 / False, False, True |
| XAML: Tab2 と Tab3 に IsSelected="True": SelectedIndex / Tab1..3 の IsSelected | 1 / False, True, False |
| コード: Tab2、続けて Tab3 を IsSelected=true に: SelectedIndex | 2 |
| Mode なしで IsSelected をバインド、SelectedIndex = 1: Tab1 / Tab2 のソースの値 | False / True |
| 続けて Tab1 のソースを True に: SelectedIndex / Tab1 / Tab2 のソースの値 | 0 / True / False |
| 続けて Tab2 の見出しを実際にマウスでクリック: SelectedIndex / Tab1 / Tab2 のソースの値 | 1 / False / True |
| Tab2 が IsEnabled=False、コードから SelectedIndex = 1: SelectedIndex / 表示される内容 | 1 / Item2 |
| Tab2 が IsEnabled=False、UI オートメーションの ISelectionItemProvider.Select(): 結果 / SelectedIndex | ElementNotEnabledException / 0 |
| Tab2 が IsEnabled=False、見出しを実際にマウスでクリック: SelectedIndex | 0 |
選択、メタデータ、無効なタブ。.NET 10 / Windows 11 で計測。
内容が作られる時点と、使い回される場合
表示されてレイアウトされるのは選択中のタブの内容だけですが、他のタブの内容が何もしていないわけではありません。XAML に直接書いた内容は、XAML の読み込み時に作られます。3 つのタブで試すと、ウィンドウを開いた時点で 3 つの内容すべてに Loaded が発生し、測られた(Measure された)のは選択中の 1 つだけでした。タブ 1 からタブ 2 へ切り替えて戻すと、戻ってきた内容にもう一度 Loaded が発生し、同じインスタンスが再び表示されました。重い処理を遅らせたい場合は、内容のコンストラクターや Loaded ではなく、そのタブが初めて選ばれたときに始めます。
ItemsSource と ContentTemplate で作った場合は、内容の要素は 1 つだけ作られ、どのタブでもデータだけを入れ替えて使い回されました。そのため、スクロール位置やバインドしていないテキストボックスの文字など、項目にバインドしていない状態は、タブを切り替えても引き継がれます。
生成したタブのヘッダーは ItemTemplate で決めます。TabControl には HeaderTemplate プロパティがありません。TabControl に設定するテンプレートは、ItemTemplate(ヘッダー)、ContentTemplate(ページ)、Template です。SelectedContentTemplate は、選択中のタブで使われているテンプレートを返す読み取り専用のプロパティです。HeaderTemplate は TabItem 側のプロパティです。
| 内容の渡し方 | 計測値 | 戻したときに同じインスタンスか |
|---|---|---|
| TabItem.Content に要素: タブごとの Loaded の回数 | 最初 1 / 1 / 1、1 -> 2 -> 1 の後: 2 / 2 / 1 | True |
| 同じ条件、最初のタブごとの測定の回数 | 1 / 0 / 0 | - |
| ItemsSource + ContentTemplate: 作られた内容のインスタンスの数 | 最初 1 個作成、1 -> 2 -> 1 の後: 1 個作成 | True |
3 つのタブを 1 → 2 → 1 と切り替えたときの、内容が読み込まれるタイミングと使い回し。.NET 10 / Windows 11 で計測。
ヘッダー、タブの帯の位置、テンプレート
文字列のヘッダーは AccessText で表示されるため、アンダースコアはアクセスキーの指定になります。Header="File_Name" では N がアクセスキーになり、アンダースコア自体は表示されません。WPF の Label でアンダーバーが消える理由と回避方法で、TabItem のヘッダーでも消えることを確かめています。同じ記事では、同じく AccessText を使う Label で __ と書くとアンダースコアが 1 つ表示されることも確かめています。タブの幅はヘッダーに合わせて変わり、計測した環境では「File_Name」で 65.65、「A」で 19.74 でした(幅はフォントと表示スケールによって変わります)。
TabItem の TabStripPlacement は読み取り専用で、親の TabControl に従います。TabControl.TabStripPlacement="Left" では、2 つの TabItem のどちらも Left になりました。設定するのは TabControl 側です。TabItem 側の値は、タブの帯がどの辺にあるかを知りたいテンプレートやトリガーで使います。
既定の TabItem のテンプレートには表示状態のグループ(VisualStateGroup)がなく、16 個のトリガーで見た目を切り替えています。既定のテンプレートをコピーして直す場合は、このトリガーを編集します。
デモアプリで試す

デモアプリの TabItem のページには 2 つの欄があります。IsSelected の欄では、各タブの IsSelected にチェックボックスを Mode を指定せずにバインドしているので、タブをクリックするとチェックボックスも変わります。TabStripPlacement の欄では、コンボボックスでタブの帯の位置を変え、各 TabItem の読み取り専用の TabStripPlacement を表示します。各欄の下の「Show Code」リンクで、その欄の XAML を表示できます。次の XAML は、IsSelected の欄(TabItemUsageControl.xaml)から、スタイルと周囲の GroupBox を省き、名前空間の宣言を加えたものです。
<StackPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<CheckBox x:Name="IsSelectedTab1" VerticalContentAlignment="Center" Content="Tab1" />
<CheckBox x:Name="IsSelectedTab2" VerticalContentAlignment="Center" Content="Tab2" />
<TabControl x:Name="IsSelectedTabControl">
<TabItem Content="Item1" Header="Tab1"
IsSelected="{Binding IsChecked, ElementName=IsSelectedTab1}" />
<TabItem Content="Item2" Header="Tab2"
IsSelected="{Binding IsChecked, ElementName=IsSelectedTab2}" />
</TabControl>
</StackPanel>
IsSelected は既定で TwoWay にバインドされるので、チェックボックスはタブを操作するだけでなく、タブの状態にも追従します。Tab2 を選んでいる状態で Tab1 のソースを True にする(チェックボックスをオンにする操作に当たる)と、Tab1 が選ばれ、Tab2 のソースは False になりました。
関連するコントロールと記事
- TabControl — 同時に 1 つの TabItem を選択し、その内容を表示する親コントロールです。
- WPF の Label でアンダーバーが消える理由と回避方法 — TabItem のヘッダーにも当てはまるアクセスキーの挙動を扱います。
ソースコードと計測の方法
このページの挙動の記述は、すべて .NET 10 / Windows 11 で実際に動かして確かめたものです。計測には、このサイトのスクリーンショット生成ツールの TabItemDemoScene を使いました。タブの見出しのクリックは、計測用のウィンドウの上で実際のマウスを使って行いました。