【Qt】QListWidget::setSelectionModel()を使った複数リストの選択同期とカスタムロジック実装例

2025-05-31

簡単に言うと、この関数はQListWidgetが使用する選択モデルを設定するために使われます。

QListWidgetは、リスト形式でアイテムを表示し、ユーザーがそれらのアイテムを選択できるようにするウィジェットです。通常、QListWidgetは内部的に独自の選択モデル(QItemSelectionModelのサブクラス)を持っています。しかし、setSelectionModel()関数を使用すると、開発者はこのデフォルトの選択モデルを独自のカスタム選択モデルに置き換えることができます。

QItemSelectionModelとは?

QItemSelectionModelは、データモデル(この場合はQListWidgetが内部的に持つモデル)内のアイテムの選択状態を管理するためのクラスです。具体的には、以下の情報を提供します。

  • 選択がどのように変化したか(シグナルを発行)
  • どのアイテムが現在のアイテム(フォーカスのあるアイテム)であるか
  • どのアイテムが選択されているか

setSelectionModel()の目的

この関数を使用する主な目的は以下の通りです。

  1. 複数のビュー間で選択を共有する: もし、同じデータモデルを表示する複数のビュー(例えば、同じデータを表示するQListWidgetQTableViewなど)がある場合、それぞれのビューが独自の選択モデルを持つと、あるビューで選択を変更しても他のビューには反映されません。setSelectionModel()を使って同じQItemSelectionModelインスタンスを複数のビューに設定することで、選択状態を共有し、どのビューで選択を変更しても他のビューにも同期して反映されるようになります。

  2. カスタムの選択ロジックを実装する: アプリケーションの要件によっては、デフォルトの選択動作では不十分な場合があります。例えば、特定の条件を満たすアイテムのみを選択できるようにしたい、選択時に追加の処理を実行したい、といったケースです。このような場合、QItemSelectionModelを継承したカスタムクラスを作成し、そのカスタム選択モデルをsetSelectionModel()QListWidgetに設定することで、選択に関するより高度な制御を行うことができます。

  3. モデル/ビューアーキテクチャの利点を活用する: Qtのモデル/ビューアーキテクチャでは、データ(モデル)、表示(ビュー)、選択(選択モデル)が分離されています。setSelectionModel()はこのアーキテクチャの一部であり、選択の管理を独立させることで、より柔軟で再利用可能なコードを作成することができます。

注意点

  • 通常、QListWidgetのコンストラクタ後にsetSelectionModel()を呼び出すことで、デフォルトの選択モデルを置き換えることができます。
  • setSelectionModel()に渡されたQItemSelectionModelオブジェクトの所有権は、QListWidgetには渡されません。つまり、QListWidgetは渡された選択モデルを削除しません。したがって、開発者が選択モデルのライフサイクルを適切に管理する必要があります。


QItemSelectionModelのモデルとの不一致

エラーメッセージの例
QAbstractItemView::setSelectionModel() failed: Trying to set a selection model, which works on a different model than the view.

原因
QListWidgetは内部的にモデルを持っています(QListWidgetItemを管理するプライベートなモデル)。setSelectionModel()に渡すQItemSelectionModelのコンストラクタは、通常、その選択モデルが管理するデータモデルへのポインタを引数に取ります。このとき、QListWidgetの内部モデルと、QItemSelectionModelに設定されているモデルが異なる場合にこのエラーが発生します。

トラブルシューティング
QItemSelectionModelを生成する際に、QListWidgetの既存のモデルを渡す必要があります。

// QListWidgetのインスタンス
QListWidget* myListWidget = new QListWidget(this);

// 新しいカスタム選択モデルを生成
// myListWidget->model() を引数に渡すことで、QListWidgetの内部モデルと関連付ける
MyCustomSelectionModel* mySelectionModel = new MyCustomSelectionModel(myListWidget->model(), myListWidget);

// QListWidgetにカスタム選択モデルを設定
myListWidget->setSelectionModel(mySelectionModel);

ポイント
QListWidgetQListViewとは異なり、直接setModel()を呼び出すことはできません。QListWidgetは便利クラスであり、内部で独自のモデルを管理しています。そのため、setSelectionModel()を使用する際も、この内部モデルを意識する必要があります。

シグナル/スロット接続の喪失

