Qt QTreeViewの選択アイテム管理: selectedIndexes()とその応用

2025-05-27

この関数は、QTreeView ウィジェット内で現在選択されているすべてのアイテムのモデルインデックス(QModelIndex)のリストを返します。

詳細

  • QModelIndexList: QModelIndexオブジェクトのリスト(QList<QModelIndex>のエイリアス)です。
  • QModelIndex: Qtのモデル/ビューアーキテクチャにおいて、データモデル内の特定のアイテムを指し示すために使用されるクラスです。QModelIndexは、そのアイテムの行、列、親アイテムに関する情報を持っています。
  • QTreeView: Qtのビューウィジェットの一つで、階層的なデータをツリー構造で表示するために使用されます。ファイルシステム、XMLデータ、あるいは独自の階層データなど、様々なデータを表現できます。

動作

QTreeView::selectedIndexes() は、以下のような場合に特に役立ちます。

  1. 選択されたアイテムのデータにアクセスする: 取得したQModelIndexList内の各QModelIndexを使って、対応するデータモデルからそのアイテムのデータを取得できます。例えば、アイテムの表示テキスト、ID、あるいは関連するカスタムデータなどを取得できます。

使い方(典型的なパターン)

通常、QTreeViewから選択されたアイテムの情報を取得するには、以下のような手順を踏みます。

  1. QTreeViewのインスタンスから selectionModel() を呼び出し、QItemSelectionModelを取得します。
  2. 取得したQItemSelectionModelselectedIndexes() メソッドを呼び出します。これにより、選択されたすべてのQModelIndexのリストが返されます。

例(C++):

// myTreeView は QTreeView のインスタンスとします
QModelIndexList selected = myTreeView->selectionModel()->selectedIndexes();

foreach (const QModelIndex &index, selected) {
    // 各選択されたインデックスに対して処理を行う
    // 例えば、データモデルからデータを取得する
    qDebug() << "Selected item data: " << index.data().toString();

    // あるいは、行と列を取得する
    qDebug() << "Row: " << index.row() << ", Column: " << index.column();
}

重要な注意点:

  • しかし、現在のQtバージョン(Qt 5以降)では、QTreeViewから直接 selectionModel() を呼び出し、そのQItemSelectionModelオブジェクトに対して selectedIndexes() を呼び出すのが一般的です。これは公開されているAPIであり、より推奨される方法です。上記のコード例もこの方法に基づいています。
  • QTreeView::selectedIndexes() は、Qt 4.8以前ではprotectedメンバーでした。そのため、QTreeViewを直接継承したクラスからしか呼び出せないという制限がありました。


QTreeView::selectedIndexes() は、選択されたアイテムのモデルインデックスを取得する非常に便利な関数ですが、いくつか一般的な落とし穴や誤解があります。

エラー:「selectedIndexes()がprotectedメンバーです」

  • トラブルシューティング:
    • 現在の推奨される方法: Qt 5以降では、QTreeViewselectionModel() メソッドを使ってQItemSelectionModelオブジェクトを取得し、そのQItemSelectionModelから selectedIndexes() を呼び出すのが正しい方法です。
      QModelIndexList selected = ui->treeView->selectionModel()->selectedIndexes();
      
    • この方法は、Qtのモデル/ビューアーキテクチャの設計思想に則っており、QTreeViewが内部的に使用している選択モデルを介して選択情報を取得します。
  • エラーの内容: 古いQtのバージョン(特にQt 4.xの一部)では、QTreeView::selectedIndexes() がprotectedメンバーとして宣言されており、QTreeViewを直接継承したクラスからしか呼び出せないという制限がありました。そのため、ui->treeView->selectedIndexes() のように直接呼び出そうとすると、コンパイルエラーになることがありました。

