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.