PySide Model/View【第2弾】QTreeViewで作るフォルダ構造・階層リストのGUI表示

\ 迷ったらまずTechAcademyの無料カウンセリング! /

前回の記事では QTableView と QStandardItemModel を使って、表形式のデータをきれいに表示する方法を学びました。
しかし実際のアプリでは「フォルダ構造」や「カテゴリ分けされたリスト」のように、階層構造を持つデータを扱いたい場面がよくあります。

そこで今回は、そのような場面で威力を発揮する QTreeView を取り上げます。
QTreeView を使えば、エクスプローラーのような ツリー形式のGUI を簡単に作ることができます。さらに、QStandardItemModel と組み合わせることで、前回の表形式の知識をそのまま応用できるのも大きな魅力です。

この記事では、初心者でも理解しやすいように QTreeView の基本操作から実践的な使い方まで、順を追って解説していきます。

目次

QTreeViewとは?

QTreeView階層構造を表示できるビュー です。
内部的には QStandardItemModel を組み合わせて使うことが多く、ツリー形式のデータを簡単に作成できます。

基本コード例(階層データの表示)

from PySide6.QtWidgets import QApplication, QTreeView, QMainWindow
from PySide6.QtGui import QStandardItemModel, QStandardItem
import sys

class Window(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("QTreeView の基本")
        self.resize(400, 300)

        # モデル作成
        model = QStandardItemModel()
        model.setHorizontalHeaderLabels(["項目名", "説明"])

        # 階層データを追加
        parent1 = QStandardItem("フルーツ")
        parent1.appendRow([QStandardItem("リンゴ"), QStandardItem("赤い果物")])
        parent1.appendRow([QStandardItem("バナナ"), QStandardItem("黄色い果物")])

        parent2 = QStandardItem("野菜")
        parent2.appendRow([QStandardItem("ニンジン"), QStandardItem("オレンジ色の野菜")])
        parent2.appendRow([QStandardItem("キャベツ"), QStandardItem("緑の野菜")])

        model.appendRow([parent1, QStandardItem("果物のカテゴリ")])
        model.appendRow([parent2, QStandardItem("野菜のカテゴリ")])

        # TreeView 設定
        tree = QTreeView()
        tree.setModel(model)
        tree.expandAll()  # すべて展開して表示

        self.setCentralWidget(tree)

app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec())

主なプロパティとメソッド一覧(全解説)

Noメソッド / プロパティ説明
1tree.setModel(model) / tree.model()View と Model を関連付け/取得します。
2model.setHorizontalHeaderLabels(list)ヘッダー(列名)をまとめて設定します。
3model.invisibleRootItem()見えない最上位ルートを取得します(階層の起点)。
4item.appendRow(items) / item.insertRow(i, items) / item.removeRow(i)親アイテムの子行を追加/挿入/削除します。
5model.indexFromItem(item) / model.itemFromIndex(index)Item と QModelIndex を相互変換します。
6model.findItems(text, flags, column)テキスト検索(部分一致/正規表現/再帰)を行います。
7tree.setEditTriggers(flags)編集開始のトリガーを制御します。
8tree.setSelectionMode(mode) / tree.setSelectionBehavior(behav)選択の方法(単一・複数/行・項目)を制御します。
9tree.expandAll() / tree.collapseAll() / tree.setExpanded(idx, bool)展開・折りたたみを制御します。
10tree.setHeaderHidden(bool) / tree.header()setSectionResizeMode(...) / resizeColumnToContents(col)ヘッダーの表示や列幅の自動調整など見た目を制御します。

各プロパティ・メソッド詳細解説

【1】モデルを設定する

概要QTreeView は自分でデータを持たないため、QStandardItemModel などのモデルを関連付けて表示します。複数の View で同じ Model を共有できます。
メソッドtree.setModel(model) / tree.model()
使い方(コード例)

model = QStandardItemModel(0, 2)   # 0行・2列
model.setHorizontalHeaderLabels(["項目名", "説明"])

# TreeView 設定
tree = QTreeView()
tree.setModel(model)
tree.expandAll()  # すべて展開して表示
self.setCentralWidget(tree)

【2】ヘッダー(列名)をまとめて設定する

