Skip to content

布局管理

PyQt5中主要通过两种方法进行布局:采用绝对位置布局和通过布局类进行布局。

布局类中有四种布局方式和两种布局方法:

四种布局方式:

  • 水平布局(QHBoxLayout):将所添加的控件在水平方向上依次排列

  • 垂直布局(QVBoxLayout):将所添加的控件在垂直方向上依次排列

  • 网格布局(QGridLayout):将所添加的控件以网格的形式排列

  • 表单布局(QFormLayout):将所添加的控件以两列的形式排列

两种布局方法:

  • addLayout():在布局中插入子布局
  • addWidget():在布局中插入控件

Qt Designer可视化工具

布局类

Qt Designer提供了4种窗口布局方式:

  • Vertical Layout(垂直布局)

    Vertical Layout(垂直布局):控件默认按照从上到下的顺序进行纵向添加

  • Horizontal Layout(水平布局)

    Horizontal Layout(水平布局):控件默认按照从左到右的顺序进行横向添加

  • Grid Layout(网格/栅格布局)

    Grid Layout(栅格布局):控件放入网格中,分成若干行和列,其交叉划分出来的空间就是单元,并把窗口控件放入合适的单元中

  • Form Layout(表单布局)

    Form Layout(表单布局):控件以两列的形式布局在表单中,其左列包含标签,右列包含输入控件

选中想要布局的控件,选择相关的布局方式,依次尝试不同的布局方式:

布局的层次:一般用父子关系来表示层次,一般在对象查看器中查看

可以看出有窗口(Dialog) --> 布局(Layout) --> 控件的层次关系(lineEdit和pushButton),窗口是作为顶层显示的

绝对布局

解读控件在属性编辑器的重要属性:geometrysizePolicyminimumSizemaximumSize

绝对布局:

geometry属性是设置控件在窗口中的绝对坐标和控件自身大小的,在界面文件中改变参数进行改变部件的布局

在界面文件中发现Push Button(按钮)对应的代码如下:

python
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(460, 290, 101, 28))   # 可以修改参数使控件改变布局。
self.pushButton.setObjectName("pushButton")

布局管理器的进阶使用

通过绝对布局改变部件的位置信息较为麻烦,通常我们使用布局管理器进行布局

  • 垂直布局对应的相关代码如下:

    python
    self.verticalLayout = QtWidgets.QVBoxLayout(self.widget)
    self.verticalLayout.setContentsMargins(0, 0, 0, 0)
    self.verticalLayout.setObjectName("verticalLayout")
    self.label_6 = QtWidgets.QLabel(self.widget)
    self.label_6.setObjectName("label_6")
    self.verticalLayout.addWidget(self.label_6)
    self.label = QtWidgets.QLabel(self.widget)
    self.label.setObjectName("label")
    self.verticalLayout.addWidget(self.label)
    self.label_2 = QtWidgets.QLabel(self.widget)
    self.label_2.setObjectName("label_2")
    self.verticalLayout.addWidget(self.label_2)
    self.label_3 = QtWidgets.QLabel(self.widget)
    self.label_3.setObjectName("label_3")
    self.verticalLayout.addWidget(self.label_3)

    在使用布局管理器后,涉及到的相关控件的geometry属性就不能随意更改,控件的属性由布局管理器接管

  • 网络布局对应的相关代码如下:

    python
    self.gridLayout = QtWidgets.QGridLayout(self.widget)
    self.gridLayout.setContentsMargins(0, 0, 0, 0)
    self.gridLayout.setObjectName("gridLayout")
    self.label_4 = QtWidgets.QLabel(self.widget)
    self.label_4.setObjectName("label_4")
    self.gridLayout.addWidget(self.label_4, 0, 0, 1, 1)
    self.label_5 = QtWidgets.QLabel(self.widget)
    self.label_5.setObjectName("label_5")
    self.gridLayout.addWidget(self.label_5, 0, 1, 1, 1)
    self.doubleSpinBox_returns_min = QtWidgets.QDoubleSpinBox(self.widget)
    self.doubleSpinBox_returns_min.setObjectName("doubleSpinBox_returns_min")
    self.gridLayout.addWidget(self.doubleSpinBox_returns_min, 1, 0, 1, 1)
    self.doubleSpinBox_returns_max = QtWidgets.QDoubleSpinBox(self.widget)
    self.doubleSpinBox_returns_max.setObjectName("doubleSpinBox_returns_max")
    self.gridLayout.addWidget(self.doubleSpinBox_returns_max, 1, 1, 1, 1)
    self.doubleSpinBox_maxdrawdown_min = QtWidgets.QDoubleSpinBox(self.widget)
    self.doubleSpinBox_maxdrawdown_min.setObjectName("doubleSpinBox_maxdrawdown_min")
    self.gridLayout.addWidget(self.doubleSpinBox_maxdrawdown_min, 2, 0, 1, 1)
    self.doubleSpinBox_maxdrawdown_max = QtWidgets.QDoubleSpinBox(self.widget)
    self.doubleSpinBox_maxdrawdown_max.setObjectName("doubleSpinBox_maxdrawdown_max")
    self.gridLayout.addWidget(self.doubleSpinBox_maxdrawdown_max, 2, 1, 1, 1)
    self.doubleSpinBox_sharp_min = QtWidgets.QDoubleSpinBox(self.widget)
    self.doubleSpinBox_sharp_min.setObjectName("doubleSpinBox_sharp_min")
    self.gridLayout.addWidget(self.doubleSpinBox_sharp_min, 3, 0, 1, 1)
    self.doubleSpinBox_sharp_max = QtWidgets.QDoubleSpinBox(self.widget)
    self.doubleSpinBox_sharp_max.setObjectName("doubleSpinBox_sharp_max")
    self.gridLayout.addWidget(self.doubleSpinBox_sharp_max, 3, 1, 1, 1)
    # self.gridLayout.addWidget(窗口控件,行位置,列位置,要合并的行数,要合并的列数)  后面两个是可选参数
  • 水平布局:加入分隔控件Horizontal SpacerVertical SpacerVertical Line同时将所有组件进行水平布局:更改Horizontal SpacerVertical SpacerVertical Line等控件的长宽属性需要更改sizeTypepreferred,再更改sizeHint的高度宽度尺寸

