Qt QSpinBoxの落とし穴回避!valueChanged()の一般的なエラーと解決策

2025-05-31

このQSpinBoxが持つ重要な機能の一つに、シグナルというものがあります。シグナルは、特定のイベントが発生したときに発信されるメッセージのようなものです。

void QSpinBox::valueChanged()は、まさにこのQSpinBox値が変更されたときに発信されるシグナルです。

具体的には、以下の2つのオーバーロード(引数の異なる同じ名前の関数)があります。

  1. void valueChanged(int value):

    • これは、QSpinBoxの値が整数として変更されたときに発信されるシグナルです。
    • シグナルが発信されると、新しい値がint型の引数valueとして渡されます。
    • 例えば、スピンボックスが現在の値から10に変わった場合、このシグナルが発信され、接続されたスロット(シグナルを受け取る関数)に10という値が渡されます。
  2. void valueChanged(const QString &text):

    • これは、QSpinBoxの値がテキストとして変更されたときに発信されるシグナルです。
    • シグナルが発信されると、新しい値がQString型の引数textとして渡されます。
    • このテキストには、スピンボックスに設定されたプレフィックス(接頭辞)やサフィックス(接尾辞)が含まれる場合があります。例えば、通貨記号や単位などが設定されている場合、"$10.00"のような形で渡されます。

valueChanged()シグナルの利用方法

このシグナルを利用することで、QSpinBoxの値が変更されたときに特定の処理を実行できます。これはQtのシグナル&スロットメカニズムを使って行われます。

例えば、QSpinBoxの値が変更されたら、その値を別のラベルに表示したい、といった場合にこのシグナルを接続します。

簡単なコード例

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

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

    QWidget *window = new QWidget;
    window->setWindowTitle("QSpinBox valueChanged Example");

    QSpinBox *spinBox = new QSpinBox;
    spinBox->setRange(0, 100); // 0から100までの範囲を設定
    spinBox->setValue(50);    // 初期値を50に設定

    QLabel *valueLabel = new QLabel("現在の値: 50"); // 値を表示するラベル

    // QSpinBoxのvalueChanged(int)シグナルをQLabelのsetTextスロットに接続
    // ラムダ式を使用して、int値をQStringに変換してラベルに設定
    QObject::connect(spinBox, QOverload<int>::of(&QSpinBox::valueChanged),
                     valueLabel, [=](int value){
        valueLabel->setText(QString("現在の値: %1").arg(value));
    });

    // レイアウト
    QVBoxLayout *layout = new QVBoxLayout;
    layout->addWidget(spinBox);
    layout->addWidget(valueLabel);
    window->setLayout(layout);

    window->show();

    return app.exec();
}

この例では、spinBoxvalueChanged(int)シグナルが発信されるたびに、ラムダ式が実行され、その中でvalueLabelのテキストが更新されます。

  • プログラムによる変更
    setValue()関数を使ってプログラムからQSpinBoxの値を変更した場合でも、valueChanged()シグナルは発信されます。もし、プログラムによる変更時にはシグナルを発信したくない場合は、blockSignals(true)を使って一時的にシグナルの発信をブロックし、変更後にblockSignals(false)で元に戻すことができます。
  • キーボードトラッキング
    QSpinBox::setKeyboardTracking(bool enable)関数を使用することで、ユーザーが入力中にvalueChanged()シグナルをすぐに発信する(true、デフォルト動作)か、Enterキーが押されたり、フォーカスが失われたりするまで発信を遅らせる(false)かを制御できます。


シグナルが意図せず複数回発信される

これは最もよくある問題の一つです。特に、プログラム側でQSpinBoxの値をsetValue()などで変更した場合に発生しやすいです。

問題点

  • また、ユーザーがキーボードで値を入力している最中(例: "1"の後に"2"を入力して"12"にする)にも、一文字ずつvalueChanged()が発信されるため、意図しない中間値で処理が実行されることがあります。
  • 例えば、スピンボックスAの値を変更した結果、別のスピンボックスBの値を更新する処理があり、その更新がまたvalueChanged()シグナルを発信すると、無限ループに陥る可能性があります。
  • ユーザーがスピンボックスを操作していなくても、プログラムからsetValue()を呼び出すとvalueChanged()シグナルが発信されます。

