Qt 開発Tips: QSpinBox valueFromText() を使いこなす

2025-05-27

役割と機能

QSpinBox は、ユーザーが数値を増減させるためのスピンボックスウィジェットを提供します。通常、ユーザーはラインエディット部分に直接数値を入力することも可能です。valueFromText() 関数は、このラインエディットに入力されたテキスト文字列を受け取り、それに対応する整数値を返します。

具体的な動作

  1. テキストの受け取り
    valueFromText() 関数は、引数として QString 型のテキスト文字列を受け取ります。この文字列は、QSpinBox のラインエディット部分に入力された内容です。

  2. 値への変換
    受け取ったテキスト文字列を、QSpinBox が扱う整数値に変換しようと試みます。この変換は、QSpinBox に設定されている最小値 (minimum())、最大値 (maximum())、ステップ値 (singleStep())、およびプレフィックス (prefix()) やサフィックス (suffix()) などのプロパティを考慮して行われます。

  3. 戻り値

    • テキストが有効な整数として解析できた場合、対応する int 型の整数値が返されます。
    • テキストが QSpinBox の設定に基づいて有効な整数として解釈できない場合(例えば、数値の範囲外、無効な文字が含まれているなど)、通常は現在の value() が返されます。ただし、これは QSpinBox の実装や設定によって異なる場合があります。

利用場面

valueFromText() 関数は、主に QSpinBox のサブクラスを作成し、数値のテキスト表現から整数値への変換ロジックをカスタマイズしたい場合に再実装(オーバーライド)されます。例えば、特定のフォーマットのテキスト入力を受け付けたり、範囲外の入力に対して特別な処理を行ったりする場合などに利用されます。

デフォルトの挙動

QSpinBox のデフォルトの valueFromText() の実装は、入力されたテキストを単純な整数に変換しようとします。プレフィックスやサフィックスが付いている場合は、それらを無視して数値部分のみを解析します。


もし QSpinBox のプレフィックスが "$" で、ユーザーが "$123" と入力した場合、デフォルトの valueFromText() は "123" を整数 123 に変換して返します。