minimumSizemaximumSize属性是用来设置控件在布局管理器中的最小尺寸和最大尺寸

修改按钮的minimumSizemaximumSize属性,在界面文件中对应的相关代码如下:

python
self.pushButton.setMinimumSize(QtCore.QSize(100, 100))    # 不能使该控件的宽度和高度小于100
self.pushButton.setMaximumSize(QtCore.QSize(300, 300))    # 不能使该控件的宽度和高度大于300

sizePolicy属性

每个控件都有属于自己的两个尺寸:sizeHint:尺寸提示 和 minimumSize:最小尺寸

  • sizeHint:尺寸提示是窗口控件的期望尺寸
  • minimumSize:最小尺寸是窗口控件压缩时所能够被压缩的最小尺寸

sizePolicy作用:在窗口控件不能满足我们的需求时,可以设置窗口控件的sizePolicy属性来实现微调

sizePolicy属性是每个窗口控件的特有属性

其水平,垂直策略选项具体解释

名称解释
Fixed窗口控件具有其sizeHint所提供的尺寸且尺寸不在改变
Minimum窗口控件的 sizeHint 所提示的尺寸是它的最小尺寸,窗口控件不能压缩的比这个值小,但可以更大
Maximum窗口控件的 sizeHint 所提示的尺寸是它的最大尺寸,窗口控件不能变得的比这个值大,但可以压缩到minisizeHint给定尺寸大小
Preferred窗口控件的 sizeHint 所提示的尺寸是它的期望尺寸,可以压缩到minisizeHint给定尺寸的大小,也可以变得比sizeHint提供的尺寸更大
Expanding窗口控件可以压缩到minisizeHint给定尺寸大小,也可以变得比sizeHint提供的尺寸更大,但它希望变得更大
MinimumExpanding窗口控件的sizeHint所提示的尺寸是它的最小尺寸,窗口控件不能压缩的比这个值小,但它希望可以变得更大
Ignored无视窗口控件的sizeHintminisizeHint所提示的尺寸,按照默认来设置

设置“收益”,“最大回撤”,“sharp比”三个标签为1:3:1来放缩,对应的代码如下:

python
# 收益
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(1)
sizePolicy.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth())
self.label.setSizePolicy(sizePolicy)
# 最大回撤
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(3)
sizePolicy.setHeightForWidth(self.label_2.sizePolicy().hasHeightForWidth())
self.label_2.setSizePolicy(sizePolicy)
# sharp比
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(1)
sizePolicy.setHeightForWidth(self.label_3.sizePolicy().hasHeightForWidth())
self.label_3.setSizePolicy(sizePolicy)

布局的顺序

使用Qt designer开发一个完整的GUI程序步骤:

1.拖入窗口控件在窗口中,放在大致位置上,除了容器窗口,一般不用调整大小。需要的话加入控件分隔,如Horizontal Spacer

2.对于要通过代码引用的窗口控件,要确定名字;对于需要微调的控件,需要设置其相关属性

3.选择需要布局的窗口控件,使用布局管理器或切分窗口(splitter)进行布局


PyQt5的布局管理

布局管理就是指按照某种规则将子控件摆放在父控件中

绝对位置布局

绝对位置布局是通过在窗口程序中指定每一个控件的显示坐标和大小来实现的。窗口左上角的坐标为(0,0),x为横坐标,从左到右变化;y为纵坐标,从上到下变化。

python
lbl1 = QLabel('PyQt5',self)
lbl1.move(15,10)  # move()方法来定位控件,绝对布局

在传统的绝对布局中,如果父控件的大小发生改变,其子控件的布局不会跟着改变,这是一个弊端;其次,如果隐藏掉其中的一个子控件,还只是会保留原来的布局形式,后面的子控件不会顶替上来;第三如果子控件中的内容不断变多,其控件不会跟着内容的变化而发生变化

布局管理器布局

布局管理器包含了一些特定的布局规则:横着水平排放,竖着垂直排放,网格排放和表单排放等等

布局管理器是Qt中包含一个布局管理类的集合,它们被用来描述控件如何在应用程序的用户界面呈现

当可用空间发生变化时,这些布局将自动调整控件的位置和大小

布局管理器不是界面控件,而是界面的定位策略,所有的QWidget控件及其子类都可以使用布局管理器策略来管理其子类的布局

QLayout(布局类)

QLayout是一个最基层的抽象类,向下细分为QBoxLayout(盒子布局),QGridLayout(表格布局),QStackedLayout(栈布局)和QFormLayout(表单布局)

QLayout基类一般不是直接使用的,是一个抽象的类别,使用时必须进行子类化,实现多个方法才能使用,只有当所有的布局管理器都不能满足需求时,在考虑使用,比如使用一个圆形布局

功能作用
设置小控件之间的间距

setSpacing(int) 设置各控件的内边距(子控件元素与元素之间的关系),通过该方法可以增加额外的空间

设置外边距

setContentsMargins(int, int, int, int) 设置外边距,默认值为11个像素点,四个int分别表示左上右下的边距

添加/替换子控件

addWidget(子控件) 往布局管理器中添加子控件,使其子控件进行布局

replaceWidget(原控件, 新控件) 将布局管理器中的某个子控件替换成另一个子控件

