Qt QSpinBoxカスタムステップ:setStepType()代替手法で自由な数値制御

2025-05-31

QSpinBox::setStepType() とは?

QSpinBox::setStepType() は、Qtの QSpinBox (数値入力ボックス) または QDoubleSpinBox (小数値入力ボックス) の値が、上下ボタンのクリックやキーボードの上下矢印キーで増減する際の「ステップの種類」を設定するための関数です。

この関数は QAbstractSpinBox クラスで定義されており、QSpinBoxQDoubleSpinBox はこのクラスを継承しています。

StepType の種類

setStepType() には、QAbstractSpinBox::StepType という列挙型 (enum) の値を渡します。主に以下の2つのステップタイプがあります。

    • これは通常のステップタイプです。setSingleStep() で設定された値 (デフォルトは1) ずつ、値が増減します。
    • 例えば、setSingleStep(5) と設定した場合、上下ボタンを押すと値が5ずつ増減します。
  1. QAbstractSpinBox::AdaptiveDecimalStepType (適応小数点ステップタイプ)

    • Qt 5.12 で導入された機能です。
    • このタイプを設定すると、ステップサイズが現在の値に基づいて自動的に調整されます。具体的には、現在の値の10分の1のオーダーでステップサイズが変化します。
    • 例:
      • 現在の値が900の場合、ステップサイズは10になることがあります。
      • 現在の値が1000の場合、ステップサイズは100になることがあります。
    • これにより、大きな数値範囲を扱う際に、細かい調整と大きな調整をより直感的に行えるようになります。

C++での使用例は以下のようになります。

#include <QtWidgets/QApplication>
#include <QtWidgets/QSpinBox>
#include <QtWidgets/QVBoxLayout>

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

    QWidget window;
    QVBoxLayout *layout = new QVBoxLayout(&window);

    QSpinBox *spinBox1 = new QSpinBox();
    spinBox1->setRange(0, 10000);
    spinBox1->setSingleStep(1); // デフォルトのステップタイプではこの値が使われる
    spinBox1->setValue(123);
    spinBox1->setPrefix("Default: ");
    // デフォルトのステップタイプ
    spinBox1->setStepType(QAbstractSpinBox::DefaultStepType);
    layout->addWidget(spinBox1);

    QSpinBox *spinBox2 = new QSpinBox();
    spinBox2->setRange(0, 10000);
    spinBox2->setValue(123);
    spinBox2->setPrefix("Adaptive: ");
    // 適応小数点ステップタイプ
    spinBox2->setStepType(QAbstractSpinBox::AdaptiveDecimalStepType);
    layout->addWidget(spinBox2);

    window.show();

    return a.exec();
}

このコードを実行すると、2つのQSpinBoxが表示されます。DefaultStepTypeのSpinBoxは常にsetSingleStep()で設定した値(この場合1)ずつ増減しますが、AdaptiveDecimalStepTypeのSpinBoxは、現在の値によってステップサイズが自動的に調整されるのがわかります。

QSpinBox::setStepType() は、QSpinBoxQDoubleSpinBox の値の増減挙動を制御するための重要な関数です。特にAdaptiveDecimalStepTypeは、ユーザーが広範囲の数値を効率的に操作するのに役立ちます。



QSpinBox::setStepType() は比較的単純な関数ですが、いくつか注意すべき点や、それに関連するエラーが発生する可能性があります。

Qtバージョンの問題 (最も一般的)

エラーの症状
setStepType() 関数が存在しない、またはコンパイルエラーが発生する。特に AdaptiveDecimalStepType を使用しようとすると発生しやすい。

原因
QAbstractSpinBox::AdaptiveDecimalStepType はQt 5.12で導入された機能です。それ以前のQtバージョンを使用している場合、このステップタイプは利用できません。setStepType() 自体は以前から存在しますが、新しい列挙値を使用しようとするとエラーになります。

