I have subclassed QAbstractTableModel (using PyQt4), including the setData method. My data is held in a nested list, with column headers ("col_headers") and row headers ("row_headers) stored in lists. Row and col headers are not unique in case that is relevant.
Here's the init:
def __init__(self):
self.arraydata = [[]]
self.col_headers = [] # Columns
self.row_headers = [] # Rows
I have implemented setData as follows (with some debugging print statements):
def setData(self, index, value, role=QtCore.Qt.EditRole):
row = index.row()
column = index.column()
print('row {} col {}'.format(row, col) # This gives the expected values
print('data before: {}'.format(self.arraydata) # The before state
self.arraydata[row][column] = value
print('data after: {}'.format(self.arraydata) # The after state = the problem
self.dataChanged.emit(index, index)
This usually works when I first start running it, and the table populates as expected, one cell at a time.
However, as more setData calls come through, instead of only updating the particular item, it often (but not always) updates the value for the same column for most or all of the other rows. This problem is made worse (or triggered if it hasn't already happened) if my view of the model (a QTableView) is scrolled or resized, or the window is resized.
So my question is, how do I setData on my model so that only one item in the model is changed? And is a list of lists the correct way for me to be holding the data? Finally, could the problem be caused by too many emits in quick succession to the view, with the subsequent callbacks "under the hood" by the view to the model messing with the indexing?
Full (simplified) code:
from PyQt4 import QtCore, QtGui
ROWS = 0
COLS = 0
class MyTableModel(QtCore.QAbstractTableModel):
def updateData(self, row_header, col_header, value):
ix_to_chg = []
for i, row_h in enumerate(self.row_headers):
for j, col_h in enumerate(self.col_headers):
if row_h == row_header and col_h == col_header:
ix_to_chg.append((i, j))
for i, j in ix_to_chg:
index=self.index(i, j)
self.setData(index=index, value=value, role=QtCore.Qt.EditRole)
def insertRows(self, position, count, new_row_headers, parent=QtCore.QModelIndex()):
self.beginInsertRows(QtCore.QModelIndex(), position, position + count -1)
empty_row = ['' for x in range(self.columnCount())]
for i in range(count):
self.arraydata.insert(position, empty_row)
new_row_header = new_row_headers[::-1][i]
self.row_headers.insert(position, new_row_header)
self.endInsertRows()
def insertColumns(self, position, count, new_col_headers, parent=QtCore.QModelIndex()):
self.beginInsertColumns(QtCore.QModelIndex(), position, position + count -1)
empty_cell = ''
for i in range(count):
self.arraydata[0].insert(position+i, empty_cell)
new_col_header = new_col_headers[i]
self.col_headers.insert(position + i, new_col_header)
self.endInsertColumns()
def setData(self, index, value, role=QtCore.Qt.EditRole):
row = index.row()
column = index.column()
print('row {} col {}'.format(row, column)) # This gives the expected values
print('data before: {}'.format(self.arraydata)) # The before state
self.arraydata[row][column] = value
print('data after: {}'.format(self.arraydata)) # The after state = the problem
self.dataChanged.emit(index, index)
def data(self, index, role=QtCore.Qt.DisplayRole):
if role == QtCore.Qt.DisplayRole:
i = index.row()
j = index.column()
return '{}'.format(self.arraydata[i][j])
else:
return None
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if role == QtCore.Qt.DisplayRole:
if orientation == QtCore.Qt.Horizontal:
if self.columnCount() == 0:
return []
else:
return self.col_headers[section]
else:
if self.rowCount() == 0:
return []
else:
return self.row_headers[section]
def flags(self, index):
return QtCore.Qt.ItemIsEnabled
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self.arraydata)-1
def columnCount(self, parent=QtCore.QModelIndex()):
return len(self.arraydata[0])
def setupModel(self, numRows, numCols):
for row_num in range(numRows):
for col_num in range(numCols):
if self.rowCount(self) < numRows:
for i in range(numRows - self.rowCount(self)):
self.arraydata.append([])
if self.columnCount(self) < numCols:
for i in range(numCols - self.columnCount(self)):
for j in range(self.rowCount(self)):
item = '_'.join(['row', str(j), 'col', str(i)])
self.arraydata[j].append(item)
def __init__(self, col_headers=None, row_headers=None, rows=ROWS, cols=COLS):
QtCore.QAbstractTableModel.__init__(self)
self.arraydata = [[]]
col_headers = ['emptyColHeader' for i in range(cols)] if col_headers is None else col_headers
row_headers = ['emptyRowHeader' for i in range(rows)] if row_headers is None else row_headers
self.col_headers = col_headers
self.row_headers = row_headers
EDIT:
Solved it (with thanks to List of lists changes reflected across sublists unexpectedly).
In insertRows()
I was adding the same list in a loop to each row - they were all referring to the same list. The new code for that function:
def insertRows(self, position, count, new_row_headers, parent=QtCore.QModelIndex()):
self.beginInsertRows(QtCore.QModelIndex(), position, position + count -1)
empty_row = ['' for x in range(self.columnCount())]
for i in range(count):
self.arraydata.insert(position, empty_row.copy())
new_row_header = new_row_headers[::-1][i]
self.row_headers.insert(position, new_row_header)
self.endInsertRows()