被替换的子控件不再被布局管理器管理,我们需要将原控件进行隐藏或删除或者重新添加到新的布局管理器中,隐藏控件方法:hide(),隐藏控件,控件是没有从内存中消除的。删除/释放控件的方法:setParent(None),没有父对象引用就相当于释放了控件

添加子布局

所有的布局之间都是可以嵌套的,比如水平布局内可以嵌套一个垂直布局

addLayout(QLayout) 往布局管理器中添加子布局,添加时注意添加的位置

布局管理器的能用性

setEnabled(bool) 设置布局管理器的能用性,默认是True,若设置为False时,布局管理器就失效

布局管理器的使用步骤
  1. 创建布局对象

    创建的对象是不需要设置父控件的,具体形式为:layout = QLayout()

  2. 设置布局对象参数,包括外边距,内边距以及对其方式等等

    API描述
    setContentsMargins(int, int, int, int)设置外边距,默认值为11个像素点,四个int分别表示左上右下的边距
    setSpacing(int)设置各控件的内边距(子控件元素与元素之间的关系),通过该方法可以增加额外的空间
    setAlignment(Qt.AlignmentFlag)设置对齐方式
  3. 设置给需要布局子控件的父控件,同时可以进行布局方向的调整

    将子控件设置给父控件的方法:QWidget.setLayout(QLayout)

    调整布局方向的方法:QWidget.setLayoutDirection(值),默认是从左到右,从上到下的

    其中枚举值有如下的形式:

    枚举值描述
    Qt.RightToLeft从右到左
    Qt.LeftToRight从左到右
    Qt.LayoutDirectionAuto自动布局
  4. 将布局控件内部的子控件添加到布局管理器中,自动进行布局,具体形式为:layout.addWidget(子控件)

    最终其父子关系为:布局管理器对象和子控件都拥有相同的父对象,也就是主窗口,如果主窗口被干掉后,布局管理器对象和子控件都会通通被干掉

    python
    label1 = QLabel("标签1")
    label2 = QLabel("标签2")
    label3 = QLabel("标签3")
    v_layout = QVBoxLayout()
    v_layout.addWidget(label1)
    v_layout.addWidget(label2)
    v_layout.addWidget(label3)
    # 设置布局对象参数
    v_layout.setContentsMargins(20, 20, 20, 20)
    v_layout.setSpacing(60)
    # 注意:当我们创建布局内的子控件以及创建布局管理器对象时,不需要进行设置其父对象,self.setLayout(v_layout)代码会自动的在内部给我们设置好
    self.setLayout(v_layout)
    self.setLayoutDirection(Qt.RightToLeft)

QBoxLayout(盒子布局)

通过QBoxLayout类可以在水平和垂直方向上排列控件,QHBoxLayoutQVBoxLayout类继承自QBoxLayout类,QBoxLayout类继承自QLayout抽象类,可以使用QLayout类的所有方法

QBoxLayout类一般很少去使用,更多的使用其封装好的子类:QHBoxLayoutQVBoxLayout类,要么设置水平布局,要么设置垂直布局

功能作用
构造函数

QBoxLayout(QBoxLayout.Direction, parent: QWidget = None)

在创建QBoxLayout布局管理器时,需要传递一个方向参数,其参数的枚举值为:

枚举值描述
QBoxLayout.LeftToRight布局从左到右水平,对应参数为0
QBoxLayout.RightToLeft布局从右到左水平,对应参数为1
QBoxLayout.TopToBottom布局从上到下垂直,对应参数为2
QBoxLayout.BottomToTop布局从下到上垂直,对应参数为3
修改布局方向

setDirection(QBoxLayout.Direction) 修改布局管理器的布局方向

direction() 获取方向参数

python
# 设置布局方向每隔1秒改变一次
timer = QTimer(self)
def test():
    layout.setDirection((layout.direction() + 1) % 4)
timer.timeout.connect(test)
设置元素

子控件操作

API描述
addWidget(QWidget,stretch,Qt.Alignment)在布局中添加子控件:stretch(伸缩量),只适用于QBoxLayout,控件和窗口会随着伸缩量的变大而变大;alignment:指定对齐方式
insertWidget(int, QWidget)往布局管理器中插入子控件,int表示要插入位置的索引值,索引int的位置放的是插入的新控件
replaceWidget(原控件, 新控件)将布局管理器中的某个子控件替换成另一个子控件
removeWidget(QWidget)移除控件,移除的控件不在布局管理器中,但是还是在窗口父控件上,如果想要消失在父控件中,可以进行隐藏或删除:QWidget.hide()或者QWidget.setParent(None)
QWidget.hide()从布局管理器中移除控件,再次调用show()方法可以是其控件重新参与到布局中

其中参数Qt.Alignment的枚举值:

参数描述
Qt.AlignLeft水平方向居左对齐
Qt.AlignRight水平方向居右对齐
Qt.AlignCenter水平方向居中对齐
Qt.AlignJustify水平方向两端对齐
Qt.AlignTop垂直方向靠上对齐
Qt.AlignBottom垂直方向靠下对齐
Qt.AlignVCenter垂直方向居中对齐

添加子布局

API描述
addLayout(QLayout,stretch=0)在布局管理器中添加子布局,使用stretch(伸缩量)进行伸缩,伸缩量默认为0
insertLayout(QLayout,stretch=0)在布局管理器中插入子布局,int表示要插入位置的索引值,索引int的位置放的是插入的新布局

添加空白

QBoxLayout(盒子布局)可以看做一个大的盒子,里面有很多的小盒子,有的盒子中放的是一个控件,有的盒子中放的是一个子布局,空白可以理解成一个空白的盒子,只是占据一定的尺寸和位置,来方便我们调整其他盒子的尺寸和位置,添加空白只是改变数个盒子之间的间距和尺寸的方法,如果想要整体改变盒子的间距和空白,我们可以使用添加伸缩方法

