【Qt】QTreeView 選択変更シグナル (selectionChanged) の使い方と実装例

2025-05-27

void QTreeView::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) は、QTreeView クラス(ツリー構造を表示するウィジェット)で提供されるシグナルの一つです。

このシグナルは、ツリービュー内の選択状態が変化したときに発行されます。具体的には、以下のいずれかの状況で発生します。

  • プログラムのコードによって、ツリービューの選択状態が変更された(例えば、QItemSelectionModel のメソッドを呼び出した場合など)。
  • ユーザーが既に選択されていたアイテムの選択を解除した。
  • ユーザーがマウスやキーボード操作によって、アイテムを新たに選択した。

このシグナルを受け取るためには、通常、Qt のシグナルとスロットの仕組みを使って、スロットと呼ばれる関数をこのシグナルに接続します。そうすることで、選択状態が変化した際に、接続されたスロット関数が自動的に呼び出され、必要な処理を実行できます。

selectionChanged() シグナルは、二つの引数を持ちます。

  1. const QItemSelection &selected: 今回、新たに選択されたアイテムの情報を格納した QItemSelection オブジェクトです。このオブジェクトを通じて、新しく選択されたアイテムのインデックスなどを取得できます。
  2. const QItemSelection &deselected: 今回、選択が解除されたアイテムの情報を格納した QItemSelection オブジェクトです。このオブジェクトを通じて、選択が解除されたアイテムのインデックスなどを取得できます。

void QTreeView::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) は、QTreeView の選択状態が変化したことを通知するシグナルであり、新たに選択されたアイテムと選択が解除されたアイテムの情報を提供します。このシグナルを利用することで、ユーザーの選択操作に応じて様々な処理を実装することができます。

例えば、選択されたアイテムのデータを別のウィジェットに表示したり、選択されたアイテムに基づいて何らかの処理を実行したりする際に、このシグナルを活用します。



