QTreeView::expanded()だけじゃない!Qtツリービュー展開イベントの代替アプローチ

2025-05-27

Qtプログラミングにおけるvoid QTreeView::expanded()は、QTreeViewウィジェットのシグナルの一つです。

どのようなものか?

QTreeViewは、階層的なデータをツリー形式で表示するためのウィジェットです。ファイルエクスプローラやフォルダツリーのように、親項目の中に子項目が含まれる構造を表現します。

void QTreeView::expanded(const QModelIndex &index)シグナルは、ツリー内のある項目が展開された(子項目が表示された)ときに発行されます

主な特徴と使い方

  • 用途:
    • データの遅延ロード: 展開された項目に初めてアクセスされたときに、その子データをモデルにロードする。
    • ビューの更新: 展開された項目に合わせて、関連する別のウィジェットの表示を更新する。
    • ログ記録: ユーザーがどの項目を展開したかを記録する。
    • アクションの実行: 展開された項目に基づいて特定のアクションを実行する。
  • 引数 const QModelIndex &index: このシグナルが発行される際、どの項目が展開されたのかを示すQModelIndexオブジェクトが引数として渡されます。QModelIndexは、モデル内のデータ項目を一意に識別するためのものです。これを使って、展開された項目に関する情報を取得したり、その項目に特化した処理を行うことができます。
  • シグナル(Signal): Qtのモデル/ビューアーキテクチャでは、ウィジェットの状態変化を「シグナル」として通知します。expanded()もその一つです。
// QTreeView のインスタンスを仮定
QTreeView *myTreeView = new QTreeView(this);

// expanded() シグナルをカスタムスロットに接続
connect(myTreeView, &QTreeView::expanded, this, &MyClass::onItemExpanded);

// カスタムスロットの実装
void MyClass::onItemExpanded(const QModelIndex &index)
{
    // 展開された項目のデータを取得
    QString itemName = index.data(Qt::DisplayRole).toString();
    qDebug() << "項目が展開されました: " << itemName;

    // 例:展開された項目が特定の条件を満たす場合、子データをロード
    if (itemName == "大きなフォルダ") {
        loadChildrenData(index); // 子データをロードする関数を呼び出す
    }
}


expanded()シグナルが発行されない/期待通りに動作しない

よくある原因

  • QTreeViewの初期化が不十分
    QTreeViewが正しくモデルに設定されていない、あるいは表示可能になっていない状態で操作しようとしている場合、シグナルが発行されないことがあります。
    • トラブルシューティング
      QTreeView::setModel()が呼び出され、有効なモデルが設定されていることを確認してください。また、ウィジェットが表示される(例: show()が呼び出される)前に展開処理を行う場合、遅延実行を検討してください。
  • イベントループがブロックされている
    expanded()シグナルはQtのイベントループを通じて処理されます。もし、シグナルを接続したスロットや、それ以外の場所で長時間にわたる計算処理やブロッキング処理が行われている場合、イベントループがブロックされ、UIの更新やシグナルの処理が遅延したり、全く行われなかったりすることがあります。
    • トラブルシューティング
      時間のかかる処理は別スレッドに移動させるか、QApplication::processEvents()を定期的に呼び出してイベントループを処理する(ただし、これは慎重に使用すべきです)ことで、UIの応答性を保つようにしてください。
  • モデルが階層構造を持っていない
    QTreeViewは階層的なデータを表示するために設計されています。もしモデル(QAbstractItemModelやその派生クラス)が、hasChildren()が常にfalseを返す、またはrowCount()が常に0を返すなど、子項目を持たないように実装されている場合、expanded()シグナルが発行される機会がありません。
    • トラブルシューティング
      モデルのhasChildren()rowCount()index()メソッドが正しく実装され、実際に子項目が存在するインデックスに対して期待される値を返しているか確認してください。特に、QFileSystemModelのような動的にデータをロードするモデルの場合、データが完全にロードされる前に展開しようとすると、期待通りに動作しないことがあります。この場合、QFileSystemModel::directoryLoaded()シグナルなどを利用して、ディレクトリのロード完了後に展開処理を行うなどの工夫が必要です。

