Qt QListWidget currentRowChanged() の一般的なエラーと解決策

2025-05-31

Qtプログラミングにおけるvoid QListWidget::currentRowChanged(int currentRow)は、QListWidgetというリスト表示ウィジェットで、現在選択されている行が変更されたときに発生するシグナルです。

シグナルとは?

Qtでは、オブジェクト間でイベントを通知し合うために「シグナルとスロット」という仕組みを使います。

  • スロット (Slot)
    シグナルが発信されたときに実行される関数です。開発者は、このシグナルを受け取って特定の処理を行うスロット関数を自分で実装し、シグナルとスロットを接続(connect)することで、UIの状態変化に反応するアプリケーションを作成できます。
  • シグナル (Signal)
    ある特定のイベントが発生したことをオブジェクトが発信するものです。この場合、QListWidgetの「現在選択されている行が変更された」というイベントがシグナルとして発信されます。

currentRowChanged シグナルの詳細

  • (int currentRow)
    このシグナルが発信される際に、引数として新しい現在の行のインデックス(0から始まる整数)を渡すことを意味します。この引数を使って、どの行が新しく選択されたかを知ることができます。
  • currentRowChanged
    シグナルの名前です。
  • QListWidget
    QListWidgetクラスのメンバーであることを示します。
  • void
    戻り値がないことを意味します。

currentRowChanged が発信されるタイミング

このシグナルは、以下のような場合に発信されます。

  • プログラムからsetCurrentRow()などのメソッドを呼び出して、現在の行を明示的に変更したとき。
  • キーボードの上下キーなどを使って選択行を移動したとき。
  • ユーザーがマウスでリスト内の別のアイテムをクリックして選択を変更したとき。

例えば、QListWidgetで選択された行が変更されたときに、その行のテキストを別のラベルに表示するような場合を考えてみましょう。

#include <QApplication>
#include <QListWidget>
#include <QLabel>
#include <QVBoxLayout>
#include <QWidget>

class MyWindow : public QWidget
{
    Q_OBJECT // シグナルとスロットを使用するために必要

public:
    MyWindow(QWidget *parent = nullptr) : QWidget(parent)
    {
        listWidget = new QListWidget(this);
        listWidget->addItem("アイテム 1");
        listWidget->addItem("アイテム 2");
        listWidget->addItem("アイテム 3");

        statusLabel = new QLabel("選択されている行はありません", this);

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

        // currentRowChanged シグナルを myRowChangedSlot スロットに接続
        connect(listWidget, &QListWidget::currentRowChanged,
                this, &MyWindow::myRowChangedSlot);
    }

private slots:
    // currentRowChanged シグナルに対応するスロット
    void myRowChangedSlot(int currentRow)
    {
        if (currentRow != -1) { // -1 は何も選択されていない状態
            QListWidgetItem *currentItem = listWidget->item(currentRow);
            if (currentItem) {
                statusLabel->setText("選択された行: " + QString::number(currentRow) + " - " + currentItem->text());
            }
        } else {
            statusLabel->setText("選択されている行はありません");
        }
    }

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

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

#include "main.moc" // mocファイルをインクルード(Qtのビルドシステムが生成)

この例では、QListWidgetcurrentRowChangedシグナルが発信されると、MyWindowクラスのmyRowChangedSlotスロットが呼び出されます。myRowChangedSlotでは、引数として渡されたcurrentRowを使って、選択されたアイテムの情報を取得し、QLabelに表示しています。



シグナルが発信されない/期待通りに発信されない

考えられる原因とトラブルシューティング

  • プログラムによる変更

    • setCurrentRow()setCurrentItem()などのメソッドを使ってプログラム的に現在の行を変更した場合でも、currentRowChangedシグナルは発信されます。もし、これらの操作を行ったのにシグナルが発信されないと感じる場合は、他の問題(上記のconnectの誤りなど)が考えられます。
  • 選択モードがNoSelectionになっている