問題
setSelectionModel()を呼び出した後、QListWidgetitemSelectionChanged()シグナルやcurrentItemChanged()シグナルが発火しなくなることがあります。

原因
これはQtの特定のバージョン(特に古いバージョン)における既知のバグとして報告されています(例: QTBUG-50891)。QListWidgetのコンストラクタ内で、デフォルトの選択モデルに対してitemSelectionChanged()などのシグナルが接続されます。setSelectionModel()で新しい選択モデルに置き換えると、これらの接続が自動的に新しいモデルに引き継がれないため、シグナルが発火しなくなります。

トラブルシューティング
カスタム選択モデルを設定した後、手動で必要なシグナル/スロット接続を再確立する必要があります。

QListWidget* myListWidget = new QListWidget(this);
// ... myListWidgetにアイテムを追加するなど ...

// 古い選択モデルのポインタを取得(必要であれば解放のため)
QItemSelectionModel* oldSelectionModel = myListWidget->selectionModel();

// 新しいカスタム選択モデルを生成
MyCustomSelectionModel* mySelectionModel = new MyCustomSelectionModel(myListWidget->model(), myListWidget);

// QListWidgetにカスタム選択モデルを設定
myListWidget->setSelectionModel(mySelectionModel);

// 必要なシグナル/スロット接続を再確立
// QListWidgetが元々内部で行っていた接続を再現
QObject::connect(mySelectionModel, &QItemSelectionModel::currentChanged,
                 myListWidget, &QListWidget::currentItemChanged); // またはカスタムスロットへ

QObject::connect(mySelectionModel, &QItemSelectionModel::selectionChanged,
                 myListWidget, &QListWidget::itemSelectionChanged); // またはカスタムスロットへ

// 古い選択モデルが不要であれば解放 (親がない場合など)
if (oldSelectionModel && oldSelectionModel->parent() == nullptr) {
    oldSelectionModel->deleteLater();
}

注意
QListWidget::currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous)QListWidget::itemSelectionChanged() は、QItemSelectionModelのシグナルとは引数の型が異なります。上記のスニペットは概念的なものであり、実際には QListWidget の内部的なスロット(プライベートなものが多い)への接続が必要になります。したがって、このバグに遭遇した場合、通常はQItemSelectionModelcurrentChanged(const QModelIndex &, const QModelIndex &)selectionChanged(const QItemSelection &, const QItemSelection &)シグナルを直接監視し、それらを処理するカスタムスロットを実装する方が現実的です。

// 別の例:QItemSelectionModelのシグナルを直接監視する
connect(mySelectionModel, &QItemSelectionModel::selectionChanged,
        this, [this](const QItemSelection &selected, const QItemSelection &deselected) {
    // 選択変更時の処理をここに記述
    qDebug() << "Selection changed!";
    // selectedに含まれるQModelIndexからQListWidgetItemを取得することも可能
});

オブジェクトの所有権とメモリ管理

問題
setSelectionModel()に渡したQItemSelectionModelオブジェクトが適切に解放されず、メモリリークが発生する。

原因
QListWidget::setSelectionModel()は、渡された選択モデルの所有権を取得しません。つまり、その選択モデルの削除は開発者の責任です。もし、新しい選択モデルを設定する前に古い選択モデルが存在した場合、古いモデルを明示的に削除しないとメモリリークになります。

トラブルシューティング

  • deleteLater()を使用する: 親オブジェクトを設定できない場合や、特定のタイミングで明示的に削除したい場合は、deleteLater()を使用します。これは、イベントループの次のサイクルでオブジェクトを削除する安全な方法です。

    QItemSelectionModel* oldSelectionModel = myListWidget->selectionModel();
    // ... 新しい選択モデルを設定 ...
    if (oldSelectionModel) {
        oldSelectionModel->deleteLater(); // 古いモデルを安全に削除
    }
    

問題
カスタム選択モデルを使用している場合、QListWidget::addItem()QListWidget::clear()などのアイテム操作を行うと、選択状態がリセットされたり、予期しない動作をしたりする。

原因
QListWidgetが内部モデルを操作する際に、カスタム選択モデルとの間で状態の同期がうまくいかない場合があります。特に、モデルの構造が大きく変更される(アイテムの追加/削除/クリアなど)と、選択モデルが持つQModelIndexが無効になる可能性があります。

