PyQt6 / PySide6
Prerequisite
Qt 是 C++ 写的图形界面工具,PyQt 和 PySide 都是 Qt 的儿子
PyQt5 和 PyQt6,一家私人公司开发的,用它开发商用软件要给这家公司交钱,不交钱就吃官司(PyQt6 版本是 PyQt5 版本的升级替代者)
PySide2 和 PySide6,分别是基于 C++ 下的 Qt5 和 Qt6 开发的,用它写商用软件是允许的,不会吃官司(PySide6 版本是 PySide2 版本的升级替代者)
总结:PySide6 和 PyQt6 基本类似,只用学 PySide6 就行
PS:我之所以学 Python 版本的 GUI 是因为要完成一个《基于人工智能的图片动漫化》的项目,简单来讲就是 Python UI 界面 + 人工智能 API
入门准备
视频教程为:
参考文档为:
参考项目
针对部分类似目标的成品进行考察(主要参考外观设计和结合人工智能 API 的方式)
- 外观参考
- 结合参考
一、跟着教程学习 PySide6
项目介绍
视频:Learn Python GUI Development for Desktop – PySide6 and Qt Tutorial
知识总结:Pyside6 用法、QT designer 用法
下载
- 下载 Pyside6 命令
pip install PySide6
- 下载课件:Qt-For-Python-PySide6-GUI-For-Beginners-The-Fundamentals-
- 下载 Qt 软件官网:Install Qt
- 环境:
conda activate gui
QTWidgets 代码
- 知识点补充
- 常用控件
- 所需的控件可以看官网
- 滑块、按钮、菜单(下拉栏)、消息框、填空框、编辑控件(复制、粘贴、剪贴等)、图片、布局控件、选择器等等
大多数写法如下,这套模板非常简约,下面是 main.py
文件,还有一个控件文件
import sys
from PySide6.QtWidgets import QApplication
# 自定义类
from button_holder import ButtonHolder
app = QApplication(sys.argv)
window = XXX()
window.show()
app.exec()
PS:大致看一两个控件的演示,就亲自运行文件中的全部代码看效果(不要求记住代码,有个印象即可),然后直接跳到 3:52:00 看下一章节
QtDesigner
制作 ui 文件使用的软件:QtDesigner
编译 ui 文件至 python 文件命令:pyside6-uic widget.ui > ui_widget.py
,记得更改文件为 UTF-8
编译资源文件至 python 文件命令:pyside6-rcc resource.qrc -o resource_rc.py
关于 widget 文件(UI 控件)的模板
文件中有六个文件
名称是:main.py
、widget.py
、widget.ui
、resource.qrc
、resource_rc.py
、ui_widget.py
需要写代码的:主文件(main.py
)、控件文件(widget.py
)
在 QtDesigner 中操作的:UI 控件(widget.ui
)、资源文件(resource.qrc
)
from PySide6.QtCore import Qt
from PySide6.QtWidgets import QWidget
# UI 文件、资源文件
from ui_widget import Ui_Widget
import resource_rc
class Widget(QWidget, Ui_Widget):
def __init__(self):
super().__init__()
self.setupUi(self)
self.setWindowTitle("User data")
最后
再把项目文件中的演示全都运行一遍即可
二、初步体验实战项目
项目介绍
视频:Python+Pyside/PyQt实现的GUI桌面应用
博客:Python系列-GUI-pyqt(pyside6)
Github 项目:PyDracula - Modern GUI PySide6 / PyQt6
环境:Python 10 + PySide6
目的:在已有的项目上,进行改造和功能实现
项目结构
main.py
: 主文件main.ui
: Qt Designer 工程文件resouces.qrc
: Qt Designer 资源文件setup.py
: 编译应用程序themes/
: 主题文件modules/
: GUI 的各个模块modules/app_funtions.py
: 应用功能函数modules/app_settings.py
: 用于配置用户界面的全局变量。modules/resources_rc.py
: 使用命令为 python 编译的 resource.qrc 文件(pyside6-rcc resources.qrc -o resources_rc.py
)modules/ui_functions.py
: 与用户界面 GUI 相关的函数modules/ui_main.py
: 与 Qt Designer 导出的用户界面相关的文件images/
: 所有图像和图标
初步改造
- 作者和版本号:
modules/ui_main.py
最后两行 - 软件标题:
main.py
的 title 和 description 同时修改(其实有作用的是后者) - 图片:
images/
,记得修改后的文件名不能变 - 按钮功能禁用:
main.py
的btnName == "btn_save"
后面删除print
,接上QMessageBox.information(self, "提示", "该功能暂未实现", QMessageBox.Yes)
全部修改完成后,运行 pyside6-rcc resources.qrc -o resources_rc.py
,再将 resources_rc.py
从最外层拷贝到 modules/
中
实现功能:切换黑白主题
- 主题初始化
# 路径冻结,防止打包成 exe 后路径错乱
if getattr(sys, 'frozen', False):
absPath = os.path.dirname(os.path.abspath(sys.executable))
elif __file__:
absPath = os.path.dirname(os.path.abspath(__file__))
# 主题初始化为白色
useCustomTheme = True
self.useCustomTheme = useCustomTheme
self.absPath = absPath
themeFile = os.path.abspath(os.path.join(absPath, "themes\py_dracula_light.qss"))
- 按钮添加功能
# 新增功能:切换皮肤
widgets.btn_message.clicked.connect(self.buttonClick)
- 实现具体切换主题的功能
# 切换主题
if btnName == "btn_message":
if self.useCustomTheme:
themeFile = os.path.abspath(os.path.join(self.absPath, "themes\py_dracula_dark.qss"))
# 跟着原先的代码走
UIFunctions.theme(self, themeFile, True)
AppFunctions.setThemeHack(self)
self.useCustomTheme = False
else:
themeFile = os.path.abspath(os.path.join(self.absPath, "themes\py_dracula_light.qss"))
# 跟着原先的代码走
UIFunctions.theme(self, themeFile, True)
AppFunctions.setThemeHack(self)
self.useCustomTheme = True
实现功能:实时监控(电脑资源,如 cpu、内存)
BUG:点击绘图按钮后,再点击清除,终端会显示 "Can not add series. Series already on the chart."
,我认为是 self.chart.addSeries(self.seriesS)
代码的问题,日后待修改
- 在 QtDesigner 中创建新按钮和新页面
main.ui
一般导入 QtDesigner 后,预览 python 代码,直接粘贴到ui_main.py
(节省了用命令转换)- QtDesigner 中按 Ctrl + R 预览
使用的控件是 Push Button 和 Graphics View,记得 Graphics View 要权限提升(QChartView),完成后 Ctrl + s 保存,再回到 main.py
中添加代码
# 新增功能:电脑信息数据分析
widgets.btn_computer.clicked.connect(self.buttonClick)
widgets.computer_info_start.clicked.connect(self.start_computer_info)
widgets.computer_info_clear.clicked.connect(self.clear_computer_info)
# 电脑信息数据分析
if btnName == "btn_computer":
widgets.stackedWidget.setCurrentWidget(widgets.computer_info) # SET PAGE
UIFunctions.resetStyle(self, btnName) # RESET ANOTHERS BUTTONS SELECTED
btn.setStyleSheet(UIFunctions.selectMenu(btn.styleSheet())) # SELECT MENU
self.seriesS = QLineSeries()
self.seriesL = QLineSeries()
self.seriesS.setName("cpu")
self.seriesL.setName("memory")
- 自定义类
# 新增的类
import psutil
import time
from PySide6.QtCharts import QChart, QLineSeries
class NewThread(QThread):
# 自定义信号声明
# 使用自定义信号和 UI 主线程通讯,参数是发送信号时附带参数的数据类型,可以是 str,int,list 等
finishSignal = Signal(str)
# 带一个参数 t
def __init__(self, parent=None):
super(NewThread, self).__init__(parent)
# run 函数是子线程中的操作,线程启动后开始执行
if os.path.exists(f"./computer_info.csv"):
pass
else:
with open(r"./computer_info.csv", "w") as f:
pass
def run(self):
timer = 0
while True:
timer += 1
cpu_percent = psutil.cpu_percent(interval=1)
cpu_info = cpu_percent
virtual_memory = psutil.virtual_memory()
memory_percent = virtual_memory.percent
with open(r"./computer_info.csv", "a") as f:
f.write(f"{timer},{cpu_info}{memory_percent}\n")
time.sleep(1)
# 发射自定义信号
# 通过 emit 函数将参数 i 传递给主线程,触发自定义信号
self.finishSignal.emit("1")
- 自定义函数
def start_computer_info(self):
"""
开始获取电脑数据
"""
# 开始分析记录电脑数据,需要持续获取,然后分析
self.thread1 = NewThread() # 实例化一个线程
# 将线程 thread 的信号 finishSignal 和 UI 主线程中的槽函数 data_display 进行连接
self.thread1.finishSignal.connect(self.data_display)
# 启动线程,执行线程类中的 run 函数
self.thread1.start()
def data_display(self, str_):
"""
电脑信息的数据展示
"""
# 获取已经记录好的数据并展示
# 设置一个 flag
with open(r"./computer_info.csv", "r") as f:
reader = f.readlines()
reader_last = reader[-1].replace("\n", "").split(",")
# 横坐标
col = int(reader_last[0])
# cpu
cpu = float(reader_last[1])
# 内存
memory = float(reader_last[2])
self.seriesS.append(col, cpu)
self.seriesL.append(col, memory)
self.chart = QChart()
self.chart.setTitle("设备资源图")
self.chart.addSeries(self.seriesS)
self.chart.addSeries(self.seriesL)
self.chart.createDefaultAxes()
widgets.graphicsView.setChart(self.chart)
def clear_computer_info(self):
"""
清除设备表格信息
"""
self.seriesS.clear()
self.seriesL.clear()
self.chart.addSeries(self.seriesS)
self.chart.addSeries(self.seriesL)
实现功能:打开本地文件+打开外部网址+随机切换本地图片
- 在 QtDesigner 中创建新按钮
- 在
main.py
中添加功能
# 新增功能:打开本地文件
widgets.btn_local.clicked.connect(self.open_file)
# 新增功能:打开网站
widgets.btn_web.clicked.connect(self.open_web)
# 新增功能:切换图片
widgets.btn_pic.clicked.connect(self.change_pic)
def open_file(self):
import webbrowser
webbrowser.open("说明书" + ".docx")
def open_web(self):
import webbrowser
webbrowser.open("www.baidu.com")
def change_pic(self):
url_list = [
"./1.jpg",
"./2.jpg",
"./3.jpg",
"./4.jpg",
"./5.png"
]
import random
index = random.randint(0, 4) # 很神奇,能显示 5 张图片
lb1 = widgets.label # 同页面下的控件
pix = QPixmap(url_list[index]).scaled(lb1.size(), aspectMode=Qt.KeepAspectRatio)
lb1.setPixmap(pix)
lb1.repaint()
打包
- 下载打包库:
pip install pyinstaller
- 一个 exe(但需要静态资源),缺点是启动慢
pyinstaller -F .\main.py
- 一个 exe + 多个文件(依然需要静态资源),缺点是文件多
pyinstaller -D .\main.py