トラブルシューティング

  • 状態フラグを使用する
    複雑なシナリオでは、カスタムのブール型フラグ(例: m_isUpdatingValue)を導入し、プログラムによる値変更中はフラグをtrueに設定し、valueChanged()スロット内でこのフラグをチェックして処理をスキップするという方法もあります。

    void MyClass::onSpinBoxValueChanged(int value)
    {
        if (m_isUpdatingValue) {
            return; // プログラムによる変更なのでスキップ
        }
        // ユーザーによる変更の場合の処理
        qDebug() << "ユーザーが値を変更しました: " << value;
    }
    
    void MyClass::updateSpinBoxProgrammatically(int newValue)
    {
        m_isUpdatingValue = true;
        ui->spinBox->setValue(newValue);
        m_isUpdatingValue = false;
    }
    
  • editingFinished()シグナルを検討する
    ユーザーが値の入力を完了した(Enterキーを押すか、フォーカスが外れる)タイミングで処理を実行したい場合は、QSpinBoxの親クラスであるQAbstractSpinBoxが提供するeditingFinished()シグナルを使用することを検討してください。ただし、このシグナルは上下の矢印ボタンのクリックでは発信されない点に注意が必要です。

    QObject::connect(spinBox, &QSpinBox::editingFinished, this, &MyClass::onEditingFinished);
    
  • QSpinBox::setKeyboardTracking(false)を使用する
    ユーザーがキーボードで入力している最中にシグナルが頻繁に発信されるのを防ぎたい場合に有効です。setKeyboardTracking(false)を設定すると、valueChanged()シグナルは以下のタイミングでのみ発信されます。

    • Enterキーが押されたとき
    • スピンボックスがフォーカスを失ったとき
    • 上下の矢印ボタンがクリックされたとき
    spinBox->setKeyboardTracking(false);
    
  • QObject::blockSignals()を使用する
    プログラムからsetValue()を呼び出す際など、一時的にシグナルの発信を止めたい場合に非常に有効です。

    spinBox->blockSignals(true); // シグナルを一時的にブロック
    spinBox->setValue(newValue); // この間はvalueChanged()は発信されない
    spinBox->blockSignals(false); // シグナルを再開
    

シグナルがまったく発信されない、または正しく接続されない

問題点

  • シグナルとスロットの引数型が一致しない。
  • スピンボックスがUIファイルで定義されているが、setupUi()の呼び出しが遅れている、または行われていない。
  • 接続先のオブジェクトが存在しない(nullポインタ)。
  • QObject::connect()の引数が間違っている。特に、オーバーロードされたシグナル(valueChanged(int)valueChanged(const QString &text))の場合、どちらのシグナルに接続したいかを明示しないとコンパイルエラーになるか、意図しないシグナルに接続される可能性があります。

トラブルシューティング

  • スロットのシグネチャ(引数リスト)を確認する
    シグナルとスロットの引数型は互換性がある必要があります。スロットはシグナルよりも少ない引数を受け取ることができますが、多い引数や異なる型の引数を受け取ることはできません。

  • connectの戻り値をチェックする
    QObject::connect()は、接続が成功した場合はtrue、失敗した場合はfalseを返します。デバッグ時にはこの戻り値をqDebug()で出力すると、接続失敗の原因を探る手がかりになります。

    bool connected = QObject::connect(spinBox, QOverload<int>::of(&QSpinBox::valueChanged),
                                      this, &MyClass::myIntSlot);
    qDebug() << "SpinBox connection status: " << connected;
    

    出力にfalseと表示された場合、接続に問題があることを示しています。

  • オブジェクトの初期化を確認する
    QSpinBoxや接続先のスロットを持つオブジェクト(例: this)が正しく初期化されているか確認してください。特にui->someWidgetのようなUIデザイナーで生成されたオブジェクトは、ui->setupUi(this);が呼び出された後に初めて有効になります。

  • 新しいconnect構文を使用する(Qt5以降推奨)
    Qt5以降では、関数ポインタを使用した新しいconnect構文が推奨されています。これはコンパイル時に型チェックが行われるため、エラーを早期に発見できます。

    // int値を受け取るvalueChangedに接続する場合
    QObject::connect(spinBox, QOverload<int>::of(&QSpinBox::valueChanged),
                     this, &MyClass::myIntSlot);
    
    // QString値を受け取るvalueChangedに接続する場合
    QObject::connect(spinBox, QOverload<const QString &>::of(&QSpinBox::valueChanged),
                     this, &MyClass::myQStringSlot);
    

    古いSIGNAL()/SLOT()マクロは実行時にのみエラーが検出されるため、デバッグが難しくなります。

