Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DoubleValidator is not checking the ranges properly

Tags:

c++

qt

qml

qt5.4

Let me use an example to explain the issue.

If we have a TextField like this,

TextField {
    text: "0.0"
    validator: DoubleValidator { bottom: -359.9;
        top: 359.9;
        decimals: 1;
        notation: DoubleValidator.StandardNotation }

    onEditingFinished: {
        console.log("I'm here!");
    }                    
}

We can type numbers such as 444.9, 399.9 or -555.5. As you can see, the values are not between -359.9 and 359.9.

In the documentation we can find the following information:

Input is accepted but invalid if it contains a double that is outside the range or is in the wrong format; e.g. with too many digits after the decimal point or is empty.

I thought DoubleValidator didn't accept this kind of things, but unfortunately it does.

So I suppose the solution would be to check the final input, but again we have a problem: editingFinished is only emitted if the validator returns an acceptable state and this is not always the case.

Perhaps I'm not doing a good approach, I'm not understanding how to use DoubleValidator or maybe I need some code in C++.

By the way, I'm working with Qt 5.4.

like image 676
Tarod Avatar asked Feb 03 '16 13:02

Tarod


3 Answers

The problem lies in the fact that QML TextField accepts intermediate input:

validator : Validator

Allows you to set a validator on the TextField. When a validator is set, the TextField will only accept input which leaves the text property in an intermediate state. The accepted signal will only be sent if the text is in an acceptable state when enter is pressed.

The validate()-function of QDoubleValidator describes when it returns QValidator::Intermediate:

State QValidator::validate(QString & input, int & pos) const

This virtual function returns Invalid if input is invalid according to this validator's rules, Intermediate if it is likely that a little more editing will make the input acceptable (e.g. the user types "4" into a widget which accepts integers between 10 and 99), and Acceptable if the input is valid.

So that means, the validator returns QValidator::Intermediate, as long as a double value is entered and because TextField is okay with "intermediate", you can type anything as long as it is a number.

What you can do is to subclass QDoubleValidator and to override validate(), so that it does not return Intermediate when the values are out of bounds:

class TextFieldDoubleValidator : public QDoubleValidator {
public:
    TextFieldDoubleValidator (QObject * parent = 0) : QDoubleValidator(parent) {}
    TextFieldDoubleValidator (double bottom, double top, int decimals, QObject * parent) :
    QDoubleValidator(bottom, top, decimals, parent) {}

    QValidator::State validate(QString & s, int & pos) const {
        if (s.isEmpty() || (s.startsWith("-") && s.length() == 1)) {
            // allow empty field or standalone minus sign
            return QValidator::Intermediate;
        }
        // check length of decimal places
        QChar point = locale().decimalPoint();
        if(s.indexOf(point) != -1) {
            int lengthDecimals = s.length() - s.indexOf(point) - 1;
            if (lengthDecimals > decimals()) {
                return QValidator::Invalid;
            }
        }
        // check range of value
        bool isNumber;
        double value = locale().toDouble(s, &isNumber);
        if (isNumber && bottom() <= value && value <= top()) {
            return QValidator::Acceptable;
        }
        return QValidator::Invalid;
    }

};
like image 99
x squared Avatar answered Oct 22 '22 20:10

x squared


I found an easier way.

TextField {
    id: control
    onTextChanged:
    {
        if(!acceptableInput)
            control.undo()
    }
}

When text in TextField is invalid, acceptableInput would change to false, so when text changed ,check the property, if it's false, then call undo() to undo the changes.

like image 42
cang hai Avatar answered Oct 22 '22 20:10

cang hai


The answer provided by @xsquared was perfect. I think it's a good idea to share with anyone curious how to integrate the solution with QML.

TextFieldDoubleValidator is the class which @xsquared suggested.

So the first thing is to register the new type in our main.

main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtQml>
#include "textfielddoublevalidator.h"

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);
    qmlRegisterType<TextFieldDoubleValidator>("TextFieldDoubleValidator", 1,0,
                                              "TextFieldDoubleValidator");

    QQmlApplicationEngine engine;
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

    return app.exec();
}

After that, we can use the new type in our QML application:

main.qml

import QtQuick 2.5
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
import TextFieldDoubleValidator 1.0

Window {
    visible: true

    // DoubleValidator doesn't clear the TextField when
    // text > 359.9 or < -359.9
    TextField {
        text: "0.0"
        validator: DoubleValidator {
            bottom: -359.9;
            top: 359.9;
            decimals: 1;
            notation: DoubleValidator.StandardNotation
        }
    }

    // Solution: use your own DoubleValidator.
    // This works OK and text is cleared out when
    // text > 359.9 or < -359.9
    TextField {
        y: 50
        text: "0.0"
        validator: TextFieldDoubleValidator {
            bottom: -359.9;
            top: 359.9;
            decimals: 1;
            notation: DoubleValidator.StandardNotation
        }
    }
}
like image 34
Tarod Avatar answered Oct 22 '22 18:10

Tarod