一般的なエラーとトラブルシューティング

    • 原因
      connect() 関数で selectionChanged() シグナルとスロット関数が正しく接続されていない可能性があります。
    • 確認事項
      • connect() の引数が正しいか (sender, SIGNAL(), receiver, SLOT()) を確認してください。特に SIGNAL()SLOT() のマクロ内のシグナルとスロットのシグネチャ(引数の型と数)が完全に一致している必要があります。const や参照 (&) の有無も重要です。
      • senderQTreeView のインスタンスを指しているか確認してください。
      • receiver がスロット関数を持つオブジェクトのインスタンスを指しているか確認してください。
      • Qt のオブジェクトが適切に生成され、生存していることを確認してください。破棄されたオブジェクト間の接続は機能しません。
    • トラブルシューティング
      • qDebug() などを使って、connect() 関数の戻り値が true であることを確認してください。false の場合は接続に失敗しています。
      • シグナルとスロットの名前を再度確認し、スペルミスがないか注意してください。
      • Qt Creator のシグナルとスロットエディタを利用して、GUI 上で接続を確認してみるのも有効です。
  1. 意図しないタイミングでスロットが呼び出される

    • 原因
      選択モデル (QItemSelectionModel) がプログラムの他の部分で意図せず変更されている可能性があります。
    • 確認事項
      • 選択モデルを操作している他の箇所がないかコード全体を確認してください。
      • QTreeView 自身のメソッド(例えば、setCurrentIndex(), selectionModel()->select(), selectionModel()->clear() など)が意図しないタイミングで呼び出されていないか確認してください。
    • トラブルシューティング
      • 選択モデルの状態が変化する箇所にブレークポイントを設定し、どのタイミングで selectionChanged() シグナルが発行されているか追跡してください。
      • 必要であれば、フラグなどを使って、特定の条件下でのみスロットの処理を実行するように制御することを検討してください。
  2. selected または deselected 引数の内容が期待通りでない

    • 原因
      複数選択が有効になっているかどうか、選択モードの設定 (setSelectionMode()) などによって、引数で渡される QItemSelection オブジェクトの内容が変わります。
    • 確認事項
      • QTreeView の選択モード (selectionMode()) を確認し、アプリケーションの要件に合っているか確認してください。例えば、単一選択のみを想定しているのに複数選択が有効になっていると、selected に複数のアイテムが含まれる可能性があります。
      • アイテムが実際に選択または選択解除されているか、デバッガなどで selected および deselected オブジェクトの内容を確認してください。indexes() メソッドなどで選択されたインデックスのリストを取得できます。
    • トラブルシューティング
      • 選択モードを明示的に設定 (treeView->setSelectionMode(QAbstractItemView::SingleSelection); など) してください。
      • スロット関数内で、selected および deselected オブジェクトが空でないかなどをチェックしてから処理を行うようにしてください。
  3. スロット関数内でのエラー

    • 原因
      selectionChanged() シグナルに接続されたスロット関数内で例外が発生したり、ロジックエラーがあったりする可能性があります。
    • 確認事項
      • スロット関数内のコードを注意深く見直し、潜在的なエラーがないか確認してください。
      • ファイルアクセス、ネットワーク処理、複雑な計算など、エラーが発生しやすい処理を行っている場合は、適切なエラーハンドリング(try-catch ブロックなど)を実装してください。
    • トラブルシューティング
      • スロット関数の先頭に qDebug() を挿入して、関数が呼び出されていることを確認してください。
      • スロット関数内の各処理のステップに qDebug() を挿入して、どこで問題が発生しているかを特定してください。
      • デバッガを使用して、スロット関数内の変数の値や処理の流れを追跡してください。
  4. パフォーマンスの問題 (スロット関数が重い)

    • 原因
      selectionChanged() シグナルが頻繁に発行される状況で、接続されたスロット関数内の処理が重いと、アプリケーションの応答性が悪くなる可能性があります。
    • 確認事項
      • スロット関数内で時間のかかる処理(例えば、大規模なデータの読み書き、複雑な計算など)を行っていないか確認してください。
    • トラブルシューティング
      • 時間のかかる処理は、別のスレッドに移動することを検討してください。
      • 不要な処理をスロット関数内で行わないように、ロジックを見直してください。
      • タイマー (QTimer) を利用して、処理を遅延させたり、間引いたりすることを検討してください。

トラブルシューティングの一般的なヒント

  • Qt のドキュメントを参照
    Qt の公式ドキュメントは、各クラスやシグナル、スロットの詳細な情報を提供しています。困ったときは、まずドキュメントを参照することをお勧めします。
  • 最小限のコードで再現
    問題を特定するために、できるだけ小さなコードで問題を再現させることを試みてください。これにより、問題の原因となっている箇所を絞り込むことができます。
  • ログ出力
    より複雑なアプリケーションでは、ログ出力の仕組みを導入して、イベントの発生や変数の変化を記録しておくと、問題発生時の解析に役立ちます。
  • ブレークポイントの設定
    Qt Creator などの統合開発環境のデバッガを使用すると、特定の行でプログラムの実行を一時停止させ、変数の状態などを詳しく調べることができます。
  • qDebug() の活用
    qDebug() は、変数の値やプログラムの実行フローを簡単に確認できる強力なツールです。積極的に利用して、問題の原因を特定してください。


例1: 選択されたアイテムのインデックスとデータをコンソールに出力する

#include <QApplication>
#include <QTreeView>
#include <QStandardItemModel>
#include <QItemSelectionModel>
#include <QDebug>

