Create a stopwatch with PyQt5: Timer accelerates after timer stop method has been used

Question:

I am trying to building a very simple GUI for a stopwatch with PyQt5 for Python 3.8.
I created a sort of finite state machine with 3 states: Running, Stop and Reset. When I start the clock the first time, it seems running correctly. Whenever I stop the timer (also for resetting), the new start of the clock seem to accelerate the clock. I don’t understand why, neither how to debug what is happening or to solve the problem. Can you help me?

from PyQt5.QtCore import QSize, Qt, QTimer
from PyQt5.QtWidgets import *

# Only needed for access to command line arguments
import sys
import time
from enum import Enum


class ClockState(Enum):
    STOP = 0
    RUNNING = 1
    RESET = 2

    def __str__(self):
        return self.name


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('Clock GUI')

        # Defining the size:
        self.setFixedSize(QSize(600, 400))

        self.State = ClockState.STOP
        self.millisecond = 0
        self.second = 0
        self.minute = 0
        self.layout = QVBoxLayout()
        self.timerlayout = QHBoxLayout()
        self.buttonlayout = QHBoxLayout()
        self.timer = QTimer()
        self.timer.setInterval(100)
        self.timer.setSingleShot(False)
        self.numberMS = QLCDNumber()
        self.numberMS.setNumDigits(3)
        self.numberS = QLCDNumber()
        self.numberS.setNumDigits(2)
        self.numberM = QLCDNumber()
        self.numberM.setNumDigits(2)

        # Inizializzo a zero cosa i timer e con il numero di cifre che voglio
        self.numberMS.display(f'{self.millisecond:03}')
        self.numberS.display(f'{self.second:02}')
        self.numberM.display(f'{self.minute:02}')

        self.timerlayout.addWidget(self.numberM)
        self.timerlayout.addWidget(self.numberS)
        self.timerlayout.addWidget(self.numberMS)

        self.start_button = QPushButton('Start')
        self.reset_button = QPushButton('Reset')
        self.stop_button = QPushButton('Stop')
        # self.start_button.setCheckable(True)
        # self.reset_button.setCheckable(False)
        # self.stop_button.setCheckable(True)

        self.buttonlayout.addWidget(self.start_button)
        self.buttonlayout.addWidget(self.reset_button)
        self.buttonlayout.addWidget(self.stop_button)
        # self.button_checked = True

        self.start_button.clicked.connect(self.running_timer)
        self.reset_button.clicked.connect(self.reset_timer)
        self.stop_button.clicked.connect(self.stop_timer)

        self.layout.addLayout(self.timerlayout)
        self.layout.addLayout(self.buttonlayout)

        self.container = QWidget()
        self.container.setLayout(self.layout)

        self.setCentralWidget(self.container)

    def update_time(self):
        self.millisecond += 100
        if self.millisecond > 900:
            self.millisecond = 0
            self.second += 1
        if self.second > 59:
            self.millisecond = 0
            self.second = 0
            self.minute += 1
        self.numberMS.display(f'{self.millisecond:03}')
        self.numberS.display(f'{self.second:02}')
        self.numberM.display(f'{self.minute:02}')

    def running_timer(self):
        self.State = ClockState.RUNNING
        self.start_button.setEnabled(False)
        self.reset_button.setEnabled(False)
        self.stop_button.setEnabled(True)
        print(f"State: {self.State}")
        if self.State == ClockState.RUNNING:
            self.timer.setInterval(100)
            self.timer.start()
            self.timer.timeout.connect(self.update_time)
        else:
            print(f"Checking if wrong state happen: {self.State}")
            # print(f"Final time: {self.minute:02}:{self.second:02}.{self.millisecond:03}")
            # self.timer.stop()
            # self.millisecond = 0
            # self.second = 0
            # self.minute = 0

    def reset_timer(self):
        if self.State == ClockState.STOP:
            self.State = ClockState.RESET
            print(f"Final time: {self.minute:02}:{self.second:02}.{self.millisecond:03}")
            self.timer.stop()
            print(f'State: {self.State} -- {self.timer.isActive()}')
            self.millisecond = 0
            self.second = 0
            self.minute = 0
            self.numberMS.display(f'{self.millisecond:03}')
            self.numberS.display(f'{self.second:02}')
            self.numberM.display(f'{self.minute:02}')

    def stop_timer(self):
        self.State = ClockState.STOP
        self.stop_button.setEnabled(False)
        self.start_button.setEnabled(True)
        self.reset_button.setEnabled(True)
        self.timer.stop()
        print(f'State: {self.State}')
        print(f"Actual time: {self.minute:02}:{self.second:02}.{self.millisecond:03}")


def main():
    # You need one (and only one) QApplication instance per application.
    # Pass in sys.argv to allow command line arguments for your app.
    # If you know you won't use command line arguments QApplication([]) works too.
    app = QApplication(sys.argv)

    # Create a Qt widget, which will be our window.
    window = MainWindow()
    window.show()  # IMPORTANT!!!!! Windows are hidden by default.

    # Start the event loop.
    app.exec_()


if __name__ == '__main__':
    main()

I thought that the problem was in interval setting. So I set again the timer interval before start the clock. But nothing changed.

Asked By: Kappa95

||

Answers:

You connect the timer slot with every call of def running_timer().

This means that the slot is called up several times for each cycle.

You should connect the slot only in constructor or use disconnect .

Answered By: Marco F.
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.