トラブルシューティング

  • DefaultStepType を使用する
    AdaptiveDecimalStepType がどうしても必要でない場合は、DefaultStepType (Qtのほとんどのバージョンで利用可能) を使用してください。
  • Qtをアップグレードする
    もし古いQtバージョンを使用している場合は、Qt 5.12以降にアップグレードすることを検討してください。
  • Qtのバージョンを確認する
    使用しているQtのバージョンが5.12以降であることを確認してください。Qt Creatorを使用している場合は、プロジェクト設定やQtのインストールパスを確認できます。

QDoubleSpinBox と QSpinBox の混同

エラーの症状
QSpinBoxAdaptiveDecimalStepType を設定したが、期待通りの挙動にならない、またはQDoubleSpinBoxのように小数点が現れない。

原因
QSpinBox は整数値を扱いますが、QDoubleSpinBox は小数値を扱います。AdaptiveDecimalStepType は、特に小数値を扱う QDoubleSpinBox でその真価を発揮します。QSpinBox でも設定は可能ですが、整数値の範囲でステップが変化するため、QDoubleSpinBox のようなきめ細かい挙動は期待できません。

トラブルシューティング

  • QDoubleSpinBox を使用する場合は、setDecimals() で小数点以下の桁数を適切に設定することも重要です。
  • 目的に合ったクラスを選ぶ
    小数値を扱いたい場合は QDoubleSpinBox を使用し、整数値のみで良い場合は QSpinBox を使用します。

setSingleStep() との関連性への誤解

エラーの症状
setStepType(QAbstractSpinBox::AdaptiveDecimalStepType) を設定したが、setSingleStep() の値が無視されるように見える、または意図しないステップサイズになる。

原因
AdaptiveDecimalStepType は、setSingleStep() で設定された値ではなく、現在の値に基づいてステップサイズを動的に調整します。DefaultStepType の場合は setSingleStep() の値が直接使用されます。

トラブルシューティング

  • DefaultStepType を検討する
    固定のステップサイズが必要な場合は、DefaultStepType を使用し、setSingleStep() で明示的にステップサイズを設定してください。
  • 挙動を理解する
    AdaptiveDecimalStepType の挙動(現在の値のオーダーでステップが変化する)を理解し、その挙動がアプリケーションの要件に合致しているか確認してください。

UIデザイナーでの設定ミス

エラーの症状
Qt Designer (Qt CreatorのUIデザイナー) でSpin Boxのプロパティを設定したが、コードで期待通りの挙動にならない。

原因
UIデザイナーで設定したプロパティが、コードで上書きされている可能性があります。または、UIデザイナーで設定したつもりが、別のウィジェットのプロパティを変更してしまっていることもあります。

トラブルシューティング

  • Qt Designerのプロパティを確認する
    正しいSpin Boxが選択されており、「stepType」プロパティが正しく設定されているか確認します。
  • コードでの設定をチェックする
    コード内で setStepType() が複数回呼び出されていないか、または意図せず上書きされていないかを確認します。
  • Generated UIファイルを確認する
    ui_*.h ファイルを調べて、setStepType() が適切に生成されているか確認します。

値の範囲とステップタイプの相互作用

エラーの症状
setRange() で設定した最小値や最大値が、AdaptiveDecimalStepType の挙動に影響を与え、期待通りの増減ができない場合がある。

原因
AdaptiveDecimalStepType は、現在の値のオーダーに基づいてステップを調整しますが、設定された範囲も考慮します。例えば、範囲の端に近い場合、ステップサイズが制限されることがあります。

  • テストで確認する
    さまざまな値の範囲とステップタイプの設定で、実際に増減操作を行い、期待通りの挙動になるかテストしてください。
  • 範囲を見直す
    Spin Boxの setRange() で設定された最小値と最大値が、ユーザーが操作する数値範囲と適切に一致しているか確認します。


QSpinBox::setStepType() は、QSpinBoxQDoubleSpinBox の増減挙動を制御するために使用されます。主に QAbstractSpinBox::DefaultStepTypeQAbstractSpinBox::AdaptiveDecimalStepType の2種類のステップタイプがあります。