シグナルが発信されたときにアプリケーションがフリーズする/応答しなくなる

問題点

  • valueChanged()シグナルに接続されたスロット内で、非常に時間のかかる処理(例: 重い計算、ファイルI/O、ネットワーク通信)を実行している場合、GUIがフリーズして応答しなくなることがあります。Qtのイベントループは、スロットの処理が完了するまでブロックされるためです。

トラブルシューティング

  • シグナルの発信頻度を減らす
    前述のsetKeyboardTracking(false)editingFinished()の使用により、valueChanged()シグナルの発信頻度を減らすことで、時間のかかる処理の呼び出し回数を減らすことができます。

  • 処理を細分化し、QCoreApplication::processEvents()を呼び出す
    非常に緊急性の低いケースで、処理を完全に別スレッドに分離するのが難しい場合、時間のかかるループ内で定期的にQCoreApplication::processEvents()を呼び出すことで、一時的にイベントループに制御を戻し、GUIの応答性を維持することができます。しかし、これは一般的には推奨される解決策ではなく、アーキテクチャの悪い兆候である可能性があります。

  • 時間のかかる処理を別スレッドに移動する
    QtではQThreadQtConcurrentなどの機能を使用して、時間のかかる処理をバックグラウンドスレッドで実行できます。これにより、GUIスレッドがブロックされず、アプリケーションの応答性が保たれます。処理が完了したら、結果をGUIスレッドに戻すためにシグナル&スロットを使用します。



基本的な valueChanged(int) の使用例:ラベルの更新

最も基本的な例として、QSpinBox の値が変更されたときに、その値を QLabel に表示する例です。

#include <QApplication> // アプリケーションの基本クラス
#include <QWidget>      // ウィジェットの基本クラス
#include <QSpinBox>     // スピンボックスウィジェット
#include <QLabel>       // テキスト表示用ウィジェット
#include <QVBoxLayout>  // 垂直レイアウト

int main(int argc, char *argv[])
{
    QApplication app(argc, argv); // QApplicationオブジェクトを作成

    QWidget *window = new QWidget; // メインウィンドウとなるQWidgetを作成
    window->setWindowTitle("QSpinBox::valueChanged(int) Example"); // ウィンドウタイトルを設定

    QSpinBox *spinBox = new QSpinBox; // QSpinBoxオブジェクトを作成
    spinBox->setRange(0, 100);       // スピンボックスの値を0から100の範囲に設定
    spinBox->setValue(50);          // 初期値を50に設定

    QLabel *valueLabel = new QLabel("現在の値: 50"); // 値を表示するQLabelを作成し、初期テキストを設定

    // QSpinBoxのvalueChanged(int)シグナルとQLabelのsetTextスロットを接続
    // シグナルが発信されると、新しいint値が渡され、QLabelのテキストが更新される
    // Qt5以降の新しい接続構文(関数ポインタ)を使用
    QObject::connect(spinBox, QOverload<int>::of(&QSpinBox::valueChanged),
                     valueLabel, [=](int value){
        // ラムダ式を使用して、受け取ったint値をQStringに変換してQLabelに設定
        valueLabel->setText(QString("現在の値: %1").arg(value));
    });

    QVBoxLayout *layout = new QVBoxLayout; // 垂直レイアウトを作成
    layout->addWidget(spinBox);           // レイアウトにQSpinBoxを追加
    layout->addWidget(valueLabel);        // レイアウトにQLabelを追加

    window->setLayout(layout); // ウィンドウにレイアウトを設定
    window->show();            // ウィンドウを表示

    return app.exec(); // アプリケーションのイベントループを開始
}

