Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Allow entry in QLineEdit only within range of QDoubleValidator

I have a set of QLineEdits that are supposed to accept double values within a certain range, (e.g., -15 to 15).

I have something along these lines when setting up each:

lineEdit->setValidator(new QDoubleValidator(minVal, maxVal, 5, lineEdit));

Ideally, the line edits would work such that only values in range can be entered. When I tried this out, I noticed that only numbers could be typed, as desired, but that they could still go out of range.

How can I dynamically force the input to fit into the range (e.g., if range is -15 to 15 and user types a 1, then attempts to type a 9, it doesn't work/display the 9...but typing 1 and then 2 does work/display the 2.) ?

Do I need to connect and call the validate() function somewhere?

like image 286
norman Avatar asked Oct 24 '13 16:10

norman


2 Answers

That's because QDoubleValidator returns QValidator::Intermediate if the value is outside the bounds and QLineEdit accepts QValidator::Intermediate values.

To implement the behavior you want you can make your own QDoubleValidator subclass like this:

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

    QValidator::State validate(QString &s, int &i) const
    {
        if (s.isEmpty()) {
            return QValidator::Intermediate;
        }

        bool ok;
        double d = s.toDouble(&ok);

        if (ok && d > 0 && d < 15) {
            return QValidator::Acceptable;
        } else {
            return QValidator::Invalid;
        }
    }
};

UPDATE: This will solve the negative sign issue, and also will accept locale double formats:

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

    QValidator::State validate(QString &s, int &i) const
    {
        if (s.isEmpty() || s == "-") {
            return QValidator::Intermediate;
        }

        QChar decimalPoint = locale().decimalPoint();

        if(s.indexOf(decimalPoint) != -1) {
            int charsAfterPoint = s.length() - s.indexOf(decimalPoint) - 1;

            if (charsAfterPoint > decimals()) {
                return QValidator::Invalid;
            }
        }

        bool ok;
        double d = locale().toDouble(s, &ok);

        if (ok && d >= bottom() && d <= top()) {
            return QValidator::Acceptable;
        } else {
            return QValidator::Invalid;
        }
    }
};
like image 84
VVV Avatar answered Oct 17 '22 15:10

VVV


It is possible to do this also without subclassing.

lineEdit = new QLineEdit();
connect(lineEdit,SIGNAL(textChanged(QString)), this, SLOT(textChangedSlot(QString)));

QDoubleValidator *dblVal = new QDoubleValidator(minVal, maxVal, 1000, lineEdit);
dblVal->setNotation(QDoubleValidator::StandardNotation);
dblVal->setLocale(QLocale::C);
lineEdit->setValidator(dblVal);

Setting of the locale may be important because it defines which characters are interpreted as a decimal separator. Format of the input string defines which locales should be used.

In the textChangedSlot, we can validate input this way:

QString str = lineEdit->text();
int i = 0;
QDoubleValidator *val = (QDoubleValidator *) lineEdit->validator();
QValidator::State st = val->validate(str, i);

if (st == QValidator::Acceptable) {
    // Validation OK
} else {
    // Validation NOK
}

In this case also QValidator::Intermediate state is interpreted as a failed case.

If we connect textChanged -signal to the textChangedSlot, validation is done after every input field change. We could also connect editingFinished() or returnPressed() -signals to the validation slot. In that case, validation is done only when user stops editing the string.

like image 32
Petri Pyöriä Avatar answered Oct 17 '22 15:10

Petri Pyöriä