QTreeViewで行が隠れているか知りたい!isRowHidden() の詳細とサンプルコード

2025-05-27

QTreeView::isRowHidden() は、Qtの QTreeView クラス(ツリー構造を表示するウィジェット)のメンバ関数の一つで、指定された行が非表示になっているかどうかを確認するために使用されます。

具体的には、以下の点を理解しておくと良いでしょう。

  • 戻り値
    • true: 指定された行が非表示になっている場合。
    • false: 指定された行が表示されている場合。
  • 対象
    QTreeView 内の特定の行(インデックスで指定します)に対して呼び出します。

もう少し詳しく

QTreeView は、階層的なデータをツリー状に表示するために使われます。データはモデル(QAbstractItemModel を継承したクラスなど)によって管理され、QTreeView はそのモデルの内容を視覚的に表現します。

データの表示方法を制御するために、ソート(並べ替え)やフィルタリング(特定の条件に合致する項目のみ表示)といった機能がよく用いられます。isRowHidden() は、特にフィルタリングが適用されている場合に、特定の行がフィルタ条件に合致せず非表示になっているかどうかを知るために役立ちます。


例えば、名前のリストをツリー表示していて、「A」で始まる名前だけを表示するフィルタを適用したとします。このとき、isRowHidden() を「B」で始まる名前の行に対して呼び出すと true が返り、「Alice」という名前の行に対して呼び出すと false が返ります。

使い方

isRowHidden() 関数は、QModelIndex を引数として取ります。QModelIndex は、モデル内の特定の項目(行と列の組み合わせ)を指すためのオブジェクトです。QTreeView の特定の行に対応する QModelIndex を取得し、それを isRowHidden() に渡すことで、その行が非表示かどうかを調べることができます。

QModelIndex index = treeView->model()->index(row, 0, parentIndex); // 特定の行のインデックスを取得 (通常は0列目)
bool hidden = treeView->isRowHidden(row, parentIndex); // 行が非表示かどうかを確認
if (hidden) {
    qDebug() << "行 " << row << " は非表示です。";
} else {
    qDebug() << "行 " << row << " は表示されています。";
}

ここで、row は行番号、parentIndex は親のインデックス(トップレベルの項目であれば QModelIndex())です。



QTreeView::isRowHidden() のよくあるエラーとトラブルシューティング

不正な QModelIndex の使用

  • トラブルシューティング
    • QModelIndex を使用する前に、必ず isValid() で有効性を確認してください。
    • index() 関数に渡す引数が正しいか、モデルの構造と一致しているかを確認してください。特に、親インデックスが適切かどうか注意が必要です。トップレベルのアイテムの場合は QModelIndex() を使用します。
    • QTreeView とモデルのライフサイクルを管理し、有効な状態で isRowHidden() を呼び出すようにしてください。
  • 原因
    • モデルの構造が変更された後に、古い QModelIndex を使用している。
    • index() 関数で誤った行番号、列番号、または親インデックスを指定している。
    • QTreeView に関連付けられたモデルがまだ設定されていない、または破棄されている。
  • エラー
    isRowHidden() に渡す QModelIndex が無効(isValid()false を返す)である場合。

行番号の誤り

  • トラブルシューティング
    • モデルの行数を取得する (model()->rowCount(parentIndex)) 関数を使用して、有効な行番号の範囲を確認してください。
    • ループの条件やインクリメント処理が正しいかを見直してください。
  • 原因
    • モデルの行数を正しく把握していない。
    • ループ処理などで、行番号の範囲を誤って設定している。
  • エラー
    存在しない行番号を index() 関数に渡している。

親インデックスの誤り

  • トラブルシューティング
    • 特定のアイテムの親インデックスを取得するには、model()->parent(childIndex) を使用します。
    • ツリー構造を意識し、各アイテムの階層関係に基づいて正しい親インデックスを指定するようにしてください。
  • 原因
    • 親子関係を正しく理解していない。
    • 再帰的な処理で親インデックスの渡し方を間違えている。
  • エラー
    階層構造を持つモデルにおいて、子アイテムの QModelIndex を取得する際に、誤った親インデックスを使用している。

フィルタリングの影響を考慮していない

  • トラブルシューティング
    • プロキシモデルを使用している場合は、プロキシモデルの index() 関数を使用して、ビューに対応した QModelIndex を取得してから isRowHidden() を呼び出す必要があります。
    • モデルのインデックスとビューのインデックスの対応関係を意識してください。mapToSource()mapFromSource() などのプロキシモデルの関数が役立ちます。
  • 原因
    QSortFilterProxyModel などのプロキシモデルを使用している場合、ビューに表示される行のインデックスと、元のモデルの行のインデックスは異なります。isRowHidden() は、ビューに表示される(または非表示の)行に対して機能します。
  • 誤解
    フィルタリングが適用されている状態で、モデルの元の行番号を使って isRowHidden() を呼び出している。

