问题
I'm writing a simple stopwatch with python (using pyqt5). So far, start/pause/resume functions are working fine but the problem is when I stop the counter and need to start from 0 a thread can't be started again I tried the subclass from here but it's not working.
import threading
from PyQt5 import QtWidgets
from stpw_ui import Ui_MainWindow
from time import sleep
# code snippet from stack overflow to stop a thread
class StoppableThread(threading.Thread):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._stop_event = threading.Event()
def stop(self):
self._stop_event.set()
def stopped(self):
return self._stop_event.is_set()
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self):
QtWidgets.QMainWindow.__init__(self)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
# buttons' actions
self.ui.start.clicked.connect(self.stopwatch)
self.ui.pause.clicked.connect(self.pse)
self.ui.stop.clicked.connect(self.stp)
self.ui.resume.clicked.connect(self.res)
# self.ui.reset.clicked.connect(self.rst)
self.t = StoppableThread(target=self.strt)
def UpdateSec(self,s):
self.ui.seconds.display(s)
def UpdateMin(self,m):
self.ui.minutes.display(m)
def stopwatch(self):
self.ui.stacked_buttons.setCurrentIndex(1)
self.pause = False
self.t.start()
pause = False
second = 0
minute = 0
def setpause(self,x):
self.pause = x
# start
def strt(self):
if (self.pause is True):
sleep(0.1)
while self.second <= 59 and self.pause == False:
self.UpdateSec(self.second)
self.second += 1
sleep(1)
if self.second==59:
self.minute += 1
self.UpdateMin(self.minute)
self.second = 0
self.strt()
# pause
def pse(self):
self.ui.stacked_buttons.setCurrentIndex(2)
self.setpause(True)
# stop
def stp(self):
self.setpause(True)
self.ui.stacked_buttons.setCurrentIndex(0)
self.t.stop()
# resume
def res(self):
self.ui.stacked_buttons.setCurrentIndex(1)
self.setpause(False)
# reset / ignore it for now
# def rst(self):
# self.ui.stacked_buttons.setCurrentIndex(0)
# self.setpause(False)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
So:
- How to make the stop button works? (what I'm doing wrong here?)
- Is there a better/simple approach to make a stopwatch? because I feel like I'm over-complicating it.
The GUI (stpw_ui.py) file is available at https://www.filedropper.com/stpwui
回答1:
Do not use sleep in a GUI because you can freeze the GUI. On the other hand using a thread is too much for this question, in a GUI the use of thread is only justified if the task is heavy, but increasing a pair of data every second is not a heavy task.
Instead, you must use QTimer to perform repetitive tasks and QTime to get the elapsed time.
For the logic a state machine must be implemented, in this case it will be StopWatch that will emit the necessary signals to notify the changes to the GUI.
from PyQt5 import QtCore, QtWidgets
from stpw_ui import Ui_MainWindow
class State:
STOPPED = 0
PAUSE = 1
RUNNING = 1 << 1
class StopWatch(QtCore.QObject, State):
State = State
QtCore.Q_ENUMS(State)
secondChanged = QtCore.pyqtSignal(int)
minuteChanged = QtCore.pyqtSignal(int)
stateChanged = QtCore.pyqtSignal(State)
def __init__(self, parent=None):
super(StopWatch, self).__init__(parent)
self._current_state = State.STOPPED
self._time = QtCore.QTime()
self._timer = QtCore.QTimer(self, interval=100, timeout=self.on_timeout)
self._delta = 0
self._seconds = 0
self._minutes = 0
def setCurrentState(self, state):
self._current_state = state
self.stateChanged.emit(state)
@QtCore.pyqtSlot()
def start(self):
self._delta = 0
self._timer.start()
self._time.start()
self.setCurrentState(State.RUNNING)
@QtCore.pyqtSlot()
def stop(self):
if self._current_state != State.STOPPED:
self._timer.stop()
self.setCurrentState(State.STOPPED)
@QtCore.pyqtSlot()
def pause(self):
if self._current_state == State.RUNNING:
self._timer.stop()
self.setCurrentState(State.PAUSE)
self._delta += self._time.elapsed()
@QtCore.pyqtSlot()
def resume(self):
if self._current_state == State.PAUSE:
self._timer.start()
self._time = QtCore.QTime()
self._time.start()
self.setCurrentState(State.RUNNING)
@QtCore.pyqtSlot()
def on_timeout(self):
t = QtCore.QTime.fromMSecsSinceStartOfDay(self._delta + self._time.elapsed())
s, m = t.second(), t.minute()
if self._seconds != s:
self._seconds = s
self.secondChanged.emit(s)
if self._minutes != m:
self._minutes = m
self.minuteChanged.emit(m)
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setupUi(self)
self._stop_watch = StopWatch(self)
self.start.clicked.connect(self._stop_watch.start)
self.pause.clicked.connect(self._stop_watch.pause)
self.stop.clicked.connect(self._stop_watch.stop)
self.resume.clicked.connect(self._stop_watch.resume)
self._stop_watch.secondChanged.connect(self.seconds.display)
self._stop_watch.minuteChanged.connect(self.minutes.display)
self._stop_watch.stateChanged.connect(self.on_stateChanged)
@QtCore.pyqtSlot(StopWatch.State)
def on_stateChanged(self, state):
if state == StopWatch.RUNNING:
self.stacked_buttons.setCurrentIndex(1)
elif state == StopWatch.PAUSE:
self.stacked_buttons.setCurrentIndex(2)
elif state == StopWatch.STOPPED:
self.stacked_buttons.setCurrentIndex(0)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
来源:https://stackoverflow.com/questions/53405789/make-a-simple-stopwatch-stoppable