解説

  • ラムダ式 [=](int value){ ... } は、シグナルが発信されたときに実行される匿名関数です。value にはスピンボックスの新しい値が渡されます。
  • QOverload<int>::of(&QSpinBox::valueChanged) を使うことで、valueChanged シグナルの int 引数バージョンに明示的に接続しています。

valueChanged(const QString &text) の使用例:プレフィックス/サフィックスを含む表示

QSpinBox には、表示される数値にプレフィックス(接頭辞)やサフィックス(接尾辞)を設定する機能があります。この場合、valueChanged(const QString &text) シグナルを使うと、それらを含む完全な文字列として値を受け取ることができます。

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

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

    QWidget *window = new QWidget;
    window->setWindowTitle("QSpinBox::valueChanged(QString) Example");

    QSpinBox *spinBox = new QSpinBox;
    spinBox->setRange(0, 1000);
    spinBox->setValue(250);
    spinBox->setPrefix("$");   // プレフィックスに「$」を設定
    spinBox->setSuffix(".00"); // サフィックスに「.00」を設定
    spinBox->setSingleStep(10); // ステップを10に設定

    QLabel *valueLabel = new QLabel("表示値: $250.00");

    // QSpinBoxのvalueChanged(const QString &)シグナルに接続
    QObject::connect(spinBox, QOverload<const QString &>::of(&QSpinBox::valueChanged),
                     valueLabel, [=](const QString &text){
        valueLabel->setText(QString("表示値: %1").arg(text));
    });

    QVBoxLayout *layout = new QVBoxLayout;
    layout->addWidget(spinBox);
    layout->addWidget(valueLabel);

    window->setLayout(layout);
    window->show();

    return app.exec();
}

解説

  • QOverload<const QString &>::of(&QSpinBox::valueChanged) を使い、QString 引数バージョンのシグナルに接続しています。これにより、ラムダ式で受け取る text には、"$250.00" のような形式の文字列がそのまま渡されます。
  • setPrefix("$")setSuffix(".00") を使用して、スピンボックスの表示形式を設定しています。

setKeyboardTracking(false) の使用例:入力完了時のみの処理

デフォルトでは、ユーザーがキーボードで数値を入力している間も、一文字入力されるごとにvalueChanged()シグナルが発信されます。これを防ぎ、ユーザーが入力(または矢印ボタン操作)を完了したときにのみ処理を行いたい場合は、setKeyboardTracking(false) を使用します。

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

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

    QWidget *window = new QWidget;
    window->setWindowTitle("QSpinBox Keyboard Tracking Example");

    QSpinBox *spinBox = new QSpinBox;
    spinBox->setRange(0, 1000);
    spinBox->setValue(123);
    spinBox->setKeyboardTracking(false); // キーボードトラッキングを無効にする

    QLabel *infoLabel = new QLabel("Enterキーを押すか、フォーカスを外すとイベント発生");

    QObject::connect(spinBox, QOverload<int>::of(&QSpinBox::valueChanged),
                     infoLabel, [=](int value){
        infoLabel->setText(QString("値が変更されました: %1").arg(value));
        qDebug() << "valueChanged(int) 発信: " << value;
    });

    QVBoxLayout *layout = new QVBoxLayout;
    layout->addWidget(spinBox);
    layout->addWidget(infoLabel);

    window->setLayout(layout);
    window->show();

    return app.exec();
}

解説

  • qDebug() を使用して、シグナルが発信されたタイミングをコンソールで確認できます。
  • spinBox->setKeyboardTracking(false); が重要なポイントです。これを設定することで、valueChanged() シグナルは以下のときにのみ発信されます。
    • スピンボックスの値が上下の矢印ボタンで変更されたとき。
    • ユーザーがキーボードで数値を入力し、Enter キーを押したとき。
    • スピンボックスがフォーカスを失ったとき(別のウィジェットをクリックするなど)。

プログラム側で QSpinBox の値を変更する際に、一時的に valueChanged() シグナルの発信を抑制したい場合があります。これは、例えば無限ループを防ぎたいときなどに役立ちます。

#include <QApplication>
#include <QWidget>
#include <QSpinBox>
#include <QLabel>
#include <QPushButton>
#include <QVBoxLayout>
#include <QDebug>

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