エラー:「選択しているはずなのにリストが空になる」

  • 考えられる原因とトラブルシューティング:
    • 選択モデルの確認: QTreeViewに有効なモデルが設定されており、そのモデルが選択可能(Qt::ItemIsSelectableフラグが設定されている)であることを確認してください。
    • シグナル/スロットのタイミング: selectedIndexes()を呼び出すタイミングが早すぎる可能性があります。ユーザーがアイテムを選択したことをトリガーにするには、QItemSelectionModelselectionChanged() シグナルにスロットを接続するのが一般的です。 ```cpp connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, [this](const QItemSelection &selected, const QItemSelection &deselected){ QModelIndexList indexes = ui->treeView->selectionModel()->selectedIndexes(); // ここでindexesを使って処理を行う });
    • フォーカスがない: QTreeViewがフォーカスを持っていない場合、選択状態が正しく反映されないことがあります。クリックイベントなどでQTreeViewがアクティブになっているか確認してください。
  • エラーの内容: ユーザーがQTreeViewでアイテムを選択しているにもかかわらず、selectedIndexes()が空のQModelIndexListを返すことがあります。

エラー:「同じアイテムが複数回リストに含まれる」

  • 考えられる原因とトラブルシューティング:
    • 複数列のデータ: QTreeViewは、各アイテムが複数の列を持つことができます。selectedIndexes()は、選択された行のすべての列のインデックスを返す場合があります。もし1つのアイテム(行)に対して1つのインデックスだけが欲しいのであれば、以下のようにフィルタリングする必要があります。
      QModelIndexList selected = ui->treeView->selectionModel()->selectedIndexes();
      QModelIndexList uniqueRows;
      QSet<int> rows; // 既に追加された行番号を記録するためのセット
      
      foreach (const QModelIndex &index, selected) {
          if (!rows.contains(index.row())) {
              // その行の最初の列のインデックスを追加するか、
              // 単にそのインデックスを追加する
              uniqueRows.append(index);
              rows.insert(index.row());
          }
      }
      // uniqueRows には重複しない行のインデックスが含まれる
      
    • 通常、列は0番目(index.column() == 0)のインデックスだけを処理すれば十分な場合が多いです。
  • エラーの内容: 1つのアイテムしか選択していないにもかかわらず、selectedIndexes()が同じQModelIndexを複数回含むリストを返すことがあります。

エラー:「選択されたアイテムの順序が期待通りではない」

  • 考えられる原因とトラブルシューティング:
    • selectedIndexes()の保証: selectedIndexes()は、返されるQModelIndexListの順序を特に保証していません。内部的な選択モデルの構造に依存するため、常に予測可能な順序になるとは限りません。
    • 手動でのソート: 特定の順序で処理したい場合は、取得したQModelIndexListを自分でソートする必要があります。例えば、行番号や親のQModelIndexに基づいてソートできます。
      QModelIndexList selected = ui->treeView->selectionModel()->selectedIndexes();
      
      // 行番号でソートする例
      std::sort(selected.begin(), selected.end(), [](const QModelIndex &a, const QModelIndex &b) {
          return a.row() < b.row();
      });
      
      // 親と子を考慮したソートはより複雑になる
      
    • もしドラッグ&ドロップなどでアイテムの順序が重要になる場合は、より高度な選択モデルの管理が必要になることがあります。
  • エラーの内容: selectedIndexes()が返すリストの順序が、ユーザーが選択した順序やツリービューの表示順序と異なることがあります。

エラー:「選択されたアイテムのデータが取得できない、あるいは正しくない」

  • 考えられる原因とトラブルシューティング:
    • Roleの指定: index.data() を呼び出す際、デフォルトでは Qt::DisplayRole のデータが返されます。もし異なる役割(Role、例: Qt::UserRole + n)でデータをモデルに格納している場合は、明示的にRoleを指定する必要があります。
      QVariant data = index.data(Qt::UserRole + 1); // 特定のRoleのデータを取得
      
    • モデルの実装: QAbstractItemModelを継承してカスタムモデルを作成している場合、data() メソッドの実装が正しく行われているか確認してください。特に、渡されたQModelIndexとRoleに対して適切なデータを返すように実装されているかチェックします。
    • 無効なインデックス: QModelIndexisValid() かどうかを確認してください。無効なインデックスからデータを取得しようとすると、空のQVariantや不正な値が返される可能性があります。
  • エラーの内容: QModelIndexから data() メソッドを呼び出しても、期待するデータが得られない。


準備: 基本的なQtアプリケーションのセットアップ

これらの例を実行するために、まず基本的なQtアプリケーションのセットアップが必要です。QApplicationQMainWindow(またはQWidget)、QTreeViewQStandardItemModelを使用します。

main.cpp

#include <QApplication>
#include <QMainWindow>
#include <QTreeView>
#include <QStandardItemModel>
#include <QVBoxLayout>
#include <QWidget>
#include <QPushButton>
#include <QDebug> // デバッグ出力用

// MyWindowクラスの宣言 (以下で定義します)
class MyWindow : public QMainWindow
{
    Q_OBJECT

public:
    MyWindow(QWidget *parent = nullptr);

private slots:
    void on_getSelectedItemsButton_clicked();
    void on_selectionModel_selectionChanged(const QItemSelection &selected, const QItemSelection &deselected);

private:
    QTreeView *treeView;
    QStandardItemModel *model;

    void setupModel();
    void setupUi();
};

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

    MyWindow w;
    w.show();

    return a.exec();
}

mywindow.h (MyWindowクラスのヘッダファイル)

#ifndef MYWINDOW_H
#define MYWINDOW_H

#include <QMainWindow>
#include <QTreeView>
#include <QStandardItemModel>
#include <QItemSelection> // QItemSelection を使う場合

class MyWindow : public QMainWindow
{
    Q_OBJECT

public:
    MyWindow(QWidget *parent = nullptr);
    ~MyWindow() {}

private slots:
    void on_getSelectedItemsButton_clicked();
    void on_selectionModel_selectionChanged(const QItemSelection &selected, const QItemSelection &deselected);

private:
    QTreeView *treeView;
    QStandardItemModel *model;

    void setupModel();
    void setupUi();
};

#endif // MYWINDOW_H

mywindow.cpp (MyWindowクラスの実装ファイル)

#include "mywindow.h"
#include <QVBoxLayout>
#include <QPushButton>
#include <QDebug>
#include <QMessageBox> // メッセージボックス用

MyWindow::MyWindow(QWidget *parent)
    : QMainWindow(parent)
{
    setupUi();
    setupModel();
}

void MyWindow::setupUi()
{
    QWidget *centralWidget = new QWidget(this);
    QVBoxLayout *layout = new QVBoxLayout(centralWidget);

    treeView = new QTreeView(this);
    layout->addWidget(treeView);

    // 選択モードを複数選択可能に設定
    treeView->setSelectionMode(QAbstractItemView::ExtendedSelection); // Ctrl/Shift で複数選択

    QPushButton *button = new QPushButton("選択アイテムをコンソールに出力", this);
    layout->addWidget(button);

    setCentralWidget(centralWidget);

    // ボタンのクリックイベントとスロットを接続
    connect(button, &QPushButton::clicked, this, &MyWindow::on_getSelectedItemsButton_clicked);

    // 選択モデルの変更シグナルとスロットを接続
    connect(treeView->selectionModel(), &QItemSelectionModel::selectionChanged,
            this, &MyWindow::on_selectionModel_selectionChanged);
}

void MyWindow::setupModel()
{
    model = new QStandardItemModel(0, 2, this); // 2列のモデル
    model->setHeaderData(0, Qt::Horizontal, "名前");
    model->setHeaderData(1, Qt::Horizontal, "値");

    // ルートアイテム
    QStandardItem *rootItem = model->invisibleRootItem();

    // 親アイテム1
    QStandardItem *parent1 = new QStandardItem("親アイテム A");
    parent1->setData("Value A", Qt::UserRole); // カスタムデータを設定
    rootItem->appendRow(parent1);

    // 子アイテム1.1
    QStandardItem *child1_1 = new QStandardItem("子アイテム A-1");
    child1_1->setChild(0, 1, new QStandardItem("データ A-1")); // 2列目のデータ
    parent1->appendRow(child1_1);

    // 子アイテム1.2
    QStandardItem *child1_2 = new QStandardItem("子アイテム A-2");
    child1_2->setChild(0, 1, new QStandardItem("データ A-2"));
    parent1->appendRow(child1_2);

    // 親アイテム2
    QStandardItem *parent2 = new QStandardItem("親アイテム B");
    rootItem->appendRow(parent2);

    // 子アイテム2.1
    QStandardItem *child2_1 = new QStandardItem("子アイテム B-1");
    child2_1->setChild(0, 1, new QStandardItem("データ B-1"));
    parent2->appendRow(child2_1);

    treeView->setModel(model);

    // ツリーを全て展開 (任意)
    treeView->expandAll();
}

// --- 以下に各コード例のスロットを実装します ---

例1: ボタンクリック時に選択されたアイテムを取得する

最も一般的な使用例です。ユーザーがボタンをクリックしたときに、現在QTreeViewで選択されているすべてのアイテムの情報を取得します。

// mywindow.cpp に追加

void MyWindow::on_getSelectedItemsButton_clicked()
{
    qDebug() << "--- ボタンクリック: 選択されたアイテム ---";

    // QModelIndexList を取得
    QModelIndexList selectedIndexes = treeView->selectionModel()->selectedIndexes();

    if (selectedIndexes.isEmpty()) {
        qDebug() << "何も選択されていません。";
        return;
    }

    // 取得したQModelIndexListを反復処理
    // 注意: selectedIndexes() は、選択された行のすべての列のインデックスを返す可能性があります。
    // 同じ行の重複を避けるために、列0のインデックスだけを処理することが多いです。
    QSet<QStandardItem*> processedItems; // 重複処理を防ぐためのセット (QStandardItemを使う場合)

    foreach (const QModelIndex &index, selectedIndexes) {
        // 通常は列0 (表示されるテキストがある列) のデータに注目することが多い
        if (index.column() == 0) {
            QStandardItem *item = model->itemFromIndex(index);
            if (item && !processedItems.contains(item)) {
                qDebug() << "選択されたアイテム (表示テキスト): " << index.data(Qt::DisplayRole).toString();
                qDebug() << "  行:" << index.row() << ", 列:" << index.column();
                qDebug() << "  親の表示テキスト:" << (index.parent().isValid() ? index.parent().data(Qt::DisplayRole).toString() : "なし");
                qDebug() << "  カスタムデータ (UserRole):" << index.data(Qt::UserRole).toString(); // setupModelで設定したカスタムデータ

                // 2列目のデータがある場合はそれも取得
                QModelIndex secondColIndex = model->index(index.row(), 1, index.parent());
                if (secondColIndex.isValid()) {
                    qDebug() << "  2列目のデータ: " << secondColIndex.data(Qt::DisplayRole).toString();
                }
                processedItems.insert(item); // 処理済みとしてマーク
            }
        }
    }
    qDebug() << "---------------------------------------";
}

例2: 選択が変更されたときにアイテムの情報をリアルタイムで表示する

QItemSelectionModel::selectionChangedシグナルを使用すると、QTreeViewの選択状態が変更されるたびに自動的に処理を実行できます。

// mywindow.cpp に追加 (既に追加済みですが、内容を説明します)

void MyWindow::on_selectionModel_selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
{
    // selected: 新たに選択されたアイテムのモデルインデックスを含むQItemSelection
    // deselected: 選択が解除されたアイテムのモデルインデックスを含むQItemSelection

    qDebug() << "\n--- 選択が変更されました ---";

    // 現在選択されているすべてのアイテムを再取得する
    // (selected/deselected 引数から直接処理することも可能ですが、
    // ここではselectedIndexes()の動作を示すために全体を再取得します)
    QModelIndexList currentSelectedIndexes = treeView->selectionModel()->selectedIndexes();

    if (currentSelectedIndexes.isEmpty()) {
        qDebug() << "現在、何も選択されていません。";
        return;
    }

    qDebug() << "現在選択中のアイテム数: " << currentSelectedIndexes.size();
    QSet<QStandardItem*> processedItems; // 重複処理を防ぐ

    foreach (const QModelIndex &index, currentSelectedIndexes) {
        if (index.column() == 0) { // 列0のインデックスのみを考慮
            QStandardItem *item = model->itemFromIndex(index);
            if (item && !processedItems.contains(item)) {
                qDebug() << "  選択されたアイテム: " << index.data(Qt::DisplayRole).toString();
                processedItems.insert(item);
            }
        }
    }
    qDebug() << "---------------------------";
}

例3: 選択されたアイテムを削除する

選択されたアイテムをモデルから削除する例です。モデルを変更する際は、beginRemoveRows() / endRemoveRows() などのヘルパー関数を使うことが重要です。

// mywindow.cpp に追加 (ボタンを追加するか、別のスロットから呼び出す)

void MyWindow::on_deleteSelectedItemsButton_clicked() // このスロットをボタンと接続する
{
    qDebug() << "--- 選択されたアイテムを削除 ---";

    QModelIndexList selectedIndexes = treeView->selectionModel()->selectedIndexes();

    if (selectedIndexes.isEmpty()) {
        QMessageBox::information(this, "削除", "削除するアイテムが選択されていません。");
        return;
    }

    // モデルから削除する際は、子アイテムへの影響を考慮して、
    // 最も深い(子)アイテムから削除していくのが安全です。
    // また、同じ親を持つアイテムをまとめて削除するためにソートすることも有効です。

    // 親インデックスと子インデックスのペアを保持するマップ
    // <親インデックス, その親の子の行番号のリスト>
    QMap<QModelIndex, QList<int>> itemsToDeleteMap;

    foreach (const QModelIndex &index, selectedIndexes) {
        // 列0のインデックスのみを考慮し、有効なアイテムか確認
        if (index.column() == 0 && index.isValid()) {
            itemsToDeleteMap[index.parent()].append(index.row());
        }
    }

    // 各親の行番号を降順にソートして、親から子を削除する際のインデックスのずれを防ぐ
    QMapIterator<QModelIndex, QList<int>> i(itemsToDeleteMap);
    while (i.hasNext()) {
        i.next();
        QModelIndex parentIndex = i.key();
        QList<int> rows = i.value();
        std::sort(rows.begin(), rows.end(), std::greater<int>()); // 降順ソート

        foreach (int row, rows) {
            qDebug() << "削除: 行 " << row << ", 親: " << (parentIndex.isValid() ? parentIndex.data(Qt::DisplayRole).toString() : "ルート");
            model->removeRow(row, parentIndex);
        }
    }

    QMessageBox::information(this, "削除", QString("%1 個のアイテムを削除しました。").arg(selectedIndexes.size()));
    qDebug() << "----------------------------------";
}

on_deleteSelectedItemsButton_clicked()の呼び出し方: MyWindow::setupUi() に以下のようなボタンを追加して接続する必要があります。

    // ... layout->addWidget(button); の後に追加
    QPushButton *deleteButton = new QPushButton("選択アイテムを削除", this);
    layout->addWidget(deleteButton);
    connect(deleteButton, &QPushButton::clicked, this, &MyWindow::on_deleteSelectedItemsButton_clicked);

これらのコード例は、QModelIndexList QTreeView::selectedIndexes() の基本的な使い方から、リアルタイムでの選択状態の監視、さらには選択されたアイテムの削除といった応用までをカバーしています。QModelIndexから取得できる情報(row(), column(), parent(), data())を理解し、適切に利用することが重要です。 QtプログラミングにおけるQTreeView::selectedIndexes()に関連するプログラミング例をいくつかご紹介します。これらの例は、QTreeViewで選択されたアイテムのモデルインデックスを取得し、それらを操作する基本的な方法を示しています。

これらの例を実行するには、まず基本的なQtのGUIアプリケーションを作成する必要があります。Qt Creatorを使用すると、プロジェクトの作成が簡単です。

  1. 新しいQt Widgets Applicationプロジェクトを作成します。
  2. mainwindow.ui を開き、QTreeView ウィジェットをフォームに追加します。オブジェクト名はデフォルトの treeView を使用すると仮定します。
  3. mainwindow.hmainwindow.cpp にコードを追加します。

mainwindow.h (抜粋)

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QStandardItemModel> // QTreeViewで使用するモデル

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    void on_pushButton_getSelected_clicked(); // ボタンクリックで選択されたアイテムを取得するスロット
    void handleSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected); // 選択変更時のスロット

private:
    Ui::MainWindow *ui;
    QStandardItemModel *model; // モデルのポインタ
    void setupTreeView(); // ツリービューの初期設定を行うヘルパー関数
};
#endif // MAINWINDOW_H

mainwindow.cpp (抜粋)

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
#include <QPushButton> // ボタンを追加する場合

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    model = new QStandardItemModel(this); // モデルを初期化

    setupTreeView(); // ツリービューのセットアップを呼び出し

    // 例1: ボタンをクリックしたときに選択されたアイテムを取得
    // UIにQPushButtonを追加し、オブジェクト名を 'pushButton_getSelected' とした場合
    connect(ui->pushButton_getSelected, &QPushButton::clicked, this, &MainWindow::on_pushButton_getSelected_clicked);

    // 例2: 選択が変更されたときに自動的に処理を行う
    connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged,
            this, &MainWindow::handleSelectionChanged);

    // 複数選択を有効にする
    ui->treeView->setSelectionMode(QAbstractItemView::ExtendedSelection);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::setupTreeView()
{
    // ツリービューにモデルを設定
    ui->treeView->setModel(model);

    // テストデータをモデルに追加
    QStandardItem *parentItem = model->invisibleRootItem();

    QStandardItem *item1 = new QStandardItem("Parent Item 1");
    item1->appendRow(new QStandardItem("Child 1.1"));
    item1->appendRow(new QStandardItem("Child 1.2"));
    parentItem->appendRow(item1);

    QStandardItem *item2 = new QStandardItem("Parent Item 2");
    item2->appendRow(new QStandardItem("Child 2.1"));
    item2->appendRow(new QStandardItem("Child 2.2"));
    parentItem->appendRow(item2);

    QStandardItem *item3 = new QStandardItem("Standalone Item");
    parentItem->appendRow(item3);

    // 列ヘッダーを設定 (QStandardItemModelはデフォルトで1列)
    model->setHorizontalHeaderLabels({"Item Name"});
}

例1: ボタンクリックで選択されたアイテムを取得

最も一般的なユースケースの一つは、ユーザーがボタンをクリックしたときなど、特定のイベントで現在の選択状態を取得することです。

// mainwindow.cpp の追加部分

void MainWindow::on_pushButton_getSelected_clicked()
{
    qDebug() << "--- Getting selected items on button click ---";

    // QModelIndexList を取得
    QModelIndexList selectedIndexes = ui->treeView->selectionModel()->selectedIndexes();

    if (selectedIndexes.isEmpty()) {
        qDebug() << "No items selected.";
        return;
    }

    // 取得したインデックスをループして情報を表示
    // QTreeView::selectedIndexes()は、選択された各行のすべての列のインデックスを返す場合があることに注意。
    // 通常は0列目のデータ(表示されるテキストなど)に注目することが多い。
    QSet<QModelIndex> processedRows; // 重複する行を避けるためにセットを使用

    foreach (const QModelIndex &index, selectedIndexes) {
        // 同じ行の異なる列のインデックスが複数含まれることを避けるために、
        // 0列目のインデックスのみを処理するか、行で重複を排除する。
        // ここでは、行のインデックスがまだ処理されていないことを確認する。
        QModelIndex rowRepresentativeIndex = model->index(index.row(), 0, index.parent());

        if (!processedRows.contains(rowRepresentativeIndex)) {
            // インデックスが有効であることを確認
            if (index.isValid()) {
                // モデルからデータを取得 (Qt::DisplayRole は表示テキスト)
                QString itemText = index.data(Qt::DisplayRole).toString();

                // 階層パスを取得する例 (オプション)
                QString path = itemText;
                QModelIndex current = index.parent();
                while (current.isValid()) {
                    path = current.data(Qt::DisplayRole).toString() + "/" + path;
                    current = current.parent();
                }

                qDebug() << "Selected Item: " << itemText
                         << " (Row:" << index.row()
                         << ", Col:" << index.column()
                         << ", Path:" << path << ")";

                // もしIDやカスタムデータがQt::UserRoleに格納されている場合
                // QVariant customData = index.data(Qt::UserRole);
                // if (customData.isValid()) {
                //     qDebug() << "Custom Data:" << customData.toString();
                // }
            }
            processedRows.insert(rowRepresentativeIndex); // この行は処理済みとしてマーク
        }
    }
}

例2: 選択変更イベントをハンドルする

ユーザーがツリービューの選択を変更するたびに自動的に処理を実行したい場合は、QItemSelectionModel::selectionChanged シグナルを接続します。

// mainwindow.cpp の追加部分

void MainWindow::handleSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
{
    // selected と deselected は、それぞれ新しく選択されたアイテムと選択解除されたアイテムの情報を含む
    // QItemSelection オブジェクトです。
    // より詳細な変更を追跡したい場合に便利です。

    qDebug() << "--- Selection changed ---";

    // 現在選択されているすべてのアイテムを取得
    QModelIndexList currentSelectedIndexes = ui->treeView->selectionModel()->selectedIndexes();

    if (currentSelectedIndexes.isEmpty()) {
        qDebug() << "No items currently selected.";
        return;
    }

    // 重複を避ける処理 (例1と同様)
    QSet<QModelIndex> processedRows;

    foreach (const QModelIndex &index, currentSelectedIndexes) {
        QModelIndex rowRepresentativeIndex = model->index(index.row(), 0, index.parent());
        if (!processedRows.contains(rowRepresentativeIndex)) {
            if (index.isValid()) {
                qDebug() << "Currently Selected Item: " << index.data(Qt::DisplayRole).toString();
            }
            processedRows.insert(rowRepresentativeIndex);
        }
    }
}

例3: 複数選択と単一列のデータの取得

QTreeView::selectedIndexes() は、選択された行のすべての列のインデックスを返す傾向があります。もし、ある行の特定の列(例えば0列目)のデータのみに興味がある場合、以下のようにフィルタリングできます。

// 別のスロットまたは関数として

void MainWindow::processSelectedItemsColumnZero()
{
    QModelIndexList selectedIndexes = ui->treeView->selectionModel()->selectedIndexes();

    qDebug() << "--- Processing only column 0 of selected items ---";

    // 各行に対して、0列目のインデックスのみを収集
    QList<QModelIndex> uniqueSelectedRows;
    QSet<QString> uniqueRowPaths; // 親パスと行番号で重複をチェックするためのセット

    foreach (const QModelIndex &index, selectedIndexes) {
        // 同じ行の異なる列のインデックスがリストに含まれているため、
        // 0列目のインデックスのみを考慮し、かつ重複を避ける。
        if (index.column() == 0) {
            QString pathKey = index.parent().isValid() ?
                              model->data(index.parent(), Qt::DisplayRole).toString() + "/" + QString::number(index.row()) :
                              QString::number(index.row());

            if (!uniqueRowPaths.contains(pathKey)) {
                uniqueSelectedRows.append(index);
                uniqueRowPaths.insert(pathKey);
            }
        }
    }

    if (uniqueSelectedRows.isEmpty()) {
        qDebug() << "No unique items selected in column 0.";
        return;
    }

    foreach (const QModelIndex &index, uniqueSelectedRows) {
        qDebug() << "Selected Item (Col 0): " << index.data(Qt::DisplayRole).toString()
                 << " (Row:" << index.row() << ")";
    }
}
  • ユーザーの選択変更にリアルタイムで反応するには、QItemSelectionModel::selectionChanged シグナルを接続するのが一般的です。
  • アイテムのデータを取得するには、QModelIndex::data() メソッドを使用し、必要に応じて適切な Qt::ItemDataRole を指定します。
  • 返される QModelIndexList は、選択された各アイテム(行)について、複数列のインデックスを含む場合があります。特定の列のデータのみが必要な場合は、index.column() をチェックしてフィルタリングするか、行で重複排除ロジックを追加します。
  • QTreeView::selectedIndexes() を呼び出すには、ui->treeView->selectionModel()->selectedIndexes() の形式を使用します。


QTreeView::selectedIndexes() は、QItemSelectionModel クラスのメソッドであり、QTreeView が内部的に使用している選択モデルから選択情報を取得します。この基本を踏まえつつ、他の選択情報の取得方法を見ていきましょう。

QItemSelectionModel::currentIndex() - 現在の(フォーカスされている)アイテムを取得する

  • selectedIndexes() との違い: selectedIndexes() が「選択されているすべてのアイテム」のリストを返すのに対し、currentIndex() は「現在フォーカスがある(または最後に操作された)アイテム」を単一で返します。複数選択の場合、currentIndex()selectedIndexes() リストの一部である可能性がありますが、必ずしもそうではありません(例えば、選択解除されたアイテムにフォーカスが移動した場合など)。
  • コード例:
    QModelIndex currentIndex = ui->treeView->selectionModel()->currentIndex();
    if (currentIndex.isValid()) {
        qDebug() << "Current Item: " << currentIndex.data(Qt::DisplayRole).toString();
    } else {
        qDebug() << "No current item.";
    }
    
  • 用途:
    • 単一選択モード(QAbstractItemView::SingleSelection)の場合、selectedIndexes() は常に1つのアイテムを含むリストを返しますが、currentIndex() も同様にその単一のアイテムを効率的に取得できます。
    • 複数選択モードであっても、ユーザーが最後に操作したアイテム(例えば、プロパティ表示の対象など)に焦点を当てたい場合に有用です。

QItemSelectionModel::selectedRows(int column = 0) - 特定の列に絞って選択行のインデックスを取得する

  • selectedIndexes() との違い: selectedIndexes() が「選択されたすべてのセル」のインデックスを返す可能性があるのに対し、selectedRows() は「選択された行の、指定された列のセル」のインデックスのみを返します。これにより、同じ行のインデックスが重複してリストに含まれるのを防ぎやすくなります。
  • コード例:
    // 0列目(通常はアイテム名が表示される列)の選択された行のインデックスを取得
    QModelIndexList selectedRowsCol0 = ui->treeView->selectionModel()->selectedRows(0);
    
    qDebug() << "--- Selected Rows (Column 0) ---";
    foreach (const QModelIndex &index, selectedRowsCol0) {
        if (index.isValid()) {
            qDebug() << "Selected Row Item (Col 0): " << index.data(Qt::DisplayRole).toString()
                     << " (Row:" << index.row() << ")";
        }
    }
    
  • 用途:
    • ツリービューが複数の列を持つが、選択されたアイテムのデータを取得する際には常に主キーや表示テキストが含まれる0列目のデータのみが必要な場合に非常に便利です。selectedIndexes() の結果から手動でフィルタリングする手間が省けます。

QAbstractItemView::clicked(const QModelIndex &index) シグナル - アイテムがクリックされたときに即座に反応する

  • selectedIndexes() との違い: clicked シグナルは「クリックされた単一のアイテム」にのみ反応します。複数選択の場合、selectedIndexes() はクリックによって変化した選択状態全体を考慮しますが、clicked はそのクリックされたアイテムの情報のみを提供します。また、キーボードによる選択変更では clicked は発されません。
  • コード例:
    // mainwindow.cpp のコンストラクタ内で
    connect(ui->treeView, &QAbstractItemView::clicked,
            this, &MainWindow::handleItemClicked);
    
    // mainwindow.h にスロットを追加
    // private slots:
    //    void handleItemClicked(const QModelIndex &index);
    
    // mainwindow.cpp にスロットの実装を追加
    void MainWindow::handleItemClicked(const QModelIndex &index)
    {
        if (index.isValid()) {
            qDebug() << "Item Clicked: " << index.data(Qt::DisplayRole).toString()
                     << " (Row:" << index.row() << ", Col:" << index.column() << ")";
            // ここでクリックされたアイテムに対する処理を行う
        }
    }
    
  • 用途:
    • アイテムがクリックされた瞬間に、そのアイテムのプロパティを表示したり、特定の操作を実行したりする場合に最適です。
    • 「選択状態の変更」とは異なり、「クリック」というアクションそのものに反応したい場合に用います。

QAbstractItemView::pressed(const QModelIndex &index) / doubleClicked(const QModelIndex &index)

  • コード例:
    // doubleClicked の例
    connect(ui->treeView, &QAbstractItemView::doubleClicked,
            this, &MainWindow::handleItemDoubleClicked);
    
    void MainWindow::handleItemDoubleClicked(const QModelIndex &index)
    {
        if (index.isValid()) {
            qDebug() << "Item Double Clicked: " << index.data(Qt::DisplayRole).toString();
            // 例: アイテムの編集ダイアログを開く
            // ui->treeView->edit(index);
        }
    }
    
  • 用途:
    • pressed: クリックよりも早いタイミング(マウスボタンが押された瞬間)で処理を開始したい場合。
    • doubleClicked: アイテムをダブルクリックしたときに編集モードに入る、別ウィンドウを開く、詳細を表示するといった、特別なアクションを実行したい場合。

QItemSelectionModel::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) シグナル

  • コード例: 上記「例2」を参照してください。
  • 用途:
    • 選択状態の変更に最も包括的に対応したい場合に用います。ユーザーがShiftキーやCtrlキーを使って複数アイテムを選択したり、一部を選択解除したりした場合でも、このシグナルは正確な変更情報を提供します。
    • selectedIndexes() を呼び出す前の「トリガー」として非常に重要です。