Remove and insertrow for Martin Fitzpatricks example

Heads up! You've already completed this tutorial.

David_Hansson | 2020-06-30 11:46:58 UTC | #1

Hello, how would remove and insertrow look like for Martin Fitzpatricks example above?

First one needs def removeRows()/def InserRows() for the QAbstractTableModel class and also some behaviour inside the QMainwindow class

python
    # This I have for my QAbstractTabelModel class
    def insertRows(self, position, rows, QModelIndex, parent):
        self.beginInsertRows(QModelIndex(), position, position+rows-1)
        for i in range(rows):
            default_row = ['']*len(self._headers)
            self_data.insert(position, default_row)
        self.endInsertRows()
        return true


    def removeRows(self, position, rows, QModelIndex, parent):
        self.beginRemoveRows(QModelIndex(), position, position+rows-1)
        for i in range(rows):
            del(self._data[position])
        self.endRemoveRows()
        return true

Not sure if that is correct, Its what I've pieced together from examples on other sites.

For the QMainwindow class I'm not relay sure how to proceed, the examples on other sites are not very clear to understand.

Some structured extra explanation on this subject for Martins example of this would be helpful.

The row of data should be removed for both the model and the view.


mike2750 | 2020-06-20 19:16:07 UTC | #2

Not sure if this is what your looking for but this is what I have working for my sqlite tableview setup.

python
def initializedModel(self):
    self.model.setTable("commands")
    # self.model.setEditStrategy(QSqlTableModel.OnFieldChange)
    self.model.setEditStrategy(QSqlTableModel.OnManualSubmit)
    self.model.select()
    self.model.setHeaderData(0, Qt.Horizontal, "ID")
    self.model.setHeaderData(1, Qt.Horizontal, "Category")
    self.model.setHeaderData(2, Qt.Horizontal, "command_alias")
    self.model.setHeaderData(3, Qt.Horizontal, "command")
    self.model.setHeaderData(4, Qt.Horizontal, "requires")
    self.model.setHeaderData(5, Qt.Horizontal, "description")
    self.model.setHeaderData(6, Qt.Horizontal, "controlpanel")
    self.model.setHeaderData(7, Qt.Horizontal, "verification")

def onAddRow(self):
    self.model.insertRows(self.model.rowCount(), 1)
    self.model.submit()

def onDeleteRow(self):
    self.model.removeRow(self.tableView.currentIndex().row())
    self.model.submit()
    self.model.select()

def closeEvent(self, event):
    db.close()

To clear all the rows or content you can do something like the below.

python
self.w_tablewidget.clearContents()
self.w_tablewidget.setRowCount(0)

David_Hansson | 2020-06-28 10:23:26 UTC | #3

This is how I have set up my QTableView(not subclassed) and QAbstractTableModel (subclassed as TableModel):

python
    self.model = TableModel(data)
    self.proxyModel = QSortFilterProxyModel()
    self.proxyModel.setSourceModel(self.model)
    self.table_clients.setSortingEnabled(True)
    self.table_clients.setModel(self.proxyModel)
    self.selectionModel = self.table_clients.selectionModel()
    self.table_clients.setSelectionBehavior(QTableView.SelectRows)

I for the QAbstractTableModel class I know have this:

python
def removeRows(self, position, rows, parent):
    print("passed def removingrows")
    #print "\n\t\t ...removeRows() Starting position: '%s'"%position, 'with the total rows to be deleted: ', rows
    self.beginRemoveRows(parent or QModelIndex(), position, position + rows - 1)
    print("passed beginremovingrows")
    for i in range(rows):
        del(self._data[position])
        print("passed deleted row")
    self.endRemoveRows()
    print("passed endRemoveRows")
    return true

For the Mainwindow class I have this:

python
def removerow(self):
    #self.table_clients.model().layoutAboutToBeChanged.emit()
    print("passed def removerow")
    proxy_index = self.table_clients.selectedIndexes()[0]
    print("passed proxyindex = " + str(proxy_index))
    #convert
    source_index = self.proxyModel.mapToSource(proxy_index)
    print("passed sorceindex = " + str(source_index))
    #delete row in source model
    self.model.removeRow(source_index.row())
    # ---- --- something is wrong after this passage - it crashes here ----
    print("passed modelremoveRow source index")
    #self.model.layoutChanged.emit()
    self.table_clients.clearSelection()

It crahes after self.model.removeRow(source:index.row()) because the promt prints this: = RESTART: C:\Users\svart\Documents\Python\Pythonprojects\AIXCRM\PRM_AIX_200629.py passed def removerow passed proxyindex = passed sorceindex = passed def removingrows passed beginremovingrows passed deleted row passed endRemoveRows

This might be something basic thing that is escaping me? And it doesnt change anything if I remove the # from the lines with layoutAboutToBeChanged/layoutChanged, it still crashes at the same place.


