I'm trying to make a GUI which have multiple timers. When I click one button, it will start a timer and display in a tableWidget. And this tableWidget have two columns which represent number and time, just like this:(each timer will run simultaneously)
Name | Time Elapse |
---|---|
Thing#1 | 00:01 |
Thing#2 | 00:03 |
Thing#3 | 01:23 |
My original code is this:(not the whole part)
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)
And what I get is when I clicked that button in order to create Thing#1, and it works at first. However, when I clicked that button again, Thing#1 stopped and Thing#2 started. The strangest thing is the speed of time doubled, just like fastfoward 2x in watching video. And when I clicked again, it's 3x speed and so on.
I really don't know why, and I can't find anything about it on the Internet. However, I do find something about how to make multiple timer in PyQt, it said to create multiple QTimer, so I changed the code to this:
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)
It just for each 'Thing', create corresponding QTimer object. However, I got the same result as the original one.
But I do have some new findings. When I accidentally do this:
def btn(self):
self.timer_dict={}
self.time_dict={}
#set Thing#1, Thing#2... to the Name column
self.newItem = QTableWidgetItem('{}'.format(self.num))
.........
It will reset the self.timer_dict and self.time_dict each time when I clicked that button. It can't make multiple timer, but at least the speed of time won't be 2x, 3x.
I guess that I use self.tablewidget.setItem(self.x - 1, 1, self.newItem2)(last line of code) to update the timer. If I clicked the button, it will moved to the next row and the last row will not update. Thus, I can only have one timer at one time although it should be multiple timer in the background.
And maybe I should use multithread or QThread to achieve my goal? I really don't get it.
UPDATE: Some reproducible code about my question, I think maybe the reason why I got speed up time is that I use another QTimer to update the current time?
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_())
CodePudding user response:
The problem is that you are using the same attribute for the QTimer causing you to delete the previous object (the same for newItem and newItem2). A possible solution is to create a class that stores an access to the items for example using QPersintentModelIndex and for the time use QTimer and 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()