Qt QListWidget currentRowChanged() の一般的なエラーと解決策
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のビルドシステムが生成)
この例では、QListWidget
のcurrentRowChanged
シグナルが発信されると、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
シグナルも検討できます。
- ドキュメントの参照
QListWidget
やcurrentRowChanged
に関する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();
}
解説
MainWindow
クラスはQMainWindow
を継承し、Q_OBJECT
マクロを含んでいます。これにより、シグナルとスロットのメカニズムが有効になります。QListWidget
とQLabel
のインスタンスを作成し、レイアウトに配置します。connect
関数を使用して、listWidget
のcurrentRowChanged(int)
シグナルをMainWindow
クラスのon_listWidget_currentRowChanged(int)
スロットに接続しています。- ユーザーがリスト内のアイテムを選択(クリックまたはキーボード操作)すると、
currentRowChanged
シグナルが発信され、新しい行のインデックスがcurrentRow
引数としてon_listWidget_currentRowChanged
スロットに渡されます。 - スロット内で、
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("選択されている色はありません");
}
}
current
がnullptr
になるのは、リストからすべてのアイテムが削除された場合などです。previous
がnullptr
になるのは、アプリケーション起動時や、以前に何も選択されていなかった場合に最初のアイテムが選択されたときなどです。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*) | ダブルクリックされたアイテムポインタ | マウスによるダブルクリック | アイテムを「開く」「編集」などのアクション。 |