expanded()シグナルで渡されるQModelIndexが不正/期待と異なる

よくある原因

  • モデルの変更後にQModelIndexが無効になる
    モデルのデータが変更されたり、項目が追加/削除されたりすると、以前に取得したQModelIndexが無効になることがあります。これは特に、expanded()シグナルを受け取った後に、そのインデックスとは関係ないモデルの変更があった場合に起こり得ます。
    • トラブルシューティング
      永続的な参照が必要な場合は、QModelIndexの代わりにQPersistentModelIndexを使用することを検討してください。QPersistentModelIndexは、モデルの内部的な変化に対して、可能な限り有効な状態を保とうとします。
  • プロキシモデル(QSortFilterProxyModelなど)を使用している
    QSortFilterProxyModelを使用している場合、expanded()シグナルで渡されるQModelIndexビューのインデックスであり、元のソースモデルのインデックスではありません。このインデックスを使って直接ソースモデルのデータを参照しようとすると、誤ったデータにアクセスしたり、無効なインデックスになったりすることがあります。
    • トラブルシューティング
      プロキシモデルを使用している場合は、QSortFilterProxyModel::mapToSource()メソッドを使用して、ビューのインデックスをソースモデルのインデックスに変換してから、ソースモデルのデータを操作してください。逆に、ソースモデルのインデックスからビューのインデックスを取得する場合は、mapFromSource()を使用します。

パフォーマンスの問題

よくある原因

  • 不必要なデータのロード
    expanded()シグナルを受け取るたびに、その項目のすべての子孫データを無条件にロードしてしまうと、特にツリーが深い場合やデータ量が多い場合にパフォーマンスの問題が発生します。
    • トラブルシューティング
      必要なデータのみをロードする「遅延ロード(Lazy Loading)」を実装してください。例えば、項目が展開されたときに、その直下の子項目のみをモデルに追加し、さらにその子項目が展開されたときに、その子項目を追加するといった段階的なロードを行います。
  • expanded()スロット内で重い処理
    expanded()シグナルを受け取るスロット内で、ファイルI/O、ネットワーク通信、複雑な計算など、時間のかかる処理を実行すると、UIの応答性が低下したり、フリーズしたりすることがあります。
    • トラブルシューティング
      上記「イベントループがブロックされている」と同様に、重い処理は別スレッドに移動させ、結果をシグナルでUIスレッドに通知する(スレッドセーフな方法で)ようにしてください。または、処理を細かく分割し、QTimer::singleShot(0, ...)などを利用して、イベントループに処理を分割して返すことで、UIの応答性を維持する方法も有効です。
  • モデルがまだ完全に構築されていない
    expandAll()expandToDepth()を呼び出す際、もしモデルがまだすべてのデータをロードし終えていない場合(特に動的にデータをロードするモデル)、現時点で利用可能な項目のみが展開され、期待する全ての項目が展開されないことがあります。
    • トラブルシューティング
      モデルが完全にロードされたことを示すシグナル(例: QFileSystemModeldirectoryLoaded()など)を待ってから、これらの展開メソッドを呼び出すようにしてください。また、QAbstractItemModellayoutChanged()シグナルと組み合わせ、短いタイマーで遅延実行することで、ビューのレイアウトが更新された後に展開処理が確実に実行されるようにするテクニックもあります。


例1: 基本的な接続と展開された項目の表示

この例では、シンプルなモデルを作成し、QTreeViewの項目が展開されたときに、どの項目が展開されたかをデバッグ出力する基本的な方法を示します。