一般的なエラーとトラブルシューティング

    • 原因
      valueFromText() の実装内で、入力されたテキスト (QString) を int 型の数値に正しく変換できていない。例えば、toInt() 関数の使用方法が間違っている、数値以外の文字が含まれている場合にエラー処理が適切に行われていないなど。
    • トラブルシューティング
      • QString::toInt() 関数の戻り値を確認し、変換が成功したかどうか(ok フラグ)をチェックする。
      • 数値以外の文字(空白、記号など)が入力された場合の処理を検討する。例えば、それらの文字を無視する、エラーメッセージを表示するなど。
      • 正規表現 (QRegularExpression) を使用して、入力テキストが期待される数値フォーマットに合致するかどうかを検証する。
  1. 範囲外の値 (Out-of-Range Values)

    • 原因
      valueFromText() が生成した整数値が、QSpinBox に設定されている最小値 (minimum()) と最大値 (maximum()) の範囲外である。
    • トラブルシューティング
      • valueFromText() の実装内で、生成した値が範囲内にあるかどうかを明示的にチェックする。
      • 範囲外の値が生成された場合の処理を決定する。例えば、範囲内の最も近い値を返す、エラーを示す特別な値を返すなど。
      • QSpinBox::setRange() で適切な最小値と最大値が設定されていることを確認する。
  2. プレフィックス・サフィックスの処理ミス (Incorrect Handling of Prefix and Suffix)

    • 原因
      QSpinBox にプレフィックス (prefix()) やサフィックス (suffix()) が設定されている場合に、valueFromText() がこれらの文字列を適切に処理できていない。例えば、プレフィックスやサフィックスを含んだまま toInt() を呼び出して変換に失敗する、あるいは変換後にプレフィックスやサフィックスを考慮した値の調整を行っていないなど。
    • トラブルシューティング
      • QSpinBox::prefix()QSpinBox::suffix() を使用して、プレフィックスとサフィックスの文字列を取得する。
      • 入力テキストからプレフィックスとサフィックスを取り除いた数値部分のみを toInt() で変換する。
      • valueFromText() が返す値は、プレフィックスやサフィックスを含まない生の整数値であるべきであることを理解する。
  3. ステップ値の考慮漏れ (Ignoring the Step Value)

    • 原因
      QSpinBox にステップ値 (singleStep()) が設定されている場合に、valueFromText() がこのステップ値を考慮した値の検証や変換を行っていない。
    • トラブルシューティング
      • valueFromText() は、ユーザーが直接入力したテキストから値を生成する役割を持つため、通常はステップ値を直接考慮する必要はありません。ステップ値は、スピンボタンの増減操作時に影響します。
      • もし、特定のテキスト入力パターンがステップ値と関連付けられている必要がある場合は、そのロジックを valueFromText() 内に実装する必要があります。
  4. シグナルとスロットの連携問題 (Issues with Signals and Slots)

    • 原因
      valueFromText() の再実装が、関連するシグナル (valueChanged(), textChanged()) の発行や、他のスロットとの連携に予期せぬ影響を与えている。
    • トラブルシューティング
      • valueFromText() の実装が、QSpinBox の内部状態を正しく更新しているか確認する。
      • カスタムの valueFromText() が呼び出された際に、意図したシグナルが適切なタイミングで発行されているかデバッグする。
      • 関連するスロットの処理が、valueFromText() の変更によって期待通りに動作しているか検証する。
  5. locale (地域設定) の影響 (Influence of Locale)

    • 原因
      数値の表現(小数点、桁区切りなど)は地域設定によって異なる場合があります。valueFromText() の実装が特定の地域設定に依存していると、異なる地域設定の環境で正しく動作しない可能性があります。
    • トラブルシューティング
      • QLocale クラスを使用して、地域設定に依存しない数値変換を行うことを検討する。
      • QString::toDouble()QLocale::toDouble() などの関数を使用する際に、適切な QLocale オブジェクトを指定する。ただし、QSpinBox は整数値を扱うため、通常は toInt() で十分です。もし浮動小数点数を扱う必要がある場合は、QDoubleSpinBox の使用を検討してください。

デバッグのヒント

  • さまざまな入力パターン(有効な数値、無効な文字を含む数値、範囲外の数値、プレフィックス・サフィックス付きの数値など)でテストを行い、挙動を確認する。
  • ブレークポイントを設定して、valueFromText() の実行ステップを追跡し、変数の状態を確認する。
  • qDebug() を使用して、valueFromText() に渡されるテキストや、生成された値をログ出力する。


例1: カンマ区切りの数値を処理する CommaSeparatedSpinBox

この例では、ユーザーがカンマ区切りの数値を入力した場合でも、それを整数値として正しく解釈する QSpinBox のサブクラスを作成します。

#include <QSpinBox>
#include <QString>
#include <QLocale>
#include <QDebug>

class CommaSeparatedSpinBox : public QSpinBox {
public:
    CommaSeparatedSpinBox(QWidget *parent = nullptr) : QSpinBox(parent) {}

protected:
    int valueFromText(const QString &text) const override {
        QString cleanedText = text;
        cleanedText.remove(','); // カンマを削除

        bool ok;
        int value = cleanedText.toInt(&ok);

        if (ok) {
            return value;
        } else {
            // 変換に失敗した場合は、現在の値を返す(または適切なエラー処理を行う)
            return QSpinBox::valueFromText(text);
        }
    }

    QString textFromValue(int value) const override {
        // 値をカンマ区切りの文字列に変換(必要に応じて)
        return QLocale().toString(value);
    }
};

