Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PyQt: Adding rows to QTableView using QAbstractTableModel

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_())
like image 881
user3439556 Avatar asked Apr 01 '14 16:04

user3439556


3 Answers

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()
like image 117
Andrey Topoleov Avatar answered Nov 19 '22 23:11

Andrey Topoleov


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'
like image 44
ekhumoro Avatar answered Nov 20 '22 00:11

ekhumoro


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()
like image 1
NL23codes Avatar answered Nov 19 '22 23:11

NL23codes