WPF 標準コントロールデモアプリ › TreeView
TreeView List
TreeView は階層的なデータを、展開・折りたたみのできるノードとして表示するコントロールです。ノードは入れ子にした TreeViewItem か、データと HierarchicalDataTemplate から作ります。
既定では仮想化されない
TreeView は ItemsControl を継承しています。各ノードは HeaderedItemsControl を継承した TreeViewItem で、自分の子ノードを持ちます。ListBox と違い、TreeView は既定では仮想化されません。高さ 100 の TreeView に最上位のノードを 1,000 個入れると、VirtualizingPanel.IsVirtualizing は False で、項目は StackPanel に並び、TreeViewItem が 1,000 個すべて作られました。VirtualizingPanel.IsVirtualizing="True" にすると、パネルは VirtualizingStackPanel になり、作られたのは 12 個でした。大きなツリーでは有効にします。
IsExpanded のバインドは TwoWay でないと外れる
TreeViewItem の IsExpanded は、ノードが子を表示しているかどうかです。IsSelected と違い、既定では TwoWay にバインドされません。チェックボックスを Mode を指定せずに Node 1 の IsExpanded にバインドした状態で、Node 1 をその展開ボタンで折りたたむと、このバインドが外れました。ノードは折りたたまれましたが、チェックボックスはオンのままで、その後チェックを外して付け直してもノードは展開されませんでした。このようなバインドには Mode=TwoWay を指定します。
ItemContainerStyle で TwoWay にバインドすると、状態はデータ側に保たれます。折りたたまれた親の下で、ソースの IsExpanded を true にした子は、まだコンテナーがありませんでしたが、親を展開した時点で展開されました。展開の状態はデータに持たせます。
展開したときに子を読み込むフォルダーのツリーでは、仮の子を置いて Expanded のハンドラーで入れ替える方法が使えました。展開ボタンを実際にクリックすると、本来の 2 つの子が表示されました。
選択は SelectedItem ではなく IsSelected で行う
選択できるノードは一度に 1 つです。SelectedItem と SelectedValue は読み取り専用で、SelectedItem にバインドを設定しようとすると ArgumentException が発生しました。選択は、各ノードの IsSelected で変えます。詳しくはWPF TreeView で任意のノードをコードから選択・展開する方法と SelectedItem が読み取り専用である理由で扱っています。SelectedItem は、XAML に書いたノードでは TreeViewItem を、ItemsSource から作ったノードではデータのオブジェクトを返しました。
IsSelected は既定で TwoWay にバインドされます。ノードを選ぶと前のノードの選択が外れ、そのソースに False が書き戻されます。ItemContainerStyle で IsSelected をバインドし、データで Desktop、Mobile の順に True にすると、Mobile が選ばれ、Desktop のソースは False になりました。
読み取り専用の IsSelectionActive(添付プロパティ Selector.IsSelectionActive)は、選択がキーボードフォーカスを持っているかどうかを表します。フォーカスのない状態でコードから Node 2 を選ぶと IsSelectionActive は False、フォーカスを得ると True でした。フォーカスが TextBox へ移ると、IsSelected は True のまま、IsSelectionActive は False に戻りました。
SelectedValuePath には、SelectedValue として返す選択中のノードのプロパティを指定します。Header で “Gaming PC” を選ぶと、SelectedValue は “Gaming PC” になりました。
HierarchicalDataTemplate と、ノードのデータが届く先
HierarchicalDataTemplate は、ノードの見た目と、ItemsSource で子ノードの取得先を表します。TreeView の ItemTemplate に指定すると、すべての階層に適用され、2 階層目のノードもテンプレートを通して “Workstation PC” と表示されました。リソースに置いた暗黙のテンプレートとして使う場合は、DataType の型の項目にだけ適用されます。DataType が合わないとき、ノードは空にはならず、項目の ToString() が表示され、展開もできませんでした。
ItemContainerStyle に設定した ContextMenu は、開く前は DataContext が空でしたが、開いている間はノードのデータオブジェクトが DataContext になりました。
キー操作
ノード A(子を持ち、最初の子にもさらに子がある)を選び、フォーカスを置いた状態で試すと、下矢印キーで次のノードが選ばれました。右矢印キーで A が展開され、左矢印キーで再び折りたたまれました。テンキーの * で、A とその下のすべてのノードが展開されました。Space キーと Enter キーでは、選択も展開も変わりませんでした。
| 条件 | 計測値 |
|---|---|
| デモの IsExpanded の欄、展開ボタンでノードを折りたたむ: IsExpanded / CheckBox / バインド | False / True / 外れる |
| 続けて CheckBox を外してもう一度チェック: IsExpanded | False |
| 折りたたまれた親の下の子が IsExpanded=true(ソース): コンテナー(展開前 / 展開後) | 作られていない / 作られている、IsExpanded True、葉 作られている |
| 明示した HierarchicalDataTemplate: 2 階層目のノードの文字 | "Workstation PC" |
| 暗黙の HierarchicalDataTemplate、DataType=Node(項目は Node) | 最初のノードの表示 "Desktop"、展開できるか True |
| 暗黙の HierarchicalDataTemplate、DataType=String(項目は Node) | 最初のノードの表示 "ToString:Desktop"、展開できるか False |
| ルートのノード 1,000 個、IsVirtualizing 指定なし: 値, パネル, TreeViewItem の数 | False, StackPanel, 1000 |
| ルートのノード 1,000 個、IsVirtualizing True: 値, パネル, TreeViewItem の数 | True, VirtualizingStackPanel, 12 |
| A(選択、フォーカスあり)で Down キー | 選択 B; A 折りたたみ, A1 折りたたみ |
| A(選択、フォーカスあり)で Right キー | 選択 A; A 展開, A1 折りたたみ |
| A(選択、フォーカスあり)で Left(Right の後) キー | 選択 A; A 折りたたみ, A1 折りたたみ |
| A(選択、フォーカスあり)で Space キー | 選択 A; A 折りたたみ, A1 折りたたみ |
| A(選択、フォーカスあり)で Enter キー | 選択 A; A 折りたたみ, A1 折りたたみ |
| A(選択、フォーカスあり)で テンキーの * キー | 選択 A; A 展開, A1 展開 |
| ItemContainerStyle の ContextMenu(Mobile): DataContext(開く前 / 開いている間) | null / ToString:Mobile |
| ScrollViewer.HorizontalScrollBarVisibility / VerticalScrollBarVisibility(値の出どころ) | Auto (DefaultStyle) / Auto (DefaultStyle) |
| Expanded で仮の項目を置き換える: 前; 展開ボタンを実際にクリックした後(読み込みの回数) | (loading); Child 1, Child 2 (1) |
展開、テンプレート、仮想化、キー操作、コンテキストメニュー。.NET 10 / Windows 11 で計測。
デモアプリで試す