トラブルシューティング

  • QListViewとカスタムモデルの使用を検討する: QListWidgetは「便利クラス」であり、特定のユースケースでは便利ですが、より複雑なデータモデルや選択ロジックが必要な場合は、QListViewQAbstractListModelを継承したカスタムモデルを組み合わせて使用する方が、柔軟性と制御性が高まります。QListViewsetModel()setSelectionModel()の両方を直接操作でき、モデル/ビューアーキテクチャの利点を最大限に活用できます。
  • アイテム操作後に選択状態を再設定する: addItem()などでアイテムを追加した後、必要に応じて選択状態をプログラム的に再設定します。


例1:複数のQListWidget間で選択状態を共有する

この例では、2つのQListWidgetが同じ選択モデルを共有することで、どちらかのリストでアイテムを選択すると、もう一方のリストでも同じアイテムが選択されるようにします。

#include <QApplication>
#include <QListWidget>
#include <QVBoxLayout>
#include <QWidget>
#include <QItemSelectionModel>
#include <QDebug> // デバッグ出力用

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

    QWidget window;
    QVBoxLayout *layout = new QVBoxLayout(&window);

    // --- 最初のQListWidget ---
    QListWidget *listWidget1 = new QListWidget(&window);
    listWidget1->setWindowTitle("List Widget 1");
    listWidget1->addItem("Apple");
    listWidget1->addItem("Banana");
    listWidget1->addItem("Cherry");
    listWidget1->addItem("Date");
    layout->addWidget(listWidget1);

    // --- 2番目のQListWidget ---
    QListWidget *listWidget2 = new QListWidget(&window);
    listWidget2->setWindowTitle("List Widget 2");
    listWidget2->addItem("Apple");
    listWidget2->addItem("Banana");
    listWidget2->addItem("Cherry");
    listWidget2->addItem("Date");
    layout->addWidget(listWidget2);

    // --- 選択モデルを作成し、共有する ---
    // QListWidgetの内部モデルは QAbstractListModel のサブクラスです。
    // 最初のlistWidget1のモデルを使って選択モデルを作成します。
    QItemSelectionModel *sharedSelectionModel = new QItemSelectionModel(listWidget1->model(), &window);

    // 各QListWidgetに同じ選択モデルを設定します。
    listWidget1->setSelectionModel(sharedSelectionModel);
    listWidget2->setSelectionModel(sharedSelectionModel);

    // 選択が変更されたことを確認するためのシグナル接続 (オプション)
    // QItemSelectionModel::selectionChanged シグナルを直接監視するのがより確実です。
    QObject::connect(sharedSelectionModel, &QItemSelectionModel::selectionChanged,
                     [](const QItemSelection &selected, const QItemSelection &deselected) {
        qDebug() << "Selection changed!";
        if (!selected.isEmpty()) {
            qDebug() << "Selected indexes:";
            for (const QModelIndex &index : selected.indexes()) {
                qDebug() << "  Row:" << index.row() << "Column:" << index.column();
            }
        }
    });

    window.resize(400, 300);
    window.show();

    return a.exec();
}

解説

  1. 2つのQListWidget (listWidget1, listWidget2) を作成し、それぞれに同じアイテムを追加します。
  2. QItemSelectionModel *sharedSelectionModel = new QItemSelectionModel(listWidget1->model(), &window); で新しいQItemSelectionModelインスタンスを作成します。ここで重要なのは、listWidget1->model()を引数に渡している点です。これにより、選択モデルはlistWidget1が内部的に使用しているデータモデルを認識し、そのデータモデルのアイテムの選択状態を管理するようになります。
    • 重要
      QListWidgetは内部的にモデルを持っているため、そのモデルへのポインタをQItemSelectionModelのコンストラクタに渡す必要があります。これはQListWidgetmodel()メソッドで取得できます。
  3. listWidget1->setSelectionModel(sharedSelectionModel);listWidget2->setSelectionModel(sharedSelectionModel); で、両方のリストウィジェットに同じsharedSelectionModelを設定します。
  4. これで、どちらかのリストウィジェットでアイテムを選択すると、sharedSelectionModelの状態が変更され、それが共有されているもう一方のリストウィジェットにも反映されるようになります。

例2:カスタム選択モデルを作成して選択動作を制御する

