Qt QSpinBoxのよくあるエラーとデバッグ術:`value()`を使いこなす
QtプログラミングにおけるQSpinBox::value
は、QSpinBox
ウィジェットが現在表示している整数値を取得するためのメソッドです。
QSpinBox
は、数値の入力に特化したウィジェットで、上下の矢印ボタンをクリックしたり、キーボードの矢印キーを押したり、直接数値を入力したりすることで、値を増減させることができます。
このvalue()
メソッドを使うことで、プログラムはユーザーがQSpinBox
で選択または入力した現在の数値を取得し、その値を使って他の処理を行うことができます。
例えば、以下のような場面でQSpinBox::value()
が使われます。
- スピンボックスの初期値を設定し、その後ユーザーが操作した現在の値を取得する。
- スピンボックスの値が変更されたときに、その新しい値に基づいて計算を行う。
- ユーザーがスピンボックスで設定した年齢や数量などの値を取得する。
valueChanged() シグナルと value() のタイミング
最もよくある混乱の一つは、QSpinBox
の値が変更されたときにトリガーされるvalueChanged(int)
シグナルと、value()
メソッドを使って値を取得するタイミングです。
問題点
- value() の取得タイミング
valueChanged()
シグナルが発火したときにすぐにvalue()
を取得しても、それがユーザーの最終的な入力値ではない可能性があります。特に、ユーザーがまだ入力中である場合。 - valueChanged() の多重発火
ユーザーがキーボードで数値を入力している途中や、プログラムでsetValue()
を複数回呼び出す場合など、valueChanged()
シグナルが意図せず何度も発火することがあります。例えば、「155」と入力すると、「1」「15」「155」とそれぞれ入力されるたびにシグナルが発火し、そのたびにスロットが実行されることがあります。
トラブルシューティング
- ユーザー操作とプログラム操作の区別
valueChanged
のスロット内で、値の変更がユーザー操作によるものか、プログラムによるものかを区別したい場合、例えばQSpinBox::hasFocus()
を使って、スピンボックスにフォーカスがあるかどうかで判断するなどの方法もあります。 - QObject::blockSignals(bool) の利用
プログラム側でsetValue()
を使って値を設定する際に、一時的にvalueChanged()
シグナルの発火を抑制したい場合は、spinBox->blockSignals(true);
でシグナルをブロックし、値を設定した後にspinBox->blockSignals(false);
で解除します。これにより、プログラムによる値変更がvalueChanged()
シグナルをトリガーしなくなります。 - editingFinished() シグナルの利用
editingFinished()
シグナルは、ユーザーがスピンボックスの編集を終えたときに発火します(例:Enterキーを押す、フォーカスを移すなど)。矢印ボタンによる変更には対応しませんが、テキスト入力からの最終値を取得したい場合に有効です。 - setKeyboardTracking(bool) の利用
QSpinBox::setKeyboardTracking(false)
を設定すると、ユーザーがキーボードで入力している間はvalueChanged()
シグナルが発火しなくなります。シグナルは、Enterキーが押されたとき、フォーカスが失われたとき、またはスピンボックスの矢印ボタンが使われたときにのみ発火します。これが、入力途中のシグナル多重発火を防ぐ最も一般的な方法です。
範囲外の値と値の丸め込み
QSpinBox
にはminimum
とmaximum
の範囲が設定されています。
問題点
- 初期値と範囲
QSpinBox
の初期値が設定したminimum
を下回る場合、自動的にminimum
の値に設定されます。これによりvalueChanged
シグナルが発火することがあります。 - 範囲外の値の入力
ユーザーが直接数値を入力した場合、QSpinBox
は自動的にその値を設定された範囲内に丸め込みます。しかし、これを意識せずにvalue()
を取得すると、入力されたままの値と異なる可能性があります。
トラブルシューティング
- value() 取得前の範囲確認
value()
を取得する前に、必要であればminimum()
やmaximum()
メソッドで設定されている範囲を確認し、取得した値がその範囲内にあるかをプログラム側で再確認することもできます。 - setRange(min, max) で適切な範囲を設定
ユーザーが入力すべき値の範囲を明確に設定しておくことで、予期しない値の丸め込みを防ぎ、ユーザーにも分かりやすくします。
paintEvent内でのsetValue()
問題点
QWidget::paintEvent()
の中にQSpinBox::setValue()
を記述すると、無限ループに陥る可能性があります。paintEvent
はウィジェットが再描画されるたびに呼び出され、setValue()
を呼び出すとウィジェットの再描画が必要になり、再びpaintEvent
が呼び出されるという連鎖が起こるためです。
トラブルシューティング
- paintEventは描画のみに専念させる
paintEvent
はGUIの描画のみを行うべきです。値の変更やUIのロジックは別の場所(例えば、シグナルとスロットの接続先や、ボタンのクリックイベントなど)で処理するようにします。
QSpinBox
をサブクラス化して、表示されるテキストと内部的な数値のマッピングをカスタマイズする場合(例えば、16進数を表示するなど)、valueFromText()
やmapValueToText()
をオーバーライドすることがあります。
問題点
- バリデーションの問題
カスタムの入力形式に対してQValidator
を使用する場合、そのバリデーションロジックが正しくないと、ユーザーの入力が受け付けられず、value()
が更新されないことがあります。 - 不適切な変換ロジック
valueFromText()
やmapValueToText()
の実装に誤りがあると、ユーザーが入力したテキストが正しく数値に変換されなかったり、その逆で数値が正しくテキストに変換されなかったりすることがあります。
- デバッガーでの確認
valueFromText()
やvalidate()
メソッド内でデバッガーを使って、入力されたテキストや返される状態が正しいかを確認します。 - 変換ロジックの徹底的なテスト
サブクラス化した場合は、さまざまな入力パターンでvalueFromText()
とmapValueToText()
が期待通りに動作するかをテストします。
例1: 基本的な QSpinBox
の作成と値の取得
最も基本的な使い方です。
#include <QApplication>
#include <QWidget>
#include <QVBoxLayout>
#include <QSpinBox>
#include <QLabel>
#include <QPushButton>
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QWidget window;
window.setWindowTitle("QSpinBox Basic Example");
QVBoxLayout *layout = new QVBoxLayout(&window);
QLabel *label = new QLabel("数量を選択してください:");
layout->addWidget(label);
QSpinBox *spinBox = new QSpinBox();
spinBox->setRange(0, 100); // 0から100までの範囲を設定
spinBox->setValue(10); // 初期値を10に設定
layout->addWidget(spinBox);
QPushButton *button = new QPushButton("現在の値を取得");
layout->addWidget(button);
QLabel *resultLabel = new QLabel("現在の値: -");
layout->addWidget(resultLabel);
// ボタンがクリックされたら、spinBoxの現在の値を取得して表示
QObject::connect(button, &QPushButton::clicked, [&]() {
int currentValue = spinBox->value(); // QSpinBox::value() で現在の値を取得
resultLabel->setText(QString("現在の値: %1").arg(currentValue));
});
window.show();
return app.exec();
}
解説
- ボタンがクリックされたときに、
spinBox->value()
を呼び出して現在の整数値を取得し、QLabel
に表示しています。 QSpinBox
を作成し、setRange()
で最小値と最大値を、setValue()
で初期値を設定しています。
例2: valueChanged()
シグナルを利用したリアルタイム更新
QSpinBox
の値が変更されるたびに、QLabel
を更新する例です。
#include <QApplication>
#include <QWidget>
#include <QVBoxLayout>
#include <QSpinBox>
#include <QLabel>
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QWidget window;
window.setWindowTitle("QSpinBox Value Changed Example");
QVBoxLayout *layout = new QVBoxLayout(&window);
QLabel *promptLabel = new QLabel("数値を変更してください:");
layout->addWidget(promptLabel);
QSpinBox *spinBox = new QSpinBox();
spinBox->setRange(0, 200);
spinBox->setValue(50);
layout->addWidget(spinBox);
QLabel *currentValueLabel = new QLabel(QString("現在の値: %1").arg(spinBox->value())); // 初期値を表示
layout->addWidget(currentValueLabel);
// QSpinBoxの値が変更されるたびに、currentValueLabelを更新
QObject::connect(spinBox, QOverload<int>::of(&QSpinBox::valueChanged),
[&](int newValue) {
currentValueLabel->setText(QString("現在の値: %1").arg(newValue));
});
window.show();
return app.exec();
}
解説
- シグナルが発火すると、そのスロット(ラムダ関数)が呼び出され、
newValue
として変更後の値が渡されます。このnewValue
は、spinBox->value()
と同じ値を返します。 QSpinBox
のvalueChanged(int)
シグナルをconnect()
しています。
例3: setKeyboardTracking(false)
を使用した多重発火の抑制
ユーザーがキーボードで数値を入力している途中にvalueChanged()
が何度も発火するのを防ぐ例です。
#include <QApplication>
#include <QWidget>
#include <QVBoxLayout>
#include <QSpinBox>
#include <QLabel>
#include <QDebug> // デバッグ出力用
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QWidget window;
window.setWindowTitle("QSpinBox Keyboard Tracking Example");
QVBoxLayout *layout = new QVBoxLayout(&window);
QLabel *label1 = new QLabel("Keyboard Tracking: ON (デフォルト)");
layout->addWidget(label1);
QSpinBox *spinBoxOn = new QSpinBox();
spinBoxOn->setRange(0, 1000);
spinBoxOn->setValue(123);
// spinBoxOn->setKeyboardTracking(true); // デフォルトなので省略可能
layout->addWidget(spinBoxOn);
QObject::connect(spinBoxOn, QOverload<int>::of(&QSpinBox::valueChanged),
[&](int newValue) {
qDebug() << "Tracking ON: Value Changed to" << newValue;
});
QLabel *label2 = new QLabel("Keyboard Tracking: OFF");
layout->addWidget(label2);
QSpinBox *spinBoxOff = new QSpinBox();
spinBoxOff->setRange(0, 1000);
spinBoxOff->setValue(456);
spinBoxOff->setKeyboardTracking(false); // キーボードトラッキングを無効化
layout->addWidget(spinBoxOff);
QObject::connect(spinBoxOff, QOverload<int>::of(&QSpinBox::valueChanged),
[&](int newValue) {
qDebug() << "Tracking OFF: Value Changed to" << newValue;
});
window.show();
return app.exec();
}
解説
spinBoxOff
はsetKeyboardTracking(false)
を設定しており、ユーザーがキーボードで入力中にvalueChanged()
は発火せず、Enterキーを押すか、フォーカスを失うか、矢印ボタンを使用した場合にのみ発火することを確認できます。これにより、デバッグ出力が削減されます。spinBoxOn
はデフォルトのsetKeyboardTracking(true)
のままで、キーボード入力中にvalueChanged()
が頻繁に発火することを確認できます。
プログラムからsetValue()
を呼び出す際に、valueChanged()
シグナルが発火するのを一時的に抑制する例です。
#include <QApplication>
#include <QWidget>
#include <QVBoxLayout>
#include <QSpinBox>
#include <QLabel>
#include <QPushButton>
#include <QDebug>
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QWidget window;
window.setWindowTitle("QSpinBox Block Signals Example");
QVBoxLayout *layout = new QVBoxLayout(&window);
QSpinBox *spinBox = new QSpinBox();
spinBox->setRange(0, 100);
spinBox->setValue(10);
layout->addWidget(spinBox);
QLabel *statusLabel = new QLabel("状態:");
layout->addWidget(statusLabel);
QPushButton *setButtonNormal = new QPushButton("通常設定 (シグナル発火)");
layout->addWidget(setButtonNormal);
QPushButton *setButtonBlocked = new QPushButton("ブロックして設定 (シグナル抑制)");
layout->addWidget(setButtonBlocked);
QObject::connect(spinBox, QOverload<int>::of(&QSpinBox::valueChanged),
[&](int newValue) {
qDebug() << "valueChanged() fired! New value:" << newValue;
statusLabel->setText(QString("valueChanged() fired! 新しい値: %1").arg(newValue));
});
QObject::connect(setButtonNormal, &QPushButton::clicked, [&]() {
qDebug() << "Setting value (normal)...";
spinBox->setValue(50); // シグナルが発火する
});
QObject::connect(setButtonBlocked, &QPushButton::clicked, [&]() {
qDebug() << "Setting value (blocked)...";
bool oldState = spinBox->blockSignals(true); // シグナルをブロック
spinBox->setValue(75); // このsetValueではvalueChanged()は発火しない
spinBox->blockSignals(oldState); // シグナルの状態を元に戻す
qDebug() << "Value set to 75 (but no signal). Current value via value():" << spinBox->value();
statusLabel->setText(QString("値は75に設定されました(シグナル抑制)。現在の値: %1").arg(spinBox->value()));
});
window.show();
return app.exec();
}
- 「ブロックして設定」ボタンをクリックすると、
spinBox->blockSignals(true)
で一時的にシグナルをブロックしてからspinBox->setValue(75)
を呼び出します。このときvalueChanged()
シグナルは発火しないため、デバッグ出力もstatusLabel
の更新も行われません(ただし、spinBox->value()
で値は正しく取得できます)。最後にspinBox->blockSignals(oldState)
で元の状態に戻すことが重要です。 - 「通常設定」ボタンをクリックすると、
spinBox->setValue(50)
が呼び出され、valueChanged()
シグナルが発火し、デバッグ出力とstatusLabel
が更新されます。
QDoubleSpinBox::value() (浮動小数点数)
整数ではなく、浮動小数点数を扱いたい場合は、QDoubleSpinBox
を使用します。QDoubleSpinBox
も value()
メソッドを持ちますが、こちらは double
型の値を返します。
特徴
setRange()
で浮動小数点数の範囲を設定できます。setSingleStep()
で増減ステップを浮動小数点数で指定できます。- 小数点以下の桁数を
setDecimals()
で設定できます。
使用例
価格や測定値など、小数点以下の精度が必要な場合。
#include <QApplication>
#include <QWidget>
#include <QVBoxLayout>
#include <QDoubleSpinBox>
#include <QLabel>
#include <QPushButton>
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QWidget window;
window.setWindowTitle("QDoubleSpinBox Example");
QVBoxLayout *layout = new QVBoxLayout(&window);
QLabel *label = new QLabel("測定値を入力してください:");
layout->addWidget(label);
QDoubleSpinBox *doubleSpinBox = new QDoubleSpinBox();
doubleSpinBox->setRange(0.0, 100.0); // 0.0から100.0の範囲
doubleSpinBox->setDecimals(2); // 小数点以下2桁
doubleSpinBox->setSingleStep(0.1); // 0.1ずつ増減
doubleSpinBox->setValue(5.25);
layout->addWidget(doubleSpinBox);
QPushButton *button = new QPushButton("現在の値を取得");
layout->addWidget(button);
QLabel *resultLabel = new QLabel("現在の値: -");
layout->addWidget(resultLabel);
QObject::connect(button, &QPushButton::clicked, [&]() {
double currentValue = doubleSpinBox->value(); // double型の値を取得
resultLabel->setText(QString("現在の値: %1").arg(currentValue));
});
window.show();
return app.exec();
}
QLineEdit と QValidator (より柔軟なテキスト入力とバリデーション)
数値入力の見た目をより自由にしたい、あるいは複雑なバリデーションルールを適用したい場合は、QLineEdit
と QValidator
の組み合わせが強力です。
特徴
QLineEdit::text()
メソッドで文字列として値を取得し、必要に応じて数値に変換します。- QValidator
QLineEdit
に入力されるテキストを検証するための抽象クラス。QIntValidator
: 整数値の範囲を検証。QDoubleValidator
: 浮動小数点数の範囲と精度を検証。QRegExpValidator
: 正規表現に基づいてテキストを検証(最も柔軟)。
- QLineEdit
単一行のテキスト入力フィールド。ユーザーは任意のテキストを入力できます。
利点
- ユーザーが入力中にリアルタイムでフィードバック(有効/無効)を提供したい場合。
- 特定のフォーマット(例: IPアドレス、電話番号、特定のコード)を持つ数値やテキストを扱いたい場合。
QSpinBox
のような矢印ボタンがない、シンプルな数値入力フィールドにしたい場合。
使用例 (QIntValidatorと組み合わせ)
整数のみを受け付け、特定の範囲に制限されたテキスト入力フィールド。
#include <QApplication>
#include <QWidget>
#include <QVBoxLayout>
#include <QLineEdit>
#include <QLabel>
#include <QPushButton>
#include <QIntValidator> // 整数バリデータ
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QWidget window;
window.setWindowTitle("QLineEdit with QIntValidator Example");
QVBoxLayout *layout = new QVBoxLayout(&window);
QLabel *label = new QLabel("年齢を入力してください (0-150):");
layout->addWidget(label);
QLineEdit *lineEdit = new QLineEdit();
// 0から150までの整数のみを許可するバリデータ
QIntValidator *validator = new QIntValidator(0, 150, &window);
lineEdit->setValidator(validator);
lineEdit->setPlaceholderText("年齢を入力..."); // プレースホルダーテキスト
layout->addWidget(lineEdit);
QPushButton *button = new QPushButton("現在の値を取得");
layout->addWidget(button);
QLabel *resultLabel = new QLabel("現在の値: -");
layout->addWidget(resultLabel);
// ボタンがクリックされたら、QLineEditのテキストを取得して数値に変換
QObject::connect(button, &QPushButton::clicked, [&]() {
QString text = lineEdit->text();
bool ok;
int age = text.toInt(&ok); // 文字列をintに変換
if (ok) {
resultLabel->setText(QString("現在の年齢: %1").arg(age));
} else {
resultLabel->setText("無効な入力です");
}
});
window.show();
return app.exec();
}
広範囲の数値から直感的に値を選択させたい場合に適しています。
特徴
value()
メソッドで現在の整数値を取得します。valueChanged(int)
シグナルを発します。setRange()
で範囲、setSingleStep()
やsetPageStep()
でステップサイズを設定します。- ユーザーがスライダーをドラッグすることで値を変更します。
使用例
音量調整、明るさ設定、ズームレベルなど、連続的な範囲から値を選ぶ場合。
#include <QApplication>
#include <QWidget>
#include <QVBoxLayout>
#include <QSlider>
#include <QLabel>
#include <QDebug> // デバッグ出力用
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QWidget window;
window.setWindowTitle("QSlider Example");
QVBoxLayout *layout = new QVBoxLayout(&window);
QLabel *label = new QLabel("音量を調整してください:");
layout->addWidget(label);
QSlider *slider = new QSlider(Qt::Horizontal); // 横方向のスライダー
slider->setRange(0, 100); // 0から100の範囲
slider->setValue(50); // 初期値を50に設定
slider->setTickPosition(QSlider::TicksBelow); // 目盛りを下側に表示
slider->setTickInterval(10); // 10ごとに目盛りを表示
layout->addWidget(slider);
QLabel *volumeLabel = new QLabel(QString("現在の音量: %1").arg(slider->value()));
layout->addWidget(volumeLabel);
// スライダーの値が変更されるたびに、QLabelを更新
QObject::connect(slider, &QSlider::valueChanged,
[&](int newValue) {
qDebug() << "Slider value changed to:" << newValue;
volumeLabel->setText(QString("現在の音量: %1").arg(newValue));
});
window.show();
return app.exec();
}
QSpinBox::value()
は整数入力に特化したシンプルで便利なメソッドですが、要件に応じて以下の代替手段を検討できます。
- 直感的な範囲選択
QSlider::value()
- 自由な入力形式と強力なバリデーション
QLineEdit
とQValidator
(特にQIntValidator
,QDoubleValidator
,QRegExpValidator
) - 浮動小数点数
QDoubleSpinBox::value()