PyQt5。行编辑

PyQt5. Row editing

我制作了 PyQt5 应用程序,它创建 SQlite3 数据库并在 QTableView 小部件中显示来自它的数据。 我在编辑小部件中的行时遇到问题。有 3 个按钮“添加”、“更改”和“删除”,它们应该删除、修改和添加新行到小部件,以及编辑数据库本身,但按钮不能正常工作。

  1. “添加”按钮 - 单击添加按钮后,当输入所有新数据并单击 Enter 时,所有值都消失了并且“!”符号正在显示。我需要按添加按钮,在单元格中输入新数据,单击 Enter,所有数据必须保存在 SQL 数据库中并实时显示在 QTableView 小部件中。

  2. “更改”按钮 - 当单击更改并在单元格编辑后按下 Enter 时,所有数据都消失了。按下更改按钮后,所有数据都必须实时保存为 SQL 数据库。

    3)“删除”按钮 - 此按钮不删除行。没有错误,应用程序没有任何答复。

如何正确编程按钮? 我需要使用委托吗? 使用图形界面和 SQL 数据库时,哪种编程方法更可取?

我做了一个可重现的例子。启动时,将创建一个包含 1 table 和 2 行的数据库。

import sys, os, sqlite3
from datetime import datetime 
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtSql import *
from PyQt5.QtCore import *

CONFIG_NAME = 'config.ini'
DB_NAME = 'nsi.db'

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super().__init__() 
        self.window_pref()
        self.show_widgets()

    def window_pref(self):
        self.setWindowTitle('PyQt5 APP')
        self.def_width = 800
        self.def_height = 400
        self.def_size = self.setMinimumSize(self.def_width, self.def_height)

    def show_widgets(self):
        self.createConnection()        
        self.fillDB()       
        self.setupMainWidgets()  

    def createConnection(self):
        db = QSqlDatabase.addDatabase("QSQLITE")
        db.setDatabaseName(DB_NAME)

        if not db.open():
            QMessageBox.warning(self, 'PyQt5 APP', 
                'Error:{}'.format(db.lastError().text()))
            sys.exit(1)

    def fillDB(self):
        query = QSqlQuery()
        query.exec_("""\
            CREATE TABLE sprav (
                id_nsi INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
                nsi_name TEXT UNIQUE NOT NULL,
                file_date TEXT NOT NULL,
                file_name TEXT NOT NULL)
                """)

        query.prepare("""\
            INSERT INTO sprav (nsi_name, file_date, file_name)VALUES (?, ?, ?)
            """)


        sample_list = (('nsi1', 'january', 'file1'), ('nsi2', 'may', 'file2'))

        for i in sample_list:
            query.addBindValue(i[0])           
            query.addBindValue(i[1]) 
            query.addBindValue(i[2])    
            query.exec_()
            
    def setupMainWidgets(self):
        mw_widget = QWidget()               
        main_panel = QHBoxLayout(mw_widget) 

        # SQL Table
        self.modelSql = QSqlTableModel()
        self.modelSql.setTable('sprav') 

        self.modelSql.setQuery(QSqlQuery(
                    'SELECT nsi_name, file_date, file_name FROM sprav'))

        self.modelSql.setHeaderData(self.modelSql.fieldIndex('nsi_name'),
                                Qt.Horizontal, 'Name')
        self.modelSql.setHeaderData(self.modelSql.fieldIndex('file_date'),
                                Qt.Horizontal, 'Date')
        self.modelSql.setHeaderData(self.modelSql.fieldIndex('file_name'),
                                Qt.Horizontal, 'File')
        self.modelSql.setEditStrategy(QSqlTableModel.OnFieldChange)
        self.modelSql.select()

        # QTableView() 
        self.table_view = QTableView()
        self.table_view.setSelectionBehavior(1) 
        self.table_view.setAlternatingRowColors(True) 
        self.table_view.setModel(self.modelSql)     

        self.table_view.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch)

        main_panel.addWidget(self.table_view)

        # QVBoxLayout()
        right_panel = QVBoxLayout() 
        line = QFrame()
        line.setFrameShape(QFrame.HLine)

        self.add_record = QPushButton('Add', self)
        self.add_record.clicked.connect(self.addRecord)

        self.change_record = QPushButton('Change', self)
        self.change_record.clicked.connect(self.changeRecord)      
        self.delete_record = QPushButton('Delete', self)
        self.delete_record.clicked.connect(self.delRecord)

        right_panel.addSpacing(20)
        right_panel.addWidget(line)
        right_panel.addWidget(self.add_record)
        right_panel.addWidget(self.change_record)
        right_panel.addWidget(self.delete_record)
        right_panel.addStretch()

        main_panel.addLayout(right_panel) 

        self.setCentralWidget(mw_widget)

    def addRecord(self):
        row = self.modelSql.rowCount()
        self.modelSql.insertRow(row)

        index = self.modelSql.index(row, 0)
        self.table_view.setCurrentIndex(index) 
        self.table_view.edit(index)

    def delRecord(self):
        cur_item = self.table_view.selectedIndexes()
        for index in cur_item:
            self.modelSql.removeRow(index.row())
        self.modelSql.select()

    def changeRecord(self):
        self.table_view.edit(self.table_view.currentIndex())      

