Qtプログラミング: QListWidgetのアイテム選択・データ変更を検知する代替シグナル徹底比較

2025-05-31

void QListWidget::currentTextChanged() は、Qt の QListWidget クラスが提供するシグナル(signal)の一つです。

これは何をするシグナルか?

このシグナルは、QListWidget 内で現在選択されているアイテムのテキストが変更されたときに発せられます。より正確に言うと、選択されているアイテムが変わり、それに伴って現在のテキストが変わった場合に発せられるシグナルです。

なぜ void なのか?

シグナルの戻り値の型が void であるのは、シグナル自体が直接値を返すものではないためです。シグナルは単に何らかのイベントが発生したことを通知するメカニズムであり、そのイベントに応答するスロット(slot)を呼び出すためのものです。

どのような時に使われるか?

例えば、以下のようなシナリオでこのシグナルを利用できます。

  • 選択されたアイテムのテキストに基づいて、アプリケーションの他の部分の動作を変更したい場合(例: 選択されたファイル名に基づいて、ファイルの情報を表示する)。
  • QListWidget でユーザーがアイテムを選択するたびに、そのアイテムのテキストを別のウィジェット(例: QLabelQLineEdit)に表示したい場合。

使用例(概念)

概念的な使用例としては、以下のようにシグナルとスロットを接続します。

QListWidget* myListWidget = new QListWidget();
// ... myListWidget にアイテムを追加 ...

// シグナルをスロットに接続
// ここではラムダ式を使用していますが、QObject::connect のオーバーロードに応じて、
// 既存のメンバースロットやグローバル関数を接続することもできます。
connect(myListWidget, &QListWidget::currentTextChanged, [=](const QString &text){
    qDebug() << "現在の選択テキストが変更されました: " << text;
    // 例えば、text を QLabel に表示するなどの処理
    // myLabel->setText(text);
});

currentTextChanged() シグナルは、選択が変更されるたびに新しい選択アイテムのテキストを引数として提供します。これにより、変更後のテキストに基づいて適切な処理を実行できます。



シグナルが意図せず初回起動時に発火する

問題
アプリケーションを起動し、QListWidget が表示された際に、まだユーザーが何も選択していないにもかかわらず、currentTextChanged() シグナルが発火してしまうことがあります。通常、これはリストの最初のアイテムのテキストで発火します。

原因
Qtのウィジェットは、ウィンドウが表示される際に、自動的にフォーカス可能な最初のウィジェットにフォーカスを設定しようとします。QListWidget がフォーカスを受け取ると、自動的に最初の(選択可能な)アイテムを「現在のアイテム」として設定することがあります。この「現在のアイテム」が設定される際に currentTextChanged() シグナルが発火します。

トラブルシューティング

  • setFocusPolicy(Qt::NoFocus) を設定する(状況による)
    QListWidget が初期フォーカスを受け取らないように setFocusPolicy(Qt::NoFocus) を設定することも考えられますが、これはウィジェットの通常のフォーカス挙動を変えてしまうため、アプリケーション全体のユーザーエクスペリエンスに影響を与える可能性があります。他のウィジェットが存在し、そちらにフォーカスを移したい場合にのみ検討するべきです。

  • 初期化時にシグナルとスロットの接続を一時的に無効にする
    もしどうしても currentTextChanged() を使いたいが、初期化時の自動発火を避けたい場合は、以下の方法を検討できます。

    1. QListWidget の初期化時に、一時的にシグナルとスロットの接続を無効にします。
    2. QListWidget にアイテムを追加し、初期選択を設定します。
    3. その後、シグナルとスロットの接続を有効に戻します。
    // connect前にブロックする(一時的にシグナルを停止)
    myListWidget->blockSignals(true);
    
    // アイテムの追加や初期選択の設定
    myListWidget->addItem("Item A");
    myListWidget->addItem("Item B");
    myListWidget->setCurrentRow(0); // 最初のアイテムを選択
    
    // シグナルを再度有効にする
    myListWidget->blockSignals(false);
    
    // シグナルとスロットを接続
    connect(myListWidget, &QListWidget::currentTextChanged, this, &MyClass::onCurrentTextChanged);
    
  • itemSelectionChanged() や currentItemChanged() を検討する
    currentTextChanged() は「現在のアイテムのテキストが変更された」ときに発火しますが、ユーザーが明示的に選択を変更したときにのみ処理を実行したい場合は、QListWidget::itemSelectionChanged() (選択範囲の変更) や QListWidget::currentItemChanged(QListWidgetItem* current, QListWidgetItem* previous) (現在のアイテムの変更) シグナルの方が適切かもしれません。これらのシグナルは、ユーザー操作による選択変更により特化しています。

