DoubleValidator is not checking the ranges properly

7.2k views Asked by At

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.

4

There are 4 answers

6
x squared On BEST ANSWER

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;
    }

};
0
Tarod On

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
        }
    }
}
1
Jeff Spicer On

Here is a pure qml version:

TextFieldInput{
validator: DoubleValidator
{
    bottom: minimumValue
    top: maximumValue
}

property real maximumValue: constants._MAXIMUM_INTEGER
property real minimumValue: constants._MINIMUM_INTEGER
property string previousText: ""

onTextChanged:
{
    var numericValue = getValue()
    if (numericValue > maximumValue || numericValue < minimumValue)
    {
        text = previousText
    }
    previousText = text
}

function setValue(_value)
{
    text = String(_value)
}

function getValue()
{
    return Number(text)
}}
1
cang hai On

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.