目录
前言
本系列文章为b站PySide6教程以及官方文档的学习笔记
原视频传送门:【已完结】PySide6百炼成真,带你系统性入门Qt
官方文档链接:Qt for Python
基础框架
我们来实现一个最简单的窗口,并借由其代码来初步认识pyside6的结构
from PySide6.QtWidgets import QApplication, QMainWindow
class MyWindow(QMainWindow):
def __init__(self):
super().__init__()
if __name__ =="__main__":
app =QApplication()
window = MyWindow()
window.show()
app.exec()
首先是导入的QApplication
和QMainWindow
类,这些类是用于创建 GUI 应用程序的基本类。
然后我们从QMainWindow
类继承我们自己的窗口类Mywindow
,这个类将用于创建应用程序的主窗口,此时类中只调用了父类的构造函数。
主程序中则创建了QApplication
和MyWindow
类的实例,QApplication 是一个必需的类,它管理应用程序的控制流和主要设置。
window.show()
用于显示MyWindow 实例,这将使窗口可见并允许用户与它进行交互
app.exec()
用于启动应用程序的事件循环。事件循环是一个无限循环,它等待用户输入和系统事件,并相应地更新应用程序的状态。
这段代码的运行效果如下
基础控件
一般来说一个应用程序的运行逻辑无非是用户输入->用户交互->输出
那么这就涉及到三种最基本的控件:按钮、标签和输入框
想要给窗体添加控件,需要在窗体类的构造函数中添加控件实例
QPushButton
该控件需要从PySide6.QtWidgets
导入
from PySide6.QtWidgets import QPushButton
btn = QPushButton("Click me", self)
但是光一个控件肯定不行,我们还需要设置它的一些属性,来满足高级需求
事实上,当我们想了解一个控件有哪些属性,以及这些属性分别有什么功能时,可以在Qt Designer上进行测试
当我们配置好vscode中的扩展插件PYQT Integration后,只需在文件上右键就能快速打开Qt Designer
我们只需拖动一个部件到窗体上,即可在右侧窗口查看并调试它的一些属性
这里列出几个PushButton常用的属性
属性 | 作用 |
---|---|
geometry(几何) | 坐标位置、尺寸大小 |
text | 按钮上显示的文字 |
toolTip | 鼠标放在按钮上时显示的提示文字 |
想要为控件实例设置属性,需要调用set+属性名
的方法
如下示例
from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton
class MyWindow(QMainWindow):
def __init__(self):
super().__init__()
btn = QPushButton("Click me", self)
btn.setGeometry(100, 100, 200, 50) #设置(x,y)坐标为(100,100),而宽高分别为200和50
btn.setText("new text") #重新设置的文字会覆盖初始化时的文字
btn.setToolTip("tips")
QLabel
该控件需要从PySide6.QtWidgets
导入
from PySide6.QtWidgets import QLable
lb = QLable("Hello", self)
下面是一些常用的标签特有属性
属性 | 作用 |
---|---|
text | 标签上显示的文字 |
textFormat | 如PlainText、MarkdownText和RichText形式 |
alignment | 文本对齐方式 |
pixmap | 显示图片 |
QLineEdit
该控件需要从PySide6.QtWidgets
导入
from PySide6.QtWidgets import QLineEdit
input = QLineEdit("框中预留文字", self)
下面是一些常用的输入框特有属性
属性 | 作用 |
---|---|
maxLength | 最大输入长度 |
readOnly | 是否设置为只读模式 |
placeholderText | 框中无任何输入时显示的文字 |
pixmap | 显示图片 |
初识QtDesigner
制作一个简单页面
登录框
首先我们需要考虑页面中会出现哪些种控件
一般来说登录页面会需要输入账号密码,所以会需要输入框
而提示以及提交则需要标签和按钮控件
将这些元素拖拽入窗口中,并进行初步的属性设置,我们就能得到一个简易的窗口模板
此时按Ctrl
+R
或者点击窗体>预览,我们就能预览当前窗口的效果
我们会发现窗口的标题还是默认的,我们可以直接去设置窗体本身的属性
当设计完毕后,我们可以将设计文件保存为.ui
后缀的文件
计算器
同样我们也能拖拉出一个计算器的UI,这些过程能让我们逐渐熟悉QtDesigner的操作
编译UI文件
在QtDesigner中设计好了界面UI后,只能对其进行预览
如果想在程序中运行并显示我们设计的UI,则需要进一步将其编译为python源码,即py文件
我们可以之间执行如下指令(在安装pyside6的python环境下)
pyside6-uic xxx.ui -o xxx.py
或者在vscode中,我们可以利用插件PYQT Integration的功能
右键ui文件,选择PYQT:Compile Form即可
使用编译得到的py文件
上一步中,我们通过编译login.ui
文件得到了UI_login.py
的py源码
窗口文件在一个叫Ui_Form的类中
有两种方法在其他的程序中调用这个生成的窗口UI
-
在需要调用的地方创建一个该对象的实例
from Ui_login import Ui_Form class MyWindow(QWidget): def __init__(self): super().__init__() self.login = Ui_Form() self.login.setupUi(self)
setupUi为我们生成的UI的类中的函数,参数需要将我们当前的窗体传进去,这里我们直接传self
需要注意的是我们的MyWindow继承的窗口类型需要与UI文件的一致
-
第二种方法则是利用python多继承的特性,即我们的窗口可以继承多个类
将Ui_Form也作为我们窗口的父类
from Ui_login import Ui_Form class MyWindow(QWidget,Ui_Form): def __init__(self): super().__init__() self.setupUi(self)
信号与槽
概念
PYQT界面的交互需要依靠信号与槽,类似于其他图形界面编程中的事件响应
事件响应机制的图形界面会不断地update,来检测页面中是否有什么元素发生了变化
而信号与槽的机制中,只有界面元素发出信号给相应的槽,页面才会进行修改
信号 (Signals)
- 定义:信号是PySide6(和Qt)中的一个关键概念,是从对象发送的消息,表明发生了某种事件或状态变化。
- 特点:信号不包含处理逻辑,它们只负责通知事件的发生。
槽 (Slots)
- 定义:槽是用来接收信号的方法。当与信号相连的特定事件发生时,相应的槽函数会被调用。
- 特点:槽可以是任何可调用的Python函数或方法。
信号与槽 vs 事件触发响应
事件触发响应
- 机制:基于事件循环,当用户进行操作(如点击、键入)时,事件被生成并放入事件队列,然后由应用程序逐个处理。
- 应用:通常用于处理用户输入、窗口变化等。
- 优点:
- 直观性:事件处理通常更直观,易于理解。
- 控制性:可以在事件处理中有更多控制,如事件过滤。
- 缺点:
- 紧耦合:事件处理函数通常与特定控件或场景紧密相关。
- 处理复杂性:对于复杂的交互,事件处理可能变得复杂和冗长。
信号与槽
- 机制:基于信号的发送和槽的接收,更侧重于对象间的通信。
- 应用:适合于不同组件间的通信,例如,一个组件的行为触发另一个组件的反应。
- 优点:
- 解耦:发信者和接收者不需要知道彼此的存在。
- 灵活性:可以连接多个槽到一个信号,或将一个槽连接到多个信号。
示例
from PySide6.QtWidgets import QApplication, QPushButton
app = QApplication([])
# 创建一个按钮
button = QPushButton("Click me")
# 定义槽函数
def on_button_clicked():
print("Button clicked!")
# 将按钮的clicked信号连接到槽函数
button.clicked.connect(on_button_clicked)
button.show()
app.exec()
在这个例子中,当按钮被点击时,clicked
信号被发出,然后on_button_clicked
槽函数被调用。
我们通过.connect
将信号与槽连接起来
实践
完善登录框
为了了解我们之前设计的UI中各控件的对象名,我们可以回到QtDesigner中查看
例如第一个输入框,它被自动命名为lineEdit
,那么我们在代码中就能通过self.lineEdit
调用它
from PySide6.QtWidgets import QApplication, QWidget, QLineEdit
from Ui_login import Ui_Form
class MyWindow(QWidget,Ui_Form):
def __init__(self):
super().__init__()
self.setupUi(self)
self.pushButton.clicked.connect(self.loginFuc)
def loginFuc(self):
username = self.lineEdit.text()
password = self.lineEdit_2.text()
if username =="admin" and password =="123456":
print("登录成功")
else:
print("登录失败")
if __name__ =="__main__":
app =QApplication()
window = MyWindow()
window.show()
app.exec()
在代码中,我们让之前的UI界面有了响应,即用户名和密码输对时会在控制台输出"登录成功",否则输出"登陆失败“
完善计算器
我们来对之前的计算器界面重新布局一下
布局的好处是缩放界面时,控件的位置与大小也能自动的做出相应调整
对每一行按钮水平布局后,我们再对整体进行垂直布局
此时的控件还没有与窗口的位置形成相对关系,无法对页面缩放做出响应
我们将页面整体改为垂直布局,并适当调整最上方输入框的高度
此时我们就能得到一个较为整齐的计算器界面
为了后续在代码中更清晰地编写信号与槽的逻辑,我们需要对页面控件重新命名
例如.
按钮,我们将其命名为pushButton_dot
那么计算器的逻辑中我们没有必要绑定太多槽
一个思路是先利用其他按钮的信号生成算式的字符串,每次调用槽的时候刷新输入框的显示
def addNumber(self,number):
self.lineEdit.clear()
self.expression+=number
self.lineEdit.setText(str(self.expression))
在代码中,我们新建一个bind()
函数来记录绑定关系,然后在初始化函数中一并调用bind
即可
这样能保证初始化函数的简洁性
def bind(self):
self.pushButton_0.clicked.connect(lambda:self.addNumber('0'))
self.pushButton_1.clicked.connect(lambda:self.addNumber('1'))
self.pushButton_2.clicked.connect(lambda:self.addNumber('2'))
self.pushButton_3.clicked.connect(lambda:self.addNumber('3'))
self.pushButton_4.clicked.connect(lambda:self.addNumber('4'))
self.pushButton_5.clicked.connect(lambda:self.addNumber('5'))
self.pushButton_6.clicked.connect(lambda:self.addNumber('6'))
self.pushButton_7.clicked.connect(lambda:self.addNumber('7'))
self.pushButton_8.clicked.connect(lambda:self.addNumber('8'))
self.pushButton_9.clicked.connect(lambda:self.addNumber('9'))
self.pushButton_add.clicked.connect(lambda:self.addNumber('+'))
self.pushButton_sub.clicked.connect(lambda:self.addNumber('-'))
self.pushButton_mul.clicked.connect(lambda:self.addNumber('*'))
self.pushButton_div.clicked.connect(lambda:self.addNumber('/'))
self.pushButton_dot.clicked.connect(lambda:self.addNumber('.'))
self.pushButton_enter.clicked.connect(self.count)
{% note info %}
在 Python 编程语言中,lambda
关键字用于创建匿名函数,这种函数称为 lambda 函数。Lambda 函数可以接受任何数量的参数,但只能有一个表达式。它们通常用于需要函数对象的地方,但又不想在代码中定义完整的函数。Lambda 函数的基本语法如下:
lambda arguments: expression
{% endnote %}
在绑定信号时如果我们直接写成:
self.pushButton_0.clicked.connect(self.addNumber('0'))
这样当python解释器读到这一行代码时会立即执行 self.addNumber('0')
,这并不是我们想要的。我们希望的是,每次按钮被点击时,才调用 self.addNumber('0')
。我们需要传递一个函数对象给connect
为了解决这个问题,我们使用 lambda
创建了一个匿名函数,这个匿名函数没有参数,并在被调用时执行 self.addNumber('0')
。这样,每次按钮被点击时,实际上是调用这个匿名函数,然后这个匿名函数再去调用 self.addNumber('0')
{% note primary %}
使用 lambda
这种方式使得我们可以在不创建额外的命名函数的情况下,传递带有参数的方法作为信号的槽函数。这
{% endnote %}
当最后当点击计算按钮时,借助python中的eval()函数,我们能将生成的字符串变为算式
def count(self):
self.result = eval(self.expression)
self.lineEdit.setText(str(self.result))
self.expression = str(self.result)
当然我们也可以为计算器加上清空和回退的功能
self.pushButton_clear.clicked.connect(self.clear)
self.pushButton_back.clicked.connect(self.back)
......
def clear(self):
self.lineEdit.clear()
self.expression = ''
def back(self):
self.lineEdit.clear()
self.expression = self.expression[:-1]
self.lineEdit.setText(str(self.expression))
最终的效果如下