#include <QApplication>
#include <QHBoxLayout>
#include <QLabel>

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

    QWidget window;
    QHBoxLayout layout(&window);

    QLabel label("カンマ区切り入力:");
    CommaSeparatedSpinBox spinBox;
    spinBox.setRange(0, 1000000); // 範囲を設定

    layout->addWidget(&label);
    layout->addWidget(&spinBox);

    window.show();

    return a.exec();
}

解説

  • textFromValue(int value) const override 関数も再実装しており、ここでは QLocale().toString(value) を使用して、値を地域設定に応じた文字列(必要に応じてカンマ区切りになる)に変換しています。
  • 変換に成功した場合はその整数値を返します。失敗した場合は、親クラスの valueFromText() を呼び出してデフォルトの処理を行います。
  • 削除後のテキストを toInt(&ok) で整数に変換し、変換の成否を ok フラグで確認します。
  • 入力されたテキストから remove(',') を使用してカンマを削除します。
  • valueFromText(const QString &text) const override 関数を再実装しています。
  • CommaSeparatedSpinBox クラスは QSpinBox を継承しています。

例2: 特定のプレフィックスを無視する PrefixIgnoringSpinBox

この例では、特定のプレフィックスが付いたテキストが入力された場合でも、数値部分のみを整数として解釈する QSpinBox のサブクラスを作成します。

#include <QSpinBox>
#include <QString>
#include <QDebug>

class PrefixIgnoringSpinBox : public QSpinBox {
public:
    PrefixIgnoringSpinBox(const QString &prefix, QWidget *parent = nullptr)
        : QSpinBox(parent), m_prefix(prefix) {}

protected:
    int valueFromText(const QString &text) const override {
        QString processedText = text;
        if (processedText.startsWith(m_prefix)) {
            processedText.remove(0, m_prefix.length()); // プレフィックスを削除
        }

        bool ok;
        int value = processedText.toInt(&ok);

        if (ok) {
            return value;
        } else {
            return QSpinBox::valueFromText(text);
        }
    }

    QString textFromValue(int value) const override {
        return m_prefix + QSpinBox::textFromValue(value);
    }

private:
    QString m_prefix;
};

#include <QApplication>
#include <QHBoxLayout>
#include <QLabel>

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

    QWidget window;
    QHBoxLayout layout(&window);

    QLabel label("ドル記号付き入力:");
    PrefixIgnoringSpinBox spinBox("$");
    spinBox.setRange(0, 100);

    layout->addWidget(&label);
    layout->addWidget(&spinBox);

    window.show();

    return a.exec();
}

解説

  • textFromValue() では、値を文字列に変換する際に、再びプレフィックスを付加しています。
  • その後、残りのテキストを整数に変換します。
  • valueFromText() では、入力テキストが m_prefix で始まっているかどうかを startsWith() で確認し、始まっている場合は remove() でプレフィックス部分を削除します。
  • PrefixIgnoringSpinBox クラスは QSpinBox を継承し、コンストラクタで無視するプレフィックス (m_prefix) を受け取ります。

例3: 16進数入力を処理する HexSpinBox

この例では、ユーザーが "0x" で始まる16進数形式のテキストを入力した場合に、それを整数値として解釈する QSpinBox のサブクラスを作成します。

#include <QSpinBox>
#include <QString>
#include <QDebug>

class HexSpinBox : public QSpinBox {
public:
    HexSpinBox(QWidget *parent = nullptr) : QSpinBox(parent) {}

protected:
    int valueFromText(const QString &text) const override {
        QString hexText = text;
        if (hexText.startsWith("0x", Qt::CaseInsensitive)) {
            hexText.remove(0, 2); // "0x" を削除
        }

        bool ok;
        int value = hexText.toInt(&ok, 16); // 16進数として変換

        if (ok) {
            return value;
        } else {
            return QSpinBox::valueFromText(text);
        }
    }

    QString textFromValue(int value) const override {
        return QString("0x%1").arg(value, 0, 16).toUpper();
    }
};