isRowHidden() の呼び出しタイミング

  • トラブルシューティング
    • フィルタリングやソートの処理が完了したことを確認してから isRowHidden() を呼び出すようにしてください。必要に応じて、信号 (rowsAboutToBeRemoved, rowsRemoved, layoutChanged など) を利用して、処理の完了を検知できます。
  • 原因
    ビューの更新がまだ完了しておらず、isRowHidden() が古い情報に基づいて誤った結果を返す可能性があります。
  • 問題
    フィルタリングやソートの処理が完了する前に isRowHidden() を呼び出している。

カスタムモデルの実装ミス

  • トラブルシューティング
    • カスタムモデルの index() 関数、parent() 関数、rowCount() 関数などの実装を再度確認し、論理的な誤りがないか検証してください。
    • モデルのデータ構造と、それに対応するインデックスの生成方法を慎重に設計する必要があります。
  • 原因
    カスタムモデルの index() 関数や rowCount() 関数などの実装が正しくないため、QModelIndex が正しく生成されず、isRowHidden() が意図しない動作をする。
  • 問題
    自身で QAbstractItemModel を継承してカスタムモデルを作成している場合に、データの管理やインデックスの処理に誤りがある。
  • Qt のドキュメント参照
    QTreeView および関連するクラス(QAbstractItemModel, QModelIndex, QSortFilterProxyModel など)の公式ドキュメントを読み返し、関数の仕様や注意点を確認します。
  • 簡単なテストケース
    問題を再現する最小限のコードを作成し、そこで isRowHidden() の動作を確認します。
  • ステップ実行
    デバッガを使用して、isRowHidden() が呼び出される前後の変数の状態を追跡します。
  • デバッグ出力
    qDebug() を使用して、QModelIndex の有効性、行番号、親インデックスなどを出力し、期待される値と実際の値を確認します。


例1: フィルタリングされたツリービューで行が非表示かどうかを確認する

この例では、QSortFilterProxyModel を使用してツリービューをフィルタリングし、特定の条件に合致しない行が非表示になっているかどうかを isRowHidden() で確認します。

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

// カスタムフィルタクラス
class NameFilter : public QSortFilterProxyModel {
public:
    NameFilter(QObject *parent = nullptr) : QSortFilterProxyModel(parent) {}

protected:
    bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override {
        QModelIndex index = sourceModel()->index(source_row, 0, source_parent);
        if (index.isValid()) {
            QString name = sourceModel()->data(index).toString();
            return name.startsWith("A"); // "A" で始まる名前のみ表示
        }
        return false;
    }
};

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

    // 標準アイテムモデルの作成
    QStandardItemModel model;
    QStandardItem *rootItem = model.invisibleRootItem();

    QStandardItem *alice = new QStandardItem("Alice");
    QStandardItem *bob = new QStandardItem("Bob");
    QStandardItem *amy = new QStandardItem("Amy");
    QStandardItem *ben = new QStandardItem("Ben");

    rootItem->appendRow(alice);
    rootItem->appendRow(bob);
    rootItem->appendRow(amy);
    rootItem->appendRow(ben);

    // ソート/フィルタプロキシモデルの作成
    NameFilter proxyModel;
    proxyModel.setSourceModel(&model);

    // ツリービューの作成とモデルの設定
    QTreeView treeView;
    treeView.setModel(&proxyModel);
    treeView.show();

    // 各行が非表示かどうかを確認
    for (int i = 0; i < model.rowCount(); ++i) {
        QModelIndex sourceIndex = model.index(i, 0);
        QModelIndex proxyIndex = proxyModel.mapFromSource(sourceIndex);

        bool isHidden = treeView.isRowHidden(proxyIndex.row(), proxyIndex.parent());
        QString name = model.data(sourceIndex).toString();

        qDebug() << "名前:" << name << ", 元の行:" << i << ", プロキシ行:" << proxyIndex.row() << ", 非表示:" << isHidden;
    }

    return app.exec();
}

この例では、NameFilter というカスタムフィルタクラスを作成し、名前が "A" で始まるアイテムのみを表示するようにしています。main 関数内では、元のモデルの各行に対応するプロキシモデルのインデックスを取得し、isRowHidden() を使ってその行がビューで非表示になっているかどうかを確認しています。