シグナルが複数回発火する

問題
ユーザーがアイテムを選択した際に、currentTextChanged() シグナルが一度ではなく複数回発火することがあります。特に、QListWidget がまだフォーカスを持っておらず、初めてアイテムをクリックした際に発生しやすいです。

原因
これは、Qtのフォーカス管理と選択の仕組みに関連する場合があります。

  • 内部的なモデル/ビューの更新が原因で、一時的に選択状態が複数回変更されるように見える場合。
  • QListWidget がフォーカスを得る際に一度、そしてその後にユーザーがアイテムを選択することで再度シグナルが発火する。

トラブルシューティング

  • currentItemChanged() と itemSelectionChanged() を再検討
    前述の通り、currentTextChanged() は「現在のアイテムのテキストが変更された」ときに発火します。もし、単に「アイテムが選択された」ことを検知したいのであれば、currentItemChanged(QListWidgetItem* current, QListWidgetItem* previous)itemSelectionChanged() の方が意図に合致し、複数回の発火を避けやすい可能性があります。itemSelectionChanged() は、複数のアイテムが選択される可能性がある場合にも対応できます。
  • イベントフィルターを使用する(高度なケース)
    特定のイベント(例: マウスクリック)に細かく反応を制御したい場合、eventFilter を実装して、不要なシグナル発火を抑制することも可能ですが、これはより複雑な解決策です。
  • blockSignals() を使用する
    特定の操作(例: 大量のアイテムを追加する処理など)中にシグナルが不要な場合は、blockSignals(true) で一時的にシグナルを停止し、処理が完了したら blockSignals(false) で再開します。

誤ったシグナルとの混同

問題
QListWidget には似たようなシグナルが複数あり、混同して意図しない動作になることがあります。

  • itemClicked(QListWidgetItem *item): アイテムがクリックされたとき。
  • itemSelectionChanged(): 選択されているアイテムの集合が変更されたとき(複数選択も含む)。
  • itemChanged(QListWidgetItem *item): リスト内の任意のアイテムのデータ(テキスト、チェック状態など)が変更されたとき。
  • currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous): 現在のアイテム自体が変更されたとき。
  • currentTextChanged(const QString &text): 現在のアイテムのテキストが変更されたとき。

トラブルシューティング

  • アプリケーションの要件を明確にする
    「何を検知したいのか?」を明確にすることで、適切なシグナルを選択できます。
    • 「ユーザーがリストアイテムを選んだら、そのテキストを使いたい」→ currentTextChanged() または currentItemChanged()
    • 「ユーザーがリストアイテムを編集したら、その変更を保存したい」→ itemChanged()
    • 「複数選択の状況を含め、選択状態が変わったら何かしたい」→ itemSelectionChanged()
  • 各シグナルのドキュメントを確認する
    Qtのリファレンスドキュメントで、各シグナルのトリガー条件を正確に理解することが重要です。それぞれのシグナルは異なるイベントに対応しています。
    • currentTextChanged(): 主にユーザーが選択を変更した結果、現在のアイテムのテキストが変わった場合に利用します。
    • itemChanged(): リストアイテムの内容そのものがプログラム的に、またはユーザーによって編集された場合に利用します(例: setFlags(Qt::ItemIsEditable) を設定してユーザーがテキストを直接編集した時)。