基本的な使用例:DefaultStepType と AdaptiveDecimalStepType の比較

この例では、2つの QSpinBox と2つの QDoubleSpinBox を作成し、それぞれ異なるステップタイプを設定します。

#include <QtWidgets/QApplication>
#include <QtWidgets/QWidget>
#include <QtWidgets/QVBoxLayout>
#include <QtWidgets/QHBoxLayout>
#include <QtWidgets/QSpinBox>
#include <QtWidgets/QDoubleSpinBox>
#include <QtWidgets/QLabel>
#include <QtWidgets/QGroupBox> // グループボックスでUIを整理

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

    QWidget window;
    window.setWindowTitle("QSpinBox::setStepType Examples");

    QVBoxLayout *mainLayout = new QVBoxLayout(&window);

    // --- QSpinBox の例 ---
    QGroupBox *spinBoxGroup = new QGroupBox("QSpinBox Examples");
    QVBoxLayout *spinBoxLayout = new QVBoxLayout(spinBoxGroup);

    // デフォルトのステップタイプ (DefaultStepType) の QSpinBox
    QLabel *defaultSpinBoxLabel = new QLabel("Default QSpinBox (step 10):");
    QSpinBox *defaultSpinBox = new QSpinBox();
    defaultSpinBox->setRange(0, 1000);
    defaultSpinBox->setSingleStep(10); // ステップサイズを10に設定
    defaultSpinBox->setValue(50);
    defaultSpinBox->setStepType(QAbstractSpinBox::DefaultStepType); // 明示的にデフォルトを設定
    spinBoxLayout->addWidget(defaultSpinBoxLabel);
    spinBoxLayout->addWidget(defaultSpinBox);

    // 適応小数点ステップタイプ (AdaptiveDecimalStepType) の QSpinBox
    // QSpinBoxでは、AdaptiveDecimalStepTypeは整数値のオーダーでステップが変化します。
    // (例: 1, 10, 100...)
    QLabel *adaptiveSpinBoxLabel = new QLabel("Adaptive QSpinBox:");
    QSpinBox *adaptiveSpinBox = new QSpinBox();
    adaptiveSpinBox->setRange(0, 100000); // 広い範囲で挙動が分かりやすい
    adaptiveSpinBox->setValue(95);
    adaptiveSpinBox->setStepType(QAbstractSpinBox::AdaptiveDecimalStepType);
    spinBoxLayout->addWidget(adaptiveSpinBoxLabel);
    spinBoxLayout->addWidget(adaptiveSpinBox);

    mainLayout->addWidget(spinBoxGroup);

    // --- QDoubleSpinBox の例 ---
    QGroupBox *doubleSpinBoxGroup = new QGroupBox("QDoubleSpinBox Examples");
    QVBoxLayout *doubleSpinBoxLayout = new QVBoxLayout(doubleSpinBoxGroup);

    // デフォルトのステップタイプ (DefaultStepType) の QDoubleSpinBox
    QLabel *defaultDoubleSpinBoxLabel = new QLabel("Default QDoubleSpinBox (step 0.1):");
    QDoubleSpinBox *defaultDoubleSpinBox = new QDoubleSpinBox();
    defaultDoubleSpinBox->setRange(0.0, 100.0);
    defaultDoubleSpinBox->setSingleStep(0.1); // ステップサイズを0.1に設定
    defaultDoubleSpinBox->setDecimals(2);     // 小数点以下2桁表示
    defaultDoubleSpinBox->setValue(10.5);
    defaultDoubleSpinBox->setStepType(QAbstractSpinBox::DefaultStepType);
    doubleSpinBoxLayout->addWidget(defaultDoubleSpinBoxLabel);
    doubleSpinBoxLayout->addWidget(defaultDoubleSpinBox);

    // 適応小数点ステップタイプ (AdaptiveDecimalStepType) の QDoubleSpinBox
    // QDoubleSpinBoxでAdaptiveDecimalStepTypeを使用すると、
    // 小数点以下の桁数も考慮しつつ、よりきめ細かいステップ調整が行われます。
    QLabel *adaptiveDoubleSpinBoxLabel = new QLabel("Adaptive QDoubleSpinBox:");
    QDoubleSpinBox *adaptiveDoubleSpinBox = new QDoubleSpinBox();
    adaptiveDoubleSpinBox->setRange(0.001, 10000.0); // 広い範囲で挙動が分かりやすい
    adaptiveDoubleSpinBox->setDecimals(4);         // 小数点以下4桁表示
    adaptiveDoubleSpinBox->setValue(9.876);
    adaptiveDoubleSpinBox->setStepType(QAbstractSpinBox::AdaptiveDecimalStepType);
    doubleSpinBoxLayout->addWidget(adaptiveDoubleSpinBoxLabel);
    doubleSpinBoxLayout->addWidget(adaptiveDoubleSpinBox);

    mainLayout->addWidget(doubleSpinBoxGroup);

    window.show();

    return a.exec();
}