    • QListWidgetの選択モードがQAbstractItemView::NoSelectionに設定されている場合、アイテムは選択できず、したがってcurrentRowChangedシグナルも発信されません。
    • 確認点
      listWidget->setSelectionMode(QAbstractItemView::SingleSelection);などの適切な選択モードが設定されているか。
  • Q_OBJECTマクロの欠落

    • カスタムクラスでシグナルやスロットを定義している場合、クラス定義の先頭にQ_OBJECTマクロを記述し、moc(Meta-Object Compiler)による処理を確実に行う必要があります。これがないと、シグナル/スロットの仕組みが機能しません。
    • 確認点
      カスタムクラスのヘッダファイルにQ_OBJECTがあるか。mocファイルがプロジェクトのビルドに含まれているか。
    • シグナルとスロットの接続(connect)が正しく行われていない可能性があります。SIGNAL()/SLOT()マクロを使用している場合、引数の型やシグナル/スロットの名前が完全に一致している必要があります。C++11の関数ポインタ構文(&QListWidget::currentRowChanged)を使用している場合は、コンパイル時に型チェックが行われるため、この種のエラーは減ります。
    • 確認点
      connect文にスペルミスがないか、引数の型が一致しているか(特にint引数)。
    // 悪い例 (スペルミスや引数不一致)
    // connect(listWidget, SIGNAL(currentChanged(int)), this, SLOT(mySlot(int))); // currentRowChangedではない
    // connect(listWidget, SIGNAL(currentRowChanged()), this, SLOT(mySlot(int))); // 引数が足りない
    
    // 良い例 (C++11 スタイル)
    connect(listWidget, &QListWidget::currentRowChanged, this, &MyClass::mySlot);
    
    // 良い例 (旧来のSIGNAL/SLOTマクロ)
    connect(listWidget, SIGNAL(currentRowChanged(int)), this, SLOT(mySlot(int)));
    

シグナルが複数回発信される/予期せぬタイミングで発信される

考えられる原因とトラブルシューティング

  • 複数のconnect

    • 誤って同じシグナルとスロットのペアを複数回connectしてしまうと、スロットが複数回呼び出されることがあります。
    • 確認点
      connect文がプログラム内で一度だけ実行されるように記述されているか。
  • アイテムの追加/削除

    • QListWidgetにアイテムを追加したり、既存のアイテムを削除したりすると、現在の行が自動的に変更される場合があります(特に、選択されていたアイテムが削除された場合など)。このときもcurrentRowChangedシグナルが発信されます。
    • トラブルシューティング
      アイテムの追加/削除時にcurrentRowChangedの処理を実行したくない場合は、これらの操作を行う前にblockSignals(true)を使用し、操作後にblockSignals(false)で元に戻すことを検討してください。
  • スロット内でのcurrentRowChangedの再トリガー

    • currentRowChangedシグナルを受け取るスロット内で、再度setCurrentRow()setCurrentItem()などを呼び出すと、無限ループになったり、予期せぬ再発信が発生したりすることがあります。
    • トラブルシューティング
      スロット内でQListWidgetの状態を変更する必要がある場合は、シグナルの再発信を防ぐためにdisconnectしてから処理を行い、処理後にconnectし直すか、blockSignals(true)/blockSignals(false)を使用することを検討してください。
    void MyClass::myRowChangedSlot(int currentRow)
    {
        // シグナルの再発信を防ぐ
        listWidget->blockSignals(true);
    
        // ここでQListWidgetの状態を変更する処理
        // 例: listWidget->setCurrentRow(someOtherRow);
    
        listWidget->blockSignals(false);
    }
    

スロット内の処理が重い/UIがフリーズする

考えられる原因とトラブルシューティング

