2014年3月26日水曜日

QValidatorとboost::functionを使って関数型言語みたいに入力値のチェックを行うようにしてみた.

QValidatorとboost::functionの組み合わせ

はじめに

GUIツールキットのQtでGUIを作成したとき,QLineEditなどの入力フォームからの入力値が適切であるかどうかを判定する必要があります.このとき,QValidatorの機構を用いると入力値が適切であるかのチェックが簡単になります.これは前回「Qt validator 入力フォームの値をチェックする」で実装しました.

今回はboost::functionを利用して,チェック用の関数を個別に指定できるバリデートクラスを作成してみました.

関数を引数にとって判定を行うクラス

int型を対象としたQLambdaIntValidateクラスとdouble型を対象としたQLambdaDoubleValidateクラスの2つを書いてみました.この2つはint型を扱うのか,double型を扱うのかの違い意外に差異はありません.それぞれQIntValidator及びQDoubleValidatorを継承しています.
(関数を受け取るためクラスのためラムダ式のラムダから命名したのですがもっと名前ないかな~~ww)

QLambdaIntValidator

#include <qintvalidator>
#include <qdoublevalidator>
#ifndef Q_MOC_RUN
#include <boost function.hpp>
#endif

class QLambdaIntValidator: public QIntValidator {
    Q_OBJECT
    using QIntValidator::validate;
public:
    explicit QLambdaIntValidator(QObject* parent = 0);
    explicit QLambdaIntValidator(int min, int max, QObject* parent = 0);
    
    /**
     * @param function 確認用関数
     */
    explicit QLambdaIntValidator(boost::function<bool (int)> function, QObject* parent = 0);
    
    /**
     * @param function 確認用関数
     */
    explicit QLambdaIntValidator(boost::function<bool (int)> function, int min, int max, QObject* parent = 0);
    
    /**
     * int型のチェックに加え,チェックに用いる関数を設定します.
     * @param funciton チェックに用いる関数
     */
    void setValidateFunction(boost::function<bool (int)> function);
    
    /**
     * 指定した関数がfalseを返した場合,どのステートを返すかを指定します.
     * @param state false時に用いるステート
     */
    void setStateWhenFalse(QValidator::State state);
    
    /**
     * 入力値をチェックします.
     * @param text
     * @param pos
     */
    virtual QValidator::State validate(QString& text, int& pos) const;
    
private:
    boost::function<bool (int)> validateFunc;
    QValidator::State falseState;
};
プライベートメンバ変数のvalidateFuncにチェック用の関数を代入します.チェック用メソッドであるvalidate()の中で実行されます.
falseStateはvalidateFuncの戻り値がfalseのとき,QValidator::Stateの戻り値をInvalidにするかIntermediateにするかを指定します.
validate()メソッドの中身を以下に記述します.親クラスのvalidate()を実行後,戻り値がInvalidでなければ,指定されたチェック関数を行います.
QValidator::State QLambdaDoubleValidator::validate(QString &text, int &pos) const {
    QIntValidator::State state = QDoubleValidator::validate(text, pos);
    if(state != QValidator::Invalid && !validateFunc.empty()) {
        bool isOk;
        double num = text.toDouble(&isOk);
        state = (validateFunc(num) && isOk ? QValidator::Acceptable : falseState);
    }
    return state;
}

使い方(使う人いないかもだけど..)

チェック用関数を別途定義

/**
* チェック関数の例
* @param num 調べる値
* @return true=偶数, false=奇数
*/
bool isEvenNumber(int num);

このチェック関数を指定します.
QLambdaIntValidator* validator = new QLambdaIntValidator(&isEvenNumber);
lineEdit->setValidator(validator);

チェックの仕方はいつもと変わりません.
int position = 0;
QStrint str = lineEdit->text();
QLambdaIntValidator* validator = (QLambdaIntValidator*)linEdit->validator();
QValidator::State state = validator->validate(str, position);

どうでしょうか?

2014年3月25日火曜日

Qt validator 入力フォームの値をチェックする

QValidatorで入力値をチェックしてみた

GUIツールキットのQtでGUIを作っています.
QLineEditで入力フォームを作ったときに,入力された値が適切であるかどうかをQtのQValidatorを使って確認するサンプルを作成してみました.

入力値をチェックする機能を自作していたのですが,QtにはQValidatorという便利な機能があることを初めて知りました.コレを使うとスッキリと入力値がチェック出来そうです.

まずQtデザイナを使って次のようなウィンドウを作成しました.
3つのQLineEditを持つウィンドウです.それぞれ次のような役割とします.

ラベルobjectName概要
int form intLineEdit20から500までの整数を入力するフォーム
double form doubleLineEdit浮動小数を入力するフォーム
original form myLineEdit偶数の整数を入力するフォーム

QValidatorは抽象クラスであるため,これを実装したサブクラスを使用します.QtではQIntValidator, QDoubleValidatorとQRegExpValidatorが用意されているみたいですね.
  • QIntValidator 入力値がint型であるかを確認する
  • QDoubleValidator 入力値がdouble型であるかを確認する
  • QRegExpValidator 入力値が指定した正規表現の範囲にあるかを確認する
整数ならQIntValidator, 小数ならQDoubleValidator, 使用できない記号などが含まれていないか確認する場合はQRegExpValidatorと言ったところでしょうか.これ以外にもQValidatorを実装した独自に作成したクラスでもいいですね.

加えて,QIntValidatorとQDoubleValidatorは入力できる数値の範囲も指定できるようです.
// 範囲指定の例: 20から500まで
QIntValidator validator = new QIntValidator(20, 500, this);
ここではintLineEditにQIntValidator, doubleLineEditにQDoubleValidator, myLineEditに独自作成したQEvenIntValidatorを設定します.