このコードの解説:

  1. インクルード
    必要なQtウィジェットのヘッダーをインクルードしています。
  2. QApplication と QWidget
    Qtアプリケーションとメインウィンドウの基本を設定します。
  3. QVBoxLayout と QGroupBox
    レイアウトマネージャとグループボックスを使用して、複数のSpin Boxを整理して表示します。
  4. QSpinBox (整数値)
    • setRange(0, 1000): 0から1000までの範囲を設定します。
    • setSingleStep(10): DefaultStepType の場合、上下ボタンで10ずつ値が変化します。
    • setStepType(QAbstractSpinBox::DefaultStepType): 明示的にデフォルトのステップタイプを設定します。
    • setStepType(QAbstractSpinBox::AdaptiveDecimalStepType): 適応小数点ステップタイプを設定します。この場合、setSingleStep() の値は直接使用されず、値のオーダーに応じてステップが自動調整されます(例: 値が小さい場合は1ずつ、100を超えると10ずつ、1000を超えると100ずつなど)。
  5. QDoubleSpinBox (浮動小数点値)
    • setRange(0.0, 100.0): 浮動小数点数の範囲を設定します。
    • setSingleStep(0.1): DefaultStepType の場合、0.1ずつ値が変化します。
    • setDecimals(2): 小数点以下2桁まで表示するように設定します。これはQDoubleSpinBox固有の重要な設定です。
    • setStepType(QAbstractSpinBox::AdaptiveDecimalStepType): QDoubleSpinBox でこのタイプを使用すると、小数点以下の微調整から大きな数値の調整まで、非常に直感的な操作が可能になります。現在の値の桁数や小数部の状況に応じて、ステップサイズが動的に変化します。
  6. setValue()
    各Spin Boxの初期値を設定します。


QSpinBox::setStepType() の代替手法

Qtの QSpinBox および QDoubleSpinBox クラスは、その挙動をカスタマイズするための仮想関数を提供しています。これにより、setStepType() が提供する範囲を超えた、より複雑なステップロジックを実装できます。

stepBy() 仮想関数の再実装 (Subclassing)

これは、QSpinBoxQDoubleSpinBox のステップ挙動を最も柔軟にカスタマイズできる方法です。QAbstractSpinBox クラスには virtual void stepBy(int steps) という関数があり、上下ボタンがクリックされたり、上下矢印キーが押されたりするたびに呼び出されます。

使用例
現在の値が2倍/半分になるカスタムSpinBox (幾何数列)

#include <QtWidgets/QApplication>
#include <QtWidgets/QSpinBox>
#include <QtWidgets/QVBoxLayout>
#include <QtWidgets/QLabel>