#include <QApplication>
#include <QHBoxLayout>
#include <QLabel>

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

    QWidget window;
    QHBoxLayout layout(&window);

    QLabel label("16進数入力:");
    HexSpinBox spinBox;
    spinBox.setRange(0, 255);

    layout->addWidget(&label);
    layout->addWidget(&spinBox);

    window.show();

    return a.exec();
}
  • textFromValue() では、整数値を "0x" で始まる16進数文字列に変換しています。
  • toInt(&ok, 16) を使用して、残りのテキストを16進数として整数に変換します。
  • valueFromText() では、入力テキストが "0x" (大文字・小文字を区別しない) で始まっているかを startsWith() で確認し、始まっている場合は削除します。
  • HexSpinBox クラスは QSpinBox を継承しています。


QValidator の利用

QValidator クラスを使用すると、QLineEditQSpinBox などの入力ウィジェットに入力されるテキストを検証するためのルールを定義できます。カスタムバリデータを作成することで、特定のフォーマットに合致しない入力を拒否したり、修正したりできます。

#include <QSpinBox>
#include <QLineEdit>
#include <QValidator>
#include <QString>
#include <QDebug>

class CommaSeparatedValidator : public QValidator {
public:
    CommaSeparatedValidator(QObject *parent = nullptr) : QValidator(parent) {}

    State validate(QString &input, int &pos) const override {
        QString cleanedInput = input;
        cleanedInput.remove(',');

        bool ok;
        cleanedInput.toInt(&ok);

        if (ok || cleanedInput.isEmpty()) {
            return Acceptable;
        } else {
            // カンマが適切に配置されていれば中間状態とする
            int commaCount = input.count(',');
            QStringList parts = input.split(',');
            bool intermediate = true;
            for (const QString &part : parts) {
                if (!part.isEmpty() && !part.toInt(&ok)) {
                    intermediate = false;
                    break;
                }
            }
            if (intermediate) {
                return Intermediate;
            }
            return Invalid;
        }
    }
};

#include <QApplication>
#include <QHBoxLayout>
#include <QLabel>

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

    QWidget window;
    QHBoxLayout layout(&window);

    QLabel label("カンマ区切り入力 (Validator使用):");
    QSpinBox spinBox;
    spinBox.setRange(0, 1000000);
    spinBox.lineEdit()->setValidator(new CommaSeparatedValidator(&spinBox));

    layout->addWidget(&label);
    layout->addWidget(&spinBox);

    window.show();

    return a.exec();
}

解説

  • バリデータを QSpinBoxlineEdit() に設定することで、無効な入力が QSpinBox に反映されるのを防ぎます。ただし、この方法では valueFromText() のように直接値を変換するわけではありません。
  • validate() 関数内で、入力テキストからカンマを削除して整数に変換できるかどうかをチェックしています。
  • CommaSeparatedValidatorQValidator を継承し、validate() 関数を再実装しています。

textChanged() シグナルとカスタム処理

QSpinBoxtextChanged(const QString &) シグナルを捕捉し、入力されたテキストが変更されるたびにカスタムの処理を行うことができます。この方法では、テキストを検証し、必要に応じて setValue() を呼び出して QSpinBox の値をプログラム的に設定します。

#include <QSpinBox>
#include <QLineEdit>
#include <QString>
#include <QDebug>
#include <QRegularExpression>

#include <QApplication>
#include <QHBoxLayout>
#include <QLabel>

