界面设计和布局2
您需要高效学习,找工作? 点击咨询 报名实战班
点击查看学员就业情况
直接写代码 开发界面
前面我们都是在 Qt Designer
上添加的Layout和里面的控件。
这种做法比较吸引初学者,因为看起来比较容易上手。
其实,Qt开发熟练后, 更推荐 直接用代码写界面 ,开发效率更高。
这里介绍一下如何 用代码 直接写 界面布局 。
水平/垂直 布局
最常用的2种类型的 布局 Layout 是:
QHBoxLayout
: 水平布局
QVBoxLayout
: 垂直布局
它们都是 Qt 的 QtWidgets 模块里面定义的类型,所以可以这样导入使用
也可以这样导入使用
有些控件,通常作为 容器控件 ,内部存放其它的控件,比如: QWidget
, QFrame
等
要指定一个容器控件使用某个layout,可以使用 setLayout
方法,比如
from PySide6.QtWidgets import QApplication, QFrame, QHBoxLayout, QLabel
app = QApplication()
frame = QFrame()
hl = QHBoxLayout() # 创建水平布局 hl
frame.setLayout(hl) # 设置 hl 为 frame的布局
hl.addWidget(QLabel('控件1')) # hl布局中添加一个控件
hl.addWidget(QLabel('控件2')) # hl布局中添加一个控件
frame.resize(250, 100)
frame.show()
app.exec()
这里的 QFrame
可以想象为一个白板控件,里面可以存放其它控件,
由于指定了它使用 水平布局,所以里面添加的 子控件都是水平放置的。
运行结果如下
也可以 在创建 Layout 时的 初始化参数 中指定它作为哪个控件 的 布局。
比如,上面的代码可以等效的写为这样:
from PySide6.QtWidgets import QApplication, QFrame, QHBoxLayout, QLabel
app = QApplication()
frame = QFrame()
# 创建水平布局 hl,并设置其为 frame的布局
hl = QHBoxLayout(frame)
hl.addWidget(QLabel('控件1'))
hl.addWidget(QLabel('控件2'))
frame.resize(250, 100)
frame.show()
app.exec()
前面的代码已经告诉我们了,要在layout中添加控件,使用其 addWidget
方法。
下面再举一例:
要在layout中添加 内部 子layout ,使用其 addLayout
方法,比如
运行结果如下
这样,通过 Layout , 子Layout, 子控件, 子控件中的layout ,这些 层层组合,完成各种复杂的界面布局。
表格布局 (Grid Layout)
QGridLayout
是表格布局, 添加控件时,需要指定行号/列号,
行号/列号从 0 开始,比如 要实现如下界面
对应代码如下
from PySide6 import QtWidgets
class Window(QtWidgets.QFrame):
def __init__(self):
super().__init__()
button1 = QtWidgets.QPushButton('按钮1', self)
button2 = QtWidgets.QPushButton('按钮2', self)
button3 = QtWidgets.QPushButton('按钮3', self)
# 创建一个水平layout作为内部layout
gl = QtWidgets.QGridLayout()
gl.addWidget(button1, 0 , 0) # 添加到第1行,第1列
gl.addWidget(button2, 0 , 1) # 添加到第1行,第2列
gl.addWidget(button3, 1 , 1) # 添加到第2行,第2列
# 指定自身使用的layout
self.setLayout(gl)
app = QtWidgets.QApplication()
window = Window()
window.resize(400, 200)
window.show()
app.exec()
表格布局可以
通过 setColumnStretch 指定每个 表格列 的宽度比例
通过 setColumnMinimumWidth 指定每个 表格列 的最小宽度
层叠布局 (Stacked Layout)
QStackedLayout
是 一种层叠布局, 可以通过代码指定要显示其中的哪一层
比如这样的界面
对应代码如下
上例中,通过3个按钮触发显示不同的界面, 当然也可以通过任何其它方式触发, 比如 主窗口的菜单选择触发。
显示stack layout 里面哪个界面,可以上例中使用 setCurrentWidget
方法,参数是 界面对应的 Widget 对象。也可以使用 setCurrentIndex
方法, 通过索引指定显示,比如
# 展示第1页
stackedLayout.setCurrentIndex(0)
# 展示第2页
stackedLayout.setCurrentIndex(1)
# 展示第3页
stackedLayout.setCurrentIndex(2)
QMainWindow 里面的 Layout
上面的代码示例中, 使用QFrame作为 程序界面的 顶级控件类型,
实际应用中,我们开发的程序界面, 顶级控件类型 大都是 QMainWindow 主窗口类
因为它是 可以 具有 菜单栏
, 工具栏
, 状态栏
和 中央控件(Central Widget)
的 特殊 Widget, 正好适合作为 一个 主窗口。
主窗口的主体内容在 中央控件(Central Widget)
中。
通常,我们需要对 Central Widget 进行一些布局设置。
这些当然可以在 Qt Designer 里面设置。
如果不使用 Qt Designer, 而是直接使用代码开发界面,如何设置 Central Widget 和 里面的布局呢?
示例代码如下:
from PySide6 import QtWidgets
class MWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.resize(400, 200)
# 创建一个 中央控件 Central Widget
centralWidget = QtWidgets.QWidget(self)
self.setCentralWidget(centralWidget)
# 创建 垂直layout,作为 Central Widget 的布局,称之为 主layout
mainLayout = QtWidgets.QVBoxLayout(centralWidget)
# 创建 主layout 的 一个 子layout
topLayout = QtWidgets.QHBoxLayout()
mainLayout.addLayout(topLayout) # 添加 子 Layout
# 子layout 添加内部控件
topLayout.addWidget(QtWidgets.QLabel('姓名'))
topLayout.addWidget(QtWidgets.QLineEdit(self))
# 主layout 里面 再添加 一个编辑框
mainLayout.addWidget(QtWidgets.QPlainTextEdit(self))
app = QtWidgets.QApplication()
window = MWindow()
window.show()
app.exec()
运行结果如下:
layout内 控件的 摆放位置
水平垂直的 layout, 放入控件 通常都是 平均分散在 layout 里面的
from PySide6.QtWidgets import QApplication, QFrame, QHBoxLayout, QLabel
app = QApplication()
frame = QFrame()
frame.setFixedSize(400, 200)
frame.setStyleSheet('''
QFrame { border: 1px solid green; background: white;}
QLabel {background: SkyBlue; }
''')
hl = QHBoxLayout(frame)
label1 = QLabel('1')
label1.setFixedSize(30,30)
hl.addWidget(label1)
label2 = QLabel('2')
label2.setFixedSize(30,30)
hl.addWidget(label2)
frame.show()
app.exec()
运行效果如图
addStretch 左右靠边,居中
如果,我们想让元素尽量都挤在左边, 右边尽量空白, 可以通过layout布局的 addStretch
方法
from PySide6.QtWidgets import QApplication, QFrame, QHBoxLayout, QLabel
app = QApplication()
frame = QFrame()
frame.setFixedSize(400, 200)
frame.setStyleSheet('''
QFrame { border: 1px solid green; background: white;}
QLabel {background: SkyBlue; }
''')
hl = QHBoxLayout(frame)
label1 = QLabel('1')
label1.setFixedSize(30,30)
hl.addWidget(label1)
label2 = QLabel('2')
label2.setFixedSize(30,30)
hl.addWidget(label2)
hl.addStretch() # layout 结尾 `addStretch`
frame.show()
app.exec()
addStretch
其实就是添加了一个 QSpacerItem
(也就是Qt设计师界面上的Spacer)
layout 结尾 addStretch
, 可以让该layout后面尽量空白占据,实现 内部控件 靠左
运行效果如图
同理, layout 开头 addStretch
, 可以让该layout前面尽量空白占据,实现 内部控件 靠右
如果 开头结尾 都 addStretch
, 前后都尽量空白占据,就实现了 居中
的效果
from PySide6.QtWidgets import QApplication, QFrame, QHBoxLayout, QLabel
app = QApplication()
frame = QFrame()
frame.setFixedSize(400, 200)
frame.setStyleSheet('''
QFrame { border: 1px solid green; background: white;}
QLabel {background: SkyBlue; }
''')
hl = QHBoxLayout(frame)
hl.addStretch() # layout 开头 `addStretch`
label1 = QLabel('1')
label1.setFixedSize(30,30)
hl.addWidget(label1)
label2 = QLabel('2')
label2.setFixedSize(30,30)
hl.addWidget(label2)
hl.addStretch() # layout 结尾 `addStretch`
frame.show()
app.exec()
alignment 参数 指定位置
可以在调用 Layout 的 addWidget 方法时,通过 alignment
参数,指定控件在layout内部的位置,如下
from PySide6.QtWidgets import QApplication, QFrame, QHBoxLayout, QLabel
from PySide6 import QtGui
app = QApplication()
frame = QFrame()
frame.setFixedSize(400, 200)
frame.setStyleSheet('''
QFrame { border: 1px solid green; background: white;}
QLabel {background: SkyBlue; }
''')
hl = QHBoxLayout(frame)
label1 = QLabel('1')
label1.setFixedSize(30,30)
hl.addWidget(
label1,
alignment=QtGui.Qt.AlignLeft|QtGui.Qt.AlignTop) # 左上
label2 = QLabel('2')
label2.setFixedSize(30,30)
hl.addWidget(
label2,
alignment=QtGui.Qt.AlignRight|QtGui.Qt.AlignBottom) # 右下
frame.show()
app.exec()
Layout 内边距/间隔
一个Layout缺省是有几个像素的边距的,所以内部的内容不是紧贴着layout边界的。
可以用 Layout 的 setContentsMargins
方法 代码设置 layout对应Widget 的 内部内容的 左/上/右/下 外部边距 ,如下
from PySide6.QtWidgets import QApplication, QFrame, QHBoxLayout, QLabel
app = QApplication()
frame = QFrame()
frame.setFixedSize(400, 200)
frame.setStyleSheet('''
QFrame { border: 1px solid green; background: white;}
QLabel {background: SkyBlue; }
''')
hl = QHBoxLayout(frame)
hl.setContentsMargins(0, 0, 0, 0) # 边距设置为0
label1 = QLabel('1')
label1.setFixedSize(30,30)
hl.addWidget(label1)
label2 = QLabel('2')
label2.setFixedSize(30,30)
hl.addWidget(label2)
hl.addStretch() # layout 结尾 `addStretch`
frame.show()
app.exec()
由于边距都被设置为0,所以靠左的控件紧贴layout边界了。
但是可以发现元素之间还是有间隔。这个间隔的大小可以通过 layout的 setSpacing
方法设置
from PySide6.QtWidgets import QApplication, QFrame, QHBoxLayout, QLabel
app = QApplication()
frame = QFrame()
frame.setFixedSize(400, 200)
frame.setStyleSheet('''
QFrame { border: 1px solid green; background: white;}
QLabel {background: SkyBlue; }
''')
hl = QHBoxLayout(frame)
hl.setContentsMargins(0, 0, 0, 0)
hl.setSpacing(0) # 间隔设置为0
label1 = QLabel('1')
label1.setFixedSize(30,30)
hl.addWidget(label1)
label2 = QLabel('2')
label2.setFixedSize(30,30)
hl.addWidget(label2)
hl.addStretch()
frame.show()
app.exec()
子控件如果比较多,可以使用 setSpacing
方法整体设置统一的间隔。
如果其中某个控件后面间隔比较独特,可以使用 addSpacing
方法额外设置间隔,比如
from PySide6.QtWidgets import QApplication, QFrame, QHBoxLayout, QLabel
app = QApplication()
frame = QFrame()
frame.setFixedSize(400, 200)
frame.setStyleSheet('''
QFrame { border: 1px solid green; background: white;}
QLabel {background: SkyBlue; }
''')
hl = QHBoxLayout(frame)
hl.setContentsMargins(0, 0, 0, 0)
hl.setSpacing(20) # 间隔设置为20
label1 = QLabel('1')
label1.setFixedSize(30,30)
hl.addWidget(label1)
label2 = QLabel('2')
label2.setFixedSize(30,30)
hl.addWidget(label2)
hl.addSpacing(20) # 在原来的间隔基础上再增加20,就是40
label3 = QLabel('3')
label3.setFixedSize(30,30)
hl.addWidget(label3)
hl.addStretch()
frame.show()
app.exec()
Layout 内部元素长度比例
可以 :
-
通过 layout 的
setStretch
方法设置其 内部控件/内部布局 的长度比例。 -
通过 layout 的
setMinimumWidth
/setMaximumWidth
/setMinimumHeight
/setMaximumHeight
方法设置其 内部控件/内部布局 的 最小/最大 宽度/高度。
大家可以运行如下代码示例,调整主窗口的宽度,来验证左右两边控件的宽度比例的变化。
除了上面使用 setStretch
定义layout中控件长度比例,
layout 也可以在添加子控件时,直接指定长度比例,如下
其它技巧
从一个窗口跳转到另外一个窗口
经常有朋友问我,程序开始的时候显示一个窗口(比如登录窗口),操作后进入到另外一个窗口,怎么做。
方法很简单,主要就是 实例化另外一个窗口,显示新窗口,关闭老窗口。
如下代码所示
点击这里下载 一个登录切换到主窗口 的示例代码包
如果经常要在两个窗口来回跳转,可以使用 hide()
方法 隐藏窗口, 而不是 close()
方法关闭窗口。 这样还有一个好处:被隐藏的窗口再次显示时,原来的操作内容还保存着,不会消失。
弹出模式对话框
有的时候,我们需要弹出一个模式对话框输入一些数据,然后回到 原窗口。
所谓模式对话框,就是弹出此对话框后, 原窗口就处于不可操作的状态,只有当模式对话框关闭才能继续。
参考如下代码
from PySide6 import QtWidgets
import sys
class MyDialog(QtWidgets.QDialog):
def __init__(self):
super().__init__()
self.setWindowTitle('模式对话框')
self.resize(500, 400)
self.textEdit = QtWidgets.QPlainTextEdit(self)
self.textEdit.setPlaceholderText("请输入薪资表")
self.textEdit.move(10, 25)
self.textEdit.resize(300, 350)
self.button = QtWidgets.QPushButton('统计', self)
self.button.move(380, 80)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('主窗口')
centralWidget = QtWidgets.QWidget()
self.setCentralWidget(centralWidget)
button = QtWidgets.QPushButton('打开模式对话框')
button.clicked.connect(self.open_new_window)
grid = QtWidgets.QGridLayout(centralWidget)
grid.addWidget(button)
def open_new_window(self):
# 实例化一个对话框类
self.dlg = MyDialog()
# 显示对话框,代码阻塞在这里,
# 等待对话框关闭后,才能继续往后执行
self.dlg.exec()
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()