API描述
addSpacing(int)加空白,int表示空白的间距(包括了元素与元素之间的间距)
insertSpacing(int, int)插入空白,第一个int表示插入索引值,第二个表示间距
python
layout.addWidget(label1)
layout.addSpacing(100)
layout.addWidget(label2)
layout.addWidget(label3)
layout.addWidget(label4)
layout.insertSpacing(4, 60) # 之前的空白不占据索引值(索引计算不包括空白区域)

当改变主窗口的尺寸,在布局管理器中的子控件和子布局的大小都会随之改变,但是空白一开始设置为多少就是多少,间距大小是不会跟着主窗口变化发生改变的

添加伸缩(弹簧)

一开始子控件的大小都有一个建议大小(更据子控件中的内容来确定,主窗口能缩小到的最小大小),一开始布局管理器的范围就是其父控件的范围,如果后续改变了父控件的窗口大小,就会将布局管理器中的子控件进行一个拉伸,对于其中拉伸的比例,就是伸缩比例,用于改变布局管理器中子控件的拉伸比例,伸缩因子在默认添加子控件的时候为0,伸缩因子控制布局管理器中子控件的初始比例和拉伸宽度比例,除了添加子控件时添加伸缩因子,还可以通过以下的方法给布局管理器中的子控件设置/修改伸缩因子:

API描述
QBoxLayout.setStretchFactor(QWidget, int) -> bool给某个子控件设置伸缩因子,返回值是一个bool类型,如果这个控件不在这个布局管理器中,就会返回一个False,如果这个控件在布局管理器中,就进行设置伸缩值,同时返回一个True
QBoxLayout.setStretchFactor(QLayout, int) -> bool给某个子布局设置伸缩因子,返回值是一个bool类型,如果这个子布局不在这个布局管理器中,就会返回一个False,如果这个子布局在布局管理器中,就进行设置伸缩值,同时返回一个True

同时可以通过以下的方法添加空白是的伸缩因子作为子控件的分隔:

API描述
QBoxLayout.addStretch(int)添加空白伸缩因子,空白部分的宽度是可以缩小到0
QBoxLayout.insertStretch(int)插入伸缩因子
python
# 标签1和标签2之间有相较于它们两倍宽度的空白部分
layout.addWidget(label1, 1)
layout.addStretch(2)
layout.addWidget(label2, 1)
layout.addWidget(label3, 1)
python
btn1 = QPushButton(self)
btn2 = QPushButton(self)
btn3 = QPushButton(self)      
btn1.setText('button 1')
btn2.setText('button 2')
btn3.setText('button 3')
# 控件间等均匀伸缩
hbox = QHBoxLayout()
hbox.addStretch(1)  # 设置伸缩量为1,在第一个按钮前
hbox.addWidget( btn1 )
hbox.addStretch(1)  # 设置伸缩量为1,在第二个按钮前
hbox.addWidget( btn2 )
hbox.addStretch(1)  # 设置伸缩量为1,在第三个按钮前
hbox.addWidget( btn3 )
hbox.addStretch(1 ) # 设置伸缩量为1,在第三个按钮后    
self.setLayout(hbox)
# 伸缩量是1:1:1:1,将按钮以外的空白地方等分为4份,所有控件间的距离会随着窗口的拉伸始终相同

如果addStretch方法不设置参数,取其默认值0,及layout.addStretch()addWidget参数同样是0,空白弹簧伸缩的优先级就会调高,主窗口中多余的空白部分会优先对他使用,布局管理器中的子控件仅仅使用建议大小的空间

水平和垂直布局

QHBoxLayout(水平布局)和QVBoxLayout(垂直布局)继承自QBoxLayout类,其他功能都完全相同,只是在构造函数中确定了方向为水平方向或垂直方向,在QBoxLayout类中创建布局管理器需要对其指明一个方向,而使用QHBoxLayout(水平布局)和QVBoxLayout(垂直布局)创建布局管理器就不需要进行指明方向

python
# 水平布局按照从左到右的顺序进行添加按钮部件。
hlayout = QHBoxLayout()     # 定义一个水平布局 
hlayout.addWidget( QPushButton(str(1)))   # addWidget()方法添加控件
hlayout.addWidget( QPushButton(str(2)))
hlayout.addWidget( QPushButton(str(3)))
hlayout.addWidget( QPushButton(str(4)))        
hlayout.addWidget( QPushButton(str(5)))        
self.setLayout(hlayout)   # 设置窗口显示的内容是该布局方式

默认的水平布局是从左到右的,默认的垂直布局是从上到下的,如果想对其进行修改,可采用以下方法:

setDirection(QBoxLayout.Direction) 修改布局管理器的布局方向

对于垂直布局进行从右到左的设置,就会使其变成一个水平方向的布局管理器

python
# 水平布局按照从左到右的顺序进行添加按钮部件。
hlayout = QHBoxLayout()  
# 水平居左 垂直居上		
hlayout.addWidget( QPushButton(str(1)) , 0 , Qt.AlignLeft | Qt.AlignTop)
hlayout.addWidget( QPushButton(str(2)) , 0 , Qt.AlignLeft | Qt.AlignTop)
hlayout.addWidget( QPushButton(str(3)))
# 水平居左 垂直居下
hlayout.addWidget( QPushButton(str(4)) , 0 , Qt.AlignLeft | Qt.AlignBottom )        
hlayout.addWidget( QPushButton(str(5)), 0 , Qt.AlignLeft | Qt.AlignBottom)   
self.setLayout(hlayout)

QFormLayout(表单布局)

表单是提示用户进行交互的一种模式,其主要两列组成,第一列用于显示信息,给用户提示,一般叫做label域;第二列需要用户进行选择或输入,一般叫做field域,label关联field

QFormLayout表单布局主要应用于管理输入控件及其关联标签的形式

QFormLayout类直接继承自QLayout

功能作用
构造函数