// main.cpp
#include <QApplication>
#include <QTreeView>
#include <QStandardItemModel>
#include <QDebug> // for qDebug()

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    // 1. モデルの準備 (QStandardItemModelを使用)
    QStandardItemModel model;
    QStandardItem *parentItem;
    QStandardItem *childItem;

    // ルート項目を追加
    parentItem = new QStandardItem("Root Item 1");
    model.appendRow(parentItem);

    // 子項目を追加
    childItem = new QStandardItem("Child of Root 1 - A");
    parentItem->appendRow(childItem);
    childItem->appendRow(new QStandardItem("Grandchild 1"));
    childItem->appendRow(new QStandardItem("Grandchild 2"));

    childItem = new QStandardItem("Child of Root 1 - B");
    parentItem->appendRow(childItem);

    parentItem = new QStandardItem("Root Item 2");
    model.appendRow(parentItem);
    parentItem->appendRow(new QStandardItem("Child of Root 2 - X"));


    // 2. QTreeView の作成とモデルの設定
    QTreeView treeView;
    treeView.setModel(&model);

    // 3. expanded() シグナルをスロットに接続
    // ラムダ式を使用して、展開されたインデックスの情報を表示
    QObject::connect(&treeView, &QTreeView::expanded,
                     [&](const QModelIndex &index) {
        // 展開された項目のテキストを取得
        QString itemText = model.data(index, Qt::DisplayRole).toString();
        qDebug() << "項目が展開されました: " << itemText
                 << " (Row:" << index.row() << ", Column:" << index.column()
                 << ", Parent:" << (index.parent().isValid() ? model.data(index.parent(), Qt::DisplayRole).toString() : "N/A") << ")";
    });

    treeView.setWindowTitle("QTreeView Expanded Example");
    treeView.resize(400, 300);
    treeView.show();

    return a.exec();
}

解説

  1. QStandardItemModelを使って簡単なツリー構造のデータを作成します。
  2. QTreeViewを作成し、作成したモデルを設定します。
  3. QObject::connect()を使用して、treeViewexpanded()シグナルをラムダ式で定義されたスロットに接続します。
  4. スロット内では、引数として渡されたQModelIndexを使って、展開された項目の表示テキスト(Qt::DisplayRole)を取得し、qDebug()で出力しています。また、行、列、親項目の情報も出力しています。

このコードを実行し、ツリービューの項目をクリックして展開すると、コンソールに展開された項目の情報が表示されます。

例2: 遅延ロード (Lazy Loading) のシミュレーション

大規模なツリー構造では、すべてのデータを一度にロードするとパフォーマンスの問題が発生することがあります。このような場合、項目が展開されたときに初めてその子データをロードする「遅延ロード」が有効です。

この例では、QAbstractItemModelを継承したカスタムモデルを使用し、expanded()シグナルを利用して子データを動的に追加する方法をシミュレートします。

Model (MyLazyTreeModel.h)

#ifndef MYLAZYTREEMODEL_H
#define MYLAZYTREEMODEL_H

#include <QAbstractItemModel>
#include <QVariant>
#include <QDebug>

// ノード構造を定義
struct MyTreeNode {
    QString name;
    QVector<MyTreeNode*> children;
    bool childrenLoaded; // 子が既にロードされたかを示すフラグ
    MyTreeNode *parent;

    MyTreeNode(const QString &n, MyTreeNode *p = nullptr) : name(n), parent(p), childrenLoaded(false) {}
    ~MyTreeNode() { qDeleteAll(children); } // 子ノードを削除
};

class MyLazyTreeModel : public QAbstractItemModel
{
    Q_OBJECT

public:
    explicit MyLazyTreeModel(QObject *parent = nullptr);
    ~MyLazyTreeModel();

    // QAbstractItemModel の純粋仮想関数の実装
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
    Qt::ItemFlags flags(const QModelIndex &index) const override;
    QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
    QModelIndex parent(const QModelIndex &index) const override;
    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
    int columnCount(const QModelIndex &parent = QModelIndex()) const override;

    // 遅延ロードのための関数
    void loadChildren(const QModelIndex &index);

private:
    MyTreeNode *rootNode;
    MyTreeNode *getNode(const QModelIndex &index) const;

    // ヘルパー関数:指定されたノードにダミーの子を追加する(シミュレーション用)
    void addDummyChildren(MyTreeNode *parentNode);
};

#endif // MYLAZYTREEMODEL_H

Model (MyLazyTreeModel.cpp)

#include "MyLazyTreeModel.h"

