mikuszefski
mikuszefski

Reputation: 4043

Why QSpinBox jumps twice the Step value

I have a simple python qt application in which I want to plot something dynamically via matplotlib. I was trying to follow instructions like this and that. It works as expected except for one thing, my spinbox updates twice upon mouse click and I do not understand why. Well, during the development the valueChanged.connect() worked fine until I got to a certain point. Then the update_figure() was called twice and the displayed value jumped by two instead of the desired one. I tried to make a minimum working example, you find below. Without the sleep command in update_figue my spinbox behaves normal, with sleep it is called twice and the value jumps by two, with an delay given by sleep.

Why does the sleep in update_figure influence the spinbox, or what is happening here? Is it the way I constructed the connect()?

BTW: I observed the same behaviour on a slider.

Cheers

(I do not think it matters, but this is on openSuse Leap)

EDIT: I could make the example even "more minimal".

from PyQt5.uic import loadUiType
from PyQt5.QtWidgets import QApplication, QMainWindow
import sys
from time import sleep

Ui_MainWindow, QMainWindow = loadUiType('main.ui')

class Main(QMainWindow, Ui_MainWindow):
    def __init__(self, ):
        super(Main, self).__init__()
        self.setupUi(self)
        self.spinBox_dac.valueChanged.connect(self.update_figure)

    def update_figure(self):
        ##### The following line makes the difference!
        sleep(1) #####################################
        ##### why?
        print "damn...once or twice?"       
        return


if __name__ == '__main__':
    app = QApplication(sys.argv)
    main = Main()    
    main.show()
    sys.exit(app.exec_())

Here is the main.ui:

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>185</width>
    <height>107</height>
   </rect>
  </property>
  <property name="sizePolicy">
   <sizepolicy hsizetype="Fixed" vsizetype="Maximum">
    <horstretch>0</horstretch>
    <verstretch>0</verstretch>
   </sizepolicy>
  </property>
  <property name="maximumSize">
   <size>
    <width>1100</width>
    <height>905</height>
   </size>
  </property>
  <property name="windowTitle">
   <string>mini</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <widget class="QSpinBox" name="spinBox_dac">
    <property name="geometry">
     <rect>
      <x>80</x>
      <y>40</y>
      <width>61</width>
      <height>28</height>
     </rect>
    </property>
    <property name="minimum">
     <number>3</number>
    </property>
    <property name="maximum">
     <number>20</number>
    </property>
    <property name="singleStep">
     <number>1</number>
    </property>
    <property name="value">
     <number>4</number>
    </property>
   </widget>
  </widget>
 </widget>
 <resources/>
 <connections/>
</ui>

Upvotes: 0

Views: 1452

Answers (1)

Ceppo93
Ceppo93

Reputation: 1046

The sleep() call prevent qt to process the mouse-release and the QSpinBox "thinks" that the mouse is pressed long enough to increase/decrease the value again (auto-repeat).

See https://bugreports.qt.io/browse/QTBUG-33128

A solution could be:

def update_figure(self):
    for _ in xrange(10):
        QApplication.processEvents() 
        sleep(0.1)
    print "Only once :-)"

Anyway it's not a good practice to block the UI while performing long operations, the work is usually delegated to another thread.

EDIT

This affect Qt4 and Qt5 both, but it behave a bit differently.

There is no setAutoRepeat because in the QAbstrectSpinBox this behavior is implemented using styles, while in QAbstractButton is implemented with a timer.

The way around this is to create a ProxyStyle and set a very long "auto-repeat" interval

from PyQt5.uic import loadUiType
from PyQt5.QtWidgets import QApplication, QMainWindow, QProxyStyle, QStyle
import sys
from time import sleep

Ui_MainWindow, QMainWindow = loadUiType('main.ui')


class CustomStyle(QProxyStyle):
    def styleHint(self, hint, option=None, widget=None, returnData=None):
        if hint == QStyle.SH_SpinBox_KeyPressAutoRepeatRate:
            return 10**10
        elif hint == QStyle.SH_SpinBox_ClickAutoRepeatRate:
            return 10**10
        elif hint == QStyle.SH_SpinBox_ClickAutoRepeatThreshold:
            # You can use only this condition to avoid the auto-repeat,
            # but better safe than sorry ;-)
            return 10**10
        else:
            return super().styleHint(hint, option, widget, returnData)


class Main(QMainWindow, Ui_MainWindow):
    def __init__(self, ):
        super(Main, self).__init__()
        self.setupUi(self)
        self.spinBox_dac.setStyle(CustomStyle())
        self.spinBox_dac.valueChanged.connect(self.update_figure)

    def update_figure(self):
        sleep(1)
        print "Only once!"


if __name__ == '__main__':
    app = QApplication(sys.argv)
    main = Main()    
    main.show()
    sys.exit(app.exec_())

Upvotes: 3

Related Questions