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



我正在尝试从另一个线程设置我的QAbstractTableModel(连接到QTableView)的Data()。线。模型中的数据按预期更改,但视图本身不会更新(仅在单击引起视图更新的表视图之后)。实施此类更新的最佳方法是什么?

我正在使用pyqt 5.11.1开发Python 3.6。我试图从我的模型的 setData 方法发出 dataChanged(以及布局关于被改变、布局已更改、编辑已完成)信号 - 这些都不起作用。 然后我想出了两个可能的解决方案——

  1. 发出模型从集合数据重置 或
  2. 在模型中制作QTimer并将其连接到发出数据的方法为模型的所有索引更改

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

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


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 信号更新。

直接从另一个线程访问模型是不好的,因为 QObjects 不是线程安全的,而是创建一个 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_()

最新更新