シグナルとスロットの接続ミス (C++ の場合)

問題
connect 文の書き方が間違っているために、シグナルが全く発火しないか、コンパイルエラーになる。特にQt5以降の新しい接続記法と古い記法を混同するケース。

原因

  • Qt4スタイルのSIGNAL()/SLOT()マクロを使用しているが、引数の型が完全一致していない(Qt5スタイルのラムダやメンバ関数ポインタの方が型安全)。
  • シグナルやスロットの名前のタイプミス。
  • 引数の型が一致しない。

トラブルシューティング

  • シグナルの引数を正確に合わせる
    QListWidget::currentTextChangedconst QString & 型の引数を一つ取ります。スロットの引数もこれと一致させる必要があります。

    // MyClass のスロットの宣言
    private slots:
        void mySlotFunction(const QString &text); // 引数名は任意だが、型は必須
    
  • Qt5スタイルの接続記法を使う
    型安全性が高く、コンパイル時にエラーを検出できるため、Qt5以降では新しい記法(メンバ関数ポインタやラムダ式)を推奨します。

    // メンバ関数ポインタ
    connect(myListWidget, &QListWidget::currentTextChanged, this, &MyClass::mySlotFunction);
    
    // ラムダ式
    connect(myListWidget, &QListWidget::currentTextChanged, [=](const QString &text){
        // 処理
    });
    


基本的な使用例:選択されたアイテムのテキストをラベルに表示する

この例では、QListWidget でアイテムを選択すると、そのアイテムのテキストが QLabel に表示されるようにします。

mainwindow.h (またはメインのウィジェットのヘッダーファイル)

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QListWidget>
#include <QLabel>
#include <QVBoxLayout> // レイアウトのため

class MainWindow : public QMainWindow
{
    Q_OBJECT // Qtのメタオブジェクトシステムを使用するために必要

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

private slots:
    // QListWidget::currentTextChanged シグナルに対応するスロット
    void onCurrentTextChanged(const QString &text);

private:
    QListWidget *listWidget;
    QLabel *displayLabel;
};

#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent)
{
    // QListWidget の作成と初期化
    listWidget = new QListWidget(this);
    listWidget->addItem("Apple");
    listWidget->addItem("Banana");
    listWidget->addItem("Cherry");
    listWidget->addItem("Date");

    // 初期選択(任意)
    listWidget->setCurrentRow(0); // 最初のアイテムを選択状態にする

    // QLabel の作成と初期化
    displayLabel = new QLabel("選択されたテキストがここに表示されます", this);
    displayLabel->setAlignment(Qt::AlignCenter); // 中央揃え
    displayLabel->setStyleSheet("font-size: 20px; color: blue;"); // スタイル設定

    // レイアウトの設定
    QVBoxLayout *layout = new QVBoxLayout;
    layout->addWidget(listWidget);
    layout->addWidget(displayLabel);

    QWidget *centralWidget = new QWidget(this);
    centralWidget->setLayout(layout);
    setCentralWidget(centralWidget);

    setWindowTitle("QListWidget currentTextChanged 例");
    resize(400, 300);

    // シグナルとスロットの接続
    // QListWidgetの currentTextChanged シグナルが発火したら、
    // MainWindowの onCurrentTextChanged スロットを呼び出す
    connect(listWidget, &QListWidget::currentTextChanged,
            this, &MainWindow::onCurrentTextChanged);

    // 初期選択によるテキスト表示(もし初期選択を行った場合)
    // listWidget->currentText() は現在の選択アイテムのテキストを返す
    onCurrentTextChanged(listWidget->currentItem() ? listWidget->currentItem()->text() : "");
}

MainWindow::~MainWindow()
{
    // ウィジェットの親をMainWindowに設定しているので、明示的なdeleteは不要
    // レイアウトも所有権を管理してくれる
}