class MainWindow : public QWidget {
public:
    MainWindow(QWidget *parent = nullptr) : QWidget(parent) {
        QHBoxLayout *layout = new QHBoxLayout(this);
        QLabel *label = new QLabel("プレフィックス付き入力 (textChanged使用):");
        QSpinBox *spinBox = new QSpinBox(this);
        spinBox->setRange(0, 100);

        layout->addWidget(label);
        layout->addWidget(spinBox);

        connect(spinBox->lineEdit(), &QLineEdit::textChanged, this, &MainWindow::handleTextChanged);
        m_spinBox = spinBox;
    }

private slots:
    void handleTextChanged(const QString &text) {
        QRegularExpression regex("^\\$(\\d+)$");
        QRegularExpressionMatch match = regex.match(text);
        if (match.hasMatch()) {
            bool ok;
            int value = match.captured(1).toInt(&ok);
            if (ok) {
                m_spinBox->setValue(value);
            }
        } else if (text.isEmpty() || text == "$") {
            m_spinBox->setValue(0); // 空またはプレフィックスのみの場合は0に設定
        }
        // 無効な入力の場合は、必要に応じて元のテキストに戻すなどの処理を追加できます
    }

private:
    QSpinBox *m_spinBox;
};

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

解説

  • この方法では、QSpinBox の内部的なテキストから値への変換ロジックはそのまま使用されます。
  • マッチした場合、数値部分を抽出し、m_spinBox->setValue() を使用して QSpinBox の値を設定します。
  • handleTextChanged スロット内で、入力されたテキストが特定の正規表現 (^\$(\\d+)$) に合致するかどうかをチェックしています(例: "$123")。
  • QLineEdit::textChanged シグナルを handleTextChanged スロットに接続しています。

editingFinished() シグナルとカスタム処理

ユーザーがラインエディットの編集を終えたとき(通常は Enter キーを押すか、フォーカスが移動したとき)に発生する editingFinished() シグナルを捕捉し、その時点のテキストを検証して setValue() を呼び出す方法です。

#include <QSpinBox>
#include <QLineEdit>
#include <QString>
#include <QDebug>

#include <QApplication>
#include <QHBoxLayout>
#include <QLabel>

class MainWindow : public QWidget {
public:
    MainWindow(QWidget *parent = nullptr) : QWidget(parent) {
        QHBoxLayout *layout = new QHBoxLayout(this);
        QLabel *label = new QLabel("カスタム処理 (editingFinished使用):");
        QSpinBox *spinBox = new QSpinBox(this);
        spinBox->setRange(0, 100);

        layout->addWidget(label);
        layout->addWidget(spinBox);

        connect(spinBox->lineEdit(), &QLineEdit::editingFinished, this, &MainWindow::handleEditingFinished);
        m_spinBox = spinBox;
    }

private slots:
    void handleEditingFinished() {
        QString text = m_spinBox->lineEdit()->text();
        QString cleanedText = text;
        cleanedText.remove('$'); // ドル記号を削除

        bool ok;
        int value = cleanedText.toInt(&ok);
        if (ok) {
            m_spinBox->setValue(value);
        } else {
            // 無効な入力の場合の処理(例: 元のテキストに戻す)
            m_spinBox->lineEdit()->setText(QString::number(m_spinBox->value()));
        }
    }

private:
    QSpinBox *m_spinBox;
};

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

解説

  • 変換に成功した場合は setValue() を呼び出します。失敗した場合は、必要に応じてエラー処理を行います(上記の例では元の値に戻しています)。
  • handleEditingFinished スロット内で、編集が完了したテキストを取得し、プレフィックス(例: "$")を削除してから整数に変換を試みます。
  • QLineEdit::editingFinished シグナルを handleEditingFinished スロットに接続しています。

イベントフィルタの利用

QSpinBox またはその QLineEdit に対してイベントフィルタをインストールすることで、キーイベントやフォーカスイベントなどを監視し、特定の入力パターンに対してカスタムの処理を行うことができます。

  • より低レベルな入力制御が必要な場合
    イベントフィルタが選択肢となります。
  • 入力テキストが変更されるたびにリアルタイムに反応したい場合
    textChanged() シグナルとカスタム処理が適しています。
  • 入力の検証と制限が主な目的の場合
    QValidator が適しています。