PDA

View Full Version : UI change vs. programmatic change



ScottBell
10th November 2011, 14:51
Using Qt 4.7.3 and Designer, I placed a spin box in a group box.
In code, when a user makes a change, the value is sent away. When a reply comes back the spin box is written again.
Sometimes an external value change arrives asynchronously.


void MyGroupBox::on_MySpinBox_valueChanged(double d)
{
// (other code)
emit(signal_MyMessage(d));
}

void MyGroupBox::slot_MyExternalValue(double d)
{
// (other code)
ui->MySpinBox->setValue(d);
}

The only relevant signals from a spin box are "editingFinished" or "valueChanged". EditingFinished isn't triggered by the buttons.
I've fiddled quite a bit but can't find a direct way to distinguish the source of the change.
The abstract spinner and other base classes can see mouse presses and other events, but I'm stumbling over the fact that Designer does all its work from within the group box with "ui->".
Sorry this seems like a common programming thing to want to do, and likely another case of C++ ignorance--patience, please.

Thanks, Scott

wysota
11th November 2011, 00:34
So what's the problem exactly?

ChrisW67
11th November 2011, 01:03
I think the spin box value is changing in a way ScottBell is not expecting.

Here are some general options:

Put a breakpoint in the slot that is accepting a value. When it stops there follow the stack backtrace back until you find the emitter.
In the slot output the sender()->objectName() and this may shed some light


Consider the possibility of a loop:

The spin box changes due to user action causing a signal
That signal cause some external processing that emits a signal
That signal is connected to your slot which potentially changes the spin box value again
... causing the original signal to be sent again: See 2.

ScottBell
12th November 2011, 14:41
Wysota, I see now the issue wasn't clear amid the other info.
ChrisW67 correctly described one of the symptoms I needed to work around.

Here's another manifestation, disregarding the possible loop problem for now:

1. Say the user changes the value, directly or with buttons.
2. The box is updated and the valueChanged signal emitted.
3. My slot gets the value, makes some decisions and sends it away.

Later after processing, or maybe from an unrelated event...

4. A message arrives to update the box.
5. setValue() also emits the valueChanged signal.
That all makes sense.


QUESTION:
Can you suggest an easy way to identify where the change came from?
or
An elegant way to block the spin box signal while doing the programmatic change?

I tried using the editingFinished signal but it is not asserted by the spin arrows.


Currently I am using a clunky workaround that's not bulletproof - In the code module that handles all this, set a bool just before doing a programmatic update. The valueChanged slot checks its state and sees it was not from the UI, then clears the bool before exiting.

It's at this point where I'm embarrassed over poor C++ fluency. The spin box is subclassed and exposes only a few methods directly. Some functions in the abstract spin box look helpful.

This is where things get blurry. Mine was built in Designer and with a group box around it, so Qt code accesses it as ui->mySpinBox->(). Example code is usually not generated by Designer, or doesn't have layered widgets or doesn't often show overrides. As noobs have stated elsewhere, it must be so easy...

Scott

Rhayader
12th November 2011, 20:24
Can you suggest an easy way to identify where the change came from?
http://doc.qt.nokia.com/4.7-snapshot/qobject.html#sender
but this is not elegant and not suitable for multithread apps.

http://doc.qt.nokia.com/4.7-snapshot/qsignalmapper.html
but this is not simple for a begginner I think.


An elegant way to block the spin box signal while doing the programmatic change?

disconnect the signal, set the value, reconnect the signal

void MyGroupBox::slot_MyExternalValue(double d)
{
// (other code)
disconnect(ui->MySpinBox);
ui->MySpinBox->setValue(d);
connect(ui->MySpinBox,SIGNAL(valueChanged(double)),this,SLOT(o n_MySpinBox_valueChanged(double)));
}

ChrisW67
13th November 2011, 23:29
An elegant way to block the spin box signal while doing the programmatic change?
QObject::blockSignals(), just make sure that it gets enabled again.

wysota
14th November 2011, 00:53
First I would like to know why you want to know the source of the change.

ScottBell
14th November 2011, 14:57
Wysota,
...and thanks to the others who replied,

The UI-entered value is sent away via TCP/IP to an external device.
The external device may act on the new value or may reject it because of various conditions.
The external device can also get a new value from other users, and my UI must immediately display that new value.

When the program gets a message from the external device and the UI is updated, the spin box signal is just like a local user change. The slot then sends a TCP/IP message to the external device, causing the loop described by ChrisW67. To prevent this, I must know that the external message caused the UI update so the slot won't send out a new message.

One idea was to test and compare the external value before doing a setValue(). If identical then there's no need to rewrite the spin box. This is a good idea to reduces repainting and is always done anyway.
But (there is always a "but"...) comparing the external value isn't enough because a local user can re-type the value that's currently in the spin box.
Or the local user and external message can both try to update the box at the same time.

For now the solution I'll work with is to disconnect or block before an external setValue() operation. That isn't multi-task friendly, so there's a risk of something naughty happening.
I don't see forum instructions to mark 'solved', but consider it so - other comments are still welcome.
Scott

wysota
15th November 2011, 22:44
When you receive a value from the network, store the value somewhere as the last one the network reported it had. Then set the value on the spinbox. When the widget signals, compare the value received from the signal with the one last seen on the network. If they are different, transmit the value, otherwise ignore it as the remote end already has it set. Of course when you transmit, also overwrite the stored value.