在 PYQT5 中修改 QTableView 的单元格

5

我想修改给定 QTableView 中除表头外的任何单元格。以下是我的原始代码,它不允许做出任何更改:

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_())

我阅读了一些关于在QTableView中修改单元格的文章,发现需要修改模型而不是视图。因此,添加以下代码可以使单元格可编辑:

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

然而,更改没有保存,一旦我在其他地方点击就会消失。我尝试使用setData()函数添加以下内容,但并没有成功:

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

程序以一些关键错误退出。有什么建议应该改变吗?谢谢!
1个回答

8

您的代码有两个错误:

  • 第一个错误是由于不好的编程习惯引起的: 您不应该使用已经用于其他变量、函数或类的名称来调用变量。在您的情况下,flags 是方法的名称,因此您不应该在变量的名称中使用它:

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

第二个错误是因为您没有使用正确的方法来访问和分配pandas中的数据,这必须通过iloc方法完成。另一个问题是,您正在使用适用于PyQt4通知更改的有效方法,您必须使用新的语法加上dataChanged信号以在Qt5中添加新字段,正如您可以在文档中看到的一样:
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

附加信息:

我以前也实现过一个使用Pandas创建模型的类,如你所见在这个问题中 How to display a Pandas data frame with PyQt5


感谢您的建议/帮助。我移除了float(),现在它可以工作了。 - New2coding

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接