この例では、QItemSelectionModelを継承したカスタムクラスを作成し、特定の条件を満たすアイテムのみを選択できるようにする、といった制御を行います。ここではシンプルに「奇数行のアイテムのみを選択可能にする」という例で示します。

まず、カスタム選択モデルのヘッダファイル (mycustomselectionmodel.h) を作成します。

// mycustomselectionmodel.h
#ifndef MYCUSTOMSELECTIONMODEL_H
#define MYCUSTOMSELECTIONMODEL_H

#include <QItemSelectionModel>
#include <QDebug>

class MyCustomSelectionModel : public QItemSelectionModel
{
    Q_OBJECT
public:
    // コンストラクタ: QAbstractItemModelへのポインタと親オブジェクトを受け取ります
    explicit MyCustomSelectionModel(QAbstractItemModel *model, QObject *parent = nullptr);

    // 選択動作をオーバーライドする関数
    // フラグ (SelectionFlags) を変更することで選択動作を制御します
    void select(const QModelIndex &index, QItemSelectionModel::SelectionFlags command) override;
    void select(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command) override;
};

#endif // MYCUSTOMSELECTIONMODEL_H

次に、カスタム選択モデルの実装ファイル (mycustomselectionmodel.cpp) です。

// mycustomselectionmodel.cpp
#include "mycustomselectionmodel.h"

MyCustomSelectionModel::MyCustomSelectionModel(QAbstractItemModel *model, QObject *parent)
    : QItemSelectionModel(model, parent)
{
    qDebug() << "MyCustomSelectionModel created.";
}

void MyCustomSelectionModel::select(const QModelIndex &index, QItemSelectionModel::SelectionFlags command)
{
    // ここで選択のロジックを実装します。
    // 例: 奇数行のアイテムのみを選択可能にする
    if (index.isValid() && (index.row() % 2 != 0)) { // 行番号が奇数の場合のみ
        qDebug() << "Selecting row (odd):" << index.row();
        // 基底クラスのselectを呼び出して実際の選択を行います
        QItemSelectionModel::select(index, command);
    } else {
        qDebug() << "Ignoring selection for row (even or invalid):" << index.row();
        // 偶数行または無効なインデックスの場合は選択しない (既存の選択は維持)
        // deselected フラグがない場合は、既存の選択も維持されます。
        // もし偶数行を選択しようとしたときに既存の選択も解除したい場合は、
        // QItemSelectionModel::clearSelection() や deselect() を検討します。
    }
}

void MyCustomSelectionModel::select(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command)
{
    // 範囲選択の場合も同様に処理できます。
    // この例では、個々のインデックスのselectメソッドに委譲します。
    for (const QModelIndex &index : selection.indexes()) {
        select(index, command);
    }
}

最後に、メインのアプリケーションコード (main.cpp) です。

// main.cpp
#include <QApplication>
#include <QListWidget>
#include <QVBoxLayout>
#include <QWidget>
#include "mycustomselectionmodel.h" // カスタム選択モデルのヘッダ

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

    QWidget window;
    QVBoxLayout *layout = new QVBoxLayout(&window);

    QListWidget *listWidget = new QListWidget(&window);
    listWidget->setWindowTitle("Custom Selection List Widget");
    for (int i = 0; i < 10; ++i) {
        listWidget->addItem(QString("Item %1").arg(i));
    }
    layout->addWidget(listWidget);

    // --- カスタム選択モデルを設定 ---
    // QListWidgetの内部モデルを引数に渡すことを忘れないでください。
    MyCustomSelectionModel *customSelectionModel = new MyCustomSelectionModel(listWidget->model(), &window);
    listWidget->setSelectionModel(customSelectionModel);

    // 選択変更時のシグナル接続 (カスタムモデルからのシグナルを直接監視)
    QObject::connect(customSelectionModel, &QItemSelectionModel::selectionChanged,
                     [](const QItemSelection &selected, const QItemSelection &deselected) {
        qDebug() << "Custom Selection Model: Selection changed!";
        if (!selected.isEmpty()) {
            qDebug() << "Selected items (custom):";
            for (const QModelIndex &index : selected.indexes()) {
                qDebug() << "  Row:" << index.row();
            }
        }
    });

    window.resize(300, 400);
    window.show();

    return a.exec();
}