デモアプリの TreeView のページには、IsExpanded、IsSelected と IsSelectionActive、SelectedValuePath と SelectedValue、ItemTemplate と HierarchicalDataTemplate、スクロールバーの欄があります。IsExpanded の欄は、チェックボックスを Mode を指定せずに Node 1 の IsExpanded にバインドしているので、上で述べたようにバインドが外れます。SelectedValuePath の欄は、XAML に書いたノードに対してテキストボックスでパスを入力します。スクロールバーの欄には、縦横どちらのスクロールバーも試せるよう、長い最初のノードに 5 つの子を置いています。既定のスタイルは ScrollViewer.HorizontalScrollBarVisibility と ScrollViewer.VerticalScrollBarVisibility をどちらも Auto に設定しています。各欄の下の「Show Code」リンクで、その欄の XAML を表示できます。次の XAML は、ItemTemplate / HierarchicalDataTemplate の欄(TreeViewUsageControl.xaml)から、スタイルと周囲の GroupBox を省き、名前空間の宣言を加えたものです。デモアプリでは GroupBox のリソースにあるテンプレートを、ここでは StackPanel のリソースに置いています。DeviceTree は、Name と Children を持つ項目のコレクションです。
<StackPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel.Resources>
<HierarchicalDataTemplate x:Key="MyTreeViewTemplate" x:Name="MyTreeViewTemplate"
ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<Path Margin="0,0,5,0" VerticalAlignment="Center"
Data="M0,0 L8,4 L0,8 Z" Fill="Orange" />
<TextBlock VerticalAlignment="Center" Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
</StackPanel.Resources>
<TreeView x:Name="ItemTemplateTreeView" Height="150"
ItemTemplate="{StaticResource MyTreeViewTemplate}"
ItemsSource="{Binding DeviceTree}" />
</StackPanel>
関連するコントロールと記事
- WPF TreeView で任意のノードをコードから選択・展開する方法と SelectedItem が読み取り専用である理由 — コードからの選択と、コンテナーが作られるタイミングを扱います。
- ListBox — 階層のない一覧で、
ItemsSourceから項目を与えた場合は既定で仮想化されます(ListBoxItemを直接追加した場合は除く)。 - Expander — 1 つだけの折りたたみ可能な領域です。
ソースコードと計測の方法
このページの挙動の記述は、すべて .NET 10 / Windows 11 で実際に動かして確かめたものです。計測には、このサイトのスクリーンショット生成ツールの TreeViewDemoScene を使いました。キー操作は表示したウィンドウへキーのイベントを送り、展開ボタンは、クリックと同じ切り替え処理を実行する UI オートメーションで切り替えて再現しました。仮の子を持つノードの展開ボタンだけは、実際のマウスでクリックしました。