MyLazyTreeModel::MyLazyTreeModel(QObject *parent)
    : QAbstractItemModel(parent)
{
    rootNode = new MyTreeNode("Root");
    // 初期のダミーの子を追加
    addDummyChildren(rootNode); // 例として、ルート直下には最初からダミーの子があるとする
}

MyLazyTreeModel::~MyLazyTreeModel()
{
    delete rootNode;
}

QVariant MyLazyTreeModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return QVariant();

    MyTreeNode *node = getNode(index);
    if (!node)
        return QVariant();

    if (role == Qt::DisplayRole) {
        return node->name;
    }
    return QVariant();
}

Qt::ItemFlags MyLazyTreeModel::flags(const QModelIndex &index) const
{
    if (!index.isValid())
        return Qt::NoItemFlags;
    return QAbstractItemModel::flags(index);
}

QModelIndex MyLazyTreeModel::index(int row, int column, const QModelIndex &parent) const
{
    if (!hasIndex(row, column, parent))
        return QModelIndex();

    MyTreeNode *parentNode = getNode(parent);
    if (!parentNode || row >= parentNode->children.size())
        return QModelIndex();

    return createIndex(row, column, parentNode->children.at(row));
}

QModelIndex MyLazyTreeModel::parent(const QModelIndex &index) const
{
    if (!index.isValid())
        return QModelIndex();

    MyTreeNode *childNode = getNode(index);
    if (!childNode || childNode == rootNode || !childNode->parent)
        return QModelIndex();

    MyTreeNode *parentNode = childNode->parent;
    if (parentNode == rootNode) // 親がルートノードの場合
        return QModelIndex();

    // 親の行番号を計算
    if (parentNode->parent) {
        for (int row = 0; row < parentNode->parent->children.size(); ++row) {
            if (parentNode->parent->children.at(row) == parentNode) {
                return createIndex(row, 0, parentNode);
            }
        }
    }
    return QModelIndex(); // ここに来るべきではない
}

int MyLazyTreeModel::rowCount(const QModelIndex &parent) const
{
    MyTreeNode *parentNode = getNode(parent);
    if (!parentNode)
        return 0;

    // 子がまだロードされていない場合でも、ダミーで1つあるように見せかける
    // これにより、ツリービューに展開ボタンが表示される
    if (!parentNode->childrenLoaded && parentNode != rootNode) {
        return 1; // ダミーの子が1つあるように見せかける
    }
    return parentNode->children.size();
}

int MyLazyTreeModel::columnCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent);
    return 1; // 1列のみ
}

void MyLazyTreeModel::loadChildren(const QModelIndex &index)
{
    MyTreeNode *node = getNode(index);
    if (!node || node->childrenLoaded) {
        return; // すでにロード済み、または無効なノード
    }

    qDebug() << "Lazy loading children for:" << node->name;

    // 既存のダミーの子を削除(もしあれば)
    if (node->children.size() > 0 && node->children.at(0)->name == "Loading...") {
         beginRemoveRows(index, 0, 0);
         delete node->children.takeAt(0); // ダミーノードを削除
         endRemoveRows();
    }


    // 実際のデータをロードする代わりに、新しい子を追加するシミュレーション
    // ここで、実際のファイルシステムやデータベースからデータを取得する処理が入る
    int oldRowCount = node->children.size();
    beginInsertRows(index, oldRowCount, oldRowCount + 2); // 3つの新しい子を追加
    node->children.append(new MyTreeNode(node->name + " - Real Child 1", node));
    node->children.append(new MyTreeNode(node->name + " - Real Child 2", node));
    node->children.append(new MyTreeNode(node->name + " - Real Child 3", node));
    endInsertRows();

    // 新しく追加された実子のために、ダミーの子をさらに追加
    addDummyChildren(node->children.at(oldRowCount));
    addDummyChildren(node->children.at(oldRowCount + 1));
    addDummyChildren(node->children.at(oldRowCount + 2));


    node->childrenLoaded = true; // ロード済みフラグを設定
}

