Blocking Signals to child widgets
-
In this post, I read: "If you have to block signals then it usually means your design is somehow screwed up."
I'm trying to get my mind around the following scenario:
When the user selects an item with the mouse, I show a floating inspector widget with attributes of the item, which can be edited. The inspector is just a subclass of
QGroupBox
with someQSpinBox
andQLineEdit
in it.The widgets are connected to a method of the inspector, e.g.:
self._edit_distance.valueChanged.connect(self._value_changed)
.Since there are many widgets in the inspector, I didn't want to connect each and every one of them to a separate method. I figured I could update the entire source object when one of the values in the inspector changes. So all the widgets are connected to the same
self._value_changed
method.In that method, I update the object with the values from the widgets, like this:
self.gridpoint.source.distance = self._edit_distance.value() self.gridpoint.source.someothervalue = self._edit_someothervalue.value()
I am overriding the inspector's
show()
method, which updates the inspector widgets to show the values of the selected item (source
), like this:self._edit_distance.setValue(source.distance) self._edit_someothervalue.setValue(source.someothervalue)
Problem is, when the inspector is shown and all its values are set,
valueChanged
is emitted for every widget, which triggers theself._value_changed
method, which in turn sets the value of yet-undrawn source attributes to zero. The object which is supposed to be modified through the inspector gets modified by showing the values to be modified, not by user interaction.I thought of using
blockSignals
but apparently it's impossible to block a group of widgets. And anyway, this seems indeed to be a design problem on my end. But this is my first semi-complex GUI and I'm not sure what the correct design pattern should be here.I am working in Python but I can translate C++ if it's simple enough, anyway this is an abstract question.
Thank you for your help.
-
@Strangelove said in Blocking Signals to child widgets:
Problem is, when the inspector is shown and all its values are set, valueChanged is emitted for every widget
I am not sure I fully understand what you are saying, but....
I never use
blockSignals()
, believing it to be "semi-evil" because (a) it applies globally to all widgets and (b) I am never sure of the consequences of blocking signals which should be emitted.When I need to update all the widgets --- such as loading a model, or presumably your initial showing of all widgets --- I set a class variable like
modelBeingLoaded = true;
, load all data, call somerefreshAll()
at the end, finally resetmodelBeingLoaded = false;
. WhilemodelBeingLoaded == true
I do not spawn extra signals or do extra work/redrawing etc. If you like, it's kind of ablockSignals()
, but implemented by me.Does this apply to your situation?
-
@JonB Thank you. I
could certainly try and dodid what you suggest, whichshould applycertainly applies in my case. Since I'm still learning all this stuff, I'm just curious if this would be considered a hack or not... I'm trying to figure out what the best practices are (since this is a very common scenario with GUI applications). -
@JonB said in Blocking Signals to child widgets:
I never use
blockSignals()
, believing it to be "semi-evil" because (a) it applies globally to all widgets and (b) I am never sure of the consequences of blocking signals which should be emitted.I'm note sure what globally means here.
QObject::blockSignals(true)
will preventthis
orself
object from emitting signals other thanQObject::destroyed
. It shouldn't interfere with signals originating from other QObject instances.I agree that using it can be problematic without complete knowledge of all slots that are connected.
-
@jeremy_k
By "globally" I mean it blocks all signals from the object. I'm never sure of what the consequences of that might be, there might be some signals (either my own slots or even internally-handled Qt ones) which really need to be handled/acted upon and this will prevent that happening. -
@Strangelove said in Blocking Signals to child widgets:
I'm trying to figure out what the best practices are
I would guess that connecting signals individually is considered best practice. However, I do know of cases where there could be a circular connection between two or more widgets. If you only initialize the widgets once you can do that before connecting any signals and you will have a lot less problems.
@JonB's suggestion is a little bit better than using
blockSignals()
. In your specific case where there is a single method reacting to changes in any widget it makes sense to have a boolean variable which is checked and set at the beginning and unset at the end. Then, changes triggered within this method will not result in an endless loop. (This is the suggestion to usemodelBeingLoaded
.)One further possiblity (which we have in some places in our code) is to use a seperate wrapper class to block signals. This is, in our code, a class called
SignalBlocker
. The constructor(s) callblockSignals(true)
on the object handed to it and the destructor callsblockSignals(false)
. In a addition to thisoperator->()
is overloaded (this is C++, though), which allows for simple syntax:SignalBlocker(this->_edit_distance)->setValue(source.distance); SignalBlocker(this->_edit_someothervalue)->setValue(source.someothervalue);
I am not sure how this could be translated to Python. Maybe it is possible to write an
apply
method which then allows a syntax similar to this?:SignalBlocker(self._edit_distance).apply(setValue, source.distance) SignalBlocker(self._edit_someothervalue).apply(setValue, source.someothervalue)
-
@SimonSchroeder said in Blocking Signals to child widgets:
One further possiblity (which we have in some places in our code) is to use a seperate wrapper class to block signals. This is, in our code, a class called
SignalBlocker
. The constructor(s) call
blockSignals(true)
on the object handed to it and the destructor callsblockSignals(false)
. In a addition to thisoperator->()
is overloaded (this is C++, though), which allows for simple syntax:SignalBlocker(this->_edit_distance)->setValue(source.distance); SignalBlocker(this->_edit_someothervalue)->setValue(source.someothervalue);
There's QSignalBlocker that accomplishes the same task with slightly different syntax.
PyQt5's documentation mentions the class. -