概要:ツリーの列見出しを一括設定します。複数列構成のツリーでも可読性を保てます。
メソッドmodel.setHorizontalHeaderLabels(list)
使い方(コード例)

model.setHorizontalHeaderLabels(["カテゴリ", "説明"])

個別列は model.setHeaderData(section, Qt.Horizontal, value, Qt.DisplayRole) でも設定可能。

【3】ルート(見えない最上位)を取得する

概要invisibleRootItem() は、画面には表示されない最上位の親です。ここから appendRow で第一層(トップレベル)を作ります。
メソッドmodel.invisibleRootItem()
使い方(コード例)

root = model.invisibleRootItem()
root.appendRow([QStandardItem("フォルダA"), QStandardItem("説明A")])

【4】子行の追加/挿入/削除

概要:任意の QStandardItem の下に子行を追加・挿入し、階層データを構築します。
メソッドitem.appendRow(items) / item.insertRow(i, items) / item.removeRow(i)
使い方(コード例)

parent = QStandardItem("親カテゴリ")
desc   = QStandardItem("このカテゴリの説明")
model.invisibleRootItem().appendRow([parent, desc])

child1 = QStandardItem("子1")
child1_desc = QStandardItem("子1の説明")
parent.appendRow([child1, child1_desc])          # 末尾に追加

parent.insertRow(0, [QStandardItem("先頭子"), QStandardItem("説明")])  # 先頭に挿入
parent.removeRow(1)  # 2番目の子を削除

items列数ぶんの QStandardItem を並べたリストです。

【5】Item ⇄ Index 相互変換

概要:選択やカーソルは QModelIndex で渡されます。表示や編集の実体(QStandardItem)と相互に行き来できると便利です。
メソッドmodel.indexFromItem(item) / model.itemFromIndex(index)
使い方(コード例)

idx  = model.indexFromItem(parent)      # Item → Index
item = model.itemFromIndex(idx)         # Index → Item

index があれば model.data(index, role)setData(index, value, role) も使えます。

【6】文字列検索(部分一致・再帰)

概要findItems は列単位にテキスト検索します。Qt.MatchRecursive を付けると子孫ノードまで再帰的に検索します。
メソッドmodel.findItems(text, flags=Qt.MatchContains, column=0)
使い方(コード例)

from PySide6.QtCore import Qt
hits = model.findItems("親カテゴリ", flags=Qt.MatchContains | Qt.MatchRecursive, column=0)
for it in hits:
    idx = model.indexFromItem(it)
    tree.setExpanded(idx, True)  # 見つけた項目を展開表示
    print(idx)

# <PySide6.QtCore.QModelIndex(1,0,0x223145fb0b0,QStandardItemModel(0x223146ab8f0)) at 0x000002233B215440>

正規表現は Qt.MatchRegExp を使います(必要に応じて Python 側でフィルタした方が柔軟な場合も)。

【7】編集開始トリガーを制御する

概要:どの操作でセル編集を始めるかをコントロールします(ダブルクリック/クリック/キー入力など)。
メソッドtree.setEditTriggers(QAbstractItemView.EditTrigger)
使い方(コード例)

from PySide6.QtWidgets import QAbstractItemView as AIV
tree.setEditTriggers(AIV.NoEditTriggers)   # すべて禁止
# 例:ダブルクリックで編集
tree.setEditTriggers(AIV.DoubleClicked)

個別に編集不可にしたいときは item.setEditable(False) を併用します。

説明
tree.setEditTriggers(QAbstractItemView.DoubleClicked)編集できるタイミングを設定
tree.setEditTriggers(QAbstractItemView.NoEditTriggers)編集不可
tree.setEditTriggers(QAbstractItemView.CurrentChanged)カレントセルが変わった時に編集可
tree.setEditTriggers(QAbstractItemView.DoubleClicked)ダブルクリックで編集可
tree.setEditTriggers(QAbstractItemView.SelectedClicked)選択中のセルをクリックで編集可
treew.setEditTriggers(QAbstractItemView.EditKeyPressed)EnterキーやF2キーで編集可
tree.setEditTriggers(QAbstractItemView.AnyKeyPressed)文字入力で即編集開始
tree.setEditTriggers(QAbstractItemView.AllEditTriggers)すべてのトリガーで編集可