// mainwindow.h
class MainWindow: public QMainWindow {
    Q_OBJECT
public:

 /* 省略 */

public slots:
    void intEditTextChanged(const QString& text);
    void doubleEditTextChanged(const QString& text);
    void myEditTextChanged(const QString& text);
private:
    Ui::MainWindow *ui;

    // QValidator群
    QIntValidator *intValidator;
    QDoubleValidator *doubleValidator;
    QEvenIntValidator* evenValidator;

    /**
    * チェックした状態に応じてフォームの背景色を変更します.
    * @param state 状態
    * @param *sender 入力元フォームへのポインタ
    */
    void changeColorBy(QValidator::State state, QLineEdit* sender);
};

これに対してコンストラクタでQValidatorを設定します.setValidator()メソッドで設定するvalidatorのインスタンスを引数に与えます.QIntValidatorとQDoubleValidatorはコンストラクタに入力を受け付ける最小値と最大値を指定できるようです.ここではQIntValidatorに20から500までの数値のみを受け付けるように指定しています.

// mainwindow.cpp
/**
* コンストラクタ
*/
MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent), ui(new Ui::MainWindow) {

    /* 省略 */

    // テキスト変更時のシグナルとスロットを接続する
    connect(ui->intLineEdit, SIGNAL(textChanged(const QString&)), this, SLOT(intEditTextChanged(const QString&)));
    connect(ui->doubleLineEdit, SIGNAL(textChanged(const QString&)), this, SLOT(doubleEditTextChanged(const QString&)));
    connect(ui->myLineEdit, SIGNAL(textChanged(const QString&)), this, SLOT(myEditTextChanged(const QString&)));

    // QIntValidatorをintLineEditに設定する.
    intValidator = new QIntValidator(20, 500, this);
    ui->intLineEdit->setValidator(intValidator);

    // QDoubleValidatorをdoubleLineEditに設定する.
    doubleValidator = new QDoubleValidator(this);
    ui->doubleLineEdit->setValidator(doubleValidator);

    // 独自に作成したQEventIntValidatorをmyLineEditに設定する.
    evenValidator = new QEvenIntValidator(this);
    ui->myLineEdit->setValidator(evenValidator);
}

そして,各入力フォームのテキスト変更スロットで入力値の確認を行う部分をコーディングします.QValidatorのvalidate()メソッドで入力値が適切かどうかを取得します.validate()はQValitor::Stateの列挙対を返します.それぞれ
  • Acceptable
  • Intermediate
  • Invalid
です.それぞれ入力値が○,△,×と言ったところでしょうか.数値のように判定が容易な物はAcceptableとInvalidときれいに判定できますが,文字列などの場合は判定に曖昧さが残るため,中間のIntrermediateを使うみたいですね.


QLineEditのvalidator()メソッドで設定しておいたQValidatorへのポインタが取得できます.
ここでは入力値が適切であるかを確認し,その結果に応じてフォームの背景色を変えるようにしました.
// mainwindow.cpp
void MainWindow::intEditTextChanged(const QString& text) {
    QString str = text;
    int position = 0;
    QIntValidator *validator = (QIntValidator*)ui->intLineEdit->validator();
    QValidator::State state = validator->validate(str, position);
    changeColorBy(state, ui->intLineEdit);
}

void MainWindow::doubleEditTextChanged(const QString &text) {
    QString str = text;
    int position = 0;
    QDoubleValidator* validator = (QDoubleValidator*)ui->doubleLineEdit->validator();
    QValidator::State state = ui->doubleLineEdit->validator()->validate(str, position);
    changeColorBy(state, ui->doubleLineEdit);
}

void MainWindow::myEditTextChanged(const QString &text) {
    QString str = text;
    int position = 0;
    QEvenIntValidator* validator = (QEvenIntValidator*)ui->myLineEdit->validator();
    QValidator::State state = validator->validate(str, position);
    changeColorBy(state, ui->myLineEdit);
}

changeColorBy()でフォームの背景色を次のように変えるようにしました.

// mainwindow.cpp
void MainWindow::changeColorBy(QValidator::State state, QLineEdit *sender) {
    switch(state) {
    case QValidator::Acceptable:
        sender->setStyleSheet("background: white");
        break;
    case QValidator::Intermediate:
        sender->setStyleSheet("background: yellow");
        break;
    case QValidator::Invalid:
        sender->setStyleSheet("background: pink");
        break;
    }
}

独自作成したQEvenIntValidatorは偶数の整数のみを受け付けるクラスです.QIntValidatorを継承しました.
//qevenintvalidator.h
class QEvenIntValidator : public QIntValidator
{
    Q_OBJECT
public:
    explicit QEvenIntValidator(QObject *parent = 0);
    QValidator::State validate(QString& text, int pos);
};
// qevenintvalidator.cpp
QEvenIntValidator::QEvenIntValidator(QObject *parent) :
    QIntValidator(parent) {
}

QValidator::State QEvenIntValidator::validate(QString &text, int pos) {
    QValidator::State state = QIntValidator::validate(text, pos);
    if(state != QValidator::Invalid) {
        int num = text.toInt();
        state = (num % 2 == 0 ? QValidator::Acceptable : QValidator::Intermediate);
    }
    return state;
}

実際作成してみたら,全てのValidatorが数値を扱うための物であることが原因なのか,フォームに数字以外の文字を入力できなくなってしまいました.ま,いっかww.

サンプルコード

ここで作成したサンプルコードはgithubにアップロードしました.
https://github.com/hiroyky/qt_validator_sample

参考

http://qt-project.org/doc/qt-4.8/qvalidator.html
http://qt-project.org/doc/qt-4.8/qintvalidator.html
http://qt-project.org/doc/qt-4.8/qlineedit.html