首页 > 解决方案 > 在另一个线程中编辑抽象模型后如何正确更新 pyQt 中的视图?

问题描述

我正在尝试从另一个 threading.Thread 设置我的 QAbstractTableModel(连接到 QTableView)的 setData()。模型中的数据按预期更改,但视图不会自行更新(仅在单击触发视图更新的表视图后)。实施此类更新的最佳方式是什么?

我正在使用 pyqt 5.11.1 开发 Python 3.6。我试图从我的模型的 setData 方法发出 dataChanged(以及 layoutAboutToBeChanged、layoutChanged、editCompleted)信号——这些都不起作用。然后我想出了两个可能的解决方案 -

  1. 从 setData 发出 modelReset 或
  2. 在模型中制作 QTimer 并将其连接到为模型的所有索引发出 dataChanged 的​​方法

两者都按预期工作,但我认为这不是一个很好的解决方案,因为首先要更新整个表(我相信是这样)而且它不是真正健康的用例吗?除了显示数据的一些延迟之外,第二种解决方案只会给应用程序带来恒定的负载。

这是我的问题的最小(希望如此)可重现的例子


import sys
import threading
import time

from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt as Qt


class CopterDataModel(QtCore.QAbstractTableModel):
    def __init__(self, parent=None):
        super(CopterDataModel, self).__init__(parent)
        self.data_contents = [[1, 2]]

    def rowCount(self, n=None):
        return len(self.data_contents)

    def columnCount(self, n=None):
        return 2

    def data(self, index, role):
        row = index.row()
        col = index.column()
        #print('row {}, col {}, role {}'.format(row, col, role)) #for debug
        if role == Qt.DisplayRole:
            return self.data_contents[row][col] or ""


    @QtCore.pyqtSlot()
    def setData(self, index, value, role=Qt.EditRole):
        if not index.isValid():
            return False

        if role == Qt.EditRole:
            self.data_contents[index.row()][index.column()] = value
            print("edit", value)

            self.modelReset.emit() # working fine
            #self.dataChanged.emit(index, index, [Qt.EditRole]) # NOT WORKING

        else:
            return False

        return True

    def flags(self, index):
        roles = Qt.ItemIsSelectable | Qt.ItemIsEnabled
        return roles

if __name__ == '__main__':

    def timer():
        idc = 1001
        while True:
            myModel.setData(myModel.index(0, 0), idc)
            idc += 1
            time.sleep(1)

    app = QtWidgets.QApplication.instance()
    if app is None:
        app = QtWidgets.QApplication(sys.argv)

    tableView = QtWidgets.QTableView()
    myModel = CopterDataModel(None)

    tableView.setModel(myModel)

    tableView.show()

    t = threading.Thread(target=timer, daemon=True)
    t.start()

    app.exec_()

表视图的索引 (0, 0) 应该每秒更新一次,计数器递增(当我尝试发出 dataChanged 信号时不会发生这种情况,仅使用 modelReset)。(请注意,这只是线程的最小示例,在实际代码中具有更复杂的逻辑,并且数据不是“在计时器”时传入的)

来自https://github.com/Taar2/pyqt5-modelview-tutorial/blob/master/modelview_3.py的计时器调整也使其工作(上述解决方案的缺点)。

我希望信号以相同的方式工作,但由于某种原因它没有发生,并且视图不会使用从线程调用的 dataChanged 信号进行更新。

标签: pythonpython-3.xpyqtpyqt5python-multithreading

解决方案


直接从另一个线程访问模型是不好的,因为 QObjects 不是线程安全的,而是创建了一个 QObject,通过信号将数据发送到主线程,在这种情况下,为了一个简单的操作,我创建了 slot update_item接收行、列和数据。

import sys
import threading
import time

from PyQt5 import QtCore, QtGui, QtWidgets


class CopterDataModel(QtCore.QAbstractTableModel):
    def __init__(self, parent=None):
        super(CopterDataModel, self).__init__(parent)
        self.data_contents = [[1, 2]]

    def rowCount(self, n=None):
        return len(self.data_contents)

    def columnCount(self, n=None):
        return 2

    def data(self, index, role):
        row = index.row()
        col = index.column()
        # print('row {}, col {}, role {}'.format(row, col, role)) #for debug
        if role == QtCore.Qt.DisplayRole:
            return self.data_contents[row][col] or ""

    def setData(self, index, value, role=QtCore.Qt.EditRole):
        if not index.isValid():
            return False

        if role == QtCore.Qt.EditRole:
            self.data_contents[index.row()][index.column()] = value
            print("edit", value)
            self.dataChanged.emit(
                index, index, (QtCore.Qt.EditRole,)
            )  # NOT WORKING
        else:
            return False
        return True

    def flags(self, index):
        return QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled

    @QtCore.pyqtSlot(int, int, QtCore.QVariant)
    def update_item(self, row, col, value):
        ix = self.index(row, col)
        self.setData(ix, value)


class SignalManager(QtCore.QObject):
    fooSignal = QtCore.pyqtSignal(int, int, QtCore.QVariant)


if __name__ == "__main__":

    def timer(obj):
        idc = 1001
        while True:
            obj.fooSignal.emit(0, 0, idc)
            idc += 1
            time.sleep(1)

    app = QtWidgets.QApplication.instance()
    if app is None:
        app = QtWidgets.QApplication(sys.argv)

    foo = SignalManager()

    tableView = QtWidgets.QTableView()
    myModel = CopterDataModel()
    foo.fooSignal.connect(myModel.update_item)

    tableView.setModel(myModel)

    tableView.show()

    t = threading.Thread(target=timer, args=(foo,), daemon=True)
    t.start()

    app.exec_()

推荐阅读