class MyTreeView : public QTreeView {
public:
    MyTreeView(QWidget *parent = nullptr) : QTreeView(parent) {
        // モデルの作成と設定
        model = new QStandardItemModel(this);
        QStandardItem *rootItem = model->invisibleRootItem();
        QStandardItem *item1 = new QStandardItem("Item 1");
        QStandardItem *item2 = new QStandardItem("Item 2");
        QStandardItem *child1_1 = new QStandardItem("Child 1-1");
        QStandardItem *child1_2 = new QStandardItem("Child 1-2");
        item1->appendRow(child1_1);
        item1->appendRow(child1_2);
        rootItem->appendRow(item1);
        rootItem->appendRow(item2);
        setModel(model);

        // selectionChanged シグナルとスロットを接続
        connect(selectionModel(), &QItemSelectionModel::selectionChanged,
                this, &MyTreeView::handleSelectionChanged);
    }

private slots:
    void handleSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected) {
        qDebug() << "選択されたアイテム:";
        foreach (const QModelIndex &index, selected.indexes()) {
            qDebug() << "  インデックス:" << index << ", データ:" << model->data(index).toString();
        }

        qDebug() << "選択解除されたアイテム:";
        foreach (const QModelIndex &index, deselected.indexes()) {
            qDebug() << "  インデックス:" << index << ", データ:" << model->data(index).toString();
        }
        qDebug() << "---";
    }

private:
    QStandardItemModel *model;
};

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);
    MyTreeView treeView;
    treeView.setWindowTitle("QTreeView Selection Example");
    treeView.show();
    return a.exec();
}

コードの説明

  1. MyTreeView クラス
    QTreeView を継承したカスタムクラスを作成しています。
  2. モデルの作成と設定
    QStandardItemModel を作成し、いくつかのアイテムをツリー構造に追加しています。そして、setModel() でツリービューにこのモデルを設定しています。
  3. シグナルとスロットの接続
    • selectionModel() を呼び出して、ツリービューの選択モデルを取得します。
    • connect() 関数を使用して、選択モデルの selectionChanged シグナル (&QItemSelectionModel::selectionChanged) を、MyTreeView クラスの handleSelectionChanged スロット (&MyTreeView::handleSelectionChanged) に接続しています。
  4. handleSelectionChanged スロット
    • このスロットは、選択状態が変化したときに自動的に呼び出されます。
    • 引数として、新たに選択されたアイテムの QItemSelection オブジェクト (selected) と、選択が解除されたアイテムの QItemSelection オブジェクト (deselected) を受け取ります。
    • selected.indexes()deselected.indexes() を使用して、それぞれの QModelIndex のリストを取得します。
    • QModelIndex に対して、そのインデックス自身と、モデルから data() メソッドを使ってデータを取得し、qDebug() でコンソールに出力しています。

例2: 選択されたアイテムのテキストをラベルに表示する

#include <QApplication>
#include <QTreeView>
#include <QStandardItemModel>
#include <QItemSelectionModel>
#include <QLabel>
#include <QVBoxLayout>
#include <QWidget>