// QListWidget::currentTextChanged シグナルに対応するスロットの実装
void MainWindow::onCurrentTextChanged(const QString &text)
{
    // シグナルから渡されたテキストをQLabelに設定
    displayLabel->setText("選択中: " + text);
    qDebug() << "Current text changed to:" << text; // デバッグ出力
}

main.cpp

#include <QApplication>
#include "mainwindow.h"

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

解説

  1. MainWindow クラス
    QMainWindow を継承し、QListWidgetQLabel のインスタンスを保持します。
  2. コンストラクタ (MainWindow::MainWindow)
    • QListWidget を作成し、いくつかアイテムを追加します。
    • QLabel を作成し、初期テキストを設定します。
    • QVBoxLayout を使用して、QListWidgetQLabel を垂直に配置します。
    • 最も重要な部分が connect 文です。
      connect(listWidget, &QListWidget::currentTextChanged,
              this, &MainWindow::onCurrentTextChanged);
      
      これは、「listWidget から currentTextChanged シグナルが発せられたら、MainWindow オブジェクトの onCurrentTextChanged スロットを呼び出す」という意味です。currentTextChanged シグナルは、変更されたテキストを QString 型の引数として渡すため、スロットも同じ引数型を受け取る必要があります。
    • 初期選択を設定し、手動で onCurrentTextChanged を一度呼び出すことで、アプリケーション起動時に正しいテキストが表示されるようにしています。
  3. onCurrentTextChanged スロット
    • この関数は currentTextChanged シグナルが発火するたびに自動的に呼び出されます。
    • シグナルから渡された text 引数(現在のアイテムのテキスト)を QLabel に設定し、デバッグ出力も行います。

currentTextChanged() は、アイテムのテキストがユーザーによって編集された場合にも発火することがあります。これを利用して、編集後のテキストを処理する例を示します。

mainwindow.h (変更なし) main.cpp (変更なし)

mainwindow.cpp (一部変更)

#include "mainwindow.h"
#include <QDebug> // qDebug のために追加

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent)
{
    listWidget = new QListWidget(this);
    // アイテムを編集可能にする
    QListWidgetItem *item1 = new QListWidgetItem("Apple");
    item1->setFlags(item1->flags() | Qt::ItemIsEditable); // 編集可能フラグを設定
    listWidget->addItem(item1);

    QListWidgetItem *item2 = new QListWidgetItem("Banana");
    item2->setFlags(item2->flags() | Qt::ItemIsEditable);
    listWidget->addItem(item2);

    listWidget->addItem("Cherry"); // これは編集不可

    // 初期選択
    listWidget->setCurrentRow(0);

    displayLabel = new QLabel("選択されたテキストがここに表示されます", this);
    displayLabel->setAlignment(Qt::AlignCenter);
    displayLabel->setStyleSheet("font-size: 20px; color: green;");

    QVBoxLayout *layout = new QVBoxLayout;
    layout->addWidget(listWidget);
    layout->addWidget(displayLabel);

    QWidget *centralWidget = new QWidget(this);
    centralWidget->setLayout(layout);
    setCentralWidget(centralWidget);

    setWindowTitle("QListWidget currentTextChanged (編集可能) 例");
    resize(400, 300);

    // シグナルとスロットの接続
    connect(listWidget, &QListWidget::currentTextChanged,
            this, &MainWindow::onCurrentTextChanged);

    // 初期選択によるテキスト表示
    onCurrentTextChanged(listWidget->currentItem() ? listWidget->currentItem()->text() : "");
}

MainWindow::~MainWindow()
{
    // デストラクタは変更なし
}