QFormLayout(parent: QWidget = None) 其中parent一般不用写

具体形式为:fromlayout = QFormLayout()

行的操作

添加行

在表单布局中添加一行控件,其中的一行控件既可以是一个,也可以是两个

API描述
addRow(QWidget, QWidget)在一行中左右添加两个控件
addRow(QWidget, QLayout)可以在右边添加子布局
addRow(str, QWidget)添加一个标签控件和另外一个控件,str为标签控件的文本
addRow(str, QLayout)添加一个标签控件和另外一个子布局,str为标签控件的文本
addRow(QWidget)在一行中添加一个控件,用于登陆/提交按钮,不需要标签提示
addRow(QLayout)在一行中添加一个子布局

可以通过addRow(str, QWidget)方法设置快捷键关联:layout.addRow("姓名(&n)", name_le)

按下ctrl+n就可以快速的定位到name_le文本框中

python
fromlayout = QFormLayout()
labl1 = QLabel("标签1")
lineEdit1 = QLineEdit()
labl2 = QLabel("标签2")
lineEdit2 = QLineEdit()
labl3 = QLabel("标签3")
lineEdit3 = QLineEdit()

fromlayout.addRow(labl1, lineEdit1)  # 将label关联field
fromlayout.addRow(labl2, lineEdit2)
fromlayout.addRow(labl3, lineEdit3)

self.setLayout(fromlayout)

插入行

插入行与添加行操作方法基本相同,只是多了相关的位置索引值

API描述
insertRow(int, QWidget, QWidget)在一行中左右插入两个控件,int表示插入位置的索引
insertRow(int, QWidget, QLayout)可以在右边插入子布局,int表示插入位置的索引
insertRow(int, str, QWidget)插入一个标签控件和另外一个控件,str为标签控件的文本
insertRow(int, str, QLayout)插入一个标签控件和另外一个子布局,str为标签控件的文本
insertRow(int, QWidget)在一行中插入一个控件,用于登陆/提交按钮,不需要标签提示
insertRow(int, QLayout)在一行中插入一个子布局

如果int的值越界了,就会将该行加入到整个表格布局的最后一行

获取行的信息

API描述
rowCount() -> int获取行的总个数
getWidgetPosition(QWidget) -> Tuple[int, QFormLayout.ItemRolel]获取指定控件所在的行编号以及对应的角色,返回的结果是一个元组
getLayoutPosition(QLayout) -> Tuple[int, QFormLayout.ItemRolel]获取指定布局所在的行编号以及对应的角色

当元组Tuple中的int值为-1时表示没有找到该子控件或者子布局

其中参数QFormLayout.ItemRolel有如下的形式:

枚举值描述
QFormLayout.LabelRole标签,位于一行的左边,用0表示
QFormLayout.FieldRole输入框,位于一行的右边,用1表示
QFormLayout.SpanningRole跨越标签和输入框的控件,表示一整行,用2表示

设置行

根据行号和角色号,设置相关的控件和布局,分别控制一行中的两个角色,一般是在单元格没有被占用的情况下设置的

API描述
setWidget(int, QFormLayout.ItemRolel, QWidget)修改某个位置的子控件,int表示行,QFormLayout.ItemRole表示列
setLayout(int, QFormLayout.ItemRolel, QLayout)修改某个位置的子布局,int表示行,QFormLayout.ItemRole表示列

如果原先的位置信息是QFormLayout.SpanningRole,占据了一整行的,设置值只能加在左侧,右侧是加不了的,如果单元格中明确有控件被占用了,我们是不能设置的,强行设置会导致布局全部乱掉

移除行

移除行通常有两种形式,一种是删除对应的子控件,另一种是不删除对应的子控件

API描述
removeRow(int)删除(控件被删除释放了)某一行,int表示索引的行号
removeRow(QWidget)删除单个子控件所对应的一整行
removeRow(QLayout)删除单个子布局所对应的一整行
removeWidget(QWidget)删除单个子控件,该方法是继承自QLayout类的方法
takeRow(int)移除(控件没有被删除释放)某一行,int表示索引的行号
takeRow(QWidget)移除单个子控件所对应的一整行
takeRow(QLayout)移除单个子布局所对应的一整行

对于移除行,我们需要将其移除的控件进行隐藏hide()或者释放,否则会导致主窗口混乱

标签操作

标签操作是指根据某个控件或者某个布局获取对应的标签控件

API描述
labelForField(QWidget) -> QWidget通过子控件获取对应的标签对象
labelForField(QLayout) -> QWidget通过子布局获取对应的标签对象
python
# 给标签对象重新设置一个新的标签
layout.labelForField(name_le).setText("名字")
行的包装策略

行的包装策略主要控制主窗口宽度不够大时,每一行中左侧的标签和右侧的控件该怎么摆放的问题,默认情况下是一个左右摆放,并不会参数换行,其中相关策略如下:

API描述
setRowWrapPolicy(QFormLayout.RowWrapPolicy)设置相关的行包装策略

其中参数QFormLayout.RowWrapPolicy有如下的枚举值:

枚举值描述
QFormLayout.DontWrapRows字段总是放在标签旁边,默认情况
QFormLayout.WrapLongRows标签被赋予足够的水平空间以适合最宽的标签(标签总是被展示完整的),其余的空间被赋予字段,如果字段的最小大小比可用空间宽,则该字段将换行到下一行,最终主窗口宽度只能缩小到最长的标签的显示宽度
QFormLayout.WrapAllRows字段总是位于其标签下方
对齐方式

默认情况下,表单整体是垂直方向的顶部对齐水平方向左对齐,标签的列宽是参照最长标签的一个宽度,标签内容默认是左对齐的

API描述
setFormAlignment(Qt.Alignment)设置整个表单的对齐方式
setLabelAlignment(Qt.Alignment)设置标签的对齐方式
间距控制