解説

  1. MyCustomSelectionModelクラスはQItemSelectionModelを継承します。
  2. コンストラクタで、基底クラスのコンストラクタにQAbstractItemModelと親オブジェクトを渡します。ここでもQListWidgetmodel()メソッドで取得したモデルを渡します。
  3. select(const QModelIndex &index, QItemSelectionModel::SelectionFlags command) メソッドをオーバーライドします。このメソッドは、アイテムが選択されようとしたときに呼び出されます。
  4. オーバーライドされたselectメソッド内で、index.row() % 2 != 0という条件を追加しています。これにより、行番号が奇数の場合のみ、基底クラスのselectメソッドを呼び出して実際の選択操作を実行します。偶数行の場合は、選択操作をスキップし、QListWidgetがそのアイテムを選択するのを防ぎます。
  5. メイン関数で、MyCustomSelectionModelのインスタンスを作成し、listWidget->setSelectionModel(customSelectionModel);QListWidgetに設定します。

この例を実行すると、QListWidget上でアイテムをクリックしても、奇数行のアイテムしか選択できないことが確認できます。

プロジェクト設定の注意点

これらのコードをビルドするには、Qtのプロジェクトファイル(.proファイル)に以下を追加する必要があります。

QT += widgets

そして、カスタムクラスを作成した場合は、それらのソースファイルも指定します。

SOURCES += main.cpp \
           mycustomselectionmodel.cpp

HEADERS += mycustomselectionmodel.h

これらの例は、QListWidget::setSelectionModel()がどのように動作し、どのように利用できるかを示すためのものです。より複雑なシナリオでは、QItemSelectionModelの他のメソッドをオーバーライドしたり、より洗練された選択ロジックを実装したりすることができます。 Qt の QListWidget::setSelectionModel() に関連するプログラミングの例をいくつかご紹介します。この関数は、QListWidget の選択動作をカスタマイズしたい場合や、複数のビュー間で選択状態を共有したい場合に特に役立ちます。

複数の QListWidget 間で選択を同期する

この例では、2つの QListWidget が同じデータモデル(QListWidget の場合は内部モデル)を共有しているかのように、選択状態を同期させます。実際には、同じ QItemSelectionModel を設定することで実現します。

#include <QApplication>
#include <QListWidget>
#include <QVBoxLayout>
#include <QWidget>
#include <QItemSelectionModel>
#include <QDebug>

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

    QWidget window;
    QVBoxLayout *layout = new QVBoxLayout(&window);

    QListWidget *listWidget1 = new QListWidget(&window);
    listWidget1->addItem("Apple");
    listWidget1->addItem("Banana");
    listWidget1->addItem("Cherry");
    listWidget1->addItem("Date");

    QListWidget *listWidget2 = new QListWidget(&window);
    listWidget2->addItem("Orange"); // 別のアイテムでも選択モデルは同期される
    listWidget2->addItem("Grape");
    listWidget2->addItem("Lemon");
    listWidget2->addItem("Mango");

    layout->addWidget(listWidget1);
    layout->addWidget(listWidget2);

    // QListWidget1のデフォルト選択モデルを取得
    QItemSelectionModel *selectionModel = listWidget1->selectionModel();

    // QListWidget2にQListWidget1と同じ選択モデルを設定する
    // これにより、両方のリストウィジェットの選択が同期される
    listWidget2->setSelectionModel(selectionModel);

    // 選択モデルのシグナルを監視して、選択が同期されていることを確認
    QObject::connect(selectionModel, &QItemSelectionModel::selectionChanged,
                     [&](const QItemSelection &selected, const QItemSelection &deselected) {
        qDebug() << "Selection Changed:";
        for (const QModelIndex &index : selected.indexes()) {
            // listWidget1 または listWidget2 のモデルインデックスからアイテムを取得
            // QListWidget::itemFromIndex() を使用すると便利
            if (index.model() == listWidget1->model()) {
                QListWidgetItem *item = listWidget1->itemFromIndex(index);
                qDebug() << "  Selected in List 1:" << item->text();
            } else if (index.model() == listWidget2->model()) {
                QListWidgetItem *item = listWidget2->itemFromIndex(index);
                qDebug() << "  Selected in List 2:" << item->text();
            }
        }
    });

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

