
✅作者简介:2022年博客新星 第八。热爱国学的Java后端开发者,修心和技术同步精进。
🍎个人主页:Java Fans的博客
🍊个人信条:不迁怒,不贰过。小知识,大智慧。
💞当前专栏:Python案例分享专栏
✨特色专栏:国学周更-心性养成之路
🥭本文内容:Qt Designer 和 PyQt 开发教程
文章目录
-
- 一、环境准备
- 二、核心模块介绍
- 三、案例需求
- 四、详细代码实现及解析
-
- [1. 创建数据库和表](#1. 创建数据库和表)
- [2. PyQt主窗口设计](#2. PyQt主窗口设计)
- 五、代码详细解析
-
- [1. 界面设计](#1. 界面设计)
- [2. 数据库连接](#2. 数据库连接)
- [3. 数据模型](#3. 数据模型)
- [4. 视图设置](#4. 视图设置)
- [5. 添加联系人](#5. 添加联系人)
- [6. 删除联系人](#6. 删除联系人)
- 六、功能扩展建议
-
- [1. 搜索功能 ------ 利用`QSqlTableModel.setFilter()`实现模糊查询](#1. 搜索功能 —— 利用
QSqlTableModel.setFilter()实现模糊查询) - [2. 数据验证 ------ 电话格式正则校验](#2. 数据验证 —— 电话格式正则校验)
- [3. 批量删除 ------ 支持多选删除](#3. 批量删除 —— 支持多选删除)
- [4. 导入导出 ------ 支持CSV格式](#4. 导入导出 —— 支持CSV格式)
- [5. 分页显示 ------ 适合大数据量时提升性能](#5. 分页显示 —— 适合大数据量时提升性能)
- [1. 搜索功能 ------ 利用`QSqlTableModel.setFilter()`实现模糊查询](#1. 搜索功能 —— 利用

一、环境准备
- Python 3.x(建议3.6及以上)
- PyQt5(或PyQt6,本文以PyQt5为例)
- SQLite(Python内置,无需额外安装)
安装PyQt5命令:
bash
pip install PyQt5
二、核心模块介绍
- sqlite3:Python内置模块,适合简单数据库操作,适合脚本或小项目。
- PyQt5.QtSql:Qt框架提供的数据库模块,支持多种数据库驱动,能与Qt的MVC模型无缝结合,适合GUI应用。
- QSqlDatabase:数据库连接管理类。
- QSqlTableModel:基于数据库表的模型,支持数据的显示、编辑、插入、删除。
- QTableView:视图控件,用于显示模型数据,支持排序、选择等功能。
三、案例需求
设计一个联系人管理程序,功能包括:
- 显示联系人列表(姓名、电话)
- 添加联系人
- 删除联系人
- 修改联系人信息(直接在表格中编辑)
四、详细代码实现及解析
1. 创建数据库和表
python
import sqlite3
def create_database():
# 连接数据库,文件不存在则自动创建
conn = sqlite3.connect('contacts.db')
cursor = conn.cursor()
# 创建联系人表,包含id(主键自增)、name和phone字段
cursor.execute('''
CREATE TABLE IF NOT EXISTS contacts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
phone TEXT NOT NULL
)
''')
conn.commit()
conn.close()
if __name__ == '__main__':
create_database()
解析:
- 使用
sqlite3.connect()连接数据库,文件不存在时自动创建。 CREATE TABLE IF NOT EXISTS保证表只创建一次,避免重复创建错误。id字段设置为主键且自增,方便唯一标识每条记录。name和phone字段均设置为非空,保证数据完整性。
2. PyQt主窗口设计
python
import sys
from PyQt5.QtWidgets import (
QApplication, QWidget, QVBoxLayout, QPushButton, QHBoxLayout,
QLineEdit, QMessageBox, QTableView, QLabel
)
from PyQt5.QtSql import QSqlDatabase, QSqlTableModel
class ContactManager(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("联系人管理")
self.resize(500, 400)
self.init_ui()
self.create_connection()
self.init_model()
self.init_view()
def init_ui(self):
layout = QVBoxLayout()
# 输入区域,水平布局
form_layout = QHBoxLayout()
self.name_edit = QLineEdit()
self.name_edit.setPlaceholderText("姓名")
self.phone_edit = QLineEdit()
self.phone_edit.setPlaceholderText("电话")
self.add_btn = QPushButton("添加联系人")
self.add_btn.clicked.connect(self.add_contact)
# 添加控件到form_layout
form_layout.addWidget(QLabel("姓名:"))
form_layout.addWidget(self.name_edit)
form_layout.addWidget(QLabel("电话:"))
form_layout.addWidget(self.phone_edit)
form_layout.addWidget(self.add_btn)
# 表格视图显示联系人列表
self.table_view = QTableView()
# 删除按钮
self.delete_btn = QPushButton("删除选中联系人")
self.delete_btn.clicked.connect(self.delete_contact)
# 将所有控件添加到主布局
layout.addLayout(form_layout)
layout.addWidget(self.table_view)
layout.addWidget(self.delete_btn)
self.setLayout(layout)
def create_connection(self):
# 创建数据库连接,使用QSQLITE驱动
self.db = QSqlDatabase.addDatabase('QSQLITE')
self.db.setDatabaseName('contacts.db')
if not self.db.open():
QMessageBox.critical(self, "数据库错误", self.db.lastError().text())
sys.exit(1)
def init_model(self):
# 初始化数据模型,绑定contacts表
self.model = QSqlTableModel(self, self.db)
self.model.setTable('contacts')
# 设置编辑策略为OnFieldChange,编辑后自动提交
self.model.setEditStrategy(QSqlTableModel.OnFieldChange)
self.model.select() # 加载数据
# 设置表头显示文字
self.model.setHeaderData(1, 0, "姓名")
self.model.setHeaderData(2, 0, "电话")
def init_view(self):
# 绑定模型到视图
self.table_view.setModel(self.model)
# 隐藏id列,用户无需看到主键
self.table_view.hideColumn(0)
# 自动调整列宽
self.table_view.resizeColumnsToContents()
# 设置选择行为为整行选择
self.table_view.setSelectionBehavior(QTableView.SelectRows)
# 设置只能单选
self.table_view.setSelectionMode(QTableView.SingleSelection)
def add_contact(self):
# 获取输入框内容
name = self.name_edit.text().strip()
phone = self.phone_edit.text().strip()
if not name or not phone:
QMessageBox.warning(self, "输入错误", "姓名和电话不能为空")
return
# 创建空记录
record = self.model.record()
record.setValue("name", name)
record.setValue("phone", phone)
# 插入记录到模型
if not self.model.insertRecord(-1, record):
QMessageBox.critical(self, "添加失败", self.model.lastError().text())
else:
self.model.select() # 刷新视图
self.name_edit.clear()
self.phone_edit.clear()
def delete_contact(self):
# 获取当前选中行索引
index = self.table_view.currentIndex()
if not index.isValid():
QMessageBox.warning(self, "删除错误", "请选择要删除的联系人")
return
# 删除选中行
self.model.removeRow(index.row())
# 提交删除操作
if not self.model.submitAll():
QMessageBox.critical(self, "删除失败", self.model.lastError().text())
else:
self.model.select() # 刷新视图
if __name__ == '__main__':
create_database() # 确保数据库和表存在
app = QApplication(sys.argv)
window = ContactManager()
window.show()
sys.exit(app.exec_())
五、代码详细解析
1. 界面设计
- 使用
QVBoxLayout作为主布局,垂直排列输入区域、表格视图和删除按钮。 - 输入区域用
QHBoxLayout水平排列姓名输入框、电话输入框和添加按钮。 QLineEdit设置setPlaceholderText提示用户输入内容。QTableView用于显示联系人列表,绑定QSqlTableModel模型。- 删除按钮绑定删除功能。
2. 数据库连接
- 使用
QSqlDatabase.addDatabase('QSQLITE')创建SQLite数据库连接。 - 设置数据库文件名为
contacts.db。 - 调用
open()打开数据库,失败则弹出错误并退出程序。
3. 数据模型
QSqlTableModel绑定contacts表。- 设置编辑策略为
OnFieldChange,即用户在表格中编辑数据后,自动提交到数据库,无需手动调用submitAll()。 - 调用
select()加载数据。 - 使用
setHeaderData设置表头显示的列名,参数1和2分别对应name和phone字段的列索引(第0列是id)。
4. 视图设置
- 将模型绑定到
QTableView。 - 隐藏
id列,避免显示无意义的主键。 - 调用
resizeColumnsToContents()自动调整列宽,保证内容完整显示。 - 设置选择行为为整行选择,方便删除操作。
- 设置单选,避免多选删除复杂度。
5. 添加联系人
- 从输入框获取姓名和电话,去除空白字符。
- 校验输入不能为空,避免插入无效数据。
- 通过
model.record()创建空记录,设置字段值。 - 调用
insertRecord(-1, record)在末尾插入新记录。 - 插入失败弹出错误提示。
- 插入成功后刷新模型,清空输入框。
6. 删除联系人
- 获取当前选中行的索引。
- 校验是否选中有效行。
- 调用
removeRow(row)删除对应行。 - 调用
submitAll()提交删除操作,失败则弹出错误。 - 成功后刷新模型。
六、功能扩展建议
好的,下面我将基于之前的联系人管理程序,逐项补充功能扩展的示例代码和详细说明,帮助你快速实现搜索、数据验证、批量删除、CSV导入导出和分页显示功能。
1. 搜索功能 ------ 利用QSqlTableModel.setFilter()实现模糊查询
思路
- 在界面顶部添加一个搜索输入框。
- 用户输入关键词后,调用
setFilter()设置SQL过滤条件,实现按姓名或电话模糊查询。 - 过滤条件示例:
name LIKE '%keyword%' OR phone LIKE '%keyword%'
代码示例
python
from PyQt5.QtWidgets import QLineEdit
class ContactManager(QWidget):
def init_ui(self):
layout = QVBoxLayout()
# 搜索框
self.search_edit = QLineEdit()
self.search_edit.setPlaceholderText("搜索姓名或电话")
self.search_edit.textChanged.connect(self.search_contacts)
layout.addWidget(self.search_edit)
# 之前的输入区域和按钮
# ...(保持不变)
def search_contacts(self, text):
# 构造过滤条件,防止SQL注入,简单替换单引号
keyword = text.replace("'", "''")
filter_str = f"name LIKE '%{keyword}%' OR phone LIKE '%{keyword}%'"
self.model.setFilter(filter_str)
self.model.select()
说明:
textChanged信号实时响应用户输入。setFilter()设置SQL WHERE子句,自动刷新显示符合条件的记录。
2. 数据验证 ------ 电话格式正则校验
思路
- 使用Python内置
re模块定义电话格式正则表达式。 - 在添加联系人和编辑提交前校验电话格式,格式不正确则提示用户。
代码示例
python
import re
class ContactManager(QWidget):
def __init__(self):
# ...
self.phone_regex = re.compile(r'^\+?\d{7,15}$') # 简单示例:允许+开头,7-15位数字
def add_contact(self):
name = self.name_edit.text().strip()
phone = self.phone_edit.text().strip()
if not name or not phone:
QMessageBox.warning(self, "输入错误", "姓名和电话不能为空")
return
if not self.phone_regex.match(phone):
QMessageBox.warning(self, "输入错误", "电话格式不正确,应为7-15位数字,可带+号")
return
# 其余代码保持不变
def init_model(self):
# 绑定编辑提交信号,校验电话格式
self.model = QSqlTableModel(self, self.db)
self.model.setTable('contacts')
self.model.setEditStrategy(QSqlTableModel.OnFieldChange)
self.model.select()
self.model.setHeaderData(1, 0, "姓名")
self.model.setHeaderData(2, 0, "电话")
# 监听数据更改,校验电话格式
self.model.dataChanged.connect(self.validate_phone_on_edit)
def validate_phone_on_edit(self, topLeft, bottomRight, roles):
# 获取编辑的行和列
row = topLeft.row()
col = topLeft.column()
if col == 2: # 电话列索引为2
phone_index = self.model.index(row, col)
phone = self.model.data(phone_index)
if not self.phone_regex.match(phone):
QMessageBox.warning(self, "输入错误", "电话格式不正确,应为7-15位数字,可带+号")
# 恢复旧值,简单做法是重新select刷新
self.model.select()
说明:
- 电话格式示例正则:允许以
+开头,后面7-15位数字。 - 添加联系人时校验输入。
- 编辑表格时监听
dataChanged信号,校验电话列数据。
3. 批量删除 ------ 支持多选删除
思路
- 将
QTableView的选择模式改为多选。 - 删除按钮点击时,获取所有选中行,逐行删除。
- 提交删除操作后刷新视图。
代码示例
python
class ContactManager(QWidget):
def init_view(self):
self.table_view.setModel(self.model)
self.table_view.hideColumn(0)
self.table_view.resizeColumnsToContents()
self.table_view.setSelectionBehavior(QTableView.SelectRows)
self.table_view.setSelectionMode(QTableView.MultiSelection) # 多选模式
def delete_contact(self):
selected_indexes = self.table_view.selectionModel().selectedRows()
if not selected_indexes:
QMessageBox.warning(self, "删除错误", "请选择要删除的联系人")
return
# 逆序删除,避免索引错乱
rows = sorted([index.row() for index in selected_indexes], reverse=True)
for row in rows:
self.model.removeRow(row)
if not self.model.submitAll():
QMessageBox.critical(self, "删除失败", self.model.lastError().text())
else:
self.model.select()
说明:
selectedRows()返回所有选中行的QModelIndex列表。- 逆序删除避免删除时索引变化导致错误。
- 删除后调用
submitAll()提交数据库操作。
4. 导入导出 ------ 支持CSV格式
思路
- 导入:读取CSV文件,逐行插入数据库。
- 导出:将当前模型数据写入CSV文件。
- 使用Python内置
csv模块。 - 使用
QFileDialog选择文件路径。
代码示例
python
import csv
from PyQt5.QtWidgets import QFileDialog
class ContactManager(QWidget):
def init_ui(self):
# ... 之前代码
# 添加导入导出按钮
self.import_btn = QPushButton("导入CSV")
self.import_btn.clicked.connect(self.import_csv)
self.export_btn = QPushButton("导出CSV")
self.export_btn.clicked.connect(self.export_csv)
# 将按钮添加到布局,比如放在删除按钮旁边
btn_layout = QHBoxLayout()
btn_layout.addWidget(self.delete_btn)
btn_layout.addWidget(self.import_btn)
btn_layout.addWidget(self.export_btn)
layout.addLayout(btn_layout)
def import_csv(self):
path, _ = QFileDialog.getOpenFileName(self, "选择CSV文件", "", "CSV文件 (*.csv)")
if not path:
return
with open(path, newline='', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
name = row.get('name') or row.get('姓名')
phone = row.get('phone') or row.get('电话')
if not name or not phone:
continue
record = self.model.record()
record.setValue("name", name)
record.setValue("phone", phone)
self.model.insertRecord(-1, record)
if not self.model.submitAll():
QMessageBox.critical(self, "导入失败", self.model.lastError().text())
else:
self.model.select()
QMessageBox.information(self, "导入成功", "CSV导入完成")
def export_csv(self):
path, _ = QFileDialog.getSaveFileName(self, "保存CSV文件", "", "CSV文件 (*.csv)")
if not path:
return
with open(path, 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
# 写表头
writer.writerow(['姓名', '电话'])
for row in range(self.model.rowCount()):
name = self.model.data(self.model.index(row, 1))
phone = self.model.data(self.model.index(row, 2))
writer.writerow([name, phone])
QMessageBox.information(self, "导出成功", "CSV导出完成")
说明:
- 导入时使用
csv.DictReader方便按列名读取。 - 导出时写入表头和所有数据。
- 通过文件对话框选择文件路径。
- 导入后调用
submitAll()提交批量插入。
5. 分页显示 ------ 适合大数据量时提升性能
思路
- SQLite不支持直接分页的模型接口,需要手动控制SQL查询。
- 利用
QSqlQueryModel自定义分页查询。 - 设计"上一页""下一页"按钮控制页码。
- 每页显示固定条数,比如20条。
代码示例
python
from PyQt5.QtSql import QSqlQueryModel, QSqlQuery
from PyQt5.QtWidgets import QPushButton, QLabel
class ContactManager(QWidget):
def __init__(self):
super().__init__()
self.page_size = 20
self.current_page = 0
self.total_records = 0
self.init_ui()
self.create_connection()
self.init_paging_model()
self.init_view()
def init_ui(self):
layout = QVBoxLayout()
# 分页控制按钮和标签
page_layout = QHBoxLayout()
self.prev_btn = QPushButton("上一页")
self.next_btn = QPushButton("下一页")
self.page_label = QLabel()
self.prev_btn.clicked.connect(self.prev_page)
self.next_btn.clicked.connect(self.next_page)
page_layout.addWidget(self.prev_btn)
page_layout.addWidget(self.page_label)
page_layout.addWidget(self.next_btn)
# 其余UI保持不变,添加分页布局
# 例如在表格下方添加分页控件
# ...
layout.addLayout(page_layout)
# 其他控件添加到layout
self.setLayout(layout)
def init_paging_model(self):
self.model = QSqlQueryModel(self)
self.update_total_records()
self.load_page()
def update_total_records(self):
query = QSqlQuery(self.db)
query.exec("SELECT COUNT(*) FROM contacts")
if query.next():
self.total_records = query.value(0)
def load_page(self):
offset = self.current_page * self.page_size
sql = f"SELECT id, name, phone FROM contacts LIMIT {self.page_size} OFFSET {offset}"
self.model.setQuery(sql, self.db)
self.page_label.setText(f"第 {self.current_page + 1} 页 / 共 {((self.total_records - 1) // self.page_size) + 1} 页")
# 绑定模型到视图
self.table_view.setModel(self.model)
self.table_view.hideColumn(0)
self.table_view.resizeColumnsToContents()
def next_page(self):
if (self.current_page + 1) * self.page_size >= self.total_records:
return # 已经是最后一页
self.current_page += 1
self.load_page()
def prev_page(self):
if self.current_page == 0:
return # 已经是第一页
self.current_page -= 1
self.load_page()
说明:
- 使用
QSqlQueryModel执行自定义SQL查询,支持分页。 LIMIT和OFFSET实现分页查询。- 维护当前页码和总记录数,更新分页标签。
- "上一页""下一页"按钮控制页码切换。
- 由于
QSqlQueryModel是只读模型,若需要编辑功能,分页实现会更复杂,需要自定义模型。
码文不易,本篇文章就介绍到这里,如果想要学习更多Java系列知识,点击关注博主,博主带你零基础学习Java知识。与此同时,对于日常生活有困扰的朋友,欢迎阅读我的第四栏目:《国学周更---心性养成之路》,学习技术的同时,我们也注重了心性的养成。
