qtableview 无法显示新插入的行

Posted

技术标签:

【中文标题】qtableview 无法显示新插入的行【英文标题】:qtableview fails to display the newly inserted row 【发布时间】:2021-09-12 13:13:05 【问题描述】:

使用QtableView + QSqlTableModel组合时,插入新行后QtableView无法正确显示插入行。

id 字段被隐藏。 EditStrategy 是 OnFieldChange。在模型的末尾有一个按钮可以插入一个新的空行。在用户填写完所有字段并按回车键或单击其他行后,预期的行为是将行提交到数据库并且视图保留新行。但是QSqlTableModel成功将数据插入到数据库表中,但是tableview新插入的行变成了空,并且字段不可编辑。

如果 id 字段未隐藏并且明确提供,则行为将更改为所需的行为。然而,如果 id 未隐藏且未手动填充,则该行为再次失败,如上所述。

为什么会这样?

这是最小的可重现代码。您将不得不更改数据库配置文件。并使用自动增量 id 连接到表:

import sys

from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import QModelIndex
from PyQt5.QtSql import QSqlDatabase, QSqlTableModel
from PyQt5.QtWidgets import QPushButton


class Ui_main(object):
    def setupUi(self, main):
        main.setObjectName("main")
        main.resize(781, 652)
        main.setContextMenuPolicy(QtCore.Qt.NoContextMenu)

        self.verticalLayoutWidget = QtWidgets.QWidget(main)
        self.verticalLayoutWidget.setGeometry(QtCore.QRect(10, 10, 761, 631))
        self.verticalLayoutWidget.setObjectName("verticalLayoutWidget")
        self.verticalLayout = QtWidgets.QVBoxLayout(self.verticalLayoutWidget)
        self.verticalLayout.setContentsMargins(0, 0, 0, 0)
        self.verticalLayout.setObjectName("verticalLayout")

        # Replace values with your database configurations
        # But the table you refer to must have autoincremented primary key
        database = QSqlDatabase.addDatabase('QPSQL')
        database.setHostName('localhost')
        database.setPort(5432)
        database.setDatabaseName('some_database')
        database.setUserName('some_user')
        database.setPassword('some_password')
        database.open()

        button_add = QPushButton("AddRow")
        button_add.clicked.connect(self.addRow)
        self.verticalLayout.addWidget(button_add)

        self.tableView = QtWidgets.QTableView(self.verticalLayoutWidget)
        self.tableView.setObjectName("tableView")
        self.tableView.verticalHeader().setVisible(False)
        self.verticalLayout.addWidget(self.tableView)

        self.table_model = QSqlTableModel(self.tableView, database)
        table_name = 'some_table'
        self.table_model.setTable(table_name)

        self.table_model.setEditStrategy(QSqlTableModel.OnFieldChange)
        self.table_model.select()

        self.tableView.setModel(self.table_model)
        self.tableView.hideColumn(0)

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

    def retranslateUi(self, main):
        _translate = QtCore.QCoreApplication.translate
        main.setWindowTitle(_translate("main", "main"))

    def addRow(self):
        count = self.table_model.rowCount(QModelIndex())
        self.table_model.insertRow(count)
        self.tableView.scrollToBottom()

if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    main_window = QtWidgets.QWidget()
    window = Ui_main()
    window.setupUi(main_window)
    main_window.show()
    sys.exit(app.exec_())

更新:

我现在使用的临时解决方法是从获取的表中查找 max id 并将 max_id + 1 附加到新插入的行中。因此,该行在提交到数据库时具有一个 id。但是,这似乎不是正确的方式,因为数据库自己处理主键...

更新 2: 这是我的数据库表的样子:

设计:

book_of_accounts: table
+ columns
    id: bigint NN default nextval('book_of_accounts_id_seq'::regclass)
    code: varchar(256) NN
    name: varchar(1024) NN
    account_type: varchar(256) NN
    quantitative: boolean NN
    monetary: boolean NN
    subconoto_1: varchar(256)
    subconoto_2: varchar(256)
    subconoto_3: varchar(256)
+ indices
    book_of_accounts_pkey: unique (id)
    bookofaccounts_code: unique (code)
    bookofaccounts_name: index (name)
+ keys
    book_of_accounts_pkey: PK (id)

SQL

create table if not exists book_of_accounts
(
    id bigserial not null
        constraint book_of_accounts_pkey
        primary key,
    code varchar(256) not null,
    name varchar(1024) not null,
    account_type varchar(256) not null,
    quantitative boolean not null,
    monetary boolean not null,
    subconoto_1 varchar(256),
    subconoto_2 varchar(256),
    subconoto_3 varchar(256)
);

alter table book_of_accounts owner to hamlet;

create unique index if not exists bookofaccounts_code
    on book_of_accounts (code);

create index if not exists bookofaccounts_name
    on book_of_accounts (name);

【问题讨论】:

