Home > database >  Stop QTimer after a few seconds in PyQt6
Stop QTimer after a few seconds in PyQt6

Time:01-30

I am trying to understand how QTimer works.

I have a list to be randomized and I tried to use QTimer to do the randomization every few miliseconds interval, and would like the QTimer to stop after a few seconds after that.

Here is my code.

class Client(QObject):
    application = QApplication(sys.argv)

    def __init__(self):
        super(Client, self).__init__()
        self._window = QMainWindow()
        self._window.showMaximized()
        self._ui = Ui_MainWindow()
        self._ui.setupUi(self._window)

        name_list = ['A', 'V', 'D', 'E', 'L']
        Utils.set_name_list(name_list)

        self.timer = QTimer()
        self.timer.timeout.connect(self.draw)
        self._ui.pushButton.clicked.connect(self.start)

    def draw(self):
        self._ui.label.setText(random.choice(Utils.name_list()))

    def start(self):
        self.timer.start(250)

once the pushButton is clicked, the timer.start fires, so how should I stop the timer after a few seconds? Or should I use another timer/loop to monitor?

CodePudding user response:

You have two options.

Use an incremental count

Create an instance attribute computed by dividing the ending time by the interval amount (how many times the timer will trigger), set an instance attribute to 0 for the counter, and in the connected function increase that counter: if it is greater than the maximum count, stop the timer:

    def draw(self):
        self._ui.label.setText(random.choice(Utils.name_list()))
        self.counter  = 1
        if self.counter >= self.maxCount:
            self.timer.stop()
        

    def start(self):
        maxSecs = 5
        interval = 250
        self.counter = 0
        self.maxCount = maxSecs / interval
        self.timer.start(250)

Use QElapsedTimer

QTimer might not be as precise as you wish, especially if something temporarily blocks the event loop (including acting with the GIL) right before the timeout. For generic usage, relatively high intervals and small iteration count, that might not be an issue, but if the program is intended to be working for a lot of time (or the above situation happens very frequently), you may have inconsistent results: QTimer works with events, and events are normally queued, meaning that the code for solution one might still call draw much later than the maxSecs has passed.

A more precise solution would use QElapsedTimer, which is always monotonic (it uses system time, even if some heavy-processing function occupies the CPU for some time).

    def draw(self):
        if self.elapsedTimer.elapsed() <= self.maxSecs * 1000:
            self._ui.label.setText(random.choice(Utils.name_list()))
        else:
            self.timer.stop()
        

    def start(self):
        self.maxSecs = 5
        self.elapsedTimer = QElapsedTimer()
        self.elapsedTimer.start()
        self.timer.start(250)

Note that in the case above the execution order of draw() checks the elapsed time before setting the text, ensuring that it will always happen if and only if the system elapsed time corresponds to the maximum time limit.

This might be undesirable for smaller ratios between the QTimer interval and the maximum time: for instance, if both the QTimer interval and the limit are the same, it is possible that the text won't be updated, because the QTimer might not be as precise and there could be some other queued event before the signal processing, thus increasing the actual time passed between the QTimer timeout and the actual call to elapsed().

You may decide to invert the order above, but consider that in that case it will mean that the next first call will always happen, even if the QTimer signal was blocked for seconds by something else. A better solution would be to still check for the elapsed time before, but using a reasonable tolerance:

    def draw(self):
        if self.elapsedTimer.elapsed() <= self.maxSecs * 1000   self.tolerance:
            # ...

    def start(self):
        self.maxSecs = 5
        self.tolerance = 150
        # ...
  • Related