David_Hansson | 2020-06-28 10:25:33 UTC | #4

And thank you Mike2750 for the reply but I couldnt make anthing of that advice, partially I guess becouse my program is setup a bit different.


martin | 2020-06-30 12:10:51 UTC | #5

Hi @David_Hansson welcome to the forum! I've written up a small example with two actions which trigger add/remove of rows on the table model. One important thing is to notify the view that the layout of the data has changed after doing this, otherwise it won't update.

python
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt


class TableModel(QtCore.QAbstractTableModel):

    def __init__(self, data):
        super().__init__()
        self._data = data

    def data(self, index, role):
        if role == Qt.DisplayRole:
            value = self._data[index.row()][index.column()]
            return str(value)

    def rowCount(self, index):
        return len(self._data)

    def columnCount(self, index):
        return len(self._data[0])

    def insertRows(self, position, rows, QModelIndex, parent):
        self.beginInsertRows(QModelIndex, position, position+rows-1)
        default_row = ['']*len(self._data[0])  # or _headers if you have that defined.
        for i in range(rows):
            self._data.insert(position, default_row)
        self.endInsertRows()
        self.layoutChanged.emit()
        return True

    def removeRows(self, position, rows, QModelIndex):
        self.beginRemoveRows(QModelIndex, position, position+rows-1)
        for i in range(rows):
            del(self._data[position])
        self.endRemoveRows()
        self.layoutChanged.emit()
        return True

class MainWindow(QtWidgets.QMainWindow):

    def __init__(self):
        super().__init__()

        self.insertaction = QtWidgets.QAction("Insert")
        self.insertaction.triggered.connect(self.insert_row)
        self.deletedaction = QtWidgets.QAction("Delete")
        self.deletedaction.triggered.connect(self.delete_row)

        toolbar = QtWidgets.QToolBar("Edit")
        toolbar.addAction(self.insertaction)
        toolbar.addAction(self.deletedaction)
        self.addToolBar(toolbar)


        self.table = QtWidgets.QTableView()

        data = [
          [1, 9, 2],
          [1, 0, -1],
          [3, 5, 2],
          [3, 3, 2],
          [5, 8, 9],
        ]

        self.model = TableModel(data)
        self.table.setModel(self.model)

        self.setCentralWidget(self.table)

    def insert_row(self):
        index = self.table.currentIndex()
        print(index)
        self.model.insertRows(index.row(), 1, index, None)

    def delete_row(self):
        index = self.table.currentIndex()
        self.model.removeRows(index.row(), 1, index)



app=QtWidgets.QApplication(sys.argv)
window=MainWindow()
window.show()
app.exec_()

David_Hansson | 2020-07-23 12:51:02 UTC | #6

Thank you Martin, this worked for me. Great help.

For others that like me have a proxy model in between the Model and the QTableView I found out that I had to add self.layoutAboutToBeChanged.emit() in the begining of the def insertRows() or def removeRows(). Without that I found out that I could only delete one row or insert one row before the application would be non-responsive, or the delete or insert didnt work at alla after one row insert or delete.

so final code for QSortFilterProxyModel In the sub-classed QAbstractTableModel

python
def insertRows(self, position, rows, QModelIndex, parent):
    self.layoutAboutToBeChanged.emit()
    self.beginInsertRows(QModelIndex, position, position+rows-1)
    default_row = ['']*len(self._data[0])
    for i in range(rows):
        self._data.insert(position, default_row)
    self.endInsertRows()
    self.layoutChanged.emit()
    return True

def removeRows(self, position, rows, QModelIndex):
    self.layoutAboutToBeChanged.emit()
    self.beginRemoveRows(QModelIndex, position, position+rows-1)
    for i in range(rows):
        del(self._data[position])
    self.endRemoveRows()
    self.layoutChanged.emit()
    return True

In the Mainwindow the delete_row() would be like this

python
def delete_row(self):
    proxy_index = self.table.currentIndex()
    index = self.proxyModel.mapToSource(proxy_index)
    self.model.removeRows(index.row(), 1, index)

mike2750 | 2020-07-23 03:21:53 UTC | #7

@David_Hansson Nice thanks for the tip. I'm probably going to need that snippet soon. Saved.


Create GUI Applications with Python & Qt5 by Martin Fitzpatrick — (PyQt5 Edition) The hands-on guide to making apps with Python — Over 10,000 copies sold!

More info Get the book

Well done, you've finished this tutorial! Mark As Complete
[[ user.completed.length ]] completed [[ user.streak+1 ]] day streak

Remove and insertrow for Martin Fitzpatricks example was written by Martin Fitzpatrick .

Martin Fitzpatrick has been developing Python/Qt apps for 8 years. Building desktop applications to make data-analysis tools more user-friendly, Python was the obvious choice. Starting with Tk, later moving to wxWidgets and finally adopting PyQt.