Allow entry in QLineEdit only within range of QDoubleValidator
Asked Answered
P

8

6

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?

Pears answered 24/10, 2013 at 16:5 Comment(1)
This style of handling user input will make your users pull their hair out. Better to implement fixup() in a QValidator subclass #39552626Novak
S
12

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;
        }
    }
};
Shun answered 24/10, 2013 at 16:36 Comment(2)
I adapted this to save the min and max as members since they vary for my different line edits, and it works like a charm. It does have trouble, however, with allowing a user to enter the initial negative sign for a negative number. I'm thinking that might just be the cost of this solution..Pears
Thank you! I have wasted a day on QDoubleValidator, setNotation(QDoubleValidator::StandardNotation), and QLineEdit input masks. None of it behaved as I expected. It would have been quicker to implement old-school-parse character by character entry. Thank you for a solution that actually works! (I made a few changes to allow "+" in addition to "-", fixed char after decimal count, and made range test inclusive of top and bottom )Grout
C
4

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.

Contretemps answered 18/6, 2015 at 10:24 Comment(0)
E
3

I tried the excellent class above and it still needs a couple edits. The decimal point search was reducing the range specified by "top" because it returned a "-1" when there is no decimal point. I added a conditional statement that fixes that.

Also, it still needs to be tweaked for the case where the user tries to delete the decimal point and the resulting value is larger than the range. Right now it just prohibits that behavior rather than changing it to the maximum value which seems more intuitive to me.

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

        QLocale locale;

        QChar decimalPoint = locale.decimalPoint();
        int charsAfterPoint = s.length() - s.indexOf(decimalPoint) -1;

        if (charsAfterPoint > decimals() && s.indexOf(decimalPoint) != -1) {
            return QValidator::Invalid;
        }

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

        if (ok && d >= bottom() && d <= top()) {
            return QValidator::Acceptable;
        } else {
            return QValidator::Invalid;
        }
    }
};
Erda answered 1/9, 2014 at 23:26 Comment(0)
H
3

I spent almost a day trying to make QDoubleValidator work with reasonable user feedback when checking for acceptable range of QLineEdit input. My attempts to use Qt prescribed validator::fixup() turned out to be a waste of time. Earlier answers in this thread are much more useful but still have shortcomings. In the end I opted for a different and simpler approach.

  1. Equip QLineEdit with QDoubleValidator which performs no range checking.
  2. In a handler for QLineEdit editingFinished signal do range checking and if necessary reset of QLineEdit text.

This approach disallows typing of illegal characters, takes care of localization and corrects values outside of desired range.

Works well for me.

Halliehallman answered 2/5, 2015 at 15:31 Comment(0)
F
1

The answer of VVV works great for the orignal question of nicole. This is when the range is from negative to positive.

However as a general solution for QDoubleValidator it has one side effect when the range is from positive to positive:

Example: Range: [87.5 ... 1000.0], Input: "15" (as intermediate to reach the value 150)

The input will be declined when the QLineEdit goes under the lower limit (or starts empty). Hence I extended the solution of VVV for a general solution:

/*
 * Empty string and the negative sign are intermediate
 */
if( input.isEmpty() || input == "-" )
{
    return QValidator::Intermediate;
}
/*
 * Check numbers of decimals after the decimal point
 * and the number of decimal points
 */
QChar decimalPoint = locale().decimalPoint();
if( input.count( decimalPoint, Qt::CaseInsensitive ) > 1 )
{
    return QValidator::Invalid;
}
else if( input.indexOf( decimalPoint ) != -1)
{
    const int charsAfterPoint = input.length() - input.indexOf( decimalPoint) - 1;
    if( charsAfterPoint > decimals() )
    {
        return QValidator::Invalid;
    }
}
/*
 * Check for valid double conversion and range
 */
bool ok;
const double d = locale().toDouble( input, &ok );
if( ok && d <= top() )
{
    if( d >= bottom() )
    {
        return QValidator::Acceptable;
    }
    else
    {
        return QValidator::Intermediate;
    }
}
else
{
    return QValidator::Invalid;
}
Fontanel answered 28/10, 2016 at 11:23 Comment(0)
D
0

I came across this solution when searching for a solution, which supports scientific as well as standard notation. It is inspired by the suggestion by Petri Pyöriä, here is a solution, which uses the signal editingFinished.

I have overloaded validate to ensure that QValidator::Acceptable is returned even when the value is out of range. This triggers the editingFinished, which I use for truncating the output. In this way, both Scientific and Standard notation can be used exactly as implemented by QDoubleValidator

#include <QDoubleValidator>

class TruncationValidator : public QDoubleValidator
{
    Q_OBJECT
public:
    explicit TruncationValidator(QObject *parent = 0) : QDoubleValidator(parent) {
      connect(this->parent(), SIGNAL(editingFinished()), this, SLOT(truncate()));
    }
    TruncationValidator(double bottom, double top, int decimals, QObject * parent) : QDoubleValidator(bottom, top, decimals, parent) {
      connect(this->parent(), SIGNAL(editingFinished()), this, SLOT(truncate()));
    }

    QValidator::State validate(QString &s, int &i) const {
      QValidator::State state = QDoubleValidator::validate(s,i);

      if (s.isEmpty()) {
        return state;
      }

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

      if (ok) {
        // QDoubleValidator returns QValidator::Intermediate if out of bounds
        return QValidator::Acceptable;
      }
      return state;
    }

private slots:
    void truncate() {
      QLineEdit* le = dynamic_cast<QLineEdit*>(parent());
      if (le) {
        QString s = le->text();
        bool ok;
        double d = s.toDouble(&ok);
        if (ok) {
          if (d > this->top() || d < this->bottom()) {
            d = std::min<double>(d, this->top());
            d = std::max<double>(d, this->bottom());
            le->setText(QString::number(d));
          }
        }
      }
    }
private:
};
Desberg answered 25/5, 2017 at 22:18 Comment(0)
I
0

Here is a workaround: you can simply use QDoubleSpinBox with its buttonSymbols set to NoButtons, which would looks like a QLineEdit but you can set its range with native setMinimum(double min) and setMaximum(double max).

This method is directly available in Qt Designer.

Ineffaceable answered 4/9, 2020 at 9:3 Comment(0)
V
0

Here's a python version for those using PyQt:

from PyQt5.QtGui import QDoubleValidator, QValidator


class DoubleValidator(QDoubleValidator):
    def __init__(self, *__args):
        super().__init__(*__args)

    def validate(self, p_str, p_int):

        if not p_str:
            return QValidator.Intermediate, p_str, p_int

        if self.bottom() <= float(p_str) <= self.top():
            return QValidator.Acceptable, p_str, p_int
        else:
            return QValidator.Invalid, p_str, p_int
Viridis answered 4/8, 2021 at 19:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.