PyQt5 系统化学习: 布局管理

5 布局管理

参考:Layout management in PyQt5

在一个 GUI 程序里,布局是一个很重要的方面。布局就是如何管理应用中的元素和窗口的位置和嵌套关系。一般有两种方式:绝对布局 (absolute positioning)和 PyQt5 的 Layout 类。

5.1 绝对布局

绝对布局主要是在窗口程序中指定每一个控件的显示坐标和大小来实现布局。最开始的坐标在左上角 的位置,以 为原点定位窗口某一点的具体位置。使用 move(x,y)方法定位了每一个元素。比如:

import sys
from PyQt5.QtWidgets import QWidget, QLabel, QApplication

class Place(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        lbl1 = QLabel('A', self)
        lbl1.move(15, 10)

        lbl2 = QLabel('B', self)
        lbl2.move(35, 40)

        lbl3 = QLabel('C', self)
        lbl3.move(55, 70)

        self.setGeometry(300, 300, 250, 150)
        self.setWindowTitle('绝对布局')
        self.show()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Place()
    sys.exit(app.exec_())

显示效果:

图1 绝对布局

绝对位置布局的优点是可以直接定位每个控件的位置。但是它有很大的局限性:

● 窗口中控件的大小和位置不会随着我们更改窗口的位置和大小而变化。
● 不能适用于不同的平台和不同分辨率的显示器。
● 改变字体时可能会破坏布局。
● 如果我们决定重构这个应用,需要全部计算一下每个元素的位置和大小,既烦琐又费时。

5.2 Layout 类布局

由于绝对布局有很大的局限性,PyQt5 提供了 Layout 类进行布局。该布局方式分为盒布局、网格布局、表单布局。

5.2.1 盒布局

采用 QBoxLayout 类可以在水平和垂直方向上排列控件,分别为:QHBoxLayout 和 QVBoxLayout。

  1. QHBoxLayout(水平布局) :按照从左到右的顺序来添加控件。比如:
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QHBoxLayout, QLabel
from PyQt5.QtCore import Qt


class Winform(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("水平布局管理例子")
        self.setGeometry(300, 300, 300, 100)
        # 水平布局按照从左到右的顺序进行添加按钮部件。
        hlayout = QHBoxLayout()
        for name in 'ABCD':
            hlayout.addWidget(QLabel(name, self))
        for name in 'EFG':
            hlayout.addWidget(QLabel(name, self), stretch=1)

        # 设置控件间的间距
        hlayout.setSpacing(0)
        self.setLayout(hlayout)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    form = Winform()
    form.show()
    sys.exit(app.exec_())

效果:

图2 水平布局
  1. QVBoxLayout(垂直布局):按照从上到下的顺序添加控件。只需要替换上面的代码的 QHBoxLayoutQVBoxLayout 即可得到如下效果:
图3 垂直布局

垂直布局的间隔太小,可以设定其间距:

# 设置控件间的间距
 hlayout.setSpacing(10)

效果图:

上述代码使用到了一些盒布局的方法,具体见下表:

方法 描述
addLayout(QLayout, stretch: int = 0) 在窗口的右边添加布局,使用 stretch (伸缩量)进行 伸缩
addWidget(QWidget, stretch: int = 0, alignment: Union[Qt.Alignment, Qt.AlignmentFlag] = 0) 在布局中添加控件:stretch 指定伸缩量,alignment 指定对齐方式
addSpacing(int) 设定各控件的上下间距
addStretch(stretch: int = 0) 在布局管理器中添加一个可伸缩的控件 QSpacerItem,0 为最小值,并且将 stretch 作为伸缩量添加到布局末尾;stretch 表示均分的比例。

下面提供一个修改对齐方式的例子:

import sys
from PyQt5.QtWidgets import QApplication, QWidget, QHBoxLayout, QLabel
from PyQt5.QtCore import Qt

class Winform(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("水平布局管理例子")
        self.setGeometry(300, 300, 300, 100)
        # 水平布局按照从左到右的顺序进行添加按钮部件。
        hlayout = QHBoxLayout()
        hlayout.addWidget(QLabel('A', self), 0, Qt.AlignLeft | Qt.AlignTop)
        hlayout.addWidget(QLabel('B', self), 0, Qt.AlignLeft | Qt.AlignTop)
        hlayout.addWidget(QLabel('C', self), 0, Qt.AlignLeft | Qt.AlignBottom)
        hlayout.addWidget(QLabel('D', self), 0, Qt.AlignLeft | Qt.AlignBottom)
        self.setLayout(hlayout)
    
if __name__ == "__main__":
    app = QApplication(sys.argv) 
    form = Winform()
    form.show()
    sys.exit(app.exec_())

效果:

图4 修改盒布局的对齐方式

对齐的具体参数:

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

5.2.2 网格布局

QGridLayout(网格布局)是将窗口分隔成行和列的网格来进行排列。通常可以使用函数 addWidget() 将被管理的控件(Widget)添加到窗口中,或者使用 addLayout() 函数将布局(Layout)添加到窗口中。也可以通过 addWidget() 函数对所添加的控件设置行数和列数的跨越,最后实现网格占据多个窗格。

import sys
from PyQt5.QtWidgets import QWidget, QGridLayout, QLabel, QApplication


class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        grid = QGridLayout()
        self.setLayout(grid)
        names = ['清除', '回退', '', '关闭',
                 '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
            lb = QLabel(name, self)
            grid.addWidget(lb, *position)
        self.move(300, 150)
        self.setWindowTitle('Calculator')
        self.show()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

效果:

图5 网格布局

方法详解:

5.2.3 QFormLayout(表单布局)

QFormLayout 是 label-field 式的表单布局,顾名思义,就是实现表单方式的布局。表单是提示用户进行交互的一种模式,其主要由两列组成,第一列用于显示信息,给用户提示,一般叫作 label 域;第二列需要用户进行选择或输入,一般叫作 field 域。label 与 field 的关系就是 label 关联 field。

import sys
from PyQt5.QtWidgets import QApplication, QWidget, QFormLayout, QLineEdit, QLabel


class Winform(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("窗体布局管理例子")
        self.resize(300, 100)

        fromlayout = QFormLayout()

        forms = [
            ['姓名', '珊珊'],
            ['性别', '女'],
            ['年龄', '27']
        ]

        for name, value in forms:
            fromlayout.addRow(QLabel(name), QLabel(value))

        self.setLayout(fromlayout)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    form = Winform()
    form.show()
    sys.exit(app.exec_())

效果:

图6 表单布局

5.2.4 嵌套布局

上面介绍的都是单一的布局,可能有这样的需求:嵌套布局。具有操作方法如下:

 # 全局部件(注意参数 self),用于"承载"全局布局
wwg = QWidget(self)
 # 全局布局(注意参数 wwg)
wl = QHBoxLayout(wwg)
# 这里向局部布局内添加部件,将他加到全局布局
wl.addLayout(hlayout)  
wl.addLayout(vlayout)
wl.addLayout(glayout)
wl.addLayout(formlayout)

即向“布局”管理器,嵌入布局。

5.3 动态的布局管理器

除了上面介绍的 Layout 布局管理,PyQt5 还提供了一个特殊的布局管理器,即动态的布局管理器 QSplitter,它可以动态地拖动子控件之间的边界。QSplitter 允许用户通过拖动子控件的边界来控制子控件的大小,并提供了一个处理拖曳子控件的控制器。

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

图7 动态的布局管理器
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QFrame, QSplitter, QTextEdit, QHBoxLayout
from PyQt5.QtCore import Qt


class SplitterExample(QWidget):
    def __init__(self, *args, **kw):
        super().__init__(*args, **kw)
        self.initUI()

    def initUI(self):
        hbox = QHBoxLayout(self)

        topleft = QFrame()
        topleft.setFrameShape(QFrame.StyledPanel)

        bottom = QFrame()
        bottom.setFrameShape(QFrame.StyledPanel)

        splitter1 = QSplitter(Qt.Horizontal)
        textedit = QTextEdit()
        splitter1.addWidget(topleft)
        splitter1.addWidget(textedit)
        splitter1.setSizes([100, 200])
        splitter2 = QSplitter(Qt.Vertical)

        splitter2.addWidget(splitter1)
        splitter2.addWidget(bottom)
        hbox.addWidget(splitter2)
        self.setLayout(hbox)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = SplitterExample()
    demo.show()
    sys.exit(app.exec_())

效果:

图8 可变布局

还有一个函数 setOpaqueResize(bool opaque = true),用于设置分割窗的分割条在拖动时是否为实时更新显示(默认为 true /实时更新)。

你可能感兴趣的:(PyQt5 系统化学习: 布局管理)