Question

I have a form with two spinboxes which must be connected because of aspect ratios for width and height. When I click on first spinbox and increase/decrease value, the other, second spinbox should change its value to be in ratio with the first spinbox. I've already done the ratio connections, but there is problem, because I connect both spinboxes on SLOT valueChanged(int) and this method block whole program because endless loop. This means that when I increase value to first spinbox, value change first for this one and then for second one which is again calling the first one.

I would like to solve this problem, so when I click on one of spinboxes, to change both values in right way without endless loop.

So there is the code:

void MainWindow::on_sbHeight_valueChanged(int arg1)
{
    if (arg1 != 0) {
    if (ui->radioRatio1->isChecked()) {
        ui->sbWidth->setValue((arg1/8)*2);
    } else if (ui->radioRatio2->isChecked()) {
        ui->sbWidth->setValue((arg1/14)*3);
    }
    } else {
    ui->sbWidth->setValue(arg1);
    }
}

void MainWindow::on_sbWidth_valueChanged(int arg1)
{
    if (arg1 != 0) {
    if (ui->radioRatio1->isChecked()) {
        ui->sbHeight->setValue((arg1/2)*8);
    } else if (ui->radioRatio2->isChecked()) {
        ui->sbHeight->setValue((arg1/3)*14);
    }
    } else {
    ui->sbHeight->setValue(arg1);
    }
}
Was it helpful?

Solution

I think, the best solution here would be to block signals from spinboxes, before changing their values in slots:

I usually use the helper class like this:

class SignalsBlocker
{
public:
    SignalsBlocker(QObject* ptr):
    _ptr(ptr)
    {
        _b = ptr->blockSignals(true);
    }
    ~SignalsBlocker()
    {
        _ptr->blockSignals(_b);
    }

private:
    QObject* _ptr;
    bool _b;
};

So you can write

void MainWindow::on_sbHeight_valueChanged(int arg1)
{
    SignalsBlocker block(ui->sbWidth);
    if (arg1 != 0) {
    if (ui->radioRatio1->isChecked()) {
        ui->sbWidth->setValue((arg1/8)*2);
    //.....
}

void MainWindow::on_sbWidth_valueChanged(int arg1)
{
    SignalsBlocker block(ui->sbHeight);
    if (arg1 != 0) {
    if (ui->radioRatio1->isChecked()) {
        ui->sbHeight->setValue((arg1/2)*8);
    //....
}

The straightforward solution one can provide is

void foo(QObject* object)
{
    object->blockSignals(true); 
    // some stuff
    object->blockSignals(false);
}

However, this solution is not correct: imagine the follwing situation

QObject* obj;
obj->blockSignals(true);
foo(obj);
//some other stuff
obj->blockSignals(false);

One could hope signals will be unblocked after some otherstuff, but actually they will be unblocked inside foo function, that is not intended behavior. That is why you should save block sate and then restore it.

But again, RAII helper class is the most convenient solution that reduces code complexity.


Also note,that your calculations with integers, like

(arg1/8)*2

are not accurate at all.

For example, let arg1 = 6. Then arg1/8 is 0, and (arg1/8)*2 results in 0.

Just changing the order of calculations can increase accuracy:

 (arg1 * 2) / 8

 arg1 = 6
 arg1 * 2 = 12
 (arg1 * 2 / 8) = 1

OTHER TIPS

@lol4t0 solution is now in Qt since version 5.3 with QSignalBlocker. Meaning QSignalBlocker can be used exactly like SignalsBlocker from @lol4t0. It blocks signals on creation and restores the previous block status when it gets destroyed.

Example from @lol4t0 but with the Qt class:

void MainWindow::on_sbHeight_valueChanged(int arg1)
{
    QSignalBlocker block(ui->sbWidth);
    if (arg1 != 0) {
    if (ui->radioRatio1->isChecked()) {
        ui->sbWidth->setValue((arg1/8)*2);
    //.....
}

void MainWindow::on_sbWidth_valueChanged(int arg1)
{
    QSignalBlocker block(ui->sbHeight);
    if (arg1 != 0) {
    if (ui->radioRatio1->isChecked()) {
        ui->sbHeight->setValue((arg1/2)*8);
    //....
}

http://doc.qt.io/qt-5/qsignalblocker.html

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top