Pandas Model (QAbstractTableModel) binded with PyQt5 QTableView Add Rows not working

467 Views Asked by At

I'm using a custom Pandas Model(QAbstractTableModel) which is editable. I'm trying to add rows and then the user should be able to edit them and pass the values to the dataframe. Unfortunately, the example I found from PyQt5: Implement removeRows for pandas table model doesn't work when I add rows. This is my model class:

class pandasModel(QAbstractTableModel):
     def __init__(self, dataframe: pd.DataFrame, parent=None):
         QAbstractTableModel.__init__(self, parent)
         self._dataframe = dataframe
        
     def rowCount(self, parent=QModelIndex()) -> int:
         """ Override method from QAbstractTableModel

         Return row count of the pandas DataFrame
         """
         if parent == QModelIndex():
             return len(self._dataframe)

         return 0

     def columnCount(self, parent=QModelIndex()) -> int:
         """Override method from QAbstractTableModel

         Return column count of the pandas DataFrame
         """
         if parent == QModelIndex():
             return len(self._dataframe.columns)
         return 0

     def data(self, index: QModelIndex, role=Qt.ItemDataRole):
         """Override method from QAbstractTableModel

         Return data cell from the pandas DataFrame
         """
         if not index.isValid():
             return None
         
         if index.isValid():
            if role == Qt.DisplayRole or role == Qt.EditRole:
                try:
                    value = self._dataframe.iloc[index.row(), index.column()]
                    return str(value)
                    value=float(value)
                except ValueError:                
                    msg = QMessageBox()
                    msg.setIcon(QMessageBox.Critical)
                    msg.setText("Please, use integers or decimals.")
                    msg.setWindowTitle("Error")
                    msg.setStandardButtons(QMessageBox.Ok)
                    msg.exec_()
                    return 0 

     def headerData(
         self, section: int, orientation: Qt.Orientation, role: Qt.ItemDataRole):
         """Override method from QAbstractTableModel

         Return dataframe index as vertical header data and columns as horizontal header data.
         """
         if role == Qt.DisplayRole:
             if orientation == Qt.Horizontal:
                 return str(self._dataframe.columns[section])
             if orientation == Qt.Vertical:
                 return str(self._dataframe.index[section])
         return None
     
     def setData(self, index, value, role):
        if role == Qt.EditRole:
            self._dataframe.iloc[index.row(), index.column()] = value
            return True
        return False
    
     def flags(self, index):
        return Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable
    
     def removeRows(self, position, rows, parent=QModelIndex()):
        start, end = position, position + rows - 1
        if 0 <= start <= end and end < self.rowCount(parent):
            self.beginRemoveRows(parent, start, end)
            for index in range(start, end + 1):
                self._dataframe.drop(index, inplace=True)
            self._dataframe.reset_index(drop=True, inplace=True)
            self.endRemoveRows()
            return True
        return False
    
     def insertRows(self, position, rows, parent=QModelIndex()):
         start, end = position, position + rows - 1
         if 0 <= start <= end:
             self.beginInsertRows(parent, start, end)
             for index in range(start, end + 1):
                 default_row = [[None] for _ in range(self._dataframe.shape[1])]
                 new_df = pd.DataFrame(dict(zip(list(self._dataframe.columns), default_row)))
                 self._dataframe = pd.concat([self._dataframe, new_df])
             self._dataframe = self._dataframe.reset_index(drop=True)
             self.endInsertRows()
             return True
         return False

My global dataframe to be used:

regionalPop = {'Region':[1,1],'Starting_Index': [0,0], 'Ending_Index': [0,0]}
dfRegion=pd.DataFrame(data=regionalPop)

And the main class:

class regionalPlot(QtWidgets.QMainWindow,Ui_regionalWindow):
def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)
    self.setupUi(self)
    
    #populating REGIONS table model
    global dfRegion
    regionalPop = {'Region':[1,1],'Starting_Index': [0,0], 'Ending_Index': [0,0]}
    dfRegion=pd.DataFrame(data=regionalPop)
    self.model1 = pandasModel(dfRegion)
    #sorting enable
    self.proxyModel = QSortFilterProxyModel()
    self.proxyModel.setSourceModel(self.model1)
    #binding model to table
    self.tableView_2.setModel(self.proxyModel)
    #-------------------------------------

    #Button Actions
    #add_button pressed
    self.actionAdd_Region.triggered.connect(lambda: self.insert_row())
    #minus button pressed
    self.actionDelete_Region.triggered.connect(lambda: self.delete_row())
    #update pressed
    self.actionUpdate_Tables.triggered.connect(lambda: self.updateTable())