void MainWindow::onCurrentTextChanged(const QString &text)
{
    displayLabel->setText("現在の選択テキスト: " + text);
    qDebug() << "Current text changed to:" << text;

    // 編集可能なアイテムのテキストが変更されたかどうかを判断することも可能
    // ただし、currentTextChanged は選択が変更された場合にも発火するため、
    // アイテムの編集完了を検知したい場合は itemChanged() シグナルの方が適していることが多い。
    // 例:
    // QListWidgetItem *currentItem = listWidget->currentItem();
    // if (currentItem && (currentItem->flags() & Qt::ItemIsEditable)) {
    //     qDebug() << "編集可能なアイテムのテキストが変更されました: " << text;
    // }
}
  • 注意点
    currentTextChanged() は、ユーザーが別のアイテムを選択して現在のアイテムが変わった場合と、現在のアイテムのテキストが(編集などによって)変わった場合の両方で発火します。もし「ユーザーがアイテムのテキストを編集し終えた」というイベントを正確に検知したい場合は、QListWidget::itemChanged(QListWidgetItem *item) シグナルを使用する方がより適切です。itemChanged() は、リスト内の任意のアイテムのデータが変更されたときに発火し、変更された QListWidgetItem を引数として渡します。
  • この設定により、アイテムをダブルクリックしたり、単一クリックで選択してEnterキーを押したりすることで、テキストを編集できるようになります。
  • QListWidgetItem::setFlags(item->flags() | Qt::ItemIsEditable) を使用して、リストアイテムをユーザーが直接編集できるようにしました。


以下に、主な代替メソッドとその用途を説明します。

void QListWidget::currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous)

  • いつ使うか
    • 選択されたアイテムのテキストだけでなく、そのアイテムに付随する他の情報(例: ID、画像パス)も処理したい場合。
    • 以前選択されていたアイテムと新しいアイテムを比較して、特定の処理を行いたい場合。
  • currentTextChanged() との違い
    • currentTextChanged() は、新しい「現在のアイテム」のテキストを直接提供します。
    • currentItemChanged() は、新しい「現在のアイテム」のポインタを提供するため、そのアイテムのテキストだけでなく、ユーザーデータや他のプロパティにもアクセスできます。
    • アイテムのテキストが編集された場合でも、同じアイテムが選択されたままであれば、currentItemChanged() は発火しません(ただし、currentTextChanged() は発火する可能性があります)。
  • 用途
    「現在選択されているアイテムそのものが変更された」ときに発火します。currentTextChanged() と似ていますが、テキストの内容ではなく、選択されているアイテムのオブジェクト自体が変更されたことを重視します。新しいアイテムのポインタと、以前選択されていたアイテムのポインタを引数として受け取ります。


// mainwindow.h
class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    // ...
private slots:
    void onCurrentItemChanged(QListWidgetItem *current, QListWidgetItem *previous);
private:
    QListWidget *listWidget;
    QLabel *displayLabel;
};

// mainwindow.cpp
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
{
    // ... QListWidgetとQLabelの初期化 ...

    connect(listWidget, &QListWidget::currentItemChanged,
            this, &MainWindow::onCurrentItemChanged);

    // 初期選択の場合、手動で一度スロットを呼び出す
    onCurrentItemChanged(listWidget->currentItem(), nullptr); // 初回は previous が nullptr
}

void MainWindow::onCurrentItemChanged(QListWidgetItem *current, QListWidgetItem *previous)
{
    if (current) {
        displayLabel->setText("新しい選択アイテム: " + current->text());
        qDebug() << "新しいアイテム: " << current->text();
    } else {
        displayLabel->setText("選択解除されました");
        qDebug() << "選択解除されました";
    }

    if (previous) {
        qDebug() << "以前のアイテム: " << previous->text();
    }
}

void QListWidget::itemSelectionChanged()

  • いつ使うか
    • リストの選択モードが MultiSelectionExtendedSelection で、複数のアイテムの選択/非選択を検知したい場合。
    • 選択されたアイテムの数や、選択されたすべてのアイテムの情報を取得して処理したい場合(このシグナル内で listWidget->selectedItems() を呼び出して現在の選択を取得します)。
  • currentTextChanged() との違い
    • currentTextChanged() は単一の「現在のアイテム」に焦点を当てています。
    • itemSelectionChanged() は、どのアイテムが選択/非選択になったかに関わらず、選択状態全体の変化を通知します。
  • 用途
    「選択されているアイテムの集合が変更された」ときに発火します。QListWidget::setSelectionMode() で複数選択を許可している場合に特に役立ちます。このシグナルには引数がありません。