MyTreeNode *MyLazyTreeModel::getNode(const QModelIndex &index) const
{
    if (index.isValid()) {
        return static_cast<MyTreeNode*>(index.internalPointer());
    }
    return rootNode;
}

void MyLazyTreeModel::addDummyChildren(MyTreeNode *parentNode)
{
    // 子がまだロードされていない場合は、展開ボタンを表示させるためにダミーの子を追加
    if (!parentNode->childrenLoaded && parentNode->children.isEmpty()) {
        parentNode->children.append(new MyTreeNode("Loading...", parentNode));
    }
}

Main (main.cpp)

// main.cpp
#include <QApplication>
#include <QTreeView>
#include "MyLazyTreeModel.h" // 作成したモデルをインクルード

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    MyLazyTreeModel model;
    QTreeView treeView;
    treeView.setModel(&model);

    // expanded() シグナルをモデルの loadChildren() スロットに接続
    // QTreeView::expanded() は QModelIndex を引数に取るので、
    // それを MyLazyTreeModel::loadChildren() に渡す
    QObject::connect(&treeView, &QTreeView::expanded,
                     &model, &MyLazyTreeModel::loadChildren);

    treeView.setWindowTitle("QTreeView Lazy Loading Example");
    treeView.resize(400, 300);
    treeView.show();

    return a.exec();
}

解説

  1. MyLazyTreeModel
    • QAbstractItemModelを継承しています。
    • MyTreeNode構造体でツリーのノードを表現し、childrenLoadedフラグを持たせています。
    • rowCount()では、まだ子データがロードされていないノードに対しては、展開ボタンを表示させるためにダミーの子(ここでは1つ)があるように見せかけています。
    • loadChildren(const QModelIndex &index)メソッドがコアです。このメソッドは、指定されたインデックスの項目が展開されたときに呼び出され、実際に子データを生成(この例ではシミュレーション)し、beginInsertRows()endInsertRows()を使ってモデルに変更を通知します。
    • addDummyChildrenは、新しいノードが追加されたときに、そのノードも展開可能であるかのように見せるために、ダミーの子を追加するヘルパー関数です。
  2. main.cpp
    • MyLazyTreeModelのインスタンスを作成し、QTreeViewに設定します。
    • 最も重要な部分がconnect(&treeView, &QTreeView::expanded, &model, &MyLazyTreeModel::loadChildren);です。これにより、treeViewの項目が展開されると、modelloadChildren()メソッドが自動的に呼び出され、その項目に対応する子データがロードされます。

この例を実行すると、ツリービューの項目を展開するたびに、コンソールに"Lazy loading children for: ..."というメッセージが表示され、対応する子項目が追加されていくのがわかります。これにより、必要なデータだけを必要なときにロードするという遅延ロードの概念を理解できます。

ツリービューの項目が展開されたときに、それに関連する情報を別のウィジェット(例: QTextEdit)に表示する例です。

// main.cpp
#include <QApplication>
#include <QMainWindow>
#include <QTreeView>
#include <QStandardItemModel>
#include <QTextEdit>
#include <QVBoxLayout>
#include <QWidget>
#include <QDebug>

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr) : QMainWindow(parent)
    {
        // 1. モデルの準備
        QStandardItemModel *model = new QStandardItemModel(this);
        QStandardItem *parentItem;
        QStandardItem *childItem;

        parentItem = new QStandardItem("Section A");
        model->appendRow(parentItem);
        childItem = new QStandardItem("Topic A.1");
        parentItem->appendRow(childItem);
        childItem->setData("詳細情報:これはトピックA.1に関する詳細です。", Qt::UserRole + 1);
        childItem = new QStandardItem("Topic A.2");
        parentItem->appendRow(childItem);
        childItem->setData("詳細情報:トピックA.2は現在開発中です。", Qt::UserRole + 1);

        parentItem = new QStandardItem("Section B");
        model->appendRow(parentItem);
        childItem = new QStandardItem("Topic B.1");
        parentItem->appendRow(childItem);
        childItem->setData("詳細情報:B.1は新しい概念を導入します。", Qt::UserRole + 1);


        // 2. QTreeView の作成
        treeView = new QTreeView(this);
        treeView->setModel(model);

        // 3. QTextEdit の作成
        detailTextEdit = new QTextEdit(this);
        detailTextEdit->setReadOnly(true);
        detailTextEdit->setPlainText("ツリー項目を展開すると詳細が表示されます。");


        // 4. レイアウトの設定
        QWidget *centralWidget = new QWidget(this);
        QVBoxLayout *layout = new QVBoxLayout(centralWidget);
        layout->addWidget(treeView);
        layout->addWidget(detailTextEdit);
        setCentralWidget(centralWidget);

        // 5. expanded() シグナルをカスタムスロットに接続
        connect(treeView, &QTreeView::expanded,
                this, &MainWindow::onItemExpanded);

        setWindowTitle("QTreeView Expanded Details Example");
        resize(600, 400);
    }