【8】選択モード/選択単位を制御する

概要:単一選択/複数選択、行単位/項目単位など選択の挙動を決めます。
メソッドtree.setSelectionMode(mode) / tree.setSelectionBehavior(behavior)
使い方(コード例)

from PySide6.QtWidgets import QAbstractItemView as AIV
tree.setSelectionMode(AIV.ExtendedSelection)   # Ctrl/Shift による複数選択
tree.setSelectionBehavior(AIV.SelectItems)     # 項目(セル)単位で選択

選択結果は tree.selectedIndexes()tree.selectionModel() から取得します。

説明
tree.setSelectionMode(QAbstractItemView.SingleSelection)選択方法を設定
tree.setSelectionMode(QAbstractItemView.NoSelection)選択不可
tree.setSelectionMode(QAbstractItemView.SingleSelection)1セルのみ選択可
tree.setSelectionMode(QAbstractItemView.MultiSelection)複数セル選択可(Ctrlクリック)
tree.setSelectionMode(QAbstractItemView.ExtendedSelection)複数セル選択可(ShiftやCtrl併用)
tree.setSelectionBehavior(QAbstractItemView.SelectRows)選択対象(セル・行・列)を設定
tree.setSelectionBehavior(QAbstractItemView.SelectItems)セル単位で選択
tree.setSelectionBehavior(QAbstractItemView.SelectRows)行単位で選択
tree.setSelectionBehavior(QAbstractItemView.SelectColumns)列単位で選択

【9】展開/折りたたみを制御する

概要:個別ノードやツリー全体の展開・折りたたみを制御します。初期状態の見せ方に有効です。
メソッドtree.expandAll() / tree.collapseAll() / tree.setExpanded(index, bool)
使い方(コード例)

tree.expandAll()
# 特定ノードだけ展開
idx = model.indexFromItem(parent)
tree.setExpanded(idx, True)

大量ノードで expandAll() を多用すると描画コストが上がるので注意。

【10】ヘッダー表示と列幅調整

概要:ヘッダーの表示/非表示、列幅の伸縮モードや内容に応じた自動調整を行います。
メソッド

  • tree.setHeaderHidden(bool)
  • tree.header().setSectionResizeMode(section, mode)
  • tree.resizeColumnToContents(col)

使い方(コード例)

from PySide6.QtWidgets import QHeaderView
tree.setHeaderHidden(True)
tree.header().setSectionResizeMode(0, QHeaderView.ResizeToContents)
tree.header().setSectionResizeMode(1, QHeaderView.Stretch)

1列テキスト主体なら「最後の列だけ Stretch」にするとバランスがよくなります。

よくある質問(FAQ)

QTreeViewとQTableViewの違いは?

QTableView は表形式(2次元)専用ですが、QTreeView は階層(ツリー)構造を持てる点が違います。

QTableWidgetのように簡単に使えますか?

QTreeWidget なら簡単に使えますが、拡張性を考えるなら QTreeView + QStandardItemModel が推奨です。

ノードの展開イベントを拾えますか?

はい、expandedcollapsed シグナルを使えます。

ユーザーが編集できるようにできますか?

item.setEditable(True) を設定すれば可能です。

ツリーをファイルシステム風に使えますか?

QFileSystemModel + QTreeView を組み合わせれば可能です。

まとめ

今回の記事では QTreeView + QStandardItemModel を使って、階層構造を持つデータを表示・操作する方法を解説しました。
前回の QTableView が「表形式の一覧表示」に適していたのに対して、QTreeView は フォルダツリーや設定階層のような「親子関係を持つデータ」 を表現できるのが大きな特徴です。

実際の開発では、ファイルブラウザ風のツリー、オプション設定の階層、カテゴリごとの一覧など、応用範囲は非常に広いです。
特に QStandardItemModel は「手軽に使える柔軟なモデル」なので、Model/View の入門から実践まで幅広く活用できます。

次のステップとしては、独自モデル(QAbstractItemModel を継承)で柔軟なデータ管理を行う方法 や、チェックボックス・アイコン付きのツリーUI に進むとさらに応用力が高まるでしょう。

シェアしてくださると嬉しいです!
  • URLをコピーしました!

コメント

コメントする

目次