在另一个线程中编辑抽象模型后如何正确更新 pyQt 中的视图?

How to correctly update view in pyQt after editing abstract model in another thread?

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

我正在使用 pyqt 5.11.1 开发 Python 3.6。我尝试从我的模型的 setData 方法发出 dataChanged(以及 layoutAboutToBeChanged、layoutChanged、editCompleted)信号 - none 有效。 然后我想出了两种可能的解决方案-

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

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

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


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_()

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

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

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

直接从另一个线程访问模型是不好的,因为 QObjects 不是 thread-safe,而是创建一个 QObject,通过信号将数据发送到主线程,在这种情况下是一个简单的操作我创建了接收行、列和数据的插槽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_()