例2: 特定の条件に基づいて行の表示/非表示を切り替える

この例では、ボタンをクリックすることで、ツリービューの特定の行の表示/非表示を切り替える簡単なUIを作成します。

#include <QApplication>
#include <QTreeView>
#include <QStandardItemModel>
#include <QPushButton>
#include <QVBoxLayout>
#include <QWidget>
#include <QDebug>

class MainWindow : public QWidget {
public:
    MainWindow(QWidget *parent = nullptr) : QWidget(parent) {
        model_.setHorizontalHeaderLabels({"名前"});
        QStandardItem *item1 = new QStandardItem("Item 1");
        QStandardItem *item2 = new QStandardItem("Item 2");
        QStandardItem *item3 = new QStandardItem("Item 3");
        model_.appendRow(item1);
        model_.appendRow(item2);
        model_.appendRow(item3);

        treeView_.setModel(&model_);

        toggleButton_ = new QPushButton("Item 2 を非表示/表示");
        connect(toggleButton_, &QPushButton::clicked, this, &MainWindow::toggleRowVisibility);

        QVBoxLayout *layout = new QVBoxLayout;
        layout->addWidget(&treeView_);
        layout->addWidget(toggleButton_);
        setLayout(layout);
    }

private slots:
    void toggleRowVisibility() {
        bool isHidden = treeView_.isRowHidden(1, QModelIndex()); // 2行目 (インデックスは0から始まる)
        treeView_.setRowHidden(1, QModelIndex(), !isHidden);
        qDebug() << "Item 2 は非表示:" << !isHidden;
    }

private:
    QStandardItemModel model_;
    QTreeView treeView_;
    QPushButton *toggleButton_;
};

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    MainWindow window;
    window.show();
    return app.exec();
}

この例では、MainWindow クラス内で QTreeViewQPushButton を作成しています。ボタンがクリックされると、toggleRowVisibility() スロットが呼び出され、isRowHidden() で2行目の現在の表示状態を確認し、setRowHidden() で表示/非表示を切り替えます。

例3: 選択されたアイテムの子アイテムが非表示かどうかを確認する (階層構造)

この例では、階層構造を持つツリービューで、選択されたアイテムの子アイテムが非表示になっているかどうかを確認します。

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

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

    // 標準アイテムモデルの作成 (階層構造)
    QStandardItemModel model;
    QStandardItem *parent1 = new QStandardItem("Parent 1");
    QStandardItem *child1_1 = new QStandardItem("Child 1-1");
    QStandardItem *child1_2 = new QStandardItem("Child 1-2");
    parent1->appendRow(child1_1);
    parent1->appendRow(child1_2);
    model.appendRow(parent1);

    QStandardItem *parent2 = new QStandardItem("Parent 2");
    QStandardItem *child2_1 = new QStandardItem("Child 2-1");
    parent2->appendRow(child2_1);
    model.appendRow(parent2);

    // ツリービューの作成とモデルの設定
    QTreeView treeView;
    treeView.setModel(&model);
    treeView.expandAll(); // すべてのノードを展開

    // アイテム選択モデルを取得
    QItemSelectionModel *selectionModel = treeView.selectionModel();

    // 選択が変更されたときの処理
    QObject::connect(selectionModel, &QItemSelectionModel::currentChanged,
                     [&](const QModelIndex &current, const QModelIndex &previous) {
        if (current.isValid()) {
            int childCount = model.rowCount(current);
            qDebug() << "選択されたアイテム:" << model.data(current).toString();
            for (int i = 0; i < childCount; ++i) {
                QModelIndex childIndex = model.index(i, 0, current);
                bool isHidden = treeView.isRowHidden(childIndex.row(), current);
                qDebug() << "  子アイテム:" << model.data(childIndex).toString() << ", 非表示:" << isHidden;
            }
        }
    });

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


モデルのデータやフラグを確認する

  • 欠点
    ビューのフィルタリングやソートなど、モデルとは独立したビュー側の操作による非表示状態は直接的に把握できない。
  • 利点
    ビューの状態に依存せず、モデルのデータに基づいて論理的な表示/非表示の判断が可能。
  • 詳細
    モデルに、各アイテムの表示/非表示を制御するための独自のデータ(例えば、真偽値のフラグ)を持たせる。data() 関数でこのフラグを取得し、それに基づいて表示を制御する。また、flags() 関数で Qt::ItemIsVisible フラグを設定/解除することで、アイテムの可視性を間接的に制御できる。
  • 方法
    QAbstractItemModel (またはそのサブクラス) のデータやアイテムフラグの状態に基づいて、行が表示されるべきかどうかを判断する。