  • スロット内で時間のかかる処理を実行している
    • currentRowChangedシグナルに対応するスロット内で、ファイルI/O、ネットワーク通信、複雑な計算など、時間のかかる処理を行うと、UIスレッドがブロックされ、アプリケーションがフリーズしたように見えたり、応答性が悪くなったりします。
    • トラブルシューティング
      • 非同期処理
        時間のかかる処理は別のスレッド(QThreadなど)で行い、結果をシグナルでUIスレッドに通知するようにします。
      • タイマーによる遅延実行
        QTimer::singleShot()などを使用して、処理をわずかに遅延させて実行することで、UIの応答性を維持できる場合があります。
      • 処理の最適化
        スロット内で実行される処理自体を高速化できないか検討します。
      • プログレスバー/スピナーの表示
        処理中にユーザーに待機していることを示すUI要素を表示することで、ユーザー体験を向上させます。

currentRow引数が期待通りでない

考えられる原因とトラブルシューティング

  • 初期選択時の挙動

    • QListWidgetを初期化した際に、アイテムが自動的に選択されることがあります(例:最初のアイテムが選択される)。この際にもcurrentRowChangedシグナルが発信される場合があります。
    • トラブルシューティング
      アプリケーション起動時の初期化処理で、このシグナルが発信されても問題ないようにスロットを設計するか、初期設定中はシグナルを一時的にブロックするなどの対応を検討します。
  • アイテムの削除によるインデックスのずれ

    • QListWidgetからアイテムを削除する際に、そのアイテムよりも後のインデックスを持つアイテムのインデックスが変更されます。currentRowChangedシグナルが削除の直後に発信された場合、受け取ったcurrentRowが、元の期待していたインデックスとは異なる可能性があります。
    • トラブルシューティング
      アイテムの削除を行う前に、現在の選択状態を保存しておくか、削除後にcurrentRow()を再度取得して、新しいインデックスを確認するようにします。より堅牢な設計のためには、行インデックスではなく、QListWidgetItem*を直接扱うQListWidget::currentItemChangedシグナルも検討できます。
  • ドキュメントの参照
    QListWidgetcurrentRowChangedに関するQtの公式ドキュメントを再確認し、想定される挙動を把握します。
  • Qt Creatorのデバッガ
    Qt Creatorに内蔵されているデバッガを使用して、コードの実行フローをステップ実行し、変数の中身やシグナル/スロットの接続状況を確認します。
  • qDebug()の活用
    スロットの先頭や重要な処理の前後にqDebug()でメッセージを出力し、いつ、どのような引数でスロットが呼び出されているかを確認します。


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

これは最も基本的な使用例で、選択されたリストアイテムのテキストを別のQLabelウィジェットに表示します。

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QListWidget>
#include <QLabel>
#include <QVBoxLayout>
#include <QWidget>

class MainWindow : public QMainWindow
{
    Q_OBJECT // シグナルとスロットを使用するために必要

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

private slots:
    // QListWidget::currentRowChanged シグナルに対応するスロット
    void on_listWidget_currentRowChanged(int currentRow);

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

#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    // メインウィジェットとレイアウトの作成
    QWidget *centralWidget = new QWidget(this);
    QVBoxLayout *layout = new QVBoxLayout(centralWidget);
    setCentralWidget(centralWidget);

    // QListWidgetの作成とアイテムの追加
    listWidget = new QListWidget(this);
    listWidget->addItem("Apple");
    listWidget->addItem("Banana");
    listWidget->addItem("Cherry");
    listWidget->addItem("Date");
    listWidget->addItem("Elderberry");

    // 選択されたアイテムのテキストを表示するラベル
    statusLabel = new QLabel("アイテムを選択してください", this);
    statusLabel->setStyleSheet("font-weight: bold; color: blue;");

    // レイアウトにウィジェットを追加
    layout->addWidget(listWidget);
    layout->addWidget(statusLabel);

    // シグナルとスロットの接続
    // QListWidgetのcurrentRowChangedシグナルをカスタムスロットに接続
    connect(listWidget, &QListWidget::currentRowChanged,
            this, &MainWindow::on_listWidget_currentRowChanged);

