Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

QDoubleSpinBox - blockSignals() does not work



  • Dear all,

    I have got a problem with the QDoubleSpinBox widget.

    In order not to emit the valueChanged() signal when I set the value, I have done this in my "set" function:

        ui->startFrequency->blockSignals(true);
        ui->startFrequency->setValue(m_startFreqHz / m_startFreqScaleFactor);
        ui->startFrequency->blockSignals(false);
    

    However, although I block the signals before I set the value, the valueChanged() signal is emitted. The connection to the slot is done like this:

    connect(ui->startFrequency, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &FrequencyRangeDialog::startFrequencyChanged);
    

    Strangely, when I do not execute

        ui->startFrequency->blockSignals(false);
    

    the valueChanged() signal is not emitted. This means to me that the lock is released before the signal is emitted, but why? What am I doing wrong?

    The Qt version is 5.15.2 (open source version) on Windows 10.

    Thank you for your help,

    Ralf



  • 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.


  • Lifetime Qt Champion

    @DL5EU said in QDoubleSpinBox - blockSignals() does not work:

    This means to me that the lock is released before the signal is emitted, but why?

    Because a signal is not emitted immediately, but next time the event loop is running, which is not before your code was executed (so, after ui->startFrequency->blockSignals(false);).



  • Two comments:

    1. In this case, what is blockSignals() good for if it does not have the desired effect and how can I achieve what I need?
    2. Even if I add
    QApplication::processEvents();
    

    before unblocking the signals it does not work as expected.


  • Moderators

    @DL5EU

    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.


  • Moderators

    @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"
    


  • That is of course a solution but I thought I could avoid subclassing QDoubleSpinBox. The more you can use existing components the less code you have to maintain. However, I think I will try this way.

    Thank you very much,

    Ralf



  • 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?


  • Moderators

    @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.



  • No, I have not tried it stand alone yet but I will do so later this day.



  • Thanks @JonB, I will try this too.



  • 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.


  • Moderators

    @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


Log in to reply