// モデル内で表示/非表示を制御するフラグを持つ
class MyItem : public QStandardItem {
public:
    MyItem(const QString &text, bool visible = true) : QStandardItem(text), isVisible_(visible) {}

    QVariant data(int role) const override {
        if (role == Qt::DisplayRole) {
            return QStandardItem::data(role);
        } else if (role == Qt::UserRole + 1) { // カスタムロールで可視性フラグを返す
            return isVisible_;
        }
        return QStandardItem::data(role);
    }

    Qt::ItemFlags flags(int column) const override {
        Qt::ItemFlags f = QStandardItem::flags(column);
        if (isVisible_) {
            f |= Qt::ItemIsSelectable | Qt::ItemIsEnabled;
        } else {
            f &= ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled); // 選択と操作を不可にする例
        }
        return f;
    }

    void setVisible(bool visible) { isVisible_ = visible; emit dataChanged(index(), index()); }

private:
    bool isVisible_;
};

// ...

// モデルから可視性フラグを取得して判断
QModelIndex index = treeView->model()->index(row, 0, parentIndex);
if (index.isValid()) {
    bool visible = index.data(Qt::UserRole + 1).toBool();
    if (!visible) {
        qDebug() << "モデルのデータによると、この行は非表示です。";
    }
}

プロキシモデルを使用する

  • 欠点
    非表示になっているかどうかを直接的に問い合わせるAPIは、プロキシモデルには isRowHidden() のような形で存在しないため、フィルタリングのロジックを再評価する必要がある場合がある。
  • 利点
    元のモデルを直接変更せずに、柔軟なフィルタリングルールを適用できる。非表示のロジックが一箇所に集中する。
  • 詳細
    filterAcceptsRow() メソッドをオーバーライドすることで、特定の条件に合致する行のみをビューに表示できる。この方法では、元のモデルのデータは変更せずに、ビューに表示される内容を制御する。
  • 方法
    QSortFilterProxyModel などのプロキシモデルを介してビューにデータを表示し、プロキシモデルのフィルタリング機能を利用して行を非表示にする。


(例1で NameFilter クラスとして示しています)

この例では、NameFilter::filterAcceptsRow() の実装が非表示にする条件を定義しています。特定の行が非表示かどうかを知るには、同じフィルタリングロジックを再度適用する必要があります。

QItemSelectionModel の状態を監視する (間接的)

  • 欠点
    非表示の理由がフィルタリングなどによる場合は有効だが、setRowHidden() で明示的に非表示にされた行は選択できないだけで、選択モデルの状態だけでは非表示の理由までは特定できない。
  • 利点
    ビューの選択状態に基づいて、ユーザーが操作できる行を把握できる。
  • 詳細
    QItemSelectionModel のシグナル(例えば selectionChanged()currentChanged()) に接続し、選択状態の変化を監視する。非表示の行は通常選択できないため、選択されていない状態が続くことで非表示になっている可能性を示唆できる。
  • 方法
    選択モデルの状態変化を監視し、特定の行が選択されていないことから、間接的に非表示になっている可能性を推測する。


QItemSelectionModel *selectionModel = treeView->selectionModel();
QObject::connect(selectionModel, &QItemSelectionModel::selectionChanged,
                 [&](const QItemSelection &selected, const QItemSelection &deselected) {
    for (const QModelIndex &index : deselected.indexes()) {
        if (treeView->isRowHidden(index.row(), index.parent())) {
            qDebug() << "非表示の行が選択解除されました:" << index;
        }
    }
});

行の矩形領域を確認する (最終手段に近い)

  • 欠点
    描画の最適化などにより、非表示の行でも visualRect() が空でない場合があるため、完全に信頼できるわけではない。また、パフォーマンスへの影響も考慮する必要がある。
  • 利点
    最終的な描画結果に基づいて非表示状態を判断できる。
  • 詳細
    非表示の行は通常、ビュー上で描画されないため、visualRect() が空の QRect を返す可能性がある。
  • 方法
    visualRect() 関数を使用して、特定の行の矩形領域を取得し、それが有効な領域(例えば、幅や高さが0でない)かどうかを確認する。
QModelIndex index = treeView->model()->index(row, 0, parentIndex);
QRect rect = treeView->visualRect(index);
if (rect.isEmpty()) {
    qDebug() << "visualRect() が空なので、この行は表示されていません (非表示の可能性)。";
} else {
    qDebug() << "visualRect():" << rect;
}