    // アプリケーション起動時に最初のアイテムを選択状態にする(任意)
    if (listWidget->count() > 0) {
        listWidget->setCurrentRow(0); // これによりcurrentRowChangedが一度発信される
    }
}

MainWindow::~MainWindow()
{
    // 必要であればここでメモリを解放
}

// currentRowChangedシグナルに対応するスロットの実装
void MainWindow::on_listWidget_currentRowChanged(int currentRow)
{
    if (currentRow != -1) { // -1 は何も選択されていない状態
        // 新しく選択されたアイテムを取得
        QListWidgetItem *selectedItem = listWidget->item(currentRow);
        if (selectedItem) {
            statusLabel->setText("選択されたフルーツ: " + selectedItem->text());
        }
    } else {
        statusLabel->setText("アイテムが選択されていません");
    }
}

main.cpp

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

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.setWindowTitle("QListWidget currentRowChanged Example");
    w.resize(300, 250);
    w.show();
    return a.exec();
}

解説

  1. MainWindowクラスはQMainWindowを継承し、Q_OBJECTマクロを含んでいます。これにより、シグナルとスロットのメカニズムが有効になります。
  2. QListWidgetQLabelのインスタンスを作成し、レイアウトに配置します。
  3. connect関数を使用して、listWidgetcurrentRowChanged(int)シグナルをMainWindowクラスのon_listWidget_currentRowChanged(int)スロットに接続しています。
  4. ユーザーがリスト内のアイテムを選択(クリックまたはキーボード操作)すると、currentRowChangedシグナルが発信され、新しい行のインデックスがcurrentRow引数としてon_listWidget_currentRowChangedスロットに渡されます。
  5. スロット内で、listWidget->item(currentRow)を使って選択されたQListWidgetItemを取得し、そのテキストをstatusLabelに表示しています。currentRow-1の場合(何も選択されていない状態)も考慮しています。

例2: 選択された行に応じて異なるアクションを実行する

特定の行が選択されたときに、それに対応する別の処理を実行する例です。

mainwindow.h (変更なし)

// ... (例1と同じ) ...

mainwindow.cpp (スロットの変更)

#include "mainwindow.h"
#include <QMessageBox> // QMessageBoxを使用するために追加

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    // ... (例1のコンストラクタと同じ内容) ...
    // 初期選択
    if (listWidget->count() > 0) {
        listWidget->setCurrentRow(0);
    }
}

MainWindow::~MainWindow()
{
    // 必要であればここでメモリを解放
}

// currentRowChangedシグナルに対応するスロットの実装
void MainWindow::on_listWidget_currentRowChanged(int currentRow)
{
    if (currentRow != -1) {
        QListWidgetItem *selectedItem = listWidget->item(currentRow);
        if (selectedItem) {
            statusLabel->setText("選択されたアイテム: " + selectedItem->text());

            // 選択された行に基づいて異なるアクションを実行
            switch (currentRow) {
                case 0: // Apple
                    QMessageBox::information(this, "情報", "Appleが選択されました!");
                    break;
                case 1: // Banana
                    QMessageBox::information(this, "情報", "Bananaを選択しましたね!");
                    break;
                case 2: // Cherry
                    // 特定の処理はここでは行わない
                    break;
                default:
                    // その他のアイテムの場合
                    break;
            }
        }
    } else {
        statusLabel->setText("アイテムが選択されていません");
    }
}

解説

  • これにより、ユーザーが特定のリストアイテムを選択したときに、それに対応する特定のロジックを実行できます。
  • この例では、on_listWidget_currentRowChangedスロット内でswitch文を使用し、currentRowの値に応じて異なるQMessageBoxを表示しています。

currentRowChangedの代わりに、QListWidgetItem*を直接引数として受け取るQListWidget::currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous)シグナルを使用することもよくあります。こちらの方が、インデックスではなくアイテムそのものを扱えるため、コードが直感的になる場合があります。

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QListWidget>
#include <QLabel>
#include <QVBoxLayout>
#include <QWidget>

