Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to remove the setfill('0') characteristic of a QDoubleSpinBox in Qt?

Tags:

c++

qt

It appears that Qt by default sets the precision of the entered number to 2 decimals places. If decimals = 2 and a user inputs a number such as 4, it will display 4.00 to the user. Is it possible for it to JUST display 4 but with the option for the user to edit it up to 2 decimal places and display the updated value accordingly?

like image 489
Billy Bob Avatar asked Jan 29 '23 17:01

Billy Bob


2 Answers

The first sample code I wrote was intended to "come in touch" with the problem:

#include <QtWidgets>

int main(int argc, char **argv)
{
  qDebug() << "Qt Version:" << QT_VERSION_STR;
  QApplication app(argc, argv);
  // setup GUI
  QDoubleSpinBox qEdit;
  qEdit.show();
  // install signal handler
  QObject::connect(&qEdit,
    (void (QDoubleSpinBox::*)(double))&QDoubleSpinBox::valueChanged,
    [&](double value){ qDebug() << "qEdit.value:" << qEdit.value(); });
  // run application
  return app.exec();
}

What I hadn't expected (though it's reasonable): You cannot input more decimal digits than configured.

The alternative behavior would've been: You can input as many decimal digits as you want. The input is parsed and considered completely (so that internally the value is stored with full precision). The output is limited to the configured digits but as long as you don't edit it again the hidden decimals are still active.

The behavior of the QDoubleSpinBox may be changed by making a derived class DoubleSpinBox and overloading the respective methods.

One of these methods is
virtual QValidator::State validate(QString &text, int &pos) const override.
As the Qt. doc. didn't help much to accomplish this I had a look into the sourcecode of class QDoubleSpinBox on woboq.org:

QValidator::State QDoubleSpinBox::validate(QString &text, int &pos) const
{
    Q_D(const QDoubleSpinBox);
    QValidator::State state;
    d->validateAndInterpret(text, pos, state);
    return state;
}

So, the actual work is done by QDoubleSpinBoxPrivate::validateAndInterpret() which has (at the time of this writing) 148 lines of code - heavy stuff.

To provide a proof of concept, I decided to make this a little bit shorter and less perfect:

QValidator::State DoubleSpinBox::validate(QString &text, int&) const
{
  bool ok = false;
  locale().toDouble(text, &ok);
  return ok ? QValidator::Acceptable : QValidator::Invalid;
}

Compiling and testing again showed that values are accepted now. Now, it is possible to input e.g. 4.1234. While typing this, the output was:

4
4.1
4.12
0

Hence, the values are accepted but not yet stored internally. To achieve this, the method
virtual double valueFromText(const QString &text) const
has to be overloaded, also:

double DoubleSpinBox::valueFromText(const QString &text) const
{
  bool ok = false;
  double value = locale().toDouble(text, &ok);
  return ok ? value : QDoubleSpinBox::value();
}

Compiling and testing again showed that input of 4.1234 is now accepted and stored. The output was:

4
4.1
4.12
4.123
4.1234

When I pressed the up-arrow button, the display changed to 5.12. However, the internal value still had full precision as visible in output:

5.1234

The complete sample code testQDoubleSpinBox.cc:

#include <QtWidgets>

// a class for a customized QDoubleSpinBox
class DoubleSpinBox: public QDoubleSpinBox {
  public:
    // constructor.
    explicit DoubleSpinBox(QWidget *pQParent = nullptr):
      QDoubleSpinBox(pQParent)
    { }
    // destructor.
    virtual ~DoubleSpinBox() = default;
    // disabled:
    DoubleSpinBox(const DoubleSpinBox&) = delete;
    DoubleSpinBox& operator=(const DoubleSpinBox&) = delete;

  public:
    /* converts value from text.
     *
     * Overloaded to process accepted input correctly.
     */
    virtual double valueFromText(const QString &text) const override;

  protected:

    /* determines whether input is valid.
     *
     * Overloaded to change accepted input.
     */
    virtual QValidator::State validate(QString &text, int &pos) const override;

};

QValidator::State DoubleSpinBox::validate(QString &text, int&) const
{
  bool ok = false;
  locale().toDouble(text, &ok);
  return ok ? QValidator::Acceptable : QValidator::Invalid;
}

double DoubleSpinBox::valueFromText(const QString &text) const
{
  bool ok = false;
  double value = locale().toDouble(text, &ok);
  return ok ? value : QDoubleSpinBox::value();
}

int main(int argc, char **argv)
{
  qDebug() << "Qt Version:" << QT_VERSION_STR;
  QApplication app(argc, argv);
  // setup GUI
  DoubleSpinBox qEdit;
  qEdit.show();
  // install signal handlers
  QObject::connect(&qEdit,
    (void (QDoubleSpinBox::*)(double))&QDoubleSpinBox::valueChanged,
    [&](double){ qDebug() << "qEdit.value:" << qEdit.value(); });
  // run application
  return app.exec();
}

The Qt project file testQDoubleSpinBox.pro:

SOURCES = testQDoubleSpinBox.cc

QT += widgets

Sample session in cygwin on Windows 10:

$ qmake-qt5 testQDoubleSpinBox.pro

$ make
g++ -c -fno-keep-inline-dllexport -D_GNU_SOURCE -pipe -O2 -Wall -W -D_REENTRANT -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -I. -isystem /usr/include/qt5 -isystem /usr/include/qt5/QtWidgets -isystem /usr/include/qt5/QtGui -isystem /usr/include/qt5/QtCore -I. -I/usr/lib/qt5/mkspecs/cygwin-g++ -o testQDoubleSpinBox.o testQDoubleSpinBox.cc
g++  -o testQDoubleSpinBox.exe testQDoubleSpinBox.o   -lQt5Widgets -lQt5Gui -lQt5Core -lGL -lpthread 

$ ./testQDoubleSpinBox 
Qt Version: 5.9.2
qEdit.value: 4
qEdit.value: 4.1
qEdit.value: 4.12
qEdit.value: 4.123
qEdit.value: 4.1234
qEdit.value: 5.1234

snapshot of testQDoubleSpinBox

like image 86
Scheff's Cat Avatar answered Jan 31 '23 05:01

Scheff's Cat


Yes you can. You need to reimplement QDoubleSpinBox though.

By creating a custom class that extends the spinbox, you can override QDoubleSpinBox::textFromValue. This method as the name suggests is used to convert the double to a string representation.

Possible implementation:

QString MyDoubleSpinBox::textFromValue(double value) const
{
    return QLocale().toString(value, 'g', QLocale::FloatingPointShortest);
}
like image 29
Felix Avatar answered Jan 31 '23 06:01

Felix