DoubleValidator is not checking the ranges properly
Asked Answered
E

5

6

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.

Especial answered 3/2, 2016 at 13:34 Comment(3)
The double validator prevents invalid values to be accepted when enter is pressed. Is that not what you want?Lefton
@xsquared Actually, if I type 500 the TextField shows that value and if I press enter or change to another TextField in the window, the data is not cleared. Do you have a little example to test what you said?Especial
I checked the documentation again and indeed found the problem. Please read my answer below, I hope that it helps you.Lefton
L
11

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

};
Lefton answered 5/2, 2016 at 12:14 Comment(6)
Very good answer! However I ran into a few problems problems which I'd like to share. MyValidator should be TextFieldDoubleValidator and return QValidator::Invalid; is outside the function validate. I need to integrate this new component into my QML application but I had some compilation errors. To solve them, I had to add a new constructor TextFieldDoubleValidator (QObject * parent = 0) : QDoubleValidator(parent) {}. Please, give me your opinion on all this.Especial
Ah thank you I integrated your corrections, they are all valid.Lefton
Any idea why out-of-bounds input is a valid as an intermediate state for DoubleValidator but not for IntValidator?Denotation
The documentation says otherwise. What exactly is your out-of-bounds value and what’s your range?Lefton
@x Sorry, didn't see your comment, and I have forgotten the exact cases. I think it even permitted '-' for positive numbers, though, and '9' for values between 5 and 7.Denotation
@KyleStrand, "Any idea why out-of-bounds..." First of all, it is not true (at least in Qt6) you can type, f.e., 3000000000 (which is greater than 2^31 - 1). Second of all, this is because look at the following: you have a double validator set to your input with, say, maximum of 100.0, then you typed 1000, this is intermediate because you can type 0. in front of it. More over, you can type e-6 at the end (provided you have scientific notation).Citystate
B
4

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.

Basso answered 30/11, 2019 at 5:45 Comment(1)
IDK if it's only me but the behaviour is annoying. You can't even clear the input, for example.Citystate
E
3

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
        }
    }
}
Especial answered 8/2, 2016 at 13:10 Comment(0)
N
3

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)
}}
Nightclub answered 27/2, 2018 at 7:51 Comment(1)
Please correct me if I'm wrong, but I think there are 2 mistakes in above code. 1) TextFieldInput is not a valid QML component. 2) You never used the setValue() function. BTW when I changed these 2 things the code worked like a charm. Thanks a ton for that!! :)Metamorphose
P
1

To reject the input, you must use RegularExpressionValidator in TextField This would only accept -359.9 to 359.9

    TextField {
    width: 200
    height: 40
    anchors.centerIn: parent

    validator: RegularExpressionValidator {
        regularExpression: /(-?3[0-5][0-9]?|[1-2]?[0-9][0-9])([.]\d{1})?/
        }
    }
Puzzlement answered 19/9, 2024 at 16:48 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.