Modify cells in QTableView PYQT5
Asked Answered
E

1

5

I would like to modify any cell (except header) within given QTableView. Here below is my original code that does not allow for any changes:

import sys
import csv
from datetime import datetime, timedelta
import calendar
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import sqlite3
from pandas import DataFrame

class TBWindow(QMainWindow):
    def __init__(self, parent=None):
        super(TBWindow, self).__init__(parent)

        sql = "select * from stock"
        args = []

        conn = sqlite3.connect('DataBase.db')
        c = conn.cursor()
        c.execute(sql,args)
        data = c.fetchall()
        names = list(map(lambda x: x[0], c.description))
        c.close()
        conn.close()
        data = DataFrame(data, columns = names)

        MenuBar = self.menuBar()
        saveFile = QAction("&Save File", self)
        saveFile.setShortcut("Ctrl+S")
        saveFile.setStatusTip('Save File')
        saveFile.triggered.connect(self.file_save)
        MenuBar.addAction(saveFile)

        self.setWindowTitle('Aplikace Princezna Pampeliska')
        self.centralwidget  = QWidget(self)
        self.lineEdit       = QLineEdit(self.centralwidget)
        self.view           = QTableView(self.centralwidget)
        self.comboBox       = QComboBox(self.centralwidget)
        self.label          = QLabel(self.centralwidget)

        self.gridLayout = QGridLayout(self.centralwidget)
        self.gridLayout.addWidget(self.lineEdit, 0, 1, 1, 1)
        self.gridLayout.addWidget(self.view, 1, 0, 1, 3)
        self.gridLayout.addWidget(self.comboBox, 0, 2, 1, 1)
        self.gridLayout.addWidget(self.label, 0, 0, 1, 1)

        self.setCentralWidget(self.centralwidget)
        self.label.setText("Filter")

        self.model = PandasModel(data)

        self.proxy = QSortFilterProxyModel(self)
        self.proxy.setSourceModel(self.model)

        self.view.setModel(self.proxy)

        for column in range(self.view.horizontalHeader().count()):
            self.view.resizeColumnToContents(column)

        for i in (0,1,3):
            self.view.horizontalHeader().setSectionResizeMode(i, QHeaderView.Stretch)            

        self.comboBox.addItems(list(data.columns.values))
        self.lineEdit.textChanged.connect(self.on_lineEdit_textChanged)
        self.comboBox.currentIndexChanged.connect(self.on_comboBox_currentIndexChanged)

        self.horizontalHeader = self.view.horizontalHeader()

    def file_save(self):
        newModel = self.view.model()
        data = []
        for row in range(newModel.rowCount()):
            rowRes = []
            for column in range(newModel.columnCount()):
                index = newModel.index(row, column)
                item = newModel.data(index)
                if item != '':
                    rowRes.append(item)
            data.append(rowRes)
        dataFrame = DataFrame(data)

        options = QFileDialog.Options()
        options |= QFileDialog.DontUseNativeDialog
        fileName, fEnd = QFileDialog.getSaveFileName(self, "Save File", "", ".csv")
        if fileName:
            address = fileName+fEnd
            dataFrame.to_csv(address, index=False, header=False)

    @pyqtSlot(str)
    def on_lineEdit_textChanged(self, text):
        search = QRegExp(text, Qt.CaseInsensitive, QRegExp.RegExp)
        self.proxy.setFilterRegExp(search)

    @pyqtSlot(int)
    def on_comboBox_currentIndexChanged(self, index):
        self.proxy.setFilterKeyColumn(index)

class PandasModel(QAbstractTableModel):

    def __init__(self, data, parent=None):
        QAbstractTableModel.__init__(self, parent)
        self._data = data

    def rowCount(self, parent=None):
        return self._data.shape[0]

    def columnCount(self, parent=None):
        return self._data.shape[1]

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

    def headerData(self, col, orientation, role):
        if orientation == Qt.Horizontal and role == Qt.DisplayRole:
            return self._data.columns[col]
        return None

if __name__ == '__main__':

    app = QApplication(sys.argv)
    main = TBWindow()
    main.showMaximized()
    sys.exit(app.exec_())

I have read some articles about modifying cells within QTableView and found out that one needs to modify the model not the view. Hence the addition of this bit of code makes the cells editable:

def flags(self, index):
    flags = super(self.__class__,self).flags(index)
    flags |= Qt.ItemIsEditable
    flags |= Qt.ItemIsSelectable
    flags |= Qt.ItemIsEnabled
    flags |= Qt.ItemIsDragEnabled
    flags |= Qt.ItemIsDropEnabled
    return flags

However, the changes do not persist, they disappear right after I click elsewhere. I tried to add function setData() in this manner with no success:

def setData(self, index, value, role=Qt.EditRole):
    row = index.row()
    col = index.column()
    self._data[row][name] = value
    self.emit(SIGNAL('dataChanged()'))
    return True

The program exits with some key error. Any suggestions what should be changed? Thanks!

Esprit answered 29/12, 2017 at 11:45 Comment(0)
R
8

Your code has 2 errors:

  • the first is caused by a bad programming practice: you should not call the variables with names that you used with other variables, functions or classes. In your case flags is the name of the method and therefore you should not use it in the name of the variable:

def flags(self, index):
    fl = super(self.__class__,self).flags(index)
    fl |= Qt.ItemIsEditable
    fl |= Qt.ItemIsSelectable
    fl |= Qt.ItemIsEnabled
    fl |= Qt.ItemIsDragEnabled
    fl |= Qt.ItemIsDropEnabled
    return fl

  • The second error is caused because you are not using the proper method to access and assign the data in pandas, this must be done through the iloc method. Another problem is that you are using a valid method for PyQt4 to notify the changes, you must use the new syntax plus the dataChanged signal to add new fields in Qt5 as you can see in the docs:

def setData(self, index, value, role=Qt.EditRole):
    if index.isValid():
        row = index.row()
        col = index.column()
        self._data.iloc[row][col] = float(value)
        self.dataChanged.emit(index, index, (Qt.DisplayRole, ))
        return True
    return False

Plus:

I have also previously implemented a class that creates a model using pandas as you can see in the question How to display a Pandas data frame with PyQt5

Recrement answered 29/12, 2017 at 12:35 Comment(1)
Thanks for your recommendations/help. I removed float() and it works.Esprit

© 2022 - 2024 — McMap. All rights reserved.