#ACTIONS
def delete_row(self):
    if self.tableView_2.selectionModel().hasSelection():
        indexes =[QPersistentModelIndex(index) for index in self.tableView_2.selectionModel().selectedRows()]
        for index in indexes:
            print('Deleting row %d...' % index.row())
            self.model1.removeRow(index.row())

def insert_row(self):
    global dfRegion
    self.model1.insertRows(self.model1.rowCount(), 1)
    print (dfRegion)

def updateTable(self):
     #populating table model
     self.model1 = pandasModel(dfRegion)
     #sorting enable
     self.proxyModel = QSortFilterProxyModel()
     self.proxyModel.setSourceModel(self.model1)
     #binding model to table
     self.tableView_2.setModel(self.proxyModel)

And the Ui_regionalWindow:

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_regionalWindow(object):
    def setupUi(self, regionalWindow):
        regionalWindow.setObjectName("regionalWindow")
        regionalWindow.resize(1184, 996)
        self.centralwidget = QtWidgets.QWidget(regionalWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.tableView_2 = QtWidgets.QTableView(self.centralwidget)
        self.tableView_2.setGeometry(QtCore.QRect(10, 90, 256, 831))
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.tableView_2.sizePolicy().hasHeightForWidth())
        self.tableView_2.setSizePolicy(sizePolicy)
        self.tableView_2.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustIgnored)
        self.tableView_2.setAlternatingRowColors(True)
        self.tableView_2.setObjectName("tableView_2")
        self.label_2 = QtWidgets.QLabel(self.centralwidget)
        self.label_2.setGeometry(QtCore.QRect(10, 0, 241, 112))
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.label_2.sizePolicy().hasHeightForWidth())
        self.label_2.setSizePolicy(sizePolicy)
        font = QtGui.QFont()
        font.setPointSize(14)
        self.label_2.setFont(font)
        self.label_2.setObjectName("label_2")
        regionalWindow.setCentralWidget(self.centralwidget)
        self.toolBar = QtWidgets.QToolBar(regionalWindow)
        self.toolBar.setIconSize(QtCore.QSize(40, 40))
        self.toolBar.setObjectName("toolBar")
        regionalWindow.addToolBar(QtCore.Qt.TopToolBarArea, self.toolBar)
        self.actionAdd_Region = QtWidgets.QAction(regionalWindow)
        self.actionAdd_Region.setObjectName("actionAdd_Region")
        self.actionDelete_Region = QtWidgets.QAction(regionalWindow)
        self.actionDelete_Region.setObjectName("actionDelete_Region")
        self.actionUpdate_Tables = QtWidgets.QAction(regionalWindow)
        self.actionUpdate_Tables.setObjectName("actionUpdate_Tables")
        self.toolBar.addAction(self.actionAdd_Region)
        self.toolBar.addAction(self.actionDelete_Region)
        self.toolBar.addSeparator()
        self.toolBar.addAction(self.actionUpdate_Tables)
        self.toolBar.addSeparator()
        self.toolBar.addSeparator()

        self.retranslateUi(regionalWindow)
        QtCore.QMetaObject.connectSlotsByName(regionalWindow)

    def retranslateUi(self, regionalWindow):
        _translate = QtCore.QCoreApplication.translate
        regionalWindow.setWindowTitle(_translate("regionalWindow", "Regional Analysis"))
        self.label_2.setText(_translate("regionalWindow", "Regions"))
        self.toolBar.setWindowTitle(_translate("regionalWindow", "toolBar"))
        self.actionAdd_Region.setText(_translate("regionalWindow", "Add Region"))
        self.actionAdd_Region.setToolTip(_translate("regionalWindow", "Add region of the signal. Left field is the start and right field is the end of the region of the signal."))
        self.actionDelete_Region.setText(_translate("regionalWindow", "Delete Region"))
        self.actionDelete_Region.setToolTip(_translate("regionalWindow", "Delete an existing region of the signal."))
        self.actionUpdate_Tables.setText(_translate("regionalWindow", "Update Tables"))
        self.actionUpdate_Tables.setToolTip(_translate("regionalWindow", "Click to update changes to the tabular data"))


if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    regionalWindow = QtWidgets.QMainWindow()
    ui = Ui_regionalWindow()
    ui.setupUi(regionalWindow)
    regionalWindow.show()
    sys.exit(app.exec_())

I'm using three buttons one to add rows, one to remove rows, and one to update the table. Unfortunately, when I add rows to the dataframe it doesn't change anything and it keeps the same rows as defined before the editing, but when I trigger editing the values are changed.

To summarise: I need to add rows to the dataframe "dfRegion" with this model. Even though I see a visual change in the GUI when I click self.actionAdd_Region, I print the dfRegion and there are no rows added in the dataframe.

0

There are 0 best solutions below