private slots:
    void onItemExpanded(const QModelIndex &index)
    {
        // 展開された項目の表示テキストを取得
        QString itemText = index.data(Qt::DisplayRole).toString();
        qDebug() << "項目が展開されました: " << itemText;

        // 項目のユーザーロールデータ(詳細情報)を取得
        QString detailInfo = index.data(Qt::UserRole + 1).toString();

        if (!detailInfo.isEmpty()) {
            detailTextEdit->setPlainText(QString("展開された項目: %1\n\n詳細:\n%2").arg(itemText).arg(detailInfo));
        } else {
            detailTextEdit->setPlainText(QString("展開された項目: %1\n\n詳細情報はありません。").arg(itemText));
        }
    }

private:
    QTreeView *treeView;
    QTextEdit *detailTextEdit;
};

#include "main.moc" // connectマクロを使用する場合、mocファイルが必要

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}
  1. QMainWindowを継承したMainWindowクラスを作成します。
  2. QTreeViewQTextEditのインスタンスを作成し、レイアウトに配置します。
  3. treeViewexpanded()シグナルをMainWindowのプライベートスロットonItemExpanded()に接続します。


主な代替手段や関連するプログラミングパターンを以下に説明します。

QTreeView::collapse() シグナルと組み合わせる

expanded() と対になるシグナルが QTreeView::collapsed() です。項目が閉じられた(折りたたまれた)ときに発行されます。


    • 展開時にデータをロードし、折りたたみ時にメモリから解放する。
    • 特定の項目が展開されている/折りたたまれている状態に基づいて、別のUI要素の状態を変更する。
  • 目的
    展開/折りたたみの両方のイベントに対応して、リソースの解放やUIの更新を行う場合。
// 展開されたとき
connect(treeView, &QTreeView::expanded, this, [=](const QModelIndex &index){
    qDebug() << index.data().toString() << "が展開されました。";
    // データロードなど
});

// 折りたたまれたとき
connect(treeView, &QTreeView::collapsed, this, [=](const QModelIndex &index){
    qDebug() << index.data().toString() << "が折りたたまれました。";
    // データ解放など
});

モデル内の rowCount() の実装と QAbstractItemModel::hasChildren()

QTreeViewが項目を展開可能かどうかを判断し、展開ボタンを表示するために、モデルのrowCount()およびhasChildren()メソッドが非常に重要です。遅延ロードを行う場合、これらのメソッドを工夫することで、expanded()シグナルと連携します。


  • int MyModel::rowCount(const QModelIndex &parent) const
    {
        MyNode *parentNode = getNode(parent);
        if (!parentNode) return 0;
    
        if (!parentNode->childrenLoaded && parentNode->hasUnloadedChildren) {
            // まだ子をロードしていないが、子が存在することを知っている場合
            return 1; // ダミーの子がいるように見せる
        }
        return parentNode->children.size();
    }
    
    bool MyModel::hasChildren(const QModelIndex &parent) const
    {
        MyNode *parentNode = getNode(parent);
        if (!parentNode) return false;
    
        // rowCount()が0より大きいか、あるいはまだロードされてないが子が存在することが確実な場合
        return rowCount(parent) > 0 || parentNode->hasUnloadedChildren;
    }
    
    この例では、hasUnloadedChildrenというフラグを使って、実際に子ノードをモデルに追加する前に、展開ボタンを表示させています。ユーザーが展開ボタンをクリックすると、expanded()シグナルが発行され、loadChildren()のようなメソッドが呼ばれて実際のデータが追加されます。
  • 代替というより補完
    これはexpanded()の代替ではなく、必須の補完です。モデルが子を持つことを示さなければ、そもそもexpanded()シグナルは発行されません。
  • 目的
    expanded()シグナルをトリガーする前に、ツリービューが項目を展開可能であると認識させるため。特に、まだロードされていない子データがあることを示す「ダミーの子」を扱う場合に重要です。