if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

您的代码有两个问题。

首先,如果你使用QSqlTableModel,你应该调用setQuery():

This function simply calls QSqlQueryModel::setQuery(query). You should normally not call it on a QSqlTableModel. Instead, use setTable(), setSort(), setFilter(), etc., to set up the query.

这是您的行 addition/deletion/editing 无效的主要原因:模型变得部分无效,并且所有提交都被丢弃,因为列与 table 的记录不匹配模型,非常 也很重要,因为该模型需要一个增量密钥,QSqlTableModel 能够自行正确使用该密钥。

从您的代码中删除 setQuery(),并考虑如果您这样做只是为了隐藏一列,那么您只需 隐藏 该列:

    self.table_view.setColumnHidden(0, True)

显然,您必须记住,您将使用的所有列索引现在都将从 1 开始,因为该模型还包括 id:

def addRecord(self):
    # ...
    index = self.modelSql.index(row, 1)
    self.table_view.setCurrentIndex(index) 
    self.table_view.edit(index)

另一个问题是行的删除:即使修复了上述内容,行数及其顺序也会出错:

  • 当您循环浏览 selectedIndexes() 时,您会得到 每个 所选项目的同一行:因为您使用了 SelectRows 选择行为,对于每个选定的行,for 循环会在同一行中调用 removeRow() 三次;
  • 删除索引应该总是倒序,排序;考虑一下您是否尝试删除第 0 行和第 1 行:第一次迭代将删除第 0 行,但此时 previous 行 1 将成为新的第 0 行,因此下一次迭代将实际上删除之前是第三行;

解决方案是有一个 唯一 行号的排序列表,并以相反的顺序循环它们:

    def delRecord(self):
        # create a set of unique row numbers
        rows = set([i.row() for i in self.table_view.selectedIndexes()])
        # cycle through them in reversed sorting order
        for row in sorted(rows, reverse=True):
            self.modelSql.removeRow(row)
        self.modelSql.select()

记得打电话给 select()(我知道你打过电话,但安全总比抱歉好),如有关 removeRows() 的文档中所述:

Deletions are submitted immediately to the database. The model retains a blank row for successfully deleted row until refreshed with select().

不相关的注释: 1. 避免不必要和混乱的导入:因为您已经导入了带有通配符的 QtWidgets,所以 from PyQt5 import QtWidgets 没有意义;您要么导入子模块,要么导入它的 classes; 2. setMinimumSize returns 什么都没有,所以 self.def_size 将是 None;如果要保留默认大小的变量,请使用 self.def_size = QSize(self.def_width, self.def_height) 然后 self.setMinimumSize(self.def_size); 3. 不要在 Qt 应用程序中使用 sys.exit()(以及 QWidget class),而是使用 QApplication.exit(1); 4. 使用更冗长的名称来阐明它们的类型:按钮和功能的名称几乎相同(add_recordaddRecord),这是一个糟糕的命名选择(同时考虑到不同的书写风格) : 更好的选择是将按钮命名为 add_record_btnaddRecordBtn 以遵循 Qt 约定;