public:
    MyWidget(QWidget *parent = nullptr) : QWidget(parent)
    {
        spinBox1 = new QSpinBox;
        spinBox1->setRange(0, 100);
        spinBox1->setValue(10);

        spinBox2 = new QSpinBox;
        spinBox2->setRange(0, 100);
        spinBox2->setValue(50);

        programmaticChangeButton = new QPushButton("プログラムでspinBox1の値を変更 (シグナル抑制)");

        // spinBox1の値が変更されたら、spinBox2の値をそれに合わせる
        // ただし、spinBox2のsetValue()がvalueChanged()を発信するのを防ぐため、blockSignalsを使用
        QObject::connect(spinBox1, QOverload<int>::of(&QSpinBox::valueChanged),
                         this, &MyWidget::onSpinBox1ValueChanged);

        // spinBox2の値が変更されたら、単にデバッグ出力
        QObject::connect(spinBox2, QOverload<int>::of(&QSpinBox::valueChanged),
                         this, [](int value){
            qDebug() << "spinBox2 valueChanged: " << value;
        });

        // ボタンがクリックされたら、プログラムでspinBox1の値を変更
        QObject::connect(programmaticChangeButton, &QPushButton::clicked,
                         this, &MyWidget::onProgrammaticChangeButtonClicked);

        QVBoxLayout *layout = new QVBoxLayout(this);
        layout->addWidget(new QLabel("SpinBox 1 (ユーザー操作でspinBox2が連動):"));
        layout->addWidget(spinBox1);
        layout->addWidget(new QLabel("SpinBox 2 (spinBox1に連動 & ユーザー操作):"));
        layout->addWidget(spinBox2);
        layout->addWidget(programmaticChangeButton);
    }

private slots:
    void onSpinBox1ValueChanged(int value)
    {
        qDebug() << "spinBox1 valueChanged: " << value;

        // spinBox2のシグナルを一時的にブロック
        spinBox2->blockSignals(true);
        spinBox2->setValue(value); // spinBox2の値をspinBox1に合わせる
        spinBox2->blockSignals(false); // ブロックを解除
    }

    void onProgrammaticChangeButtonClicked()
    {
        qDebug() << "プログラムでspinBox1の値を変更中...";
        spinBox1->blockSignals(true); // spinBox1のシグナルを一時的にブロック
        spinBox1->setValue(75);       // 値を75に変更(このsetValueではspinBox1のvalueChangedは発信されない)
        spinBox1->blockSignals(false); // ブロックを解除

        // blockSignals(false)した直後、かつ実際に値が変わっていた場合に、
        // 明示的にシグナルを発信したい場合は以下のようにすることも可能
        // ただし、この例ではsetValue()がブロック解除後に呼ばれていないため、
        // ユーザー操作時と同様にvalueChanged()が発信されることを期待しない
        // もしsetValueが発信されなかった値への変更を行った後に、
        // 外部に値の変更を通知したい場合は、emit spinBox1->valueChanged(75);
        // のように手動で発信する必要がある。
        // しかし、blockSignalsの典型的な用途では、その後ユーザー操作があったり、
        // 別途setValueが呼ばれることで自然に発信されることを期待するため、
        // このような手動emitは稀。
    }

private:
    QSpinBox *spinBox1;
    QSpinBox *spinBox2;
    QPushButton *programmaticChangeButton;
};

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    MyWidget window;
    window.setWindowTitle("Block Signals Example");
    window.show();
    return app.exec();
}

#include "main.moc" // mocファイルをインクルード(クラス定義が分離されている場合)
  • onProgrammaticChangeButtonClicked では、ボタンクリックで spinBox1 の値を変更しています。ここでも spinBox1->blockSignals(true) を使用することで、このプログラム的な変更によって spinBox1 から valueChanged() シグナルが発信されるのを防いでいます。デバッグ出力を比較すると、この違いが分かります。
  • onSpinBox1ValueChanged スロットでは、spinBox1 の値変更を受けて spinBox2 の値を更新しています。この際、spinBox2->blockSignals(true)spinBox2->blockSignals(false)setValue() の前後を挟むことで、spinBox2valueChanged() シグナルが発信されないようにしています。これにより、spinBox2 の値変更がさらに別の処理をトリガーし、意図しない連鎖や無限ループに陥るのを防ぎます。


editingFinished() シグナル

