QDoubleSpinBox - blockSignals() does not work
-
try
ui->startFrequency->blockSignals(true); ui->startFrequency->setValue(m_startFreqHz / m_startFreqScaleFactor); QMetaObject::invokeMethod(this, [=]()->void{ui->startFrequency->blockSignals(false);}, Qt::QueuedConnection);
it should work, but its a hacky workaround
I would consider other paths
-
This works, but if it is a hack, it is perhaps not the best solution.
What I would like to achieve in my program is to detect when a user has modified the content of the spin box and to distinguish this from a call to "setValue()". I thought I could use QObject's "setModified()" method whenever the value is changed and just block the signals before calling "setValue()" so that this does not trigger the emission of the "valueChanged()" signal.
Is there a better way to do this? If yes, could you please let me know? I am no Qt expert...
Thanks.
-
@DL5EU I would subclass QDoubleSpinbox and make my own, better suited one:
#include <QApplication> #include <QDebug> #include <QTimer> #include <QDoubleSpinBox> class MyDoubleSpinBox : public QDoubleSpinBox { Q_OBJECT public: MyDoubleSpinBox(QWidget*parent = nullptr) : QDoubleSpinBox(parent) { connect(this, QOverload<double>::of(&MyDoubleSpinBox::valueChanged), this, &MyDoubleSpinBox::onValueChanged); } //new setter void setValueByCode(double value){ m_changedByCode = true; setValue(value); } signals: void valueChangedByUser(double value); void valueChangedByCode(double value); private slots: void onValueChanged(double value){ if(m_changedByCode) emit valueChangedByCode(value); else emit valueChangedByUser(value); m_changedByCode = false; } private: bool m_changedByCode{false}; }; int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QApplication app(argc, argv); MyDoubleSpinBox mdsb; //Test: automatically set value by code via timer QTimer t; QObject::connect(&t, &QTimer::timeout, [&]()->void{mdsb.setValueByCode(mdsb.value()+1);}); //see, if it was changed by hand or by program QObject::connect(&mdsb, &MyDoubleSpinBox::valueChangedByCode, [](double value)->void{qDebug() << "value by code" << value;}); QObject::connect(&mdsb, &MyDoubleSpinBox::valueChangedByUser, [](double value)->void{qDebug() << "value by user" << value;}); t.start(5000); mdsb.show(); return app.exec(); } #include "main.moc"
-
I have noticed that when I subclass QDoubleSpinBox as proposed, the first modification by the user is not recognised as such. This is probably due to the original problem, i.e. that the valueChanged() signal is not processed. In Qt Creator in debug mode I can see that the setter is entered but not the onValueChanged() method of my new object (in response to the call to setValueByCode()). That is the reason why m_changedByCode is still true when onValueChanged() is entered in repsponse to a modification done by the user.
This is what the code of my new double spin box looks like:
.h file:
class GDoubleSpinBox : public QDoubleSpinBox { Q_OBJECT public: explicit GDoubleSpinBox(QWidget *parent = nullptr); public slots: void setValueByCode(double value); signals: void valueChangedByCode(double value); void valueChangedByUser(double value); private: bool m_changedByCode; private slots: void onValueChanged(double value); };
.cpp file:
GDoubleSpinBox::GDoubleSpinBox(QWidget *parent) : QDoubleSpinBox(parent) , m_changedByCode(false) { connect(this, QOverload<double>::of(&GDoubleSpinBox::valueChanged), this, &GDoubleSpinBox::onValueChanged); } void GDoubleSpinBox::setValueByCode(double value) { m_changedByCode = true; setValue(value); } void GDoubleSpinBox::onValueChanged(double value) { if (m_changedByCode) { emit valueChangedByCode(value); } else { emit valueChangedByUser(value); } m_changedByCode = false; }
Any idea?
-
@DL5EU I can't see any obvious problems,
But my example is a compile ready one (you just throw it into a main.cpp and hit compile) and when I test it there, the first edit by a use is registered as an edit by the user.
Have you tried your class in a standalone project to verify it works as expected? It my be an other part of your code, thats the issue here
-
@DL5EU said in QDoubleSpinBox - blockSignals() does not work:
In Qt Creator in debug mode I can see that the setter is entered but not the onValueChanged() method of my new object
I cannot be sure whether this is your case but: Assuming you mean you placed a breakpoint and run under debugger, you will find that does not work right on a spin box. I discovered this (at great cost!) a while ago. It is because
QSpinBox::onValueChanged()
uses an internal timer to do its work, and when you slow things down by debugging it "breaks" how it works.Here is the way I wrote to connect the
QSpinBox::valueChanged()
signal so that I can break in the debugger if necessary:template <typename Context, typename Method> QMetaObject::Connection connectSpinBoxValueChanged(QSpinBox *spin, Context slotObject, Method method) { // connect `spin->valueChanged(int i)` signal to slot // see https://forum.qt.io/topic/113606/qspinbox-valuechanged-with-debugger-breakpoint-brain-damaged and https://bugreports.qt.io/browse/QTBUG-14259 // for why `Qt::QueuedConnection` is specified here return QObject::connect(spin, QOverload<int>::of(&QSpinBox::valueChanged), slotObject, method, Qt::QueuedConnection); }
which is what I now use, whether debugging or not. You can read up on the whys & wherefores in my https://forum.qt.io/topic/113606/qspinbox-valuechanged-with-debugger-breakpoint-brain-damaged and https://bugreports.qt.io/browse/QTBUG-14259.
-
This works for me, with Qt 5.15.5 on macOS
#include <QtGlobal> #include <QVBoxLayout> #include <QWidget> #include <QApplication> #include <QDoubleSpinBox> #include <QPushButton> #include <QDebug> int main(int argc, char *argv[]) { QApplication a(argc, argv); QDoubleSpinBox *box = new QDoubleSpinBox; QPushButton *button = new QPushButton(box->signalsBlocked() ? "true" : "false"); QObject::connect(button, &QPushButton::clicked, [box, button]() { bool isBlocked = box->signalsBlocked(); box->blockSignals(!isBlocked); button->setText(!isBlocked ? "true" : "false"); }); QObject::connect(box, qOverload<double>(&QDoubleSpinBox::valueChanged), [](double value) { qDebug() << "value" << value; }); QPushButton *button2 = new QPushButton("set to 1.0"); QObject::connect(button2, &QPushButton::clicked, [box](){ box->setValue(1.0);}); QWidget container; QVBoxLayout layout(&container); layout.addWidget(box); layout.addWidget(button); layout.addWidget(button2); container.show(); return a.exec(); }
When button reads "true", using the spinbox or clicking button2 does not result in a debug message. When button reads "false", the spinbox controls generate debug output, and if the value isn't already 1.0, clicking button2 also generates output.
I suspect something else is going on.
-
@J-Hilk: I have tried to compile your program in Qt Creator but main.moc is missing and without it it does not compile. As I wrote, I am no Qt expert and I am used to develop in Qt Creator where I don't have to create .moc files myself. I will set up a project with your code as soon as I have got the time (in a week).
@jeremy_k: your program works as described. Perhaps there is a problem elsewhere that I don't see yet. I will check this when I am back from holidays.
-
@DL5EU said in QDoubleSpinBox - blockSignals() does not work:
Creator but main.moc is missing and without it it does not compile.
it‘s auto generated by qmake, its missing on the first compile, if not explicitly set up in the pro file.
simply hit compile a 2nd time, and it should work.
-
@J-Hilk I had the time now to try out your example. It works indeed as expected. There must be something in my code that prevents the application from working as expected, perhaps a side effect of something that I have not found yet. I will further investigate.
Ralf