PyQt: Adding rows to QTableView using QAbstractTableModel

29,513

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()
Share:
29,513
user3439556
Author by

user3439556

Updated on July 17, 2022

Comments

  • user3439556
    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
    user3439556 about 10 years
    Wouldn'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
    TheGerm over 9 years
    Usually 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
    NL23codes about 4 years
    Technically this wouldn't work, because self.tabledata is just a list and is not really connected to the QTableView.
  • brookbot
    brookbot over 2 years
    I 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)