QSpinBox の親クラスである QAbstractSpinBox が提供するシグナルです。これは、ユーザーがスピンボックスの編集を完了したときに発信されます。

valueChanged() との違い

  • editingFinished():
    • ユーザーがスピンボックスに値を入力し、Enter キーを押したとき。
    • スピンボックスがフォーカスを失ったとき(別のウィジェットをクリックするなど)。
    • (Qtのバージョンやスタイルによって異なる場合があるが)上下の矢印ボタンがクリックされて値が確定したとき。
  • valueChanged(): スピンボックスの値が変更されるたびに(例えば、キーボードで入力中に一文字入力するたび、矢印ボタンを押し続けている間など)頻繁に発信される可能性があります。

使用するケース

  • 入力途中の値ではなく、最終的な確定値に対して処理を行いたい場合。
  • 値の変更が頻繁に行われることで、重い処理が何度も実行されるのを避けたい場合。
  • ユーザーが値の入力を「確定」した時点でのみ処理を実行したい場合。

コード例

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

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

public:
    MyWidget(QWidget *parent = nullptr) : QWidget(parent)
    {
        spinBox = new QSpinBox;
        spinBox->setRange(0, 1000);
        spinBox->setValue(100);

        statusLabel = new QLabel("値を入力してください");

        // editingFinished() シグナルを接続
        // このシグナルは引数を持たないため、直接スロットに接続できる
        QObject::connect(spinBox, &QSpinBox::editingFinished,
                         this, &MyWidget::onEditingFinished);

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

        setWindowTitle("editingFinished() Example");
    }

private slots:
    void onEditingFinished()
    {
        int finalValue = spinBox->value(); // 確定した値を取得
        statusLabel->setText(QString("入力が完了しました: %1").arg(finalValue));
        qDebug() << "editingFinished() 発信。最終値: " << finalValue;
    }

private:
    QSpinBox *spinBox;
    QLabel *statusLabel;
};

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

#include "main.moc" // mocファイルをインクルード(クラス定義が分離されている場合)

注意点

  • ユーザーが値を入力していても、Enterキーを押さず、かつフォーカスも移動させない限り、シグナルは発信されません。
  • editingFinished() は、矢印ボタンでの操作後、自動的に値が確定されたと見なされる場合にも発信されることがあります。

textChanged(const QString &text) シグナル

QSpinBox は内部的に QLineEdit を持っており、そのテキストが変更されるたびに発信されるシグナルです。valueChanged() と似ていますが、常に文字列形式で現在の表示内容(プレフィックスやサフィックスを含む)を受け取ります。

valueChanged() との違い

  • textChanged(): QSpinBox が内部的に持つ QLineEdit のテキストが変更されるたびに発信されます。これは valueChanged()QString オーバーロードと非常に似ていますが、概念的には「テキスト」が変更されたことに焦点を当てています。実際の挙動はQtのバージョンや設定(特に keyboardTracking)に依存しますが、多くのケースで valueChanged(QString) と同じタイミングで発信されます。
  • valueChanged() (QString): int と同じタイミングで発信され、プレフィックス/サフィックスを含む文字列形式の値を提供します。
  • valueChanged() (int): 常に数値 (int) としての値を提供します。

使用するケース

  • 値の変更が数値としてではなく、表示上の文字列として重要である場合。
  • スピンボックスに表示されている生のテキスト(プレフィックスやサフィックス、特殊なテキストを含む)をリアルタイムで監視したい場合。

コード例

#include <QApplication>
#include <QWidget>
#include <QSpinBox>
#include <QLabel>
#include <QVBoxLayout>
#include <QDebug>

class MyWidget : public QWidget
{
    Q_OBJECT

public:
    MyWidget(QWidget *parent = nullptr) : QWidget(parent)
    {
        spinBox = new QSpinBox;
        spinBox->setRange(0, 100);
        spinBox->setValue(25);
        spinBox->setPrefix("Qty: "); // プレフィックスを設定
        spinBox->setSuffix(" pcs");  // サフィックスを設定
        spinBox->setKeyboardTracking(true); // デフォルトですが明示

        textLabel = new QLabel("現在の表示テキスト:");

        // textChanged() シグナルを接続
        QObject::connect(spinBox, &QSpinBox::textChanged,
                         this, &MyWidget::onTextChanged);

        QVBoxLayout *layout = new QVBoxLayout(this);
        layout->addWidget(spinBox);
        layout->addWidget(textLabel);

        setWindowTitle("textChanged() Example");
    }

private slots:
    void onTextChanged(const QString &text)
    {
        textLabel->setText(QString("現在の表示テキスト: %1").arg(text));
        qDebug() << "textChanged() 発信。テキスト: " << text;
    }

private:
    QSpinBox *spinBox;
    QLabel *textLabel;
};

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

