PyQt5。行编辑
PyQt5. Row editing
我制作了 PyQt5 应用程序,它创建 SQlite3 数据库并在 QTableView 小部件中显示来自它的数据。
我在编辑小部件中的行时遇到问题。有 3 个按钮“添加”、“更改”和“删除”,它们应该删除、修改和添加新行到小部件,以及编辑数据库本身,但按钮不能正常工作。
“添加”按钮 - 单击添加按钮后,当输入所有新数据并单击 Enter 时,所有值都消失了并且“!”符号正在显示。我需要按添加按钮,在单元格中输入新数据,单击 Enter,所有数据必须保存在 SQL 数据库中并实时显示在 QTableView 小部件中。
“更改”按钮 - 当单击更改并在单元格编辑后按下 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_record
和 addRecord
),这是一个糟糕的命名选择(同时考虑到不同的书写风格) : 更好的选择是将按钮命名为 add_record_btn
或 addRecordBtn
以遵循 Qt 约定;
我制作了 PyQt5 应用程序,它创建 SQlite3 数据库并在 QTableView 小部件中显示来自它的数据。 我在编辑小部件中的行时遇到问题。有 3 个按钮“添加”、“更改”和“删除”,它们应该删除、修改和添加新行到小部件,以及编辑数据库本身,但按钮不能正常工作。
“添加”按钮 - 单击添加按钮后,当输入所有新数据并单击 Enter 时,所有值都消失了并且“!”符号正在显示。我需要按添加按钮,在单元格中输入新数据,单击 Enter,所有数据必须保存在 SQL 数据库中并实时显示在 QTableView 小部件中。
“更改”按钮 - 当单击更改并在单元格编辑后按下 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_record
和 addRecord
),这是一个糟糕的命名选择(同时考虑到不同的书写风格) : 更好的选择是将按钮命名为 add_record_btn
或 addRecordBtn
以遵循 Qt 约定;