间距控制可以控制水平方向(标签和字段之间的间距)的间距和垂直方向(每一行之间的间距)的间距

API描述
setVerticalSpacing(int)设置垂直间距
setHorizontalSpacing(int)设置水平间距
字段增长策略

当主窗口的宽度变宽时,右侧的字段控件会跟着变宽,对于字段增长有以下的策略

API描述
setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy)设置字段增长策略

其中参数QFormLayout.FieldGrowthPolicy有如下的枚举值:

枚举值描述
QFormLayout.FieldsStayAtSizeHint字段始终保持建议大小,不会随着主窗口的拉长发生变化
QFormLayout.ExpandingFieldsGrow水平大小策略为ExpandingMinimumExpanding的字段将增长以填充可用空间
QFormLayout.AllNonFixedFieldsGrow具有允许它们增长的大小策略的所有字段增长以填充可用空间

QGridLayout(网格布局)

QGridLayout将窗口分隔成行和列的网格来进行排列。使用函数addWidget()将被管理的控件(QWidget)添加到窗口中,或者使用addLayout()函数将布局(QLayout)添加到窗口中,也可以通过addWidget()函数对所添加的控件设置行数和列数的跨越。

QGridLayout类继承自QLayout

功能作用
构造函数

具体形式为:gridlayout = QGridLayout()

元素操作

添加控件

API描述
addWidget(QWidget)往网格布局中添加单独的控件,每一行每一行的添加
addWidget(QWidget, int row, int col, alignment)在某行某列中添加控件,前一个int表示行row,后一个int表示列colalignment表对齐方式,如果输入的行列前面产生了空位,则空位不会存在,后面的控件会往前挤
addWidget(QWidget, int fromRow, int formCol, int rowSpan, int colSpan, alignment)合并单元格后添加控件,fromRow表示开始的行,formCol表示开始的列,rowSpan表示行跨越几行,colSpan表示列跨越几列

添加布局

API描述
addLayout(QLayout, int row, int col, alignment)在某行某列中添加子布局,前一个int表示行row,后一个int表示列colalignment表对齐方式,如果输入的行列前面产生了空位,则空位不会存在,后面的子布局会往前挤
addLayout(QLayout, int fromRow, int formCol, int rowSpan, int colSpan, alignment)合并单元格后添加子布局,fromRow表示开始的行,formCol表示开始的列,rowSpan表示行跨越几行,colSpan表示列跨越几列

获取位置和条目

API描述
getItemPosition(int) -> Tuple[int, int, int, int]获取位置,返回值中4个int分别表示:fromRow表示开始的行,formCol表示开始的列,rowSpan表示行跨越几行,colSpan表示列跨越几列
itemAtPosition(int row, int col)获取条目
python
def initUI(self):            
    grid = QGridLayout()  # 创建一组实例
    self.setLayout(grid)  # 设置窗口布局
    # 创建标签列表
    names = ['Cls', 'Back', '', 'Close',    
             '7', '8', '9', '/',  
            '4', '5', '6', '*',  
             '1', '2', '3', '-',  
            '0', '.', '=', '+']    
    # 在网络中创建一个位置列表
    positions = [(i,j) for i in range(5) for j in range(4)]  
    for position, name in zip(positions, names):                
        if name == '':  
            continue  

        button = QPushButton(name)     # 创建按钮
        grid.addWidget(button, *position)   # 添加到布局中
python
# 跨越行和列的网络单元格
def initUI(self):    
    # 创建文本
    titleLabel = QLabel('标题')  
    authorLabel = QLabel('提交人')  
    contentLabel = QLabel('申告内容')  
    # 创建文本框
    titleEdit = QLineEdit()  
    authorEdit = QLineEdit()  
    contentEdit = QTextEdit()  

    grid = QGridLayout()  # 网格布局
    grid.setSpacing(10)   # 设置控件在水平和垂直方向的间隔

    grid.addWidget(titleLabel, 1, 0)   # 将titleLabel控件放在布局器的第一行,第零列
    grid.addWidget(titleEdit, 1, 1)  

    grid.addWidget(authorLabel, 2, 0)  
    grid.addWidget(authorEdit, 2, 1)  

    grid.addWidget(contentLabel, 3, 0)  
    grid.addWidget(contentEdit, 3, 1, 5, 1)  # 跨越行列的控件设置,跨越5行和1列

    self.setLayout(grid)
列宽行高和拉伸系数的控制

网格布局中的列宽和行高是用来限制最小的列宽和行高,如果没有进行列宽和行高的限制,当主窗口的大小发生变化时,网格中的每一列和每一行都大小要跟着变化,当主窗口挤到最小时,网格都有一个默认最小值,我们可以对其值进行一个修改,对于最小列宽和最小行高的限制,可以通过以下的方法:

API描述
setColumnMinimumWidth(int column, int minSize)设置最小列宽,第一个int表示确定那一列,后一个int表示设置尺寸为多宽
setRowMinimumHeight(int row, int minSize)设置最小行高,第一个int表示确定那一行,后一个int表示设置尺寸为多高

拉伸系数表示主窗口拉伸时控制怎样的比例来分配额外的多余空间

API描述
setColumnStretch(int column, int stretch)设置列的拉伸系数,第一个int表示确定那一列,后一个int表示拉伸比例
setRowStretch(int row, int stretch)设置行的拉伸系数,第一个int表示确定那一行,后一个int表示拉伸比例

默认的网格控件的拉伸系数是0,如果设置某行或者某列的拉伸系数为1,那么该行或该列就会占据大量的空白空间(占据百分百,其他列或行的控件只占据本身的最小尺寸)

间距控制

我们可以对垂直方向和水平方向设置间距

