Reputation: 177
I use two widgets: a QSpinBox
and a QLineEdit
. valueChanged
slot of the QSpinBox
widget is connected to the update
function. This function consist of a time-consuming processing (a loop with calculations or a time.sleep()
call) and a QLineEdit.setText()
call. At the beginning, i thought it worked as expected but I noticed that the signal seems to be emitted twice when the calculations takes a long time.
Bellow is the code:
import time
from PyQt5.QtWidgets import QWidget, QSpinBox, QVBoxLayout, QLineEdit
class Window(QWidget):
def __init__(self):
# parent constructor
super().__init__()
# widgets
self.spin_box = QSpinBox()
self.line_edit = QLineEdit()
# layout
v_layout = QVBoxLayout()
v_layout.addWidget(self.spin_box)
v_layout.addWidget(self.line_edit)
# signals-slot connections
self.spin_box.valueChanged.connect(self.update)
#
self.setLayout(v_layout)
self.show()
def update(self, param_value):
print('update')
# time-consuming part
time.sleep(0.5) # -> double increment
#time.sleep(0.4) # -> works normally!
self.line_edit.setText(str(param_value))
if __name__ == '__main__':
from PyQt5.QtWidgets import QApplication
import sys
app = QApplication(sys.argv)
win = Window()
sys.exit(app.exec_())
Another version of update
:
# alternative version, calculations in a loop instead of time.sleep()
# -> same behaviour
def update2(self, param_value):
print('update2')
for i in range(2000000): # -> double increment
x = i**0.5 * i**0.2
#for i in range(200000): # -> works normally!
# x = i**0.5 * i**0.2
self.line_edit.setText(str(param_value))
Upvotes: 1
Views: 1648
Reputation: 120818
There is no real mystery here. If you click a spin-box button, the value will increase by a single step. But if you press and hold down the button, it will increase the values continually. In order to tell the difference between a click and a press/hold, a timer is used. Presumably, the threshold is around half a second. So if you insert a small additional delay, a click may be interpreted as a short press/hold, and so the spin-box will increment by two steps instead of one.
UPDATE:
One way to work around this behaviour is by doing the processing in a worker thread, so that the delay is eliminated. The main problem with this is avoiding too much lag between spin-box value changes and line-edit updates. If you press and hold the spin-box button, a large number of signal events could be queued by the worker thread. A simplistic approach would wait until the spin-box button was released before handling all those queued signals - but that would result in a long delay whilst each value was processed separately. A better approach is to compress the events, so that only the most recent signal is handled. This will still be somewhat laggy, but if the processing time is not too long, it should result in acceptable behaviour.
Here is a demo that implements this approach:
import sys, time
from PyQt5.QtWidgets import (
QApplication, QWidget, QSpinBox, QVBoxLayout, QLineEdit,
)
from PyQt5.QtCore import (
pyqtSignal, pyqtSlot, Qt, QObject, QThread, QMetaObject,
)
class Worker(QObject):
valueUpdated = pyqtSignal(int)
def __init__(self, func):
super().__init__()
self._value = None
self._invoked = False
self._func = func
@pyqtSlot(int)
def handleValueChanged(self, value):
self._value = value
if not self._invoked:
self._invoked = True
QMetaObject.invokeMethod(self, '_process', Qt.QueuedConnection)
print('invoked')
else:
print('received:', value)
@pyqtSlot()
def _process(self):
self._invoked = False
self.valueUpdated.emit(self._func(self._value))
class Window(QWidget):
def __init__(self):
super().__init__()
self.spin_box = QSpinBox()
self.line_edit = QLineEdit()
v_layout = QVBoxLayout()
v_layout.addWidget(self.spin_box)
v_layout.addWidget(self.line_edit)
self.setLayout(v_layout)
self.thread = QThread(self)
self.worker = Worker(self.process)
self.worker.moveToThread(self.thread)
self.worker.valueUpdated.connect(self.update)
self.spin_box.valueChanged.connect(self.worker.handleValueChanged)
self.thread.start()
self.show()
def process(self, value):
time.sleep(0.5)
return value
def update(self, param_value):
self.line_edit.setText(str(param_value))
if __name__ == '__main__':
app = QApplication(sys.argv)
win = Window()
sys.exit(app.exec_())
Upvotes: 3