class MyWidget : public QWidget {
public:
    MyWidget(QWidget *parent = nullptr) : QWidget(parent) {
        // モデルの作成と設定 (例1と同様)
        model = new QStandardItemModel(this);
        QStandardItem *rootItem = model->invisibleRootItem();
        QStandardItem *item1 = new QStandardItem("Item A");
        QStandardItem *item2 = new QStandardItem("Item B");
        rootItem->appendRow(item1);
        rootItem->appendRow(item2);

        treeView = new QTreeView(this);
        treeView->setModel(model);

        label = new QLabel("選択されていません", this);

        QVBoxLayout *layout = new QVBoxLayout(this);
        layout->addWidget(treeView);
        layout->addWidget(label);
        setLayout(layout);

        // selectionChanged シグナルとスロットを接続
        connect(treeView->selectionModel(), &QItemSelectionModel::selectionChanged,
                this, &MyWidget::updateLabel);
    }

private slots:
    void updateLabel(const QItemSelection &selected, const QItemSelection &deselected) {
        if (!selected.isEmpty()) {
            // 最初に選択されたアイテムのインデックスを取得
            QModelIndex firstSelectedIndex = selected.indexes().first();
            // そのインデックスのデータを取得 (ここでは Qt::DisplayRole のテキスト)
            QString selectedText = model->data(firstSelectedIndex).toString();
            label->setText("選択されたアイテム: " + selectedText);
        } else {
            label->setText("選択されていません");
        }
    }

private:
    QStandardItemModel *model;
    QTreeView *treeView;
    QLabel *label;
};

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);
    MyWidget widget;
    widget.setWindowTitle("QTreeView Selection to Label");
    widget.show();
    return a.exec();
}
  1. MyWidget クラス
    QWidget を継承したメインのウィジェットクラスを作成しています。
  2. モデルとツリービューの作成
    例1と同様に、QStandardItemModel を作成し、QTreeView に設定しています。
  3. ラベルの作成
    選択されたアイテムのテキストを表示するための QLabel を作成しています。
  4. レイアウト
    QVBoxLayout を使用して、ツリービューとラベルを垂直に配置しています。
  5. シグナルとスロットの接続
    ツリービューの選択モデルの selectionChanged シグナルを、MyWidget クラスの updateLabel スロットに接続しています。
  6. updateLabel スロット
    • 選択されたアイテム (selected) が空でないか (!selected.isEmpty()) をチェックします。
    • 選択された最初のアイテムの QModelIndexselected.indexes().first() で取得します。
    • そのインデックスのデータを model->data(firstSelectedIndex).toString() で取得し、ラベルのテキストを更新します。
    • 選択が解除されて空になった場合は、ラベルを初期状態に戻します。


QItemSelectionModel のシグナルを利用する

QTreeView は内部的に QItemSelectionModel を使用してアイテムの選択状態を管理しています。QTreeView::selectionModel() メソッドでこの選択モデルのインスタンスを取得でき、QItemSelectionModel 自体も選択に関するシグナルを提供しています。

  • currentRowChanged(const QModelIndex &current, const QModelIndex &previous) / currentColumnChanged(...)
    現在の行または列が変更されたときに発行されます。
  • currentChanged(const QModelIndex &current, const QModelIndex &previous)
    現在のアイテムが変更されたときに発行されます。これは、選択されたアイテムが一つだけの場合や、フォーカスが移動した場合に便利です。
  • selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) (これは QTreeView のシグナルと同じです)


#include <QApplication>
#include <QTreeView>
#include <QStandardItemModel>
#include <QItemSelectionModel>
#include <QDebug>

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);
    QTreeView treeView;
    QStandardItemModel model;
    // モデルにデータを追加...
    treeView.setModel(&model);

    QItemSelectionModel *selectionModel = treeView.selectionModel();

    QObject::connect(selectionModel, &QItemSelectionModel::currentChanged,
                     [&](const QModelIndex &current, const QModelIndex &previous) {
        qDebug() << "現在のアイテム:" << current.row() << ", " << current.column();
        if (previous.isValid()) {
            qDebug() << "前のアイテム:" << previous.row() << ", " << previous.column();
        }
    });

    treeView.show();
    return a.exec();
}

利点
QItemSelectionModel のシグナルは、より具体的な選択の変化(例えば、現在のアイテムの変更)を監視したい場合に便利です。

イベントフィルタを使用する

QObject::installEventFilter() を使用して、QTreeView オブジェクトが受け取るイベントを監視し、選択に関連するイベント(例えば、マウスボタンのプレスやキーボードの操作)を捕捉することができます。


#include <QApplication>
#include <QTreeView>
#include <QStandardItemModel>
#include <QEvent>
#include <QMouseEvent>
#include <QDebug>