API描述
setVerticalSpacing(int spacing)设置垂直方向的间距,也就是行与行之间的间距
setHorizontalSpacing(int spacing)设置水平方向的间距,也就是列与列之间的间距
setSpacing(int)同时设置垂直方向和水平方向的间距
spacing()查看垂直方向和水平方向的间距,如果水平方向和垂直方向的间距不一致,其返回的值就变成了无效值-1
verticalSpacing()获取垂直方向的间距
horizontalSpacing()获取水平方向的间距
信息获取
API描述
cellRect(int row, int column) -> QRect获取某个单元格占据的区域大小
columnCount() -> int获取列的个数
rowCount() -> int获取行的个数

QStackedLayout(堆叠布局)

QStackedLayout控件是将许多个控件堆叠在一起,在某个时候只能看到一个布局的子控件,内部提供了相关的方法去切换显示的控件

使用步骤:

  1. 创建一个布局管理器对象:stackedlayout = QStackedLayout()
  2. 把布局对象设置给需要布局的父控件:self.setLayout(stackedlayout)
  3. 通过布局对象来管理布局一些子控件

区别其他布局管理器的布局方法,第二步一定要在第三步之前,否则会出现问题(布局不稳定)

第一步操作和第二步操作可以合并起来使用:stackedlayout = QStackedLayout(self)

添加/移除子控件
API描述
addWidget(QWidget)添加子控件
insertWidget(int, QWidget)插入子控件
removeWidget(QWidget)移除子控件

如果当前插入控件的索引位置小于等于当前正展示的索引位置,那么会把当时正展示的控件往后移动,把当前显示的索引值往后加一,但是仍然显示之前的控件,不显示插入的控件,如果插入的索引值是越界的,系统内部会进行处理,将其放置在所有控件的最后面,分配相应正确的索引值

如果某个控件被移除了,后面的控件会自动顶上来,代替这个索引值

获取子控件

可以根据相应的索引值来获取相应的子控件

API描述
widget(int) -> QWidget根据相应的索引值来获取相应的子控件

如果想要获取控件的文本信息,可以通过:widget(2).text()

切换子控件
API描述
setCurrentIndex(int)通过索引值来切换子控件
currentIndex() -> int获取当前展示控件的索引值
setCurrentWidget(QWidget)通过指明某个子控件来切换到该子控件
currentWidget() -> QWidget获取当前展示的控件
python
# 设置每隔1秒切换一次子控件
timer = QTimer(self)
timer.timeout.connect(lambda :stackedlayout.setCurrentIndex(stackedlayout.currentIndex() + 1) % stackedlayout.count())
timer.start(1000)
展示模式

展示模式主要是指可以控制是指展示当前的子控件,其他子控件都隐藏掉,还是所有小控件都可见,只是当前控件显示在最前面

API描述
setStackingMode(QStackedLayout.StackingMode)设置展示模式
setFixedSize(int, int)设置展示控件的大小

其中参数QStackedLayout.StackingMode有如下的枚举值:

枚举值描述
QStackedLayout.StackOne只有当前小控件可见,默认情况,隐藏掉当前展示控件,其他布局中的子控件也不会展示
QStackedLayout.StackAll所有小控件都可见,当前子控件显示在最前面,隐藏掉当前展示控件,布局中的下一个索引的子控件会被展示
相关信号
API描述
currentChanged(int index)当前显示的子控件发生变化时发射信号,可以传递的参数是其控件索引值
widgetRemoved(int index)控件被移除时发射的信号,可以传递的参数是其控件索引值

嵌套布局

对于较为复杂的布局,一般要进行布局的嵌套,在布局中添加其他布局。

python
wlayout =  QHBoxLayout()    # 全局布局(1个):是一个水平布局
# 局部布局(4个):水平、竖直、网格、表单
hlayout =  QHBoxLayout()
vlayout =  QVBoxLayout()
glayout = QGridLayout()
formlayout =  QFormLayout()
# 局部布局添加部件
hlayout.addWidget( QPushButton(str(1)) ) 
hlayout.addWidget( QPushButton(str(2)) )
vlayout.addWidget( QPushButton(str(3)) )
vlayout.addWidget( QPushButton(str(4)) )
glayout.addWidget( QPushButton(str(5)) , 0, 0 )
glayout.addWidget( QPushButton(str(6)) , 0, 1 )
glayout.addWidget( QPushButton(str(7)) , 1, 0)
glayout.addWidget( QPushButton(str(8)) , 1, 1)
formlayout.addWidget( QPushButton(str(9))  )
formlayout.addWidget( QPushButton(str(10)) )
formlayout.addWidget( QPushButton(str(11)) )
formlayout.addWidget( QPushButton(str(12)) )
# 准备四个部件
hwg =  QWidget() 
vwg =  QWidget()
gwg =  QWidget()
fwg =  QWidget()
# 四个部件设置局部布局
hwg.setLayout(hlayout) 
vwg.setLayout(vlayout)
gwg.setLayout(glayout)
fwg.setLayout(formlayout)
# 四个部件加至全局布局
wlayout.addWidget(hwg)
wlayout.addWidget(vwg)
wlayout.addWidget(gwg)
wlayout.addWidget(fwg)
# 窗体本体设置全局布局
self.setLayout(wlayout)

在布局中添加其他布局这种方法有一个缺点是需要布局多个空白控件,上述是4个,对于太多的局部布局是不合理的,所以我们可以采用在控件中添加布局的方法进行嵌套布局,这种方法不管有多少种局部布局,只需要一个空白控件,然后在空白控件中进行多种布局。

python
wwg = QWidget(self)  # 全局部件(注意参数 self),用于"承载"全局布局
wl = QHBoxLayout(wwg)  # 全局布局(注意参数 wwg)
# 局部布局
hlayout =  QHBoxLayout()
vlayout =  QVBoxLayout()
glayout = QGridLayout()
formlayout =  QFormLayout()

