当 singleShot 为 True 时,QTimer 永远不会触发超时 [关闭]

Posted

技术标签:

【中文标题】当 singleShot 为 True 时,QTimer 永远不会触发超时 [关闭]【英文标题】:QTimer never fires timeout when singleShot is True [closed] 【发布时间】:2020-08-03 22:00:00 【问题描述】:

我正在尝试使用QTimer 将缓冲行附加到我的QStandatdItemModel 子类。 请注意,在下面的代码中,我也尝试使用QTimer.singleShot(self.refresh, self.__dump_buffer) 而不是实例化QTimer,但无论是静态成员还是实例化类方法,这两种风格似乎都不起作用。我还尝试将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_())

实际上,当应向用户提供反馈时,子类化的QDialog 将从QMainWindow 的实例中实例化。 QTimer 将从任何想要调用 log 方法的潜在线​​程开始。

【问题讨论】:

您如何使用appendRow()?您可以通过提供minimal, reproducible example 来扩展您的代码吗?另外,您说“我也尝试将 QTimer 移到主线程”:您的意思是您将模型放到单独的线程中吗?如果是这样,为什么呢? @musicamante 已经这样做了。我不确定最小示例是否真的反映了使用ProgressLogItemModel 的(大)类的实际用法。 【参考方案1】:

我不知道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 不是 QObjects,而是 QStandardItemModel ),但通常最好创建 QThread 子类并使用信号/插槽与位于主线程。

【讨论】:

helpers.make_slot 完全按照装饰器的名称执行,它使用 Qt 插槽包装函数。它还做了一些其他的事情来确保插槽为正在使用的 Qt 绑定正确命名,因为qtpy 没有。另外,感谢您的解决方案,使用信号进行更改确实解决了问题。 @pyqtSlot 装饰器完全符合您的描述;无论如何,我们并不确切知道您的装饰器做了什么,并且从理论上讲,这也可能是您的问题的一个原因:为了将来参考,您要么包含与之相关的代码,要么您不使用它例子。 @musicamanate 我的装饰器只是 PyQt 和 PySide2 插槽函数的包装器。就是这样。

以上是关于当 singleShot 为 True 时,QTimer 永远不会触发超时 [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

QT singleShot设置循环

QTimer::singleShot 仅在间隔为 0 时调用 lambda

QTimer::singleShot 在 qkeyevent 中不起作用

QTimer::SingleShot 在对象被删除后触发

QTimer::singleShot(0, object SLOT(obj_slot())) 做啥?

PyQT实时显示串口数据在QtCore.QTimer.singleShot()上抛出最大递归深度超出异常