解説

  1. 2つの QListWidget (listWidget1, listWidget2) を作成し、それぞれにアイテムを追加します。
  2. listWidget1->selectionModel() を呼び出して、listWidget1 が内部的に使用しているデフォルトの QItemSelectionModel を取得します。
  3. listWidget2->setSelectionModel(selectionModel); を使用して、listWidget2 にも同じ QItemSelectionModel インスタンスを設定します。
  4. これにより、listWidget1 でアイテムを選択すると、listWidget2 の対応する(モデルインデックスが同じ)アイテムも選択されます。逆に、listWidget2 で選択しても同様です。
  5. QItemSelectionModel::selectionChanged シグナルを接続して、実際に選択が変更されたときにデバッグ出力されるようにしています。ここで index.model() をチェックしているのは、どのリストウィジェットのアイテムが選択されたかを確認するためです。

カスタム選択モデルの作成と適用 (特定の条件で選択を制限)

この例では、QItemSelectionModel を継承して、特定の条件(例えば、特定のテキストを含むアイテムは選択できない)で選択を制限するカスタム選択モデルを作成します。

#include <QApplication>
#include <QListWidget>
#include <QVBoxLayout>
#include <QWidget>
#include <QItemSelectionModel>
#include <QDebug>

// カスタム選択モデル
class RestrictedSelectionModel : public QItemSelectionModel {
public:
    // QListWidgetの内部モデルを引数に取る
    RestrictedSelectionModel(QAbstractItemModel *model, QObject *parent = nullptr)
        : QItemSelectionModel(model, parent) {
    }

    // select() メソッドをオーバーライドして選択ロジックをカスタマイズ
    void select(const QModelIndex &index, QItemSelectionModel::SelectionFlags command) override {
        // モデルインデックスからQListWidgetItemを取得
        // QListWidget の model() は QListWidget のプライベートな内部モデルなので、
        // ここでは QListWidget* を直接扱うことはできない。
        // 代わりに、QListWidget を親として渡して、親が QListWidget であればキャストしてアイテムを取得する。
        // もしくは、QListWidget::itemFromIndex() を使う。
        QListWidget* listWidget = qobject_cast<QListWidget*>(parent()); // 親がQListWidgetであることを期待

        if (listWidget) {
            QListWidgetItem* item = listWidget->itemFromIndex(index);
            if (item && item->text().contains("Restricted", Qt::CaseInsensitive)) {
                // "Restricted" を含むアイテムは選択できないようにする
                qDebug() << "Selection of restricted item attempted:" << item->text();
                return; // 選択を許可しない
            }
        }

        // それ以外のアイテムは通常の選択処理を行う
        QItemSelectionModel::select(index, command);
    }