// mainwindow.h
class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    // ...
private slots:
    void onItemSelectionChanged();
private:
    QListWidget *listWidget;
    QLabel *displayLabel;
};

// mainwindow.cpp
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
{
    // ... QListWidgetとQLabelの初期化 ...

    listWidget->setSelectionMode(QAbstractItemView::MultiSelection); // 複数選択を許可

    connect(listWidget, &QListWidget::itemSelectionChanged,
            this, &MainWindow::onItemSelectionChanged);

    // 初期状態でも処理を実行したい場合(例: 初期選択がある場合)
    onItemSelectionChanged();
}

void MainWindow::onItemSelectionChanged()
{
    QList<QListWidgetItem*> selectedItems = listWidget->selectedItems();
    QString selectedTexts;

    if (selectedItems.isEmpty()) {
        selectedTexts = "何も選択されていません";
    } else {
        selectedTexts = "選択中 (" + QString::number(selectedItems.size()) + "個): ";
        for (QListWidgetItem *item : selectedItems) {
            selectedTexts += item->text() + ", ";
        }
        selectedTexts.chop(2); // 最後の ", " を削除
    }
    displayLabel->setText(selectedTexts);
    qDebug() << selectedTexts;
}
  • いつ使うか
    • ユーザーがリストアイテムのテキストを編集し終えたときに、その変更をデータベースに保存したり、他のUI要素を更新したりしたい場合。
    • プログラムから QListWidgetItem::setText()QListWidgetItem::setCheckState() などのメソッドを呼び出してアイテムのプロパティを変更したときに、その変更を検知したい場合。
  • currentTextChanged() との違い
    • currentTextChanged() は「現在選択されているアイテム」のテキスト変更に特化しています。
    • itemChanged() は、どのアイテムが選択されているかに関わらず、任意のアイテムのデータ変更を検知します。
  • 用途
    QListWidget 内の任意のアイテムのデータが変更されたときに発火します。これには、アイテムのテキストが編集された場合(Qt::ItemIsEditable フラグが設定されている場合)だけでなく、チェック状態やアイコンなど、他のプロパティがプログラム的に変更された場合も含まれます。変更されたアイテムのポインタを引数として受け取ります。
// mainwindow.h
class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    // ...
private slots:
    void onItemDataChanged(QListWidgetItem *item);
private:
    QListWidget *listWidget;
    QLabel *displayLabel;
};

// mainwindow.cpp
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
{
    // ... QListWidgetとQLabelの初期化 ...

    // 編集可能なアイテムを追加
    QListWidgetItem *editableItem = new QListWidgetItem("編集可能なアイテム");
    editableItem->setFlags(editableItem->flags() | Qt::ItemIsEditable);
    listWidget->addItem(editableItem);

    listWidget->addItem("通常のアイテム");

    // 初期選択
    listWidget->setCurrentRow(0);

    connect(listWidget, &QListWidget::itemChanged,
            this, &MainWindow::onItemDataChanged);

    // 初期選択のテキスト表示
    onItemDataChanged(listWidget->currentItem());
}

void MainWindow::onItemDataChanged(QListWidgetItem *item)
{
    if (item) {
        qDebug() << "アイテムデータが変更されました: " << item->text();
        // 変更されたアイテムが現在の選択アイテムであれば、ラベルを更新
        if (listWidget->currentItem() == item) {
            displayLabel->setText("現在のアイテムのデータ変更: " + item->text());
        }
    }
}
  • QListWidget::itemChanged(): リスト内の任意のアイテムのデータ(テキスト、状態など)が変更されたときに反応。
  • QListWidget::itemSelectionChanged(): 選択されているアイテムの集合(複数選択を含む)が変わったときに反応。
  • QListWidget::currentItemChanged(): 現在の選択アイテムのオブジェクト自体が変わったときに反応。
  • QListWidget::currentTextChanged(): 現在の選択アイテムのテキストが変わったときに反応。