前言

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

原视频传送门如下

官方文档链接:Qt for Python

选项卡(QTabWidget)

创建选项卡

QTabWidget 提供了一个管理多个页面的堆栈,每个页面都有自己的选项卡标签。这使得用户可以通过选择不同的选项卡来切换不同的内容页面。

首先我们可以多创建几个QWidget实例,将其作为选项卡中的不同页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
self.tab1 = QWidget()
self.tab1Layout = QVBoxLayout()
self.tab1Layout.addWidget(QPushButton('Button1'))
self.tab1Layout.addWidget(QLabel('Label1'))
self.tab1.setLayout(self.tab1Layout)

self.tab2 = QWidget()
self.tab2Layout = QVBoxLayout()
self.tab2Layout.addWidget(QPushButton('Button2'))
self.tab2Layout.addWidget(QLabel('Label2'))
self.tab2.setLayout(self.tab2Layout)

self.tab3 = QWidget()
self.tab3Layout = QVBoxLayout()
self.tab3Layout.addWidget(QPushButton('Button3'))
self.tab3Layout.addWidget(QLabel('Label3'))
self.tab3.setLayout(self.tab3Layout)

接下来我们创建一个选项卡QTabWidget实例,并使用addTab方法向其中添加页面

1
2
3
4
self.tab = QTabWidget()
self.tab.addTab(self.tab1, 'Tab1')
self.tab.addTab(self.tab2, 'Tab2')
self.tab.addTab(self.tab3, 'Tab3')

效果如下

常用属性和方法

前面创建的选项卡离真实软件中的选项卡样式还有很大差距

以Vscode中的选项卡为例,它不仅有关闭按钮和上下文菜单,还可以进行拖动

这需要我们配置选项卡的高级属性和方法

我们可以使用setTabsClosable方法为选项卡增加关闭按钮

1
self.tab.setTabsClosable(True)

使用setMovable方法让选项卡能够被拖动

1
self.tab.setMovable(True)

此时选项卡就拥有关闭按钮且可拖动了,当然关闭功能还需绑定信号和槽

常用信号

tabCloseRequested信号会在我们点击关闭按钮时触发,并返回关闭的选项卡的信号

我们可以将其与removeTab方法绑定,实现关闭选项卡的功能

1
self.tab.tabCloseRequested.connect(lambda index: self.tab.removeTab(index))

剩下的三个信号也比较常用,后续在实践中也会用到

堆叠页面(QStackedWidget)

QStackedWidget 是一个容器控件,它可以包含多个子控件,但在任何给定时间只显示其中一个子控件。这对于实现向导界面或在同一位置显示不同内容的应用程序非常有用。

创建堆叠容器

QTabWidget 类似,我们可以通过创建多个 QWidget 实例作为不同的页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
self.stack1 = QWidget()
self.stack1Layout = QVBoxLayout()
self.stack1Layout.addWidget(QPushButton('Stack Button 1'))
self.stack1Layout.addWidget(QLabel('Stack Label 1'))
self.stack1.setLayout(self.stack1Layout)

self.stack2 = QWidget()
self.stack2Layout = QVBoxLayout()
self.stack2Layout.addWidget(QPushButton('Stack Button 2'))
self.stack2Layout.addWidget(QLabel('Stack Label 2'))
self.stack2.setLayout(self.stack2Layout)

self.stack3 = QWidget()
self.stack3Layout = QVBoxLayout()
self.stack3Layout.addWidget(QPushButton('Stack Button 3'))
self.stack3Layout.addWidget(QLabel('Stack Label 3'))
self.stack3.setLayout(self.stack3Layout)

使用addWidget方法将页面添加到堆叠容器中。初始情况下,QStackedWidget会显示第一个添加的页面

1
2
3
4
self.stackedWidget = QStackedWidget()
self.stackedWidget.addWidget(self.stack1)
self.stackedWidget.addWidget(self.stack2)
self.stackedWidget.addWidget(self.stack3)

效果如下

切换页面

可以使用 setCurrentIndexsetCurrentWidget 方法来切换 QStackedWidget 中当前显示的页面:

1
2
3
self.stackedWidget.setCurrentIndex(1)  # 切换到第二个页面
# 或者
self.stackedWidget.setCurrentWidget(self.stack3) # 切换到第三个页面

setCurrentIndex传入页面的序号,而setCurrentWidget传入页面的实例名称

QStackedWidget 本身没有提供动画切换页面的功能。但是,我们可以通过结合使用 QPropertyAnimation来实现页面之间的平滑过渡动画。

过渡动画

我们新建一个堆叠页面的类,在其中重写setCurrentIndex函数,并加入动画效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class AnimatedStackedWidget(QStackedWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.animation = QPropertyAnimation(self, b"geometry")
self.animation.setDuration(300) # 动画持续时间,单位为毫秒

def setCurrentIndex(self, index):
self.animation.stop() # 停止当前动画
current_widget = self.currentWidget()
next_widget = self.widget(index)

# 获取堆叠窗口的几何信息
stacked_widget_geometry = self.geometry()

# 新页面的起始位置在堆叠窗口的下方
next_widget_start_geometry = QRect(stacked_widget_geometry.x(), stacked_widget_geometry.y() + stacked_widget_geometry.height()/4, stacked_widget_geometry.width(), stacked_widget_geometry.height())

# 新页面的结束位置与当前页面相同
next_widget_end_geometry = stacked_widget_geometry

next_widget.setGeometry(next_widget_start_geometry) # 设置新页面的起始位置

self.animation.setTargetObject(next_widget)
self.animation.setStartValue(next_widget_start_geometry)
self.animation.setEndValue(next_widget_end_geometry)

super().setCurrentIndex(index) # 切换到新页面
self.animation.start()

这里使用了QtCore中的动画类QPropertyAnimation,并设置为位置动画,即平移

切换到新页面时会从下往上平移加载

最后我们在页面中添加几个切换按钮

1
2
3
4
5
6
7
8
9
for i in range(3):
btn = QPushButton(f"Go to page {i+1}")
btn.clicked.connect(self.make_switcher(i))
self.mainLayout.addWidget(btn)

def make_switcher(self, index):
def switch():
self.stackedWidget.setCurrentIndex(index)
return switch

效果如下

当然我们也可以加一下判断条件,让动画更丝滑

比如当新的页面被切换时,动画还没播放完,则停止当前动画