class SelectionEventFilter : public QObject {
public:
    SelectionEventFilter(QObject *watched) : QObject(watched) {}

protected:
    bool eventFilter(QObject *obj, QEvent *event) override {
        if (event->type() == QEvent::MouseButtonPress) {
            QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
            QTreeView *treeView = static_cast<QTreeView *>(obj);
            QModelIndex index = treeView->indexAt(mouseEvent->pos());
            if (index.isValid()) {
                qDebug() << "マウスプレス at インデックス:" << index.row() << ", " << index.column();
                // ここで選択に関する処理を行う
            }
        } else if (event->type() == QEvent::KeyPress) {
            // キーボード操作による選択変化を監視
            // ...
        }
        // 他のイベントは通常通り処理させる
        return QObject::eventFilter(obj, event);
    }
};

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);
    QTreeView treeView;
    QStandardItemModel model;
    // モデルにデータを追加...
    treeView.setModel(&model);

    SelectionEventFilter *filter = new SelectionEventFilter(&treeView);
    treeView.installEventFilter(filter);

    treeView.show();
    return a.exec();
}

利点
イベントフィルタを使用すると、選択操作のより低いレベルのイベントを捕捉できるため、より細かい制御や、標準の選択メカニズムとは異なる動作を実装するのに役立ちます。

欠点
イベントフィルタは、すべてのイベントを監視するため、パフォーマンスに影響を与える可能性があります。また、どのイベントが選択に関連するのかを自分で判断する必要があるため、実装が複雑になる場合があります。

モデルの dataChanged シグナルを利用する (間接的な方法)

選択状態がモデルのデータとして管理されている場合(例えば、チェックボックスの状態など)、モデルの dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles = QVector<int>()) シグナルを監視することで、間接的に選択の変化を検出できる場合があります。

注意
これは、選択状態がモデルのデータとして明示的に表現されている場合に限ります。通常のアイテム選択(ハイライト表示)は、モデルのデータ変更として通知されません。

例 (チェックボックスを使用する場合)

#include <QApplication>
#include <QTreeView>
#include <QStandardItemModel>
#include <QDebug>
#include <Qt>

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);
    QTreeView treeView;
    QStandardItemModel model(2, 1);
    model.setData(model.index(0, 0), "Item 1", Qt::DisplayRole);
    model.setData(model.index(0, 0), Qt::Checked, Qt::CheckStateRole);
    model.setData(model.index(1, 0), "Item 2", Qt::DisplayRole);
    model.setData(model.index(1, 0), Qt::Unchecked, Qt::CheckStateRole);
    treeView.setModel(&model);

    QObject::connect(&model, &QStandardItemModel::dataChanged,
                     [&](const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles) {
        if (roles.contains(Qt::CheckStateRole)) {
            for (int row = topLeft.row(); row <= bottomRight.row(); ++row) {
                QModelIndex index = model.index(row, 0);
                QString itemName = model.data(index, Qt::DisplayRole).toString();
                Qt::CheckState state = static_cast<Qt::CheckState>(model.data(index, Qt::CheckStateRole).toInt());
                qDebug() << itemName << "のチェック状態が変更されました:" << (state == Qt::Checked ? "チェック済み" : "未チェック");
            }
        }
    });

    treeView.show();
    return a.exec();
}

利点
モデルのデータ変更に基づいて処理を行う必要がある場合に適しています。

欠点
通常のアイテム選択(ハイライト表示)には適用できません。

  • 選択状態がモデルのデータとして管理されている場合
    モデルの dataChanged シグナルを監視することが適切な場合があります。
  • 選択操作のより低いレベルのイベントを捕捉したい場合や、標準とは異なる選択動作を実装したい場合
    イベントフィルタの使用を検討します。ただし、複雑さとパフォーマンスへの影響に注意が必要です。
  • 現在のアイテムの変化を監視したい場合
    QItemSelectionModel::currentChanged() シグナルなどが便利です。
  • 最も一般的で推奨される方法
    QTreeView::selectionChanged() シグナルを使用します。これは、アイテムの選択状態の変化を直接的かつ効率的に通知します。