    void select(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command) override {
        // 範囲選択にも対応する場合、このオーバーロードも考慮する
        // 今回はシンプルにするため、個別のインデックス選択のみを制限する
        // 実際には、selection 内のすべてのインデックスをチェックし、制限されたものがあれば除外するロジックが必要
        QItemSelection filteredSelection;
        QListWidget* listWidget = qobject_cast<QListWidget*>(parent());

        for (const QModelIndex& index : selection.indexes()) {
            if (listWidget) {
                QListWidgetItem* item = listWidget->itemFromIndex(index);
                if (item && !item->text().contains("Restricted", Qt::CaseInsensitive)) {
                    filteredSelection.select(index, index); // 制限されていないインデックスを追加
                } else {
                    qDebug() << "Selection of restricted item attempted (range):" << item->text();
                }
            } else {
                // 親がQListWidgetでない場合は通常通り追加(この例では発生しない想定)
                filteredSelection.select(index, index);
            }
        }
        
        QItemSelectionModel::select(filteredSelection, command);
    }
};

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

    QWidget window;
    QVBoxLayout *layout = new QVBoxLayout(&window);

    QListWidget *myListWidget = new QListWidget(&window);
    myListWidget->addItem("Normal Item 1");
    myListWidget->addItem("Restricted Item A"); // 選択不可
    myListWidget->addItem("Normal Item 2");
    myListWidget->addItem("Restricted Item B"); // 選択不可
    myListWidget->addItem("Another Normal Item");

    layout->addWidget(myListWidget);

    // QListWidgetのモデルを引数として、カスタム選択モデルを作成
    // ここでmyListWidgetを親として設定することで、所有権をQListWidgetに持たせる
    // また、RestrictedSelectionModel内でQListWidget::itemFromIndex()を使うために、
    // QListWidget自身を親として渡す。
    RestrictedSelectionModel *customSelectionModel = new RestrictedSelectionModel(myListWidget->model(), myListWidget);

    // QListWidgetにカスタム選択モデルを設定
    // この行の後にmyListWidget->selectionModel() を呼び出すと、
    // customSelectionModel へのポインタが返されるようになる
    myListWidget->setSelectionModel(customSelectionModel);

    // 選択モデルのシグナルを監視
    QObject::connect(customSelectionModel, &QItemSelectionModel::selectionChanged,
                     [](const QItemSelection &selected, const QItemSelection &deselected) {
        qDebug() << "Custom Selection Model: selectionChanged signal emitted.";
        for (const QModelIndex &index : selected.indexes()) {
            // ここで選択されたインデックスに対応するテキストを表示する
            // QListWidget::itemFromIndex() を使用するためには、QListWidgetのインスタンスが必要
            // 実際には、myListWidgetのポインタをキャプチャするか、
            // QListWidget* を private メンバとして持つカスタムクラス内で処理する
            qDebug() << "  Selected Index Row:" << index.row();
        }
    });

    window.show();
    return a.exec();
}
  1. RestrictedSelectionModel という QItemSelectionModel のサブクラスを定義します。
  2. コンストラクタで、QListWidget の内部モデル (myListWidget->model()) と myListWidget 自身を親として渡します。これにより、RestrictedSelectionModelmyListWidget のモデルと関連付けられ、myListWidget が削除されるときに RestrictedSelectionModel も自動的に削除されるようになります。
  3. select() メソッドをオーバーライドします。このメソッドは、アイテムが選択されようとするときに呼び出されます。
  4. オーバーライドされた select() 内で、選択されようとしているアイテムのテキストをチェックします。もしテキストに "Restricted" が含まれていれば、return; することで、QItemSelectionModel の元の select() メソッドが呼び出されず、アイテムの選択が阻止されます。
  5. main 関数で、QListWidget にアイテムを追加し、その中に "Restricted" を含むアイテムを含めます。
  6. RestrictedSelectionModel のインスタンスを作成し、myListWidget->setSelectionModel(customSelectionModel);myListWidget に設定します。
  7. 実行すると、"Restricted" を含むアイテムをクリックしても選択されないことが確認できます。
  • QListWidget の限界: QListWidget は「便利クラス」であり、基本的なリスト表示には非常に便利ですが、複雑なデータ管理や高度な選択ロジックが必要な場合は、QAbstractListModel を継承した独自のモデルを作成し、それを QListView と組み合わせて使用する方が、より柔軟で強力なソリューションとなります。
  • シグナル接続の再確立: 以前説明したように、Qt のバージョンによっては、setSelectionModel() を呼び出した後に QListWidget::itemSelectionChanged() などの便利なシグナルが発火しなくなることがあります。その場合は、QItemSelectionModelselectionChanged()currentChanged() シグナルを直接監視し、適切なスロットで処理するようにしてください。上記の例では、QItemSelectionModel::selectionChanged を直接接続しています。
  • 所有権の管理: setSelectionModel() は渡された QItemSelectionModel の所有権を取得しません。上記の例では、RestrictedSelectionModel の親を myListWidget に設定することで、myListWidget が削除されるときに自動的に customSelectionModel も削除されるようにしています。親を設定しない場合は、deleteLater() などを使って明示的にメモリを解放する必要があります。
  • QListWidget::model() の利用: QListWidget は内部的に QAbstractListModel を継承したプライベートなモデルを使用しています。setSelectionModel() を使用する際は、この QListWidget::model() を新しい QItemSelectionModel のコンストラクタに渡す必要があります。


QListWidget::setSelectionModel() の代替手段

QListWidget::setSelectionModel() は、QListWidget の選択動作をカスタマイズしたり、複数の QListWidget 間で選択を同期させたりする際に非常に強力な機能です。しかし、状況によっては、この関数を使わずに同じ目的を達成できる、あるいはより適切な代替手段が存在します。

ここでは、QListWidget::setSelectionModel() を使わない場合の一般的な代替手段をいくつかご紹介します。

最も直接的で簡単な方法は、QListWidget が提供する既存のシグナルとスロットを利用することです。これにより、カスタムの選択モデルを作成したり、明示的に選択モデルを設定したりすることなく、選択に関するロジックを実装できます。