PyQt6 library
PyQt部分
前置步骤:
- ui 界面设计
- 数据库设计
- python.exe -m PyQt6.uic.pyuic xxx -o xxx.py
ui-py 文件初始改造
- 将 ui 生成的 py 文件复制一份至相应模块下
- 将类继承自
QWidget
或者QMainWindow
等组件 - 新增构造器
__init__(self)
- 关注是否有引用的图片是否需要修改相对路径
- 启动测试
初始化代码:
def __init__(self):
super(Ui_Form, self).__init__()
# 初始化当前页面绝对路径
self.current_dir = os.path.dirname(os.path.abspath(__file__))
self.setupUi(self)
启动测试验证:
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
ui_form = Ui_title() # 初始化
ui_form.show()
sys.exit(app.exec())
一些组件的零星设置
# 主界面背景设置
self.centralwidget.setStyleSheet("border-image:url('./images/158.jpg')")
# 设置状态栏内容
label = QLabel()
self.statusbar.addWidget(label)
# 状态栏右侧同理
label = QLabel()
self.statusbar.addPermanentWidget(label)
# 输入框设置只读以及设置颜色
self.input_number.setReadOnly(True)
self.input_number.setStyleSheet("background-color: gray")
# 链接跳转
QtGui.QDesktopServices.openUrl(QtCore.QUrl("https://saddyfire.cn"))
窗体间的切换
示例1:登录后的主窗体切换
### 登录逻辑...
# 当前界面隐藏
self.hide()
self.m = main.Ui_mainWindow() # 实例化主窗体
self.m.show() # 显示主窗体
示例2:主窗体点击菜单显示功能窗体
- 回调事件函数
# 打开图书类别窗口
def open_book_type_window(self, action):
print(action.text())
if action.text() == "图书类别添加":
self.book_type_add = book_type_add.Ui_Form()
self.book_type_add.show()
elif action.text() == "图书类别信息管理":
self.book_type_manage = book_type_manage.Ui_book_type_manage()
self.book_type_manage.show()
- 触发回调
self.menu_book_type = QtWidgets.QMenu(parent=self.menubar)
self.menu_book_type.setObjectName("menu_book_type")
# 图书类别菜单点击回调
self.menu_book_type.triggered[QAction].connect(self.open_book_type_window)
相对路径与绝对路径的引用
这里采用的方式是在初始化时同时保存绝对路径,此后都采用绝对路径的方式引用外部文件
# 初始化当前页面
self.current_dir = os.path.dirname(os.path.abspath(__file__))
####
# 外部文件的引用
icon.addPixmap(QtGui.QPixmap(f"{self.current_dir}/../images/搜索.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
计时器(QTimer + QLabel)
事件回调:
# 添加一个标签
label_timer = QLabel()
# 初始化一个计时器
timer = QTimer(self)
# 间隔1s执行一次
timer.start(1000)
# 触发事件
timer.timeout.connect(lambda: self.show_timer(label_timer))
# 再将标签绑定到组件(本次示例是将标签放在状态栏中)
self.statusbar.addPermanentWidget(label_timer)
回调函数:
# 显示时间
def show_timer(self, label: QLabel):
time = QDateTime.currentDateTime()
label.setText(time.toString("yyyy-MM-dd hh:mm:ss"))
事件绑定
self.组件对象名.clicked.connect(lambda: 触发事件())
## 示例
# 绑定添加事件
self.btn_add.clicked.connect(self.add)
消息框
QtWidgets.QMessageBox.warning(None, "提示", "需要警告的信息:xxx")
QtWidgets.QMessageBox.information(None, "提示", "需要提示的信息:xxx")
消息框的多个按钮示例:删除提醒
# 问题消息框
confirm = QMessageBox.question(
None, "提示", "确定删除吗?",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
QMessageBox.StandardButton.No # 默认选择项
)
if confirm == QMessageBox.StandardButton.No:
return
表格👍
数据的显示
表格初始化
def show_table(self):
# 模拟初始化的数据
xxx_list = ((1, '历史类', '历史类图书'), (3, '文学类', ''), (5, '艺术类', ''))
# 初始化行数
row_count = 0
# 初始化列数
columns = ["id", "图书类别名称", "图书类别描述"]
if xxx_list:
row_count = len(xxx_list)
self.table_book_type.setRowCount(row_count) # 设置行数
self.table_book_type.setColumnCount(len(columns)) # 设置列数
self.table_book_type.setColumnHidden(0, True) # 隐藏id列
# self.table_book_type.verticalHeader().setVisible(False) # 隐藏行头序号
self.table_book_type.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers) # 禁止编辑单元格
self.table_book_type.setHorizontalHeaderLabels(columns) # 设置表头
self.table_book_type.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch) # 列头自适应
self.table_book_type.setSelectionBehavior(
QAbstractItemView.SelectionBehavior.SelectRows # 设置选择行为,以行为单位
)
# 行
for i in range(row_count):
# 列
for j in range(len(columns)):
data = QTableWidgetItem(str(book_type_list[i][j])) # 这里注意,列索引从1开始
self.table_book_type.setItem(i, j, data)
窗口初始化时同时展示表格
def __init__(self):
super(Ui_book_type_manage, self).__init__()
self.setupUi(self)
# 初始化表格
self.show_table()
表格的点击事件(支持空白监听)
回调函数
def click_table(self):
# 选中的行中作为 list(这里隐藏列是没有的)
selected_as_list = self.tableWidget.selectedItems()
# 没有选中行则是 [], 则重置表单
if not selected_as_list:
self.reset_form()
return
#### 因为id是隐藏列,所以需要通过坐标定位
# 当前行号
current_row = self.tableWidget.currentRow()
# 定位坐标
book_id = self.tableWidget.item(current_row, 0).text()
self.input_book_id.setText(book_id)
#### 其他列则通过当前行即可获取
self.input_book_name.setText(selected_as_list[0].text())
self.input_auth_name.setText(selected_as_list[1].text())
绑定回调函数
# 绑定表格点击事件
self.tableWidget.itemSelectionChanged.connect(self.click_table)
表格遇到需要映射的字段
比如数据库中查回来的【图书类型】是【图书类型id】,实际要展示的是【图书类型名称】,基于此采用一个字典映射的方案
__init__
方法中定义字典
# 初始化下拉框字典
self.book_type_dict: Optional[dict] = None
获取表格数据后做一次数据处理
# 假设拿到数据
book_list = ((1, '人类简史', 'xx', '男', 100.0, 1, None), (2, '乖摸摸头', '', '', None, 3, ''))
# 针对具体列做出处理,示例:第5列
processed_list = [
# 这里要注意,因为原数据是元组,因此需要将每一段都转为list才行,最终再用一层list()包裹,否则会转换失败
list(list(book[:5]) + [self.book_type_dict.get(book[5], "未知类型")] + list(book[6:]))
for book in book_list
]
# 后续通过 processed_list 来构建表格
下拉框的初始化及重置
初始化:让 text 保持显示的中文,data 为实际的映射值
def init_combox(self):
# 先获取列表
book_type_list = book_type_dao.list()
# 先清空
self.combox_bok_type.clear()
# 设置默认项, 并且数据为 -1
self.combox_bok_type.addItem("请选择图书类别", -1)
for book_type in book_type_list:
self.combox_bok_type.addItem(book_type[1], book_type[0])
重置
# 重置下拉框
self.combox_bok_type.setCurrentIndex(0)
设置当前下拉框,使用 text 中文去设置
# 设置值可以使用中文,这样的话可以
book_type = self.tableWidget.item(row_index, 5).text()
self.combo_book_type.setCurrentText(book_type)
结果获取:通过 data 去获取实际映射值
# 获取实际映射值
book_type_id = self.combox_bok_type.currentData()
if book_type_id == -1:
QMessageBox.warning(None, "提示", "图书名称或图书类别缺失")
return
else:
pass
单选框radio的不选择技巧
因为性别这类不是必填项,但是 QRadioButton 默认会保持互斥,一旦选择就无法清楚,因此本方案采取添加一个隐藏的单选框作为"无"选项
setup 时新增一个隐藏的"无选项"
# 隐藏的无选项(parent照抄其他的)
self.radio_none = QtWidgets.QRadioButton(parent=self.gridLayoutWidget)
self.radio_none.hide()
# 将选项放入父组件中(QGridLayout 也照抄其他的)
self.gridLayout.addWidget(self.radio_none)
self.radio_none.setChecked(True)
重置
# 复选框重置,选择隐藏按钮
self.radio_none.setChecked(True)
结果获取
# 性别
if self.radio_man.isChecked():
sex = self.radio_man.text()
elif self.radio_woman.isChecked():
sex = self.radio_woman.text()
else:
sex = self.radio_none.text() # 避免硬编码
dao层mysql
db_util.py
from pymysql import Connection
def getConn():
conn = Connection(
host='localhost',
port=3306,
user='root',
password='2323',
database='dee_library',
charset='utf8',
autocommit=True
)
return conn
def closeConn(conn: Connection):
if conn:
conn.close()
craete 模板
def add(book_type: BookType) -> int:
conn = None
try:
conn = db_util.getConn()
cursor = conn.cursor()
cursor.execute(f"insert into t_book_type values (null, %s, %s)", [book_type.book_type_name, book_type.book_type_desc])
return cursor.rowcount
except Exception as e:
print(e)
if conn:
conn.rollback()
print(e)
return 0
finally:
db_util.closeConn(conn)
select 模板
查询列表
def list(s_book_type_name: str):
conn = None
try:
conn = db_util.getConn()
cursor = conn.cursor()
sql = "select id, book_type_name, book_type_desc from t_book_type where 1=1"
param = []
if s_book_type_name:
sql += " and book_type_name like %s"
param.append(f"%{s_book_type_name}%")
cursor.execute(sql, param)
return cursor.fetchall()
except Exception as e:
print(e)
if conn:
return None
finally:
db_util.closeConn(conn)
count
def count_by_book_type(book_type_id: int):
conn = None
try:
conn = db_util.getConn()
cursor = conn.cursor()
sql = "select count(1) from t_book where book_type_id=%s"
cursor.execute(sql, [book_type_id])
return cursor.fetchone()[0]
except Exception as e:
print(e)
if conn:
conn.rollback()
return None
finally:
db_util.closeConn(conn)
update 模板
def modify(book_type: BookType):
conn = None
try:
conn = db_util.getConn()
cursor = conn.cursor()
param = []
sql = "update t_book_type set book_type_name=%s, book_type_desc=%s where id=%s"
param = [book_type.book_type_name, book_type.book_type_desc, book_type.id]
cursor.execute(sql, param)
return cursor.rowcount
except Exception as e:
print(e)
if conn:
conn.rollback()
return 0
finally:
db_util.closeConn(conn)
delete 模板
def remove(id: int):
conn = None
try:
conn = db_util.getConn()
cursor = conn.cursor()
cursor.execute("delete from t_book_type where id=%s", [id])
return cursor.rowcount
except Exception as e:
print(e)
if conn:
conn.rollback()
return 0
finally:
db_util.closeConn(conn)
dao层sqlite
db_util.py
import sqlite3
from pathlib import Path
from pymysql import Connection
# 数据库文件路径(自动创建在程序目录下)
DB_PATH = Path(__file__).parent.parent / "db" / "dee_library.db"
def get_conn():
"""获取SQLite数据库连接"""
conn = sqlite3.connect(DB_PATH)
# 启用外键约束(如果需要)
conn.execute("PRAGMA foreign_keys = ON")
return conn
def close_conn(conn: Connection):
if conn:
conn.close()
create 模板
from entity.book_type_model import BookType
from util.db_util import get_conn, close_conn
import sqlite3
def add(book_type: BookType) -> int:
"""
添加图书类别
:param book_type: 图书类别实体
:return: 影响的行数 (0表示失败)
"""
conn = None
try:
conn = get_conn()
cursor = conn.cursor()
cursor.execute(
"INSERT INTO t_book_type (book_type_name, book_type_desc) VALUES (?, ?)",
(book_type.book_type_name, book_type.book_type_desc)
)
conn.commit()
return cursor.rowcount
except sqlite3.Error as e:
print(f"添加图书类别出错: {e}")
if conn:
conn.rollback()
return 0
finally:
close_conn(conn)
select 模板
def list(s_book_type_name: str = None) -> list:
"""
查询图书类别列表
:param s_book_type_name: 图书类别名称(模糊查询)
:return: 图书类别列表
"""
conn = None
try:
conn = get_conn()
cursor = conn.cursor()
sql = "SELECT id, book_type_name, book_type_desc FROM t_book_type WHERE 1=1"
params = []
if s_book_type_name:
sql += " AND book_type_name LIKE ?"
params.append(f"%{s_book_type_name}%")
cursor.execute(sql, params)
return cursor.fetchall()
except sqlite3.Error as e:
print(f"查询图书类别出错: {e}")
return []
finally:
close_conn(conn)
update 模板
def modify(book_type: BookType) -> int:
"""
修改图书类别
:param book_type: 图书类别实体
:return: 影响的行数 (0表示失败)
"""
conn = None
try:
conn = get_conn()
cursor = conn.cursor()
cursor.execute(
"UPDATE t_book_type SET book_type_name=?, book_type_desc=? WHERE id=?",
(book_type.book_type_name, book_type.book_type_desc, book_type.id)
)
conn.commit()
return cursor.rowcount
except sqlite3.Error as e:
print(f"修改图书类别出错: {e}")
if conn:
conn.rollback()
return 0
finally:
close_conn(conn)
delete 模板
def remove(id: int) -> int:
"""
删除图书类别
:param id: 图书类别ID
:return: 影响的行数 (0表示失败)
"""
conn = None
try:
conn = get_conn()
cursor = conn.cursor()
cursor.execute(
"DELETE FROM t_book_type WHERE id=?",
(id,)
)
conn.commit()
return cursor.rowcount
except sqlite3.Error as e:
print(f"删除图书类别出错: {e}")
if conn:
conn.rollback()
return 0
finally:
close_conn(conn)
count 模板
def count_by_type_name(book_type_name: str) -> int:
"""
统计同名图书类别数量
:param book_type_name: 图书类别名称
:return: 数量 (查询失败返回0)
"""
conn = None
try:
conn = get_conn()
cursor = conn.cursor()
cursor.execute(
"SELECT COUNT(1) FROM t_book_type WHERE book_type_name=?",
(book_type_name,)
)
return cursor.fetchone()[0]
except sqlite3.Error as e:
print(f"统计图书类别出错: {e}")
return 0
finally:
close_conn(conn)
entity 封装
"""
图书类别实体类
"""
from dataclasses import dataclass, field
@dataclass
class BookType:
id: int | None = field(default=None, repr=False)
book_type_name: str = ""
book_type_desc: str = ""
@classmethod
def create_default(cls, book_type_name: str, book_type_desc: str):
return cls(book_type_name=book_type_name, book_type_desc=book_type_desc)
@property
def price_value(self):
"""可以对属性做相应的转换"""
return int(self.price.replace("¥", "").replace(",", ""))
entity 的初始化
# 初始化实体
book_type = BookType(int(id), book_type_name, self.input_book_desc_add.toPlainText())
# 通过重载方法初始化实体
book_type = BookType.create_default(book_type_name, book_type_desc)