#include "main.moc"

解説

  • keyboardTracking(true) (デフォルト) の場合、ユーザーがキーボードで一文字入力するたびにこのシグナルが発信されます。
  • ユーザーがスピンボックスの値を変更すると、textChanged() シグナルが発信され、"Qty: 25 pcs" のような表示テキストがスロットに渡されます。

非常に特殊なケースで、シグナル&スロットメカニズムを使わずに、定期的にスピンボックスの現在値を取得する必要がある場合、タイマーなどを使って QSpinBox::value() メソッドをポーリングすることが考えられます。

使用するケース

  • シグナル&スロットのオーバーヘッドを避けたい(非常に稀なケース)。
  • リアルタイム性がそれほど求められず、定期的な状態監視で十分な場合。

問題点と注意点

  • ほとんどの場合、Qt のシグナル&スロットメカニズムを使う方が、効率的でイベント駆動型プログラミングに適しています。
  • リアルタイム性の欠如
    値の変更が発生してからポーリングで検知されるまでに遅延が発生します。
  • 非効率
    値が変更されていない場合でも、常にポーリングが行われるため非効率です。

コード例(概念のみ - 通常は推奨しない)

// この方法は通常推奨されません。
// より良い方法は、valueChanged() シグナルを使用することです。
// これは概念的な理解のために提示されています。

#include <QApplication>
#include <QWidget>
#include <QSpinBox>
#include <QLabel>
#include <QVBoxLayout>
#include <QTimer> // タイマー用
#include <QDebug>

class MyWidget : public QWidget
{
    Q_OBJECT

public:
    MyWidget(QWidget *parent = nullptr) : QWidget(parent)
    {
        spinBox = new QSpinBox;
        spinBox->setRange(0, 100);
        spinBox->setValue(0);

        statusLabel = new QLabel("現在の値: 0");

        pollingTimer = new QTimer(this);
        pollingTimer->setInterval(1000); // 1秒ごとにポーリング
        QObject::connect(pollingTimer, &QTimer::timeout, this, &MyWidget::checkSpinBoxValue);
        pollingTimer->start(); // タイマーを開始

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

        setWindowTitle("Polling Example (Not Recommended)");
    }

private slots:
    void checkSpinBoxValue()
    {
        int currentValue = spinBox->value();
        if (currentValue != lastKnownValue) {
            statusLabel->setText(QString("値が変更されました: %1").arg(currentValue));
            qDebug() << "ポーリングで値変更を検知: " << currentValue;
            lastKnownValue = currentValue;
        }
    }

private:
    QSpinBox *spinBox;
    QLabel *statusLabel;
    QTimer *pollingTimer;
    int lastKnownValue;
};

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

#include "main.moc"
  • 前述の通り、この方法はほとんどのケースで valueChanged() シグナルに劣ります。
  • このスロット内で spinBox->value() を呼び出し、前回の値と比較して変更があった場合にのみ処理を実行します。
  • QTimer を使用して、1秒ごとに checkSpinBoxValue() スロットを呼び出します。
  • ポーリング
    通常は推奨されません。イベント駆動型プログラミングの原則に反し、非効率的です。
  • textChanged()
    スピンボックスの表示テキスト(プレフィックス/サフィックス含む)のリアルタイムな変更を監視したい場合に検討できますが、多くの場合 valueChanged(QString) と同等の役割を果たします。
  • editingFinished()
    ユーザーが値の入力を確定した時点でのみ処理を実行したい場合に適しています。頻繁なシグナル発信を避けたい場合に有効です。
  • valueChanged() (int/QString)
    スピンボックスの値が変更されるたびに処理を実行したい場合の標準的かつ最も推奨される方法です。