class MainWindow : public QMainWindow
{
    Q_OBJECT

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

private slots:
    // QListWidget::currentItemChanged シグナルに対応するスロット
    void on_listWidget_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous);

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

#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    QWidget *centralWidget = new QWidget(this);
    QVBoxLayout *layout = new QVBoxLayout(centralWidget);
    setCentralWidget(centralWidget);

    listWidget = new QListWidget(this);
    listWidget->addItem("Red");
    listWidget->addItem("Green");
    listWidget->addItem("Blue");
    listWidget->addItem("Yellow");

    statusLabel = new QLabel("色を選択してください", this);
    statusLabel->setStyleSheet("font-weight: bold; color: teal;");

    layout->addWidget(listWidget);
    layout->addWidget(statusLabel);

    // シグナルとスロットの接続 (currentItemChanged を使用)
    connect(listWidget, &QListWidget::currentItemChanged,
            this, &MainWindow::on_listWidget_currentItemChanged);

    // 初期選択
    if (listWidget->count() > 0) {
        listWidget->setCurrentRow(0);
    }
}

MainWindow::~MainWindow()
{
}

// currentItemChanged シグナルに対応するスロットの実装
void MainWindow::on_listWidget_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous)
{
    if (current) { // 新しいアイテムがある場合
        QString previousText = "なし";
        if (previous) { // 前のアイテムがある場合
            previousText = previous->text();
        }
        statusLabel->setText("前の色: " + previousText + ", 新しい色: " + current->text());
    } else { // 何も選択されていない場合(リストが空になった場合など)
        statusLabel->setText("選択されている色はありません");
    }
}
  • currentnullptrになるのは、リストからすべてのアイテムが削除された場合などです。
  • previousnullptrになるのは、アプリケーション起動時や、以前に何も選択されていなかった場合に最初のアイテムが選択されたときなどです。
  • currentItemChangedシグナルは、新しく選択されたアイテム(current)と以前に選択されていたアイテム(previous)のポインタを直接渡します。これにより、インデックスを気にせずにアイテムのプロパティ(テキストなど)にアクセスできます。


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

これは currentRowChanged() の最も直接的な代替であり、多くの場合より推奨される方法です。

  • 欠点
    • 行番号が必要な場合は、listWidget->row(current)などで別途取得する必要があります。
  • 利点
    • アイテムそのものを直接扱える
      行番号ではなく、QListWidgetItemオブジェクト自体を受け取るため、listWidget->item(row)のように後からアイテムを取得する手間が省け、コードがより直感的になります。
    • 前後のアイテム情報
      以前選択されていたアイテムの情報も同時に取得できるため、選択が変更された際に両方のアイテムに対して処理を行う必要がある場合に便利です。
  • 引数
    current (新しく選択されたアイテムのポインタ)、previous (以前に選択されていたアイテムのポインタ)。どちらもnullptrになる可能性があります。
// 接続例
connect(listWidget, &QListWidget::currentItemChanged,
        this, &MyClass::onCurrentItemChanged);

// スロットの実装例
void MyClass::onCurrentItemChanged(QListWidgetItem *current, QListWidgetItem *previous)
{
    if (current) {
        qDebug() << "新しい選択: " << current->text();
    } else {
        qDebug() << "選択解除されました。";
    }

    if (previous) {
        qDebug() << "以前の選択: " << previous->text();
    }
}

void QListWidget::itemSelectionChanged()