QTreeView::setExpanded() を programmatic に使用する

ユーザーの操作ではなく、プログラムコードから特定の項目を展開したい場合に使用します。


  • // 特定のインデックスを取得 (例: 検索結果から)
    QModelIndex someIndex = model->index(0, 0, QModelIndex()); // 例としてルートの最初の項目
    
    // その項目を展開
    treeView->setExpanded(someIndex, true);
    
  • expanded()との関係
    setExpanded()を呼び出すと、実際にその項目が展開された場合expanded()シグナルが発行されます。つまり、これは代替というよりも、シグナルをプログラム的にトリガーする手段です。
  • 目的
    アプリケーションの起動時、特定のイベント発生時、または検索結果に基づいて特定の項目を自動的に展開したい場合。

QTreeView::expandAll() や QTreeView::expandToDepth()

ツリービューのすべての項目、または特定の深さまでのすべての項目を自動的に展開します。


  • treeView->expandAll(); // 全ての項目を展開
    treeView->expandToDepth(1); // ルート直下の子項目までを展開
    
  • expanded()との関係
    これらのメソッドも、内部的にsetExpanded()を呼び出すため、展開された各項目に対してexpanded()シグナルが発行されます。大量の項目を一度に展開すると、それだけ大量のexpanded()シグナルが発行されるため、スロットでの処理が重い場合はパフォーマンスに注意が必要です。
  • 目的
    ツリーの全体構造を最初から表示したい場合や、特定の深さまでの項目をユーザーに見せたい場合。

QTreeView::expanded()はビュー側から見たイベントですが、モデル側でデータが追加されたときに、その親項目を自動的に展開したい場合があります。これは、モデルの変更に基づいてビューを更新するシナリオです。


  • // MyModel (QAbstractItemModelの派生クラス) 内
    void MyModel::addNewChild(const QModelIndex &parentIndex, const QString &name)
    {
        // ... (子ノードを作成し、データに追加) ...
    
        // beginInsertRows() / endInsertRows() の呼び出し
        beginInsertRows(parentIndex, newRow, newRow);
        parentNode->children.append(newNode);
        endInsertRows();
    
        // モデル側からビューに展開を促すシグナルを送る
        // このシグナルはQAbstractItemModelには直接ないため、
        // 外部からQTreeView::setExpanded()を呼び出す必要がある。
        // または、カスタムシグナルをモデルから発行し、ビューに接続する。
    }
    
    // View (QTreeViewがあるクラス) 内
    connect(myModel, &MyModel::rowsInserted, this, [=](const QModelIndex &parent, int first, int last){
        // parentIndexが有効で、その親がまだ展開されていない場合
        if (parent.isValid() && !treeView->isExpanded(parent)) {
            treeView->expand(parent); // QTreeView::expand() は setExpanded(index, true) と同じ
        }
    });
    
  • 代替というより別のユースケース
    expanded()は「ユーザーが展開した」ことを通知するのに対し、rowsInserted()は「データが追加された」ことを通知します。これらは異なるイベントですが、組み合わせることでより良いUXを提供できます。
  • 目的
    モデルに新しい子項目が追加されたときに、関連する親項目を自動的に展開して、ユーザーに新しい項目が見えるようにする。

QTreeView::expanded()は、ユーザーがツリーの項目を展開したという明確なイベントを処理するための中心的なメカニズムです。ほとんどの場合、このシグナルを直接利用することが最も適切です。