I'm trying to setData() of my QAbstractTableModel (which is connected to QTableView) from another threading.Thread. Data in model changing as expected, but view isn't updating by itself (only after clicking on table view which provokes view to update). What's the best way of implementing such update?
I'm working on Python 3.6 with pyqt 5.11.1. I've tried to emit dataChanged (as well as layoutAboutToBeChanged, layoutChanged, editCompleted)signal from setData method of my model - none of that works. Then I came up with two possible solutions -
Both of that works as expected, but I think that this is not really good solutions as first making the whole table to update (I believe so) and it's not really healthy use case for it? And the second solution will just give a constant load on app aside of some delay of displaying data.
That's minimal (hope so) reproducible example of my problem
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_()
Index (0, 0) of table view should be updating every second with incrementing counter (which not happening when I trying to emit dataChanged signal, only working with modelReset). (please note, that's just minimal example of thread which have more complex logic in real code, and data not incoming "at timer")
Timer tweak from https://github.com/Taar2/pyqt5-modelview-tutorial/blob/master/modelview_3.py also making it work (cons of that solution described above).
I expect signals to work the same way, but for some reason it's not happening and view doesn't updates with dataChanged signal called from thread.
It is not good to access the model directly from another thread since the QObjects are not thread-safe, instead it creates a QObject that sends the data to the main thread through signals, in this case for a simple operation I created the slot update_item that receives the row, column and data.
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_()