// カスタムQSpinBoxクラスの定義
class CustomGeometricSpinBox : public QSpinBox {
    Q_OBJECT // Qtのメタオブジェクトシステムを使用するために必要

public:
    explicit CustomGeometricSpinBox(QWidget *parent = nullptr)
        : QSpinBox(parent) {
        setRange(1, 1024); // 例として1から1024の範囲
        setValue(1);
    }

protected:
    // stepBy仮想関数を再実装してカスタムの増減ロジックを実装
    void stepBy(int steps) override {
        int currentValue = value();
        if (steps > 0) {
            // 上方向へのステップ
            // 最大値を超えないように調整
            if (currentValue * 2 <= maximum()) {
                setValue(currentValue * 2);
            } else {
                setValue(maximum()); // 最大値に達したら最大値を設定
            }
        } else if (steps < 0) {
            // 下方向へのステップ
            // 最小値を下回らないように調整
            if (currentValue / 2 >= minimum()) {
                setValue(currentValue / 2);
            } else {
                setValue(minimum()); // 最小値に達したら最小値を設定
            }
        }
        // その他のステップ(もしあれば)は基本クラスの挙動に任せる
        // QSpinBox::stepBy(steps); // ここを呼び出すと元の挙動も混じる
    }

// moc (Meta-Object Compiler) がこのクラスを処理するために必要
// ソースファイル (.cpp) に記述することが推奨されます
#include "customgeometricspinbox.moc" // または適切なパス
};

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

    QWidget window;
    QVBoxLayout *layout = new QVBoxLayout(&window);

    QLabel *label = new QLabel("Geometric Spin Box (x2 / ÷2):");
    layout->addWidget(label);

    CustomGeometricSpinBox *spinBox = new CustomGeometricSpinBox();
    layout->addWidget(spinBox);

    window.setLayout(layout);
    window.show();

    return a.exec();
}

解説

  • maximum()minimum() を考慮して、範囲外にならないように調整しています。
  • setValue() を直接呼び出すことで、新しい値を設定しています。
  • stepBy(int steps) をオーバーライドし、steps の値(+1または-1)に基づいて、現在の値を2倍または半分にしています。
  • CustomGeometricSpinBoxQSpinBox を継承しています。

この方法により、AdaptiveDecimalStepType では実現できないような、例えば対数的なステップ、特定の数列に基づくステップ、あるいは現在の値と他の入力値の組み合わせに基づくステップなど、非常に複雑なロジックを実装することが可能になります。

valueChanged() シグナルとスロットによる制御

setStepType() を使用しない場合でも、QSpinBoxQDoubleSpinBox が値を変更するたびに発火する valueChanged() シグナルを利用して、値の調整を行うことができます。これは、ユーザーが値を手動で入力した場合にも対応できる点で強力です。

使用例
特定の増減ルールを後処理で適用する

#include <QtWidgets/QApplication>
#include <QtWidgets/QSpinBox>
#include <QtWidgets/QVBoxLayout>
#include <QtWidgets/QLabel>

class MySpinBoxController : public QObject {
    Q_OBJECT
public:
    explicit MySpinBoxController(QSpinBox *spinBox, QObject *parent = nullptr)
        : QObject(parent), m_spinBox(spinBox) {
        // valueChangedシグナルをカスタムスロットに接続
        connect(m_spinBox, QOverload<int>::of(&QSpinBox::valueChanged),
                this, &MySpinBoxController::onValueChanged);
    }

private slots:
    void onValueChanged(int newValue) {
        // 例: 値が5の倍数になるように強制する
        if (newValue % 5 != 0) {
            int correctedValue = (newValue / 5) * 5;
            if (newValue > correctedValue) {
                correctedValue += 5; // 切り上げ
            }
            // setValueを呼ぶことで、再帰的な呼び出しを防ぐためにblockSignalsを使用
            QSignalBlocker blocker(m_spinBox); // シグナルを一時的にブロック
            m_spinBox->setValue(correctedValue);
            qDebug() << "Value corrected from" << newValue << "to" << correctedValue;
        }
    }

private:
    QSpinBox *m_spinBox;
};

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

    QWidget window;
    QVBoxLayout *layout = new QVBoxLayout(&window);

    QLabel *label = new QLabel("QSpinBox (force multiple of 5):");
    layout->addWidget(label);

    QSpinBox *spinBox = new QSpinBox();
    spinBox->setRange(0, 100);
    spinBox->setSingleStep(1); // 1ずつ増減するが、後に補正される
    spinBox->setValue(0);
    layout->addWidget(spinBox);

    // コントローラーオブジェクトを作成し、SpinBoxを監視させる
    MySpinBoxController controller(spinBox);

    window.setLayout(layout);
    window.show();

    return a.exec();
}

