当 singleShot 为 True 时,QTimer 从不触发超时
QTimer never fires timeout when singleShot is True
我正在尝试使用 QTimer
将缓冲行附加到 QStandatdItemModel
的子 class。
请注意,在我下面的代码中,我还尝试使用 QTimer.singleShot(self.refresh, self.__dump_buffer)
而不是实例化 QTimer
,并且两种样式似乎都不起作用,无论是静态成员还是实例化的 class 方法。我还尝试将 QTimer
移动到主线程,但这没有帮助。无论我尝试什么,连接到 QTimer.timeout
的插槽似乎永远不会被解雇,无论我尝试什么。我已经通过 Dump buffer
从未打印到控制台这一事实验证了这一点。
class ProgressLogItemModel(QStandardItemModel):
def __init__(self, parent=None, limit=1000, refresh=20):
super().__init__(parent)
self.limit = limit
self.timer = QTimer()
self.buffer = list()
# self.timer.moveToThread(QApplication.instance().thread())
self.timer.setSingleShot(True)
self.timer.setInterval(refresh) # The default is 20ms, being 50Hz
# Every time the single-shot timer runs out after 'refresh' milliseconds, dump the buffer
self.timer.timeout.connect(self.__dump_buffer)
@helpers.make_slot()
def __dump_buffer(self):
print("Dump buffer")
self.insertRows(self.rowCount(), len(self.buffer))
for offset, item in enumerate(self.buffer):
self.setData(self.index(self.rowCount() - len(self.buffer) + offset, 0), item)
self.buffer.clear() # Reset the buffer
# Not sure if the 'stop()' this is necessary but it is here
# to ensure that 'isActive()' returns 'False'
self.timer.stop()
def __apply_limit(self):
if self.rowCount() > self.limit:
# Remove rows from the beginning, count being the number of rows over the limit
self.removeRows(0, self.rowCount() - self.limit)
def insertRows(self, row, count, _=None):
super().insertRows(row, count)
self.__apply_limit()
def appendRow(self, item):
# Append the QStandardItem to the internal list to be popped into the model on the next timeout
self.buffer.append(item)
# If the timer is not currently running (this method has not been called
# before less than 'refresh' milliseconds ago), start the next single-shot to dump the buffer
if not self.timer.isActive():
print("Timer started")
self.timer.start() # Start the next single-shot
class ExampleProgressLogDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.progress_log_list_view = QListView(self)
self.progress_log_item_model = ProgressLogItemModel(self.progress_log_list_view)
self.progress_log_list_view.setModel(self.progress_log_item_model)
self.setLayout(QVBoxLayout(self))
self.layout().addWidget(self.progress_log_list_view)
def log(self, text):
self.progress_log_item_model.appendRow(QStandardItem(text))
if __name__ == "__main__":
import sys
from threading import Thread
from qtpy.QtWidgets import QApplication
app = QApplication()
dialog = ExampleProgressLogDialog()
def __add_log_lines():
for line in range(10000):
dialog.log(f"Log line: {line}")
add_log_lines_thread = Thread(target=__add_log_lines(), daemon=True)
add_log_lines_thread.start()
dialog.show()
sys.exit(app.exec_())
实际上,当应该向用户提供反馈时,subclass 的 QDialog
将从 QMainWindow
的实例实例化。 QTimer
将从任何想要调用 log
方法的潜在线程启动。
我不知道helpers.make_slot
是做什么的,所以我只是评论它,并且正确调用了__dump_buffer
。
但是有一个大问题:使用 QStandardItemModel 插入行是不够的,您还必须使用 setItem()
(而不是 setData()
)为这些索引设置 QStandardItems。
将该循环更改为:
for offset, item in enumerate(self.buffer):
self.setItem(self.rowCount() - len(self.buffer) + offset, 0, item)
请注意,不鼓励直接从 python 线程访问 Qt 对象。虽然在这种情况下,这不是什么大问题(因为 QStandardItems 不是 QObject,而是 QStandardItemModel 是 ),通常最好创建 QThread 子类并使用 signals/slots 与之交互主线程中的 Qt 对象。
我正在尝试使用 QTimer
将缓冲行附加到 QStandatdItemModel
的子 class。
请注意,在我下面的代码中,我还尝试使用 QTimer.singleShot(self.refresh, self.__dump_buffer)
而不是实例化 QTimer
,并且两种样式似乎都不起作用,无论是静态成员还是实例化的 class 方法。我还尝试将 QTimer
移动到主线程,但这没有帮助。无论我尝试什么,连接到 QTimer.timeout
的插槽似乎永远不会被解雇,无论我尝试什么。我已经通过 Dump buffer
从未打印到控制台这一事实验证了这一点。
class ProgressLogItemModel(QStandardItemModel):
def __init__(self, parent=None, limit=1000, refresh=20):
super().__init__(parent)
self.limit = limit
self.timer = QTimer()
self.buffer = list()
# self.timer.moveToThread(QApplication.instance().thread())
self.timer.setSingleShot(True)
self.timer.setInterval(refresh) # The default is 20ms, being 50Hz
# Every time the single-shot timer runs out after 'refresh' milliseconds, dump the buffer
self.timer.timeout.connect(self.__dump_buffer)
@helpers.make_slot()
def __dump_buffer(self):
print("Dump buffer")
self.insertRows(self.rowCount(), len(self.buffer))
for offset, item in enumerate(self.buffer):
self.setData(self.index(self.rowCount() - len(self.buffer) + offset, 0), item)
self.buffer.clear() # Reset the buffer
# Not sure if the 'stop()' this is necessary but it is here
# to ensure that 'isActive()' returns 'False'
self.timer.stop()
def __apply_limit(self):
if self.rowCount() > self.limit:
# Remove rows from the beginning, count being the number of rows over the limit
self.removeRows(0, self.rowCount() - self.limit)
def insertRows(self, row, count, _=None):
super().insertRows(row, count)
self.__apply_limit()
def appendRow(self, item):
# Append the QStandardItem to the internal list to be popped into the model on the next timeout
self.buffer.append(item)
# If the timer is not currently running (this method has not been called
# before less than 'refresh' milliseconds ago), start the next single-shot to dump the buffer
if not self.timer.isActive():
print("Timer started")
self.timer.start() # Start the next single-shot
class ExampleProgressLogDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.progress_log_list_view = QListView(self)
self.progress_log_item_model = ProgressLogItemModel(self.progress_log_list_view)
self.progress_log_list_view.setModel(self.progress_log_item_model)
self.setLayout(QVBoxLayout(self))
self.layout().addWidget(self.progress_log_list_view)
def log(self, text):
self.progress_log_item_model.appendRow(QStandardItem(text))
if __name__ == "__main__":
import sys
from threading import Thread
from qtpy.QtWidgets import QApplication
app = QApplication()
dialog = ExampleProgressLogDialog()
def __add_log_lines():
for line in range(10000):
dialog.log(f"Log line: {line}")
add_log_lines_thread = Thread(target=__add_log_lines(), daemon=True)
add_log_lines_thread.start()
dialog.show()
sys.exit(app.exec_())
实际上,当应该向用户提供反馈时,subclass 的 QDialog
将从 QMainWindow
的实例实例化。 QTimer
将从任何想要调用 log
方法的潜在线程启动。
我不知道helpers.make_slot
是做什么的,所以我只是评论它,并且正确调用了__dump_buffer
。
但是有一个大问题:使用 QStandardItemModel 插入行是不够的,您还必须使用 setItem()
(而不是 setData()
)为这些索引设置 QStandardItems。
将该循环更改为:
for offset, item in enumerate(self.buffer):
self.setItem(self.rowCount() - len(self.buffer) + offset, 0, item)
请注意,不鼓励直接从 python 线程访问 Qt 对象。虽然在这种情况下,这不是什么大问题(因为 QStandardItems 不是 QObject,而是 QStandardItemModel 是 ),通常最好创建 QThread 子类并使用 signals/slots 与之交互主线程中的 Qt 对象。