能否展示一下您创建表格的说明(我想知道存在哪些类型的字段以及每个字段的特点) @eyllanesc 我已根据您的要求更新了我的问题。 你试过insertRecord()而不是insertRow()吗?另外,我会在每次更新后添加 table.show()。 @s0mbre 在这一点上,问题不在于让它工作。我有解决这个问题的方法。但我想知道这种风格/代码有什么问题?为什么会出现这个特殊问题?回答您的问题,是的,我尝试了 insertRecord 的空记录,然后由用户填充。它给出了相同的行为。 【参考方案1】:

好的,所以问题似乎是(引自Qt docs):

注意:为了防止仅将部分初始化的行插入到数据库中,OnFieldChange 将像 OnRowChange 一样处理新插入的行。

要使表格更新并显示新插入的行,您可以在数据更改时调用模型的submitAll() 方法。

完整的更新代码(更新在 cmets 中标记):

import sys

from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import QModelIndex
from PyQt5.QtSql import QSqlDatabase, QSqlTableModel
from PyQt5.QtWidgets import QPushButton

class Ui_main(object):
    def setupUi(self, main):
        main.setObjectName("main")
        main.resize(781, 652)
        main.setContextMenuPolicy(QtCore.Qt.NoContextMenu)

        self.verticalLayoutWidget = QtWidgets.QWidget(main)
        self.verticalLayoutWidget.setGeometry(QtCore.QRect(10, 10, 761, 631))
        self.verticalLayoutWidget.setObjectName("verticalLayoutWidget")
        self.verticalLayout = QtWidgets.QVBoxLayout(self.verticalLayoutWidget)
        self.verticalLayout.setContentsMargins(0, 0, 0, 0)
        self.verticalLayout.setObjectName("verticalLayout")

        # Replace values with your database configurations
        # But the table you refer to must have autoincremented primary key
        database = QSqlDatabase.addDatabase('QPSQL')
        database.setHostName('localhost')
        database.setPort(5432)
        database.setDatabaseName('dbname')
        database.setUserName('postgres')
        database.setPassword('password')
        database.open()

        button_add = QPushButton("AddRow")
        button_add.clicked.connect(self.addRow)
        self.verticalLayout.addWidget(button_add)

        self.tableView = QtWidgets.QTableView(self.verticalLayoutWidget)
        self.tableView.setObjectName("tableView")
        self.tableView.verticalHeader().setVisible(False)
        self.verticalLayout.addWidget(self.tableView)

        self.table_model = QSqlTableModel(self.tableView, database)
        table_name = 'units'
        self.table_model.setTable(table_name)

        self.table_model.setEditStrategy(QSqlTableModel.OnFieldChange)
        self.table_model.select()
        ## ========== ADDED ========== ##
        self.table_model.sort(0, 0)    # DEFAULT SORT TO SEE NEW ITEMS AT END       
        self.table_model.dataChanged.connect(self.on_dataChanged) # CONNECT DATA CHANGE SIGNAL TO CUSTOM SLOT
        ## =========================== ##

        self.tableView.setModel(self.table_model)
        self.tableView.hideColumn(0)

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

    def retranslateUi(self, main):
        _translate = QtCore.QCoreApplication.translate
        main.setWindowTitle(_translate("main", "main"))

    def addRow(self):
        count = self.table_model.rowCount(QModelIndex())
        self.table_model.insertRow(count)
        self.tableView.scrollToBottom()

    ## ========== ADDED ========== ##
    # SLOT FIRED WHEN DATA CHANGES
    def on_dataChanged(self, topLeft, bottomRight, roles):
        self.table_model.submitAll()  # COMMIT TO DB
        self.table_model.select()     # RESELECT FROM DB
        self.table_model.sort(0, 0)   # SORT
    ## =========================== ##

if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    main_window = QtWidgets.QWidget()
    window = Ui_main()
    window.setupUi(main_window)
    main_window.show()
    sys.exit(app.exec_())

【讨论】:

此代码尝试在任何字段更改时提交该行。提交失败,因为在其他一些字段上存在 Not Null 约束。而且我认为问题不在于模型的 EditStrategy,因为模型本身的行为符合预期。它成功将数据提交到数据库。问题在于 TableView 无法正确显示新插入的行。 好吧,我已经在我的数据库 (Postgres) 上测试了这段代码,它就是这样工作的。没有submitAll(),它不起作用。 您对任何列是否有 Not null 约束?我已经在我的表上尝试了你的代码(答案中提供的 postgresql 模式作为更新 2),但它没有解决问题。 是的,我没有空:sql CREATE TABLE public.units ( id int4 NOT NULL GENERATED ALWAYS AS IDENTITY, val varchar(256) NOT NULL, "search" tsvector NULL, CONSTRAINT units_pkey PRIMARY KEY (id), CONSTRAINT units_unique UNIQUE (val) INCLUDE (val) );

以上是关于qtableview 无法显示新插入的行的主要内容,如果未能解决你的问题,请参考以下文章

QTableView 中未显示的行数

插入后如何在datagridview中立即刷新或显示?

Spring Boot jpa 无法插入包含重音的字符串的行

Extjs 无法在表单面板中动态添加/删除字段

QTableView 的单元格是空的,但标题显示

PyQt - 实现一个 QAbstractTableModel 以在 QTableView 中显示