#include "main.moc" // Q_OBJECT マクロを使用する場合

解説

  • QSignalBlocker を使用して、setValue() を呼び出す際に valueChanged() シグナルが再度発火するのを防ぎ、無限ループを回避しています。
  • onValueChanged スロット内で、新しい値がカスタムルール(この例では5の倍数)に合致しない場合に値を補正します。
  • MySpinBoxController クラスを作成し、QSpinBoxvalueChanged() シグナルを捕捉します。

この方法は、ユーザー入力が完了した後(または値が変更された後)に特定のルールを適用したい場合に有効です。

setSingleStep() の動的な変更

setStepType() とは異なりますが、setSingleStep() の値をプログラム的に動的に変更することで、ある程度の「適応性」を持たせることも可能です。例えば、特定の閾値を超えたらステップサイズを大きくするなどです。

#include <QtWidgets/QApplication>
#include <QtWidgets/QSpinBox>
#include <QtWidgets/QVBoxLayout>
#include <QtWidgets/QLabel>

class DynamicStepSpinBox : public QSpinBox {
    Q_OBJECT
public:
    explicit DynamicStepSpinBox(QWidget *parent = nullptr)
        : QSpinBox(parent) {
        setRange(0, 1000);
        setSingleStep(1); // 初期ステップ
        setValue(0);
        // valueChanged シグナルに接続して、値に応じてシングルステップを変更
        connect(this, QOverload<int>::of(&QSpinBox::valueChanged),
                this, &DynamicStepSpinBox::updateSingleStep);
    }

private slots:
    void updateSingleStep(int newValue) {
        if (newValue < 100) {
            setSingleStep(1);
        } else if (newValue < 500) {
            setSingleStep(10);
        } else {
            setSingleStep(100);
        }
    }
};

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

    QWidget window;
    QVBoxLayout *layout = new QVBoxLayout(&window);

    QLabel *label = new QLabel("Dynamic Single Step Spin Box:");
    layout->addWidget(label);

    DynamicStepSpinBox *spinBox = new DynamicStepSpinBox();
    layout->addWidget(spinBox);

    window.setLayout(layout);
    window.show();

    return a.exec();
}

#include "main.moc" // Q_OBJECT マクロを使用する場合

解説

  • updateSingleStep() では、現在の値に基づいて setSingleStep() の値を変更しています。
  • valueChanged() シグナルが発火するたびに updateSingleStep() スロットが呼び出されます。
  • DynamicStepSpinBoxQSpinBox を継承しています。

これは AdaptiveDecimalStepType の簡易版のようなものですが、よりカスタマイズされた閾値やステップサイズを適用できます。

  • setSingleStep() の動的な変更

    • AdaptiveDecimalStepType ほど洗練されていなくても、値の大きさに応じて大まかなステップサイズを変更したい場合に、比較的簡単に実装できます。
    • stepBy() の再実装よりも少ないコードで実現できる場合があります。
  • valueChanged() シグナルとスロット

    • ユーザーが手動で値を入力した場合も含め、値が変更された後にその値を検証・補正したい場合に適しています。
    • ステップロジック自体ではなく、最終的な値が特定の制約を満たすようにしたい場合に利用します。
    • ユーザーが上下ボタンや矢印キーを押した際の「増減ロジックそのもの」を完全に制御したい場合に最適です。
    • 特定の数列(例:幾何数列、フィボナッチ数列)や複雑な計算に基づいて値をステップさせたい場合に非常に強力です。