前言

本系列文章为b站PySide6教程以及官方文档的学习笔记

原视频传送门如下

官方文档链接:Qt for Python

基础框架

我们来实现一个最简单的窗口,并借由其代码来初步认识pyside6的结构

1
2
3
4
5
6
7
8
9
10
11
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()

首先是导入的QApplicationQMainWindow类,这些类是用于创建 GUI 应用程序的基本类。

然后我们从QMainWindow类继承我们自己的窗口类Mywindow,这个类将用于创建应用程序的主窗口,此时类中只调用了父类的构造函数。

主程序中则创建了QApplicationMyWindow类的实例,QApplication 是一个必需的类,它管理应用程序的控制流和主要设置。

window.show()用于显示MyWindow 实例,这将使窗口可见并允许用户与它进行交互

app.exec()用于启动应用程序的事件循环。事件循环是一个无限循环,它等待用户输入和系统事件,并相应地更新应用程序的状态。

这段代码的运行效果如下

基础控件

一般来说一个应用程序的运行逻辑无非是用户输入->用户交互->输出

那么这就涉及到三种最基本的控件:按钮、标签和输入框

想要给窗体添加控件,需要在窗体类的构造函数中添加控件实例

QPushButton

该控件需要从PySide6.QtWidgets导入

1
2
from PySide6.QtWidgets import QPushButton
btn = QPushButton("Click me", self)

但是光一个控件肯定不行,我们还需要设置它的一些属性,来满足高级需求

事实上,当我们想了解一个控件有哪些属性,以及这些属性分别有什么功能时,可以在Qt Designer上进行测试

当我们配置好vscode中的扩展插件PYQT Integration后,只需在文件上右键就能快速打开Qt Designer

我们只需拖动一个部件到窗体上,即可在右侧窗口查看并调试它的一些属性

这里列出几个PushButton常用的属性

属性 作用
geometry(几何) 坐标位置、尺寸大小
text 按钮上显示的文字
toolTip 鼠标放在按钮上时显示的提示文字

想要为控件实例设置属性,需要调用set+属性名的方法

如下示例

1
2
3
4
5
6
7
8
9
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导入

1
2
from PySide6.QtWidgets import QLable
lb = QLable("Hello", self)

下面是一些常用的标签特有属性

属性 作用
text 标签上显示的文字
textFormat 如PlainText、MarkdownText和RichText形式
alignment 文本对齐方式
pixmap 显示图片

QLineEdit

该控件需要从PySide6.QtWidgets导入

1
2
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环境下)

1
pyside6-uic xxx.ui -o xxx.py

或者在vscode中,我们可以利用插件PYQT Integration的功能

右键ui文件,选择PYQT:Compile Form即可

使用编译得到的py文件

上一步中,我们通过编译login.ui文件得到了UI_login.py的py源码

窗口文件在一个叫Ui_Form的类中

两种方法在其他的程序中调用这个生成的窗口UI

  1. 在需要调用的地方创建一个该对象的实例

    1
    2
    3
    4
    5
    6
    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文件的一致

  2. 第二种方法则是利用python多继承的特性,即我们的窗口可以继承多个类

    将Ui_Form也作为我们窗口的父类

    1
    2
    3
    4
    5
    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 事件触发响应

事件触发响应

  • 机制:基于事件循环,当用户进行操作(如点击、键入)时,事件被生成并放入事件队列,然后由应用程序逐个处理。
  • 应用:通常用于处理用户输入、窗口变化等。
  • 优点
    • 直观性:事件处理通常更直观,易于理解。
    • 控制性:可以在事件处理中有更多控制,如事件过滤。
  • 缺点
    • 紧耦合:事件处理函数通常与特定控件或场景紧密相关。
    • 处理复杂性:对于复杂的交互,事件处理可能变得复杂和冗长。

信号与槽

  • 机制:基于信号的发送和槽的接收,更侧重于对象间的通信。
  • 应用:适合于不同组件间的通信,例如,一个组件的行为触发另一个组件的反应。
  • 优点
    • 解耦:发信者和接收者不需要知道彼此的存在。
    • 灵活性:可以连接多个槽到一个信号,或将一个槽连接到多个信号。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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调用它

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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

那么计算器的逻辑中我们没有必要绑定太多槽

一个思路是先利用其他按钮的信号生成算式的字符串,每次调用槽的时候刷新输入框的显示

1
2
3
4
5
def addNumber(self,number):
self.lineEdit.clear()
self.expression+=number
self.lineEdit.setText(str(self.expression))

在代码中,我们新建一个bind()函数来记录绑定关系,然后在初始化函数中一并调用bind即可

这样能保证初始化函数的简洁性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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)

在 Python 编程语言中,lambda 关键字用于创建匿名函数,这种函数称为 lambda 函数。Lambda 函数可以接受任何数量的参数,但只能有一个表达式。它们通常用于需要函数对象的地方,但又不想在代码中定义完整的函数。Lambda 函数的基本语法如下:

1
lambda arguments: expression

在绑定信号时如果我们直接写成:

1
self.pushButton_0.clicked.connect(self.addNumber('0'))

这样当python解释器读到这一行代码时会立即执行 self.addNumber('0') ,这并不是我们想要的。我们希望的是,每次按钮被点击时,才调用 self.addNumber('0')。我们需要传递一个函数对象给connect

为了解决这个问题,我们使用 lambda 创建了一个匿名函数,这个匿名函数没有参数,并在被调用时执行 self.addNumber('0')。这样,每次按钮被点击时,实际上是调用这个匿名函数,然后这个匿名函数再去调用 self.addNumber('0')

使用 lambda 这种方式使得我们可以在不创建额外的命名函数的情况下,传递带有参数的方法作为信号的槽函数。这

当最后当点击计算按钮时,借助python中的eval()函数,我们能将生成的字符串变为算式

1
2
3
4
def count(self):
self.result = eval(self.expression)
self.lineEdit.setText(str(self.result))
self.expression = str(self.result)

当然我们也可以为计算器加上清空和回退的功能

1
2
3
4
5
6
7
8
9
10
11
12
13
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))

最终的效果如下