如何在 PyQt5 中创建多个秒表计时器?
How to create multiple stopwatch timers in PyQt5?
我正在尝试制作一个具有多个计时器的 GUI。当我单击一个按钮时,它将启动一个计时器并显示在一个 tableWidget 中。而这个tableWidget有两列代表数字和时间,就像这样:(每个计时器将同时运行)
Name
Time Elapse
Thing#1
00:01
Thing#2
00:03
Thing#3
01:23
我的原代码是这样的:(不是全部)
x=0
self.Button1.clicked.connect(self.btn)
def btn(self):
#set Thing#1, Thing#2... to the Name column
self.newItem = QTableWidgetItem('{}'.format(self.num))
self.newItem.setTextAlignment(Qt.AlignCenter)
self.tablewidget.setItem(self.x, 0, self.newItem)
#start timer
self.timer = QTimer()
self.time = QTime(0, 0, 0)
self.timer.start(1000)
self.timer.timeout.connect(self.showTime)
self.x += 1
def showTime(self):
self.time = self.time.addSecs(1)
timeDisplay = self.time.toString("mm:ss")
self.newItem2 = QTableWidgetItem(timeDisplay)
self.newItem2.setTextAlignment(Qt.AlignCenter)
self.tablewidget.setItem(self.x - 1, 1, self.newItem2)
当我单击该按钮以创建 Thing#1 时,我得到的结果是,它一开始就起作用了。然而,当我再次点击那个按钮时,Thing#1 停止了,而 Thing#2 开始了。最奇怪的是时间的速度翻了一番,就像看视频快进2倍一样。当我再次点击时,它是 3 倍速等等。
真不知道为什么,网上也查不到。但是,我确实找到了一些关于如何在 PyQt 中制作多个计时器的内容,它说要创建多个 QTimer,所以我将代码更改为:
x=0
self.timer_dict={}
self.time_dict={}
self.Button1.clicked.connect(self.btn)
def btn(self):
#set Thing#1, Thing#2... to the Name column
self.newItem = QTableWidgetItem('{}'.format(self.num))
self.newItem.setTextAlignment(Qt.AlignCenter)
self.tablewidget.setItem(self.x, 0, self.newItem)
#start timer
self.timer_dict["{}".format(self.num)] = QTimer()
self.time_dict["{}".format(self.num)] = QTime(0, 0, 0)
self.timer_dict["{}".format(self.num)].start(1000)
self.timer_dict["{}".format(self.num)].timeout.connect(self.showTime)
self.x += 1
def showTime(self):
self.time_dict['{}'.format(self.num)] = self.time_dict['{}'.format(self.num)].addSecs(1)
timeDisplay = self.time_dict['{}'.format(self.num)].toString("mm:ss")
self.newItem2 = QTableWidgetItem(timeDisplay)
self.newItem2.setTextAlignment(Qt.AlignCenter)
self.tablewidget.setItem(self.x - 1, 1, self.newItem2)
它只是为每个'Thing',创建对应的QTimer对象。但是,我得到的结果和原来的一样。
但我确实有一些新发现。当我不小心这样做时:
def btn(self):
self.timer_dict={}
self.time_dict={}
#set Thing#1, Thing#2... to the Name column
self.newItem = QTableWidgetItem('{}'.format(self.num))
.........
每次单击该按钮时,它都会重置 self.timer_dict 和 self.time_dict。它不能做多个定时器,但至少时间的速度不会是2x、3x。
我想我使用 self.tablewidget.setItem(self.x - 1, 1, self.newItem2)(最后一行代码)来更新计时器。如果我点击按钮,它会移动到下一行,最后一行不会更新。因此,我一次只能有一个计时器,尽管它应该在后台有多个计时器。
也许我应该使用多线程或 QThread 来实现我的目标?实在是看不懂。
更新:
关于我的问题的一些可重现的代码,我想我加快时间的原因可能是我使用另一个 QTimer 来更新当前时间?
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import QTime, Qt, QTimer, QDateTime
from PyQt5.QtWidgets import *
import sys
class MainWindow(QMainWindow):
# create the window
def __init__(self):
super(MainWindow, self).__init__()
self.show_QinputDialog()
x = 0
# create widget on the window
def initUI(self, n):
self.setFixedSize(220, 320)
self.setWindowTitle("Multiple Timer System")
self.center()
# set layout
self.centralwidget = QtWidgets.QWidget(self)
self.widget = QtWidgets.QWidget(self.centralwidget)
self.widget.setGeometry(QtCore.QRect(10, 10, 200, 300))
self.gridLayout = QtWidgets.QGridLayout(self.widget)
self.gridLayout.setContentsMargins(0, 0, 0, 0)
self.setCentralWidget(self.centralwidget)
# Section 1: Set Timer
self.Group1 = QtWidgets.QGroupBox(self.widget)
self.horizontalLayout = QtWidgets.QHBoxLayout(self.Group1)
self.comboBox1 = QtWidgets.QComboBox(self.Group1)
for i in range(1, n + 1):
self.comboBox1.addItem("Timer#{}".format(i))
self.horizontalLayout.addWidget(self.comboBox1)
self.Button1 = QtWidgets.QPushButton(self.Group1)
self.horizontalLayout.addWidget(self.Button1)
self.gridLayout.addWidget(self.Group1, 0, 0, 1, 1)
# Section 2: Display Current Time
self.Group2 = QtWidgets.QGroupBox(self.widget)
self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.Group2)
self.label_1 = QtWidgets.QLabel(self.Group2)
self.horizontalLayout_2.addWidget(self.label_1)
self.showCurrentTime()
self.timer = QTimer()
self.timer.start(1000)
self.timer.timeout.connect(self.showCurrentTime)
self.gridLayout.addWidget(self.Group2, 1, 0, 1, 1)
# Section 3: Table
self.Group3 = QtWidgets.QGroupBox(self.widget)
self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.Group3)
self.tablewidget = QtWidgets.QTableWidget(self.Group3)
self.tablewidget.setRowCount(n)
self.tablewidget.setColumnCount(2)
self.tablewidget.setHorizontalHeaderLabels(['Name', 'Time Elapse'])
self.tablewidget.resizeRowsToContents()
self.tablewidget.setColumnWidth(0, 76)
self.tablewidget.setColumnWidth(1, 77)
# set rowHeader invisible
self.tablewidget.verticalHeader().setVisible(False)
# ban editing
self.tablewidget.setEditTriggers(QAbstractItemView.NoEditTriggers)
# ban selection
self.tablewidget.setSelectionMode(QAbstractItemView.NoSelection)
self.verticalLayout_3.addWidget(self.tablewidget)
self.gridLayout.addWidget(self.Group3, 2, 0, 1, 1)
self.Group2.setTitle("Current Time")
self.Group3.setTitle("Current Running Timer")
self.Group1.setTitle("Start Timer")
self.Button1.setText("Start")
self.timer_dict={}
self.time_dict={}
self.Button1.clicked.connect(self.start_timer)
def show_QinputDialog(self):
# show dialog to let user input the number of timers
dialog = QInputDialog(self)
dialog.setIntRange(1, 100)
dialog.setInputMode(QInputDialog.IntInput)
dialog.setLabelText("Please enter the number of timer:(1-100)")
dialog.setWindowTitle("Multiple Timer System")
dialog.setIntValue(10)
self.center()
if dialog.exec_() == QtWidgets.QDialog.Accepted:
self.n = dialog.intValue()
self.timer_list = []
for i in range(1, self.n + 1):
self.timer_list.extend(['Timer#{}'.format(i)])
self.initUI(self.n)
def center(self):
screen = QDesktopWidget().screenGeometry()
size = self.geometry()
newLeft = (screen.width() - size.width()) / 2
newTop = (screen.height() - size.height()) / 2
self.move(newLeft, newTop)
def start_timer(self):
self.num_index = self.comboBox1.currentIndex()
self.num = self.comboBox1.currentText()
if self.num:
self.comboBox1.removeItem(self.num_index)
self.newItem = QTableWidgetItem('{}'.format(self.num))
self.newItem.setTextAlignment(Qt.AlignCenter)
self.tablewidget.setItem(self.x, 0, self.newItem)
self.timer_dict["{}".format(self.num)] = QTimer()
self.time_dict["{}".format(self.num)] = QTime(0, 0, 0)
self.timer_dict["{}".format(self.num)].start(1000)
self.timer_dict["{}".format(self.num)].timeout.connect(self.showTime)
self.x += 1
def showCurrentTime(self):
self.ctime = QDateTime.currentDateTime()
timeDisplay = self.ctime.toString("yyyy-MM-dd hh:mm:ss")
self.label_1.setText(timeDisplay)
def showTime(self):
self.time_dict['{}'.format(self.num)] = self.time_dict['{}'.format(self.num)].addSecs(1)
timeDisplay = self.time_dict['{}'.format(self.num)].toString("mm:ss")
self.newItem2 = QTableWidgetItem(timeDisplay)
self.newItem2.setTextAlignment(Qt.AlignCenter)
self.tablewidget.setItem(self.x - 1, 1, self.newItem2)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
ui = MainWindow()
ui.show()
sys.exit(app.exec_())
问题是您对 QTimer 使用了相同的属性,导致您删除了之前的对象(对于 newItem 和 newItem2 也是如此)。一个可能的解决方案是创建一个 class 来存储对项目的访问,例如使用 QPersintentModelIndex 并暂时使用 QTimer 和 QElapsedTimer。
from dataclasses import dataclass
from functools import cached_property
from PyQt5.QtCore import (
QElapsedTimer,
QModelIndex,
QPersistentModelIndex,
Qt,
QTime,
QTimer,
)
from PyQt5.QtWidgets import (
QApplication,
QHeaderView,
QPushButton,
QTableWidget,
QTableWidgetItem,
QVBoxLayout,
QWidget,
)
@dataclass
class TimerData:
name_index: QPersistentModelIndex
time_index: QPersistentModelIndex
@cached_property
def timer(self):
timer = QTimer(interval=500)
timer.timeout.connect(self._handle_timeout)
return timer
@cached_property
def timer_elapsed(self):
return QElapsedTimer()
def start(self):
self.timer_elapsed.start()
self.timer.start()
self._handle_timeout()
def stop(self):
self.timer.stop()
def _handle_timeout(self):
if self.time_index.isValid():
time = QTime.fromMSecsSinceStartOfDay(self.timer_elapsed.elapsed())
model = self.time_index.model()
model.setData(QModelIndex(self.time_index), time.toString("mm:ss"))
class Widget(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self._timer_datas = list()
self.button = QPushButton("Add")
self.table = QTableWidget(0, 2)
self.table.horizontalHeader().setStretchLastSection(True)
self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
lay = QVBoxLayout(self)
lay.addWidget(self.button)
lay.addWidget(self.table)
self.button.clicked.connect(self.handle_clicked)
def handle_clicked(self):
self.add_timer()
def add_timer(self):
row = self.table.rowCount()
name_item = QTableWidgetItem(f"name-{row}")
name_item.setTextAlignment(Qt.AlignCenter)
time_item = QTableWidgetItem()
time_item.setTextAlignment(Qt.AlignCenter)
self.table.insertRow(row)
self.table.setItem(row, 0, name_item)
self.table.setItem(row, 1, time_item)
timer_data = TimerData(
QPersistentModelIndex(self.table.indexFromItem(name_item)),
QPersistentModelIndex(self.table.indexFromItem(time_item)),
)
self._timer_datas.append(timer_data)
timer_data.start()
def main():
import sys
app = QApplication(sys.argv)
w = Widget()
w.resize(640, 480)
w.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
我正在尝试制作一个具有多个计时器的 GUI。当我单击一个按钮时,它将启动一个计时器并显示在一个 tableWidget 中。而这个tableWidget有两列代表数字和时间,就像这样:(每个计时器将同时运行)
Name | Time Elapse |
---|---|
Thing#1 | 00:01 |
Thing#2 | 00:03 |
Thing#3 | 01:23 |
我的原代码是这样的:(不是全部)
x=0
self.Button1.clicked.connect(self.btn)
def btn(self):
#set Thing#1, Thing#2... to the Name column
self.newItem = QTableWidgetItem('{}'.format(self.num))
self.newItem.setTextAlignment(Qt.AlignCenter)
self.tablewidget.setItem(self.x, 0, self.newItem)
#start timer
self.timer = QTimer()
self.time = QTime(0, 0, 0)
self.timer.start(1000)
self.timer.timeout.connect(self.showTime)
self.x += 1
def showTime(self):
self.time = self.time.addSecs(1)
timeDisplay = self.time.toString("mm:ss")
self.newItem2 = QTableWidgetItem(timeDisplay)
self.newItem2.setTextAlignment(Qt.AlignCenter)
self.tablewidget.setItem(self.x - 1, 1, self.newItem2)
当我单击该按钮以创建 Thing#1 时,我得到的结果是,它一开始就起作用了。然而,当我再次点击那个按钮时,Thing#1 停止了,而 Thing#2 开始了。最奇怪的是时间的速度翻了一番,就像看视频快进2倍一样。当我再次点击时,它是 3 倍速等等。
真不知道为什么,网上也查不到。但是,我确实找到了一些关于如何在 PyQt 中制作多个计时器的内容,它说要创建多个 QTimer,所以我将代码更改为:
x=0
self.timer_dict={}
self.time_dict={}
self.Button1.clicked.connect(self.btn)
def btn(self):
#set Thing#1, Thing#2... to the Name column
self.newItem = QTableWidgetItem('{}'.format(self.num))
self.newItem.setTextAlignment(Qt.AlignCenter)
self.tablewidget.setItem(self.x, 0, self.newItem)
#start timer
self.timer_dict["{}".format(self.num)] = QTimer()
self.time_dict["{}".format(self.num)] = QTime(0, 0, 0)
self.timer_dict["{}".format(self.num)].start(1000)
self.timer_dict["{}".format(self.num)].timeout.connect(self.showTime)
self.x += 1
def showTime(self):
self.time_dict['{}'.format(self.num)] = self.time_dict['{}'.format(self.num)].addSecs(1)
timeDisplay = self.time_dict['{}'.format(self.num)].toString("mm:ss")
self.newItem2 = QTableWidgetItem(timeDisplay)
self.newItem2.setTextAlignment(Qt.AlignCenter)
self.tablewidget.setItem(self.x - 1, 1, self.newItem2)
它只是为每个'Thing',创建对应的QTimer对象。但是,我得到的结果和原来的一样。
但我确实有一些新发现。当我不小心这样做时:
def btn(self):
self.timer_dict={}
self.time_dict={}
#set Thing#1, Thing#2... to the Name column
self.newItem = QTableWidgetItem('{}'.format(self.num))
.........
每次单击该按钮时,它都会重置 self.timer_dict 和 self.time_dict。它不能做多个定时器,但至少时间的速度不会是2x、3x。
我想我使用 self.tablewidget.setItem(self.x - 1, 1, self.newItem2)(最后一行代码)来更新计时器。如果我点击按钮,它会移动到下一行,最后一行不会更新。因此,我一次只能有一个计时器,尽管它应该在后台有多个计时器。
也许我应该使用多线程或 QThread 来实现我的目标?实在是看不懂。
更新: 关于我的问题的一些可重现的代码,我想我加快时间的原因可能是我使用另一个 QTimer 来更新当前时间?
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import QTime, Qt, QTimer, QDateTime
from PyQt5.QtWidgets import *
import sys
class MainWindow(QMainWindow):
# create the window
def __init__(self):
super(MainWindow, self).__init__()
self.show_QinputDialog()
x = 0
# create widget on the window
def initUI(self, n):
self.setFixedSize(220, 320)
self.setWindowTitle("Multiple Timer System")
self.center()
# set layout
self.centralwidget = QtWidgets.QWidget(self)
self.widget = QtWidgets.QWidget(self.centralwidget)
self.widget.setGeometry(QtCore.QRect(10, 10, 200, 300))
self.gridLayout = QtWidgets.QGridLayout(self.widget)
self.gridLayout.setContentsMargins(0, 0, 0, 0)
self.setCentralWidget(self.centralwidget)
# Section 1: Set Timer
self.Group1 = QtWidgets.QGroupBox(self.widget)
self.horizontalLayout = QtWidgets.QHBoxLayout(self.Group1)
self.comboBox1 = QtWidgets.QComboBox(self.Group1)
for i in range(1, n + 1):
self.comboBox1.addItem("Timer#{}".format(i))
self.horizontalLayout.addWidget(self.comboBox1)
self.Button1 = QtWidgets.QPushButton(self.Group1)
self.horizontalLayout.addWidget(self.Button1)
self.gridLayout.addWidget(self.Group1, 0, 0, 1, 1)
# Section 2: Display Current Time
self.Group2 = QtWidgets.QGroupBox(self.widget)
self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.Group2)
self.label_1 = QtWidgets.QLabel(self.Group2)
self.horizontalLayout_2.addWidget(self.label_1)
self.showCurrentTime()
self.timer = QTimer()
self.timer.start(1000)
self.timer.timeout.connect(self.showCurrentTime)
self.gridLayout.addWidget(self.Group2, 1, 0, 1, 1)
# Section 3: Table
self.Group3 = QtWidgets.QGroupBox(self.widget)
self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.Group3)
self.tablewidget = QtWidgets.QTableWidget(self.Group3)
self.tablewidget.setRowCount(n)
self.tablewidget.setColumnCount(2)
self.tablewidget.setHorizontalHeaderLabels(['Name', 'Time Elapse'])
self.tablewidget.resizeRowsToContents()
self.tablewidget.setColumnWidth(0, 76)
self.tablewidget.setColumnWidth(1, 77)
# set rowHeader invisible
self.tablewidget.verticalHeader().setVisible(False)
# ban editing
self.tablewidget.setEditTriggers(QAbstractItemView.NoEditTriggers)
# ban selection
self.tablewidget.setSelectionMode(QAbstractItemView.NoSelection)
self.verticalLayout_3.addWidget(self.tablewidget)
self.gridLayout.addWidget(self.Group3, 2, 0, 1, 1)
self.Group2.setTitle("Current Time")
self.Group3.setTitle("Current Running Timer")
self.Group1.setTitle("Start Timer")
self.Button1.setText("Start")
self.timer_dict={}
self.time_dict={}
self.Button1.clicked.connect(self.start_timer)
def show_QinputDialog(self):
# show dialog to let user input the number of timers
dialog = QInputDialog(self)
dialog.setIntRange(1, 100)
dialog.setInputMode(QInputDialog.IntInput)
dialog.setLabelText("Please enter the number of timer:(1-100)")
dialog.setWindowTitle("Multiple Timer System")
dialog.setIntValue(10)
self.center()
if dialog.exec_() == QtWidgets.QDialog.Accepted:
self.n = dialog.intValue()
self.timer_list = []
for i in range(1, self.n + 1):
self.timer_list.extend(['Timer#{}'.format(i)])
self.initUI(self.n)
def center(self):
screen = QDesktopWidget().screenGeometry()
size = self.geometry()
newLeft = (screen.width() - size.width()) / 2
newTop = (screen.height() - size.height()) / 2
self.move(newLeft, newTop)
def start_timer(self):
self.num_index = self.comboBox1.currentIndex()
self.num = self.comboBox1.currentText()
if self.num:
self.comboBox1.removeItem(self.num_index)
self.newItem = QTableWidgetItem('{}'.format(self.num))
self.newItem.setTextAlignment(Qt.AlignCenter)
self.tablewidget.setItem(self.x, 0, self.newItem)
self.timer_dict["{}".format(self.num)] = QTimer()
self.time_dict["{}".format(self.num)] = QTime(0, 0, 0)
self.timer_dict["{}".format(self.num)].start(1000)
self.timer_dict["{}".format(self.num)].timeout.connect(self.showTime)
self.x += 1
def showCurrentTime(self):
self.ctime = QDateTime.currentDateTime()
timeDisplay = self.ctime.toString("yyyy-MM-dd hh:mm:ss")
self.label_1.setText(timeDisplay)
def showTime(self):
self.time_dict['{}'.format(self.num)] = self.time_dict['{}'.format(self.num)].addSecs(1)
timeDisplay = self.time_dict['{}'.format(self.num)].toString("mm:ss")
self.newItem2 = QTableWidgetItem(timeDisplay)
self.newItem2.setTextAlignment(Qt.AlignCenter)
self.tablewidget.setItem(self.x - 1, 1, self.newItem2)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
ui = MainWindow()
ui.show()
sys.exit(app.exec_())
问题是您对 QTimer 使用了相同的属性,导致您删除了之前的对象(对于 newItem 和 newItem2 也是如此)。一个可能的解决方案是创建一个 class 来存储对项目的访问,例如使用 QPersintentModelIndex 并暂时使用 QTimer 和 QElapsedTimer。
from dataclasses import dataclass
from functools import cached_property
from PyQt5.QtCore import (
QElapsedTimer,
QModelIndex,
QPersistentModelIndex,
Qt,
QTime,
QTimer,
)
from PyQt5.QtWidgets import (
QApplication,
QHeaderView,
QPushButton,
QTableWidget,
QTableWidgetItem,
QVBoxLayout,
QWidget,
)
@dataclass
class TimerData:
name_index: QPersistentModelIndex
time_index: QPersistentModelIndex
@cached_property
def timer(self):
timer = QTimer(interval=500)
timer.timeout.connect(self._handle_timeout)
return timer
@cached_property
def timer_elapsed(self):
return QElapsedTimer()
def start(self):
self.timer_elapsed.start()
self.timer.start()
self._handle_timeout()
def stop(self):
self.timer.stop()
def _handle_timeout(self):
if self.time_index.isValid():
time = QTime.fromMSecsSinceStartOfDay(self.timer_elapsed.elapsed())
model = self.time_index.model()
model.setData(QModelIndex(self.time_index), time.toString("mm:ss"))
class Widget(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self._timer_datas = list()
self.button = QPushButton("Add")
self.table = QTableWidget(0, 2)
self.table.horizontalHeader().setStretchLastSection(True)
self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
lay = QVBoxLayout(self)
lay.addWidget(self.button)
lay.addWidget(self.table)
self.button.clicked.connect(self.handle_clicked)
def handle_clicked(self):
self.add_timer()
def add_timer(self):
row = self.table.rowCount()
name_item = QTableWidgetItem(f"name-{row}")
name_item.setTextAlignment(Qt.AlignCenter)
time_item = QTableWidgetItem()
time_item.setTextAlignment(Qt.AlignCenter)
self.table.insertRow(row)
self.table.setItem(row, 0, name_item)
self.table.setItem(row, 1, time_item)
timer_data = TimerData(
QPersistentModelIndex(self.table.indexFromItem(name_item)),
QPersistentModelIndex(self.table.indexFromItem(time_item)),
)
self._timer_datas.append(timer_data)
timer_data.start()
def main():
import sys
app = QApplication(sys.argv)
w = Widget()
w.resize(640, 480)
w.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()