More elegant design pattern than ignoring signals?
-
Hi,
I was wondering if anyone knows of a better way to handle situations where I would normally ignore signals.
Let me give an example: I'm making aCollapsibleWidget
that shows/hides content. Here is Python code, but it's just as applicable to C++.from Qt import QtWidgets class CollapsibleWidget(QtWidgets.QWidget): def __init__(self, parent=None): super(CollapsibleWidget, self).__init__(self, parent=parent) self._is_toggling = False self.toggle_check_box = QtWidgets.QCheckBox() self.content_widget = QtWidgets.QWidget() self.setLayout(QtWidgets.QVBoxLayout()) self.layout().addWidget(self.toggle_check_box) self.layout().addWidget(self.content_widget) self.toggle_check_box.stateChanged.connect(self._on_toggle) def is_collapsed(self): return self.toggle_check_box.isChecked() def set_collapsed(self, value): self._is_toggling = True self.toggle_check_box.setChecked(value) self.content_widget.setVisible(not value) self._is_toggling = False def _on_toggle(self, state): if self._is_toggling: return self.set_collapsed(self.toggle_check_box.isChecked())
The widget can be expanded or collapsed by API or from the GUI. Because it can be set via API, this is why
self.toggle_check_box.setChecked(value)
is needed inset_collapsed
.
Withoutself._is_toggling
, the_on_toggle
method would be called, which callsset_collapsed
, which callsself.toggle_check_box.setChecked
, which emits thestateChanged
signal, which calls_on_toggle
, and around and around it goes.
Is there a design pattern that would be better than having to add a boolean flag or callblockSignals
any time this situation comes up? -
@SGaist @fcarney I suppose that's true. I was trying to show a very simple case, but could this be applied to something more complicated?
For example, a property editor. There would be some model object and aPropertyEditor
widget which would have asetModel
method.
If the model were to change, it would emitvalueChanged
signals, which thePropertyEditor
has slots for and updates the widgets that display the model values.
If a user edits the widgets in thePropertyEditor
, then these emitwidgetChanged
signals which updates the values in the model.
So you can see how this can be a loop of signals and slots.@elveatles typically, the widget/model/whatever that receives the new value starts by checking it against the current and if they are the same stops there. In the other case, the internal value is updated and the notification signal is emitted.
That's the standard technique and also why you do not get signal storms when you connect for example a QSlider to a QSpinBox and vice versa. Each will trigger the update to the other but will halt after one round trip.
-
Hi,
Shouldn't the API trigger the toggle_check_box change and it will thus trigger the rest of the chain ?
-
@elveatles said in More elegant design pattern than ignoring signals?:
self.toggle_check_box.setChecked(value)
Why are you call this? Isn't the check box already in the state you set it to?
from Qt import QtWidgets class CollapsibleWidget(QtWidgets.QWidget): def __init__(self, parent=None): super(CollapsibleWidget, self).__init__(self, parent=parent) #self._is_toggling = False self.toggle_check_box = QtWidgets.QCheckBox() self.content_widget = QtWidgets.QWidget() self.setLayout(QtWidgets.QVBoxLayout()) self.layout().addWidget(self.toggle_check_box) self.layout().addWidget(self.content_widget) self.toggle_check_box.stateChanged.connect(self._on_toggle) def is_collapsed(self): return self.toggle_check_box.isChecked() def _on_toggle(self, value): #self._is_toggling = True #self.toggle_check_box.setChecked(value) self.content_widget.setVisible(not value) #self._is_toggling = False # I assume you want to call this externally for some reason? def set_collapsed(self, state): self.toggle_check_box.setChecked(state)
-
Hi,
Shouldn't the API trigger the toggle_check_box change and it will thus trigger the rest of the chain ?
@SGaist @fcarney I suppose that's true. I was trying to show a very simple case, but could this be applied to something more complicated?
For example, a property editor. There would be some model object and aPropertyEditor
widget which would have asetModel
method.
If the model were to change, it would emitvalueChanged
signals, which thePropertyEditor
has slots for and updates the widgets that display the model values.
If a user edits the widgets in thePropertyEditor
, then these emitwidgetChanged
signals which updates the values in the model.
So you can see how this can be a loop of signals and slots. -
@SGaist @fcarney I suppose that's true. I was trying to show a very simple case, but could this be applied to something more complicated?
For example, a property editor. There would be some model object and aPropertyEditor
widget which would have asetModel
method.
If the model were to change, it would emitvalueChanged
signals, which thePropertyEditor
has slots for and updates the widgets that display the model values.
If a user edits the widgets in thePropertyEditor
, then these emitwidgetChanged
signals which updates the values in the model.
So you can see how this can be a loop of signals and slots.@elveatles typically, the widget/model/whatever that receives the new value starts by checking it against the current and if they are the same stops there. In the other case, the internal value is updated and the notification signal is emitted.
That's the standard technique and also why you do not get signal storms when you connect for example a QSlider to a QSpinBox and vice versa. Each will trigger the update to the other but will halt after one round trip.
-
@elveatles typically, the widget/model/whatever that receives the new value starts by checking it against the current and if they are the same stops there. In the other case, the internal value is updated and the notification signal is emitted.
That's the standard technique and also why you do not get signal storms when you connect for example a QSlider to a QSpinBox and vice versa. Each will trigger the update to the other but will halt after one round trip.
-
@SGaist @fcarney I suppose that's true. I was trying to show a very simple case, but could this be applied to something more complicated?
For example, a property editor. There would be some model object and aPropertyEditor
widget which would have asetModel
method.
If the model were to change, it would emitvalueChanged
signals, which thePropertyEditor
has slots for and updates the widgets that display the model values.
If a user edits the widgets in thePropertyEditor
, then these emitwidgetChanged
signals which updates the values in the model.
So you can see how this can be a loop of signals and slots.@elveatles
I wondered about this in the past. What @SGaist has said is the key: only emit...Changed
signals when a value/property changes from its current value, not just gets updated (to the same value).The QDataWidgetMapper Class is a two-way mapper between widgets and values: change the value and the widget changes, change the widget and the value changes. This exemplifies the behaviour of the "only emit signal on change", else it would ping-pong as you talk about.