-1

I'm trying to run multiple timers in a parallel manner using PyQt's QTimer objects. I'm trying to define an arbitrary number of timers (defined by n), and to launch them all at the same time : however, they'd all have different intervals (or timeouts), and these intervals would be different each time they'd start.

As of now, my test code looks like this :

from PyQt5 import QtCore, QtWidgets
import random

# Fixes an arbitrary number of timers and indicators
n = random.randint(5, 20)

class Widget(QtWidgets.QWidget):
    
    def __init__(self, parent=None):
        
        super(Widget, self).__init__(parent)
        
        # Initializes empty lists of labels and timers
        self.indicators = []
        self.clocks = []
        
        # Loops the initialization of each label/timer duo
        for i in range(0, n):
            
            id = i
            
            # Creates a label to have a feedback on the timer's behaviour
            self.indicators.append(QtWidgets.QLabel("Time1 :"))
            self.indicators[id].id = id
            
            # Creates a basic layout
            lay = QtWidgets.QVBoxLayout(self)
            lay.addWidget(self.indicators[i])
            
            # Creates the n°ID timer
            self.clocks.append(QtCore.QTimer())
            self.clocks[id].id = id
            
            # Sets it so singleshot mode,
            self.clocks[id].setSingleShot(True)
            # and links it to the event() slot
            self.clocks[id].timeout.connect(self.event(id))
            
            # Finally calls the event() to start the timer
            self.event(id)
        
        # Sets the main window's geometry,
        self.setGeometry(100, 100, 800, 400)
        # and pushes it upfront
        self.activateWindow()

    @QtCore.pyqtSlot()
    def event(self, id):
        
        # Generates a random interval (or timeout) between 1ms and 10s
        timeoutConstant = random.randint(1, 10000)
        
        # Sets the new interval
        self.clocks[id].setInterval(timeoutConstant)
        
        # Refreshes the text in the associated label
        self.indicators[id].setText("Time " + str(id) +" : " + str(timeoutConstant))
        
        # Starts the freshly updated timer
        self.clocks[id].start()
        
        # Also prints a message to be able to track things
        print("Timer " + str(id) + " over")

# Creates the app and start the QT process
if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    w = Widget()
    w.show()
    sys.exit(app.exec_())

However, the output is that : argument 1 has unexpected type 'NoneType' for the connect() line ? I cannot wrap my head around why...

And list indices must be integers or slices, not QChildEvent for the setInterval() line ???

But how else could I manage a high number of these elements easily ? Thanks in advance for your help !

PS : Note that my code, when singularized, do run smoothly (tested with 1 and 2 timers and labels at the same time)...

Hextray
  • 1
  • 4
  • Please trim your code to make it easier to find your problem. Follow these guidelines to create a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example). – Community Mar 02 '23 at 16:21
  • [`event()`](//doc.qt.io/qt-5/qwidget.html#event) is a fundamental function of all QObjects (hence, all Qt widgets), and it can *ONLY* be overridden with a *related* function (which gets a *Qt event* as argument, eventually handles it, and *always* returns a bool). You must ***NOT*** overwrite it for something else. So: 1. always choose appropriate names for objects, not generic ones ("event" is not a very good choice); 2. always check the docs to ensure that you're not overwriting an existing function: check the "List of all members, including inherited members" link in the docs of each class. – musicamante Mar 02 '23 at 18:32

1 Answers1

0

You connected event slot incorrectly, self.event(id) is a function call, which leads to error since self.event(id) returns None so program tries to do self.clocks[id].timeout.connect(None).

To bind value to a function you need to either use lambda with optional parameters or functools.partial:

self.clocks[id].timeout.connect(lambda id=id: self.event(id))

or

self.clocks[id].timeout.connect(functools.partial(self.event, id))

You can simplify things greatly and avoid this problem altogether if you combine timer and label in one entity and encapsulate timeout handling inside it.

from PyQt5 import QtCore, QtWidgets
import random
import sys

class Indicator(QtWidgets.QLabel):
    def __init__(self, id, parent = None) -> None:
        super().__init__(parent)
        timer = QtCore.QTimer()
        timer.setSingleShot(True)
        timer.timeout.connect(self.onTimeout)
        self._timer = timer
        self._id = id
    
    def start(self):
        self.onTimeout()

    def onTimeout(self):
        timeoutConstant = random.randint(1, 10000)
        self._timer.setInterval(timeoutConstant)
        self._timer.start()
        self.setText("Time " + str(self._id) +" : " + str(timeoutConstant))
        print("Timer " + str(self._id) + " over")
        
class Widget(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(Widget, self).__init__(parent)
        lay = QtWidgets.QVBoxLayout(self)
        n = 5
        indicators = [Indicator(i) for i in range(n)]
        self._indicators = indicators
        for indicator in indicators:
            lay.addWidget(indicator)
            indicator.start()
        self.setLayout(lay)

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    widget = Widget()
    widget.show()
    app.exec()
mugiseyebrows
  • 4,138
  • 1
  • 14
  • 15
  • There's an extremely important aspect that must be considered: [`event()`](https://doc.qt.io/qt-5/qwidget.html#event) is a *fundamental* function of all QObjects, and it **MUST NOT** be overwritten with another function that does something that is completely different. – musicamante Mar 02 '23 at 18:26
  • Oh you're right, I overlooked that ! Thanks for the reminder ! – Hextray Mar 03 '23 at 10:53