目录
一、QGridLayout
QGridLayout将可用空间划分成行和列,然后把widget放入正确的单元格中。
每列都有一个最小宽度和一个拉伸因子。列的最小宽度用setColumnMinimumWidth()设置,列的最小宽度也是该列中每个小部件的最小宽度。拉伸因子是使用setColumnStretch()设置的,它确定列在最小宽度之上将获得多少可用空间。
QGridLayout添加widget的函数是:
addWidget(self, a0: QWidget,
row: int,
column: int,
rowSpan: int,
columnSpan: int,
alignment: Union[Qt.Alignment, Qt.AlignmentFlag] = Qt.Alignment())
row: 行标,从0开始
column:列标,从0开始
rowSpan:占几行
columnSpan:占几列
下图是官网文档的一个示例图片,显示了一个具有五列三行网格的对话框片段(网格显示为红色边框):
此例中的第0、2和4列由QLabel、QLineEdit和QListBox组成。第1列和第3列是由setColumnMinimumWidth()构成的占位符。第0行由三个QLabel对象、第1行由三个QLineEdit对象和第2行由三个QListBox对象组成。我们使用占位符列(1和3)来获得列之间适当的空间量。
请注意,列和行的宽度和高度并不相等。如果希望两列具有相同的宽度,则必须将它们的最小宽度和拉伸因子设置为相同。您可以使用setColumnMinimumWidth()和setColumnStretch()来完成此操作。
下面我们会首先用QGridLayout重写上一章的计算器界面,然后再用代码实现这个官网的图片。
计算器界面代码:
class GridWindow(QMainWindow):
def __init__(self):
super().__init__()
# 窗口标题
self.setWindowTitle('计算器')
# 窗口大小
self.resize(300, 400)
self.setCentralWidget(QWidget())
# 把按钮的文字都设置成20px
self.setStyleSheet("""QPushButton{font-size:20px;}""")
# 主界面layout是一个QVBoxLayout
self.main_layout = QVBoxLayout()
# 用一个QLabel展示计算结果
self.l_result = QLabel('0')
self.l_result.setStyleSheet("""QLabel{background-color: red; font-size:40px;}""")
# 设置QLabel的文件靠右垂直居中
self.l_result.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
# 把l_result添加到主界面layout,拉伸因子是1
self.main_layout.addWidget(self.l_result, stretch=1)
# 按钮区域layout
self.operation_layout = QGridLayout()
self.operation_layout.setSpacing(0)
# 设置layout内控件整体的外边距(可以理解成layout的内边距)
self.operation_layout.setContentsMargins(0, 0, 0, 0)
self.btn_init = QPushButton("AC")
self.btn_back = QPushButton("X")
self.btn_remainder = QPushButton("%")
self.btn_div = QPushButton("÷")
self.operation_layout.addWidget(self.btn_init, 0, 0, 1, 1, alignment=Qt.AlignLeft)
self.operation_layout.addWidget(self.btn_back, 0, 1, 1, 1)
self.operation_layout.addWidget(self.btn_remainder, 0, 2, 1, 1)
self.operation_layout.addWidget(self.btn_div, 0, 3, 1, 1)
self.btn_7 = QPushButton("7")
self.btn_8 = QPushButton("8")
self.btn_9 = QPushButton("9")
self.btn_mul = QPushButton("x")
self.operation_layout.addWidget(self.btn_7, 1, 0, 1, 1)
self.operation_layout.addWidget(self.btn_8, 1, 1, 1, 1)
self.operation_layout.addWidget(self.btn_9, 1, 2, 1, 1)
self.operation_layout.addWidget(self.btn_mul, 1, 3, 1, 1)
self.btn_4 = QPushButton("4")
self.btn_5 = QPushButton("5")
self.btn_6 = QPushButton("6")
self.btn_sub = QPushButton("-")
self.operation_layout.addWidget(self.btn_4, 2, 0, 1, 1)
self.operation_layout.addWidget(self.btn_5, 2, 1, 1, 1)
self.operation_layout.addWidget(self.btn_6, 2, 2, 1, 1)
self.operation_layout.addWidget(self.btn_sub, 2, 3, 1, 1)
self.btn_1 = QPushButton("1")
self.btn_2 = QPushButton("2")
self.btn_3 = QPushButton("3")
self.btn_add = QPushButton("+")
self.operation_layout.addWidget(self.btn_1, 3, 0, 1, 1)
self.operation_layout.addWidget(self.btn_2, 3, 1, 1, 1)
self.operation_layout.addWidget(self.btn_3, 3, 2, 1, 1)
self.operation_layout.addWidget(self.btn_add, 3, 3, 1, 1)
self.btn_zero = QPushButton("0")
self.btn_point = QPushButton(".")
self.btn_equal = QPushButton("=")
# 按钮0占两列
self.operation_layout.addWidget(self.btn_zero, 4, 0, 1, 2)
# 由于按钮0占用了两列,按钮.的位置就在第三列(下标2)了
self.operation_layout.addWidget(self.btn_point, 4, 2, 1, 1)
self.operation_layout.addWidget(self.btn_equal, 4, 3, 1, 1)
# 按钮区域layout的拉伸因子是5
self.main_layout.addLayout(self.operation_layout, stretch=5)
self.centralWidget().setLayout(self.main_layout)
self.bat_set_policy()
def bat_set_policy(self):
for field in self.__dir__():
if field.startswith("btn_"):
# QPushButton的vPolicy默认是Fixed,要改成可拉伸的策略
getattr(self, field).setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
官网图片代码:
class WebsiteWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('官网示例界面')
self.resize(500, 100)
self.setCentralWidget(QWidget())
self.main_layout = QGridLayout()
self.main_layout.addWidget(QLabel("Font"), 0, 0, 1, 1)
self.main_layout.addWidget(QLineEdit("Times"), 1, 0, 1, 1)
self.lw1 = QListWidget()
self.lw1.addItems(['Times', 'Helvetica', 'Courier', 'Palatino', 'Gill sans', 'Test1', 'Test2'])
self.lw1.setCurrentRow(0)
self.main_layout.addWidget(self.lw1, 2, 0, 1, 1)
self.main_layout.addWidget(QLabel("Font style"), 0, 2, 1, 1)
self.main_layout.addWidget(QLineEdit("Roman"), 1, 2, 1, 1)
self.lw2 = QListWidget()
self.lw2.addItems(['Roman', 'Italic', 'Oblique'])
self.lw2.setCurrentRow(0)
self.main_layout.addWidget(self.lw2, 2, 2, 1, 1)
self.main_layout.addWidget(QLabel("Size"), 0, 4, 1, 1)
self.main_layout.addWidget(QLineEdit("10"), 1, 4, 1, 1)
self.lw3 = QListWidget()
self.lw3.addItems(['8', '10', '12', '14', '16', '18', '20', '22', '24', '26', '28', '30'])
self.lw3.setCurrentRow(0)
self.main_layout.addWidget(self.lw3, 2, 4, 1, 1)
# 占位的列
self.main_layout.setColumnMinimumWidth(1, 40)
self.main_layout.setColumnMinimumWidth(3, 20)
self.centralWidget().setLayout(self.main_layout)
二、QFormLayout
QFormLayout是一个便捷的布局,它以一个两列的表单形式来排列它的子widget,左列是标签,右列是Input类型的widget。
传统上,这样的两列形式布局使用QGridLayout也可以完美实现的。QFormLayout是一种更高级别的替代方案,具有以下优势:
- 遵守不同平台的外观指南
比如:macOS上标签会右对其,windows上标签通常是左对齐
- 支持包装长行
所谓包装长行就是在宽度不足时,把标签显示在字段的上面。
QFormLayot有个字段是RowWrapPolicy,它指明了行包装策略:
class RowWrapPolicy(int):
DontWrapRows = ... # type: QFormLayout.RowWrapPolicy
WrapLongRows = ... # type: QFormLayout.RowWrapPolicy
WrapAllRows = ... # type: QFormLayout.RowWrapPolicy
对于Qt扩展样式,默认为WrapLongRows;对于其他样式,默认为DontWrapRows。通过setRowWrapPolicy()可设置:
self.main_layout = QFormLayout()
self.main_layout.setRowWrapPolicy(QFormLayout.RowWrapPolicy.WrapLongRows)
- 便捷的API来创建 标签-字段 对
下面我们实现一下官网文档上的示例:
class FormWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('提交表单')
self.resize(300, 100)
self.setCentralWidget(QWidget())
self.main_layout = QFormLayout()
self.main_layout.addRow('Name:', QLineEdit("Gandalf"))
self.main_layout.addRow('Email Address:', QLineEdit("gg@troll.cn"))
self.spinbox = QSpinBox()
self.spinbox.setMinimum(1)
self.spinbox.setMaximum(100)
self.main_layout.addRow('Age:', self.spinbox)
self.centralWidget().setLayout(self.main_layout)
可以看到,对于表单需求的界面,用QFormLayout实现起来是很便捷的。
三、QStackedLayout
QStackedLayout可以用来创建一个类似于QTabWidget提供的那种用户界面,在同一时刻只有一个页面能显示出来。在QStackedLayout之上还构建了一个方便的QStackedWidget类(这个在后面讲控件部分再说)。
QStackedLayout没有为用户提供切换页面的内在方式(类似于选项卡那种)。通常是通过存储QStackedLayout页面标题的QComboBox或QListWidget配合QStackedLayout提供的setCurrentIndex()来完成页面切换。
子widgets都保存在一个内部列表中,addWidget()把widget添加到列表的末尾,insertWidget()可以把widget添加到指定的索引处。
使用示例:
"""
通常每个子窗口都有自己的业务逻辑,这里这里定义了三个独立的子窗口
"""
class SubWindow1(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.main_layout = QVBoxLayout()
self.main_layout.addWidget(QPushButton("这是一个按钮"))
self.setLayout(self.main_layout)
class SubWindow2(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.main_layout = QVBoxLayout()
self.main_layout.addWidget(QLabel("这是一个静态文本"))
self.setLayout(self.main_layout)
class SubWindow3(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.main_layout = QVBoxLayout()
self.main_layout.addWidget(QLineEdit("这是一个输入框"))
self.setLayout(self.main_layout)
class StackedWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('栈布局')
self.resize(500, 300)
self.setCentralWidget(QWidget())
self.main_layout = QHBoxLayout()
self.left_list_layout = QVBoxLayout()
self.switch_handles = QListWidget()
self.switch_handles.addItems(['QPushButton', 'QLabel', 'QLineEdit'])
self.switch_handles.setCurrentRow(0)
self.left_list_layout.addWidget(self.switch_handles)
self.main_layout.addLayout(self.left_list_layout, stretch=1)
self.right_stacked_layout = QStackedLayout()
# QStackedLayout添加子widget(窗口)
self.right_stacked_layout.addWidget(SubWindow1(self))
self.right_stacked_layout.addWidget(SubWindow2(self))
self.right_stacked_layout.addWidget(SubWindow3(self))
# QStackedLayout使用setCurrentIndex()来切换当前显示的窗口
self.right_stacked_layout.setCurrentIndex(0)
# 把QListWidget的currentRowChanged信号连接到QStackedLayout的setCurrentIndex,实现点击QListWidget的选项时自动切换QStackedLayout
self.switch_handles.currentRowChanged.connect(self.right_stacked_layout.setCurrentIndex)
self.main_layout.addLayout(self.right_stacked_layout, stretch=4)
self.centralWidget().setLayout(self.main_layout)