PyQt: Adding rows to QTableView using QAbstractTableModel
Solution 1
When the underlying data of the model changes, the model should emit either layoutChanged or layoutAboutToBeChanged, so that view updates properly (there's also dataChanged, if you want to update a specific range of cells).
So you just need something like this:
def test(self):
self.tabledata.append([1,1,1,1,1])
self.table.model().layoutChanged.emit()
print 'success'
Solution 2
QAbstractTableModel
have two special methods for that ( beginInsertRows() and endInsertRows()).
You can add api-point in your custom model. For example:
def insertGuest(self, guest):
self.beginInsertRows(QtCore.QModelIndex(), self.rowCount(), self.rowCount())
self.guestsTableData.append(guest)
self.endInsertRows()
Solution 3
I've made your table reference a class variable instead of an instance variable, so you could edit the data for the table from virtually anywhere in your code.
# First access the data of the table
self.tv_model = self.tv.model()
Secondly, I use the sort of pandas-dataframe-editing type approach. Lets say your data that you want to add is stored in a variable on its own:
# These can be whatever, but for consistency,
# I used the data in the OP's example
new_values = [1, 1, 1, 1, 1]
There are different ways the next step can be approached, depending on whether the data is being added to the table, or updating existing values. Adding the data as a new row would be as follows.
# The headers should also be a class variable,
# but I left it as the OP had it
header = ['col_0', 'col_1', 'col_2', 'col_3', 'col_4']
# There are multiple ways of establishing what the row reference should be,
# this is just one example how to add a new row
new_row = len(self.tv_model.dataFrame.index)
for i, col in enumerate(header):
self.tv_model.dataFrame.loc[new_row, col] = new_values[i]
Since self.tv_model is a reference to the actual data of the table, emitting the following signal will update the data, or 'commit' it to the model, so to speak.
self.tv_model.layoutChanged.emit()
user3439556
Updated on July 17, 2022Comments
-
user3439556 almost 2 years
I am super new to Qt programming. I am trying to make a simple table that can have rows added by clicking a button. I can implement the table fine but can't seem to get the updated data to show on the table. I believe my problem stems from the fact that I can't seem to properly call any sort of "change data" method using the button. I've tried several different solutions online all of which have lead to 4 year old, dead-end posts. What I have so far is the basic structure, I just can't figure out how to make the table update with new data.
This is the basic view
I have set up with some test data.
In the final implementation, the table will start empty and I would like to append rows and have them displayed in the table view.
import sys from PyQt4.QtCore import * from PyQt4.QtGui import * class MyWindow(QWidget): def __init__(self): QWidget.__init__(self) # create table self.get_table_data() self.table = self.createTable() # layout self.layout = QVBoxLayout() self.testButton = QPushButton("test") self.connect(self.testButton, SIGNAL("released()"), self.test) self.layout.addWidget(self.testButton) self.layout.addWidget(self.table) self.setLayout(self.layout) def get_table_data(self): self.tabledata = [[1234567890,2,3,4,5], [6,7,8,9,10], [11,12,13,14,15], [16,17,18,19,20]] def createTable(self): # create the view tv = QTableView() # set the table model header = ['col_0', 'col_1', 'col_2', 'col_3', 'col_4'] tablemodel = MyTableModel(self.tabledata, header, self) tv.setModel(tablemodel) # set the minimum size tv.setMinimumSize(400, 300) # hide grid tv.setShowGrid(False) # hide vertical header vh = tv.verticalHeader() vh.setVisible(False) # set horizontal header properties hh = tv.horizontalHeader() hh.setStretchLastSection(True) # set column width to fit contents tv.resizeColumnsToContents() # set row height tv.resizeRowsToContents() # enable sorting tv.setSortingEnabled(False) return tv def test(self): self.tabledata.append([1,1,1,1,1]) self.emit(SIGNAL('dataChanged()')) print 'success' class MyTableModel(QAbstractTableModel): def __init__(self, datain, headerdata, parent=None): """ Args: datain: a list of lists\n headerdata: a list of strings """ QAbstractTableModel.__init__(self, parent) self.arraydata = datain self.headerdata = headerdata def rowCount(self, parent): return len(self.arraydata) def columnCount(self, parent): if len(self.arraydata) > 0: return len(self.arraydata[0]) return 0 def data(self, index, role): if not index.isValid(): return QVariant() elif role != Qt.DisplayRole: return QVariant() return QVariant(self.arraydata[index.row()][index.column()]) def setData(self, index, value, role): pass # not sure what to put here def headerData(self, col, orientation, role): if orientation == Qt.Horizontal and role == Qt.DisplayRole: return QVariant(self.headerdata[col]) return QVariant() def sort(self, Ncol, order): """ Sort table by given column number. """ self.emit(SIGNAL("layoutAboutToBeChanged()")) self.arraydata = sorted(self.arraydata, key=operator.itemgetter(Ncol)) if order == Qt.DescendingOrder: self.arraydata.reverse() self.emit(SIGNAL("layoutChanged()")) if __name__ == "__main__": app = QApplication(sys.argv) w = MyWindow() w.show() sys.exit(app.exec_())
-
user3439556 about 10 yearsWouldn't setData() be needed as well in the model? I've looked through all the documentation and I keep seeing references to needing to have either setData() or insertRows(), etc. Problem is I'm not 100% sure how to implement the setData() method or more importantly how to call it. - EDIT: Well I just tried your edit and it worked perfectly without the setData() method so I guess it isn't needed after all! I really appreciate the help!
-
TheGerm over 9 yearsUsually you need to implement setData() when you want an editable grid, in order to update the correct value in your model. For example:
def setData(self, index, value, role = Qt.EditRole): if role == Qt.EditRole: setattr(self.arraydata[index.row()], self.columns[index.column()], value) self.dataChanged.emit(index, index, ()) return True else: return False
-
NL23codes about 4 yearsTechnically this wouldn't work, because
self.tabledata
is just alist
and is not really connected to the QTableView. -
brookbot over 2 yearsI think this is the better solution since a layoutChanged signal likely causes a redraw or at least reevaluation of the entire view. While begin/endInstertRows is a more subtle way to notify the visuals that a new row has been added and its location. If the row added is not in the current view, then it can be ignored (or just scrollbars updated/redrawn)