# 局部布局添加部件(例如:按钮)
hlayout.addWidget( QPushButton(str(1)) )
hlayout.addWidget( QPushButton(str(2)) )
vlayout.addWidget( QPushButton(str(3)) )
vlayout.addWidget( QPushButton(str(4)) )
glayout.addWidget( QPushButton(str(5)) , 0, 0 )
glayout.addWidget( QPushButton(str(6)) , 0, 1 )
glayout.addWidget( QPushButton(str(7)) , 1, 0)
glayout.addWidget( QPushButton(str(8)) , 1, 1)
formlayout.addWidget( QPushButton(str(9))  )
formlayout.addWidget( QPushButton(str(10)) )
formlayout.addWidget( QPushButton(str(11)) )
formlayout.addWidget( QPushButton(str(12)) )

# 这里向局部布局内添加部件,将他加到全局布局
wl.addLayout(hlayout)  
wl.addLayout(vlayout)
wl.addLayout(glayout)
wl.addLayout(formlayout)

QSplitter(特殊布局管理器)

除了Layout布局管理,PyQt还有一个特殊的布局管理器QSplitter,它可以动态地拖动子控件之间的边界,是一个动态布局管理器。

QSplitter允许用户通过拖动子控件的边界来控制子控件的大小,并提供一个处理拖拽子控件的控制器。

QSplitter对象中各子控件默认是横向布局的,可以使用Qt.Vertical进行垂直布局。

QSplitter类中常用的方法:

方法描述
addWidget()将小控件添加到QSplitter管理器的布局中
indexOf()返回小控件在QSplitter管理器中的索引
insertWidget()根据指定的索引将一个控件插入到QSplitter管理器中
setOrientation()设置布局方向:Qt.Horizontal:水平方向;Qt.Vertical:垂直方向
setSizes()设置控件的初始大小
count()返回小控件在QSplitter管理器中的数量
python
def initUI(self): 
    hbox = QHBoxLayout(self)
    self.setWindowTitle('QSplitter 例子')
    self.setGeometry(300, 300, 300, 200)
    # 显示了使用两个QSplitter组织的两个QFrame控件
    topleft = QFrame()
    topleft.setFrameShape(QFrame.StyledPanel)
    bottom = QFrame()
    bottom.setFrameShape(QFrame.StyledPanel)
    # 第一个QSplitter对象包含了一个QFrame对象topleft和QTextEdit对象textedit,并按照水平方向进行布局
    splitter1 = QSplitter(Qt.Horizontal)
    textedit = QTextEdit()   
    splitter1.addWidget(topleft)  # 将小控件添加到QSplitter管理器中进行布局
    splitter1.addWidget(textedit)
    splitter1.setSizes([100,200])  # 设置控件初始大小
    # 第二个QSplitter对象添加了第一个QSplitter对象(上述水平布局的整体)和另一个QFrame对象,并按照垂直方向进行布局
    splitter2 = QSplitter(Qt.Vertical)
    splitter2.addWidget(splitter1)
    splitter2.addWidget(bottom)
    hbox.addWidget(splitter2)
    self.setLayout(hbox)

布局管理器的尺寸策略

当将控件放在一个布局管理器中之后,该控件怎么确定一个默认的尺寸大小以及当主控件去缩小时,该子控件是怎么确定最小值能缩小到怎么样的尺寸,如果想要限定某个子控件是一个固定的尺寸大小,或者只是设置一个方向可以伸缩,这些都涉及到了尺寸策略这个概念

API描述
QWidget.sizeHint()设置合适的建议尺寸大小,建议的尺寸大小一般是根据内容来决定的
QWidget.minimumSizeHint()设置最小的的建议尺寸大小,当主窗口缩小到很小时,子控件最小只能缩小到最小的建议大小
python
class Label(QLabel):
    def sizeHint(self):
        return QSize(100, 60)
    
class Label(QLabel):
    def minimumSizeHint(self):
        return QSize(200, 200)

控件的尺寸策略可以告诉布局系统应该如何对它进行拉伸或收缩,Qt为它所有的内置控件都提供了合理的默认的大小策略值(如按钮控件默认只有水平方向可以拉伸,垂直方式是不能拉伸的;对于标签控件在水平和垂直方向上都可以拉伸),我们可以重新设置该默认值,其中尺寸策略的取值如下:

API描述
QWidget.setSizeIncrement(QSizePolicy.Policy, QSizePolicy.Policy)设置尺寸策略,前一个QSizePolicy.Policy表示水平方向的尺寸策略,后一个QSizePolicy.Policy表示垂直方向的尺寸策略
取值描述
QSizePolicy.Fixed控件的实际尺寸只参考sizeHint()的返回值,不能伸展和收缩
QSizePolicy.Minimum可以伸展和收缩,不过sizeHint()的返回值规定了控件能缩小到的最小尺寸
QSizePolicy.Maximum可以伸展和收缩,不过sizeHint()的返回值规定了控件能伸展到的最大尺寸
QSizePolicy.Preferred可以伸展和收缩,但没有优势去获取更大的额外空间使自己的尺寸比sizeHint()的返回值更大
QSizePolicy.Expanding可以伸展和收缩,它会尽可能多的去获取额外的空间,也就是比Preferred更具有优势(优先级更高),更有优势拿到额外的空间
QSizePolicy.MinimumExpanding可以伸展和收缩,不过sizeHint()的返回值规定了控件能缩小到的最小尺寸,它比Preferred更具优势去获取额外空间
QSizePolicy.Ignored忽略sizeHint()的作用,控件可以收缩到大小为0

如果在水平方向和垂直方向都设置了QSizePolicy.Fixed策略后,我们还想修改该子控件的大小,我们可以直接进行固定尺寸大小的设置:QWidget.setFixedSize(int, int),该方法的优先级是最高的

Released under the MIT License.