これは、QListWidget内の選択状態が変更されたときに発信されるシグナルです。複数のアイテム選択が許可されている場合に特に有用です。

  • 使い分け
    • 複数選択モード
      ユーザーが複数のアイテムを選択したり、選択解除したりするたびに、選択されたアイテムのリスト全体を処理する必要がある場合に最適です。
    • 例: 選択された複数のファイルのパスを結合して表示する、複数の項目に対して一括操作を行う。
  • 欠点
    • どのアイテムが新しく選択されたのか、どのアイテムが選択解除されたのか、といった具体的な変更内容は引数から直接は分かりません。スロット内でlistWidget->selectedItems()を呼び出して、現在の選択されたアイテムのリストを自分で取得する必要があります。
  • 利点
    • 複数選択の変更を検知
      QListWidget::setSelectionMode(QAbstractItemView::MultiSelection)などが設定されている場合、複数のアイテムが選択/解除されたときに発信されます。currentRowChanged()currentItemChanged()は単一のアイテムのフォーカス変更に焦点を当てているのに対し、こちらは選択セット全体の変更を検知します。
    • 効率
      選択されたアイテムのリスト全体を再評価する必要がある場合に適しています。
  • 引数
    なし。
// 接続例
connect(listWidget, &QListWidget::itemSelectionChanged,
        this, &MyClass::onItemSelectionChanged);

// スロットの実装例
void MyClass::onItemSelectionChanged()
{
    QList<QListWidgetItem*> selectedItems = listWidget->selectedItems();
    qDebug() << "現在選択されているアイテムの数: " << selectedItems.count();

    for (QListWidgetItem *item : selectedItems) {
        qDebug() << "選択中: " << item->text();
    }
}

これらのシグナルは、ユーザーがリストアイテムを単一クリックまたはダブルクリックしたときに発信されます。

  • 使い分け
    • 特定のアクションのトリガー
      アイテムがクリックされたときに、そのアイテムに直接関連する特定のアクションを実行したい場合。
    • 例: ファイルリストでファイルをダブルクリックして開く、チェックボックス付きリストでチェックボックスのオン/オフをトグルする(itemChangedも考慮)。
  • 欠点
    • キーボード操作による選択変更や、プログラムによる選択変更では発信されません。あくまでマウス操作によるクリックイベントに限定されます。
    • 複数選択モードでアイテムを選択した場合、itemClickedは各クリックで発信されますが、選択セット全体の変更を追跡するのには向きません。
  • 利点
    • ユーザーの意図を正確に捕捉
      選択変更だけでなく、「クリック」というユーザーの特定の操作に反応したい場合に有用です。
    • ダブルクリックアクション
      アイテムのダブルクリックを特定の「開く」または「編集」アクションにマッピングしたい場合に特に便利です。
  • 引数
    クリックされたアイテムのポインタ。
// 接続例
connect(listWidget, &QListWidget::itemClicked,
        this, &MyClass::onItemClicked);
connect(listWidget, &QListWidget::itemDoubleClicked,
        this, &MyClass::onItemDoubleClicked);

// スロットの実装例
void MyClass::onItemClicked(QListWidgetItem *item)
{
    qDebug() << "クリックされました: " << item->text();
}

void MyClass::onItemDoubleClicked(QListWidgetItem *item)
{
    qDebug() << "ダブルクリックされました: " << item->text() << " - 詳細を開く処理を実行";
    // 例: 新しいウィンドウを開く、ファイルを起動する
}
シグナル引数トリガーとなる主な操作主な用途
currentRowChanged(int)新しい行のインデックス選択された行の変更 (マウス、KB、API)以前の Qt コードや、厳密に「行番号」が必要な場合。通常はcurrentItemChangedで代替可能。
currentItemChanged(QListWidgetItem*, QListWidgetItem*)新旧のアイテムポインタ選択されたアイテムの変更 (マウス、KB、API)最も推奨される、選択されたアイテムのデータに基づく処理。
itemSelectionChanged()なし選択セット全体の変更 (追加/削除)複数選択モードで、選択されたアイテムのリスト全体を処理する場合。
itemClicked(QListWidgetItem*)クリックされたアイテムポインタマウスによる単一クリッククリックされたアイテムに直接関連する特定のアクション。
itemDoubleClicked(QListWidgetItem*)ダブルクリックされたアイテムポインタマウスによるダブルクリックアイテムを「開く」「編集」などのアクション。