PyQt6可折叠组件标题与内容区域间距过大问题及调整咨询
解决PyQt6可折叠组件标题与内容区域的多余间距问题
问题描述
使用PyQt6开发可折叠组件collapsibleSection时,发现标题区域与内容区域contentFrameH之间存在无法通过调整主布局边距消除的多余间距,相关代码及问题截图如下:
问题截图

原代码
import sys from PyQt6 import QtWidgets from PyQt6.QtCore import Qt from PyQt6.QtWidgets import * class collapsibleSection(QFrame): def __init__(self, title="", *content: QWidget, parent=None): super().__init__(parent) self.arrowButton = QPushButton() self.titleLabel = QLabel(title) self.titleFrame = QFrame(self) self.setFrameStyle(QFrame.Shape.Box) self.setLineWidth(1) self.contentFrameH = QFrame(self) self.contentFrameHLayout = QHBoxLayout() self.contentFrameV = QFrame(self) self.contentFrameVLayout = QVBoxLayout() self.init_ui(*content) def init_ui(self, *content: QWidget): layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) self.arrowButton.setCheckable(True) self.arrowButton.setFlat(True) self.arrowButton.setStyleSheet(""" QPushButton { border: none; background-color: transparent; } QPushButton:checked { background-color: transparent; } QPushButton:hover { background-color: none; } """) self.arrowButton.toggled.connect(self._toggle_collapse) self.arrowButton.setFixedWidth(16) self.arrowButton.setFixedHeight(16) self.titleLabel.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter) self.titleLabel.setStyleSheet("font-weight: bold;") titleFrameLayout = QHBoxLayout() titleFrameLayout.addWidget(self.arrowButton) titleFrameLayout.addWidget(self.titleLabel) self.titleFrame.setLayout(titleFrameLayout) self.contentFrameH.setLayout(self.contentFrameHLayout) self.contentFrameV.setLayout(self.contentFrameVLayout) # set up visual indent for content in widget self.contentFrameHLayout.addSpacing(20) self.setContent(*content) layout.addWidget(self.titleFrame) layout.addWidget(self.contentFrameH) # self.contentFrameH.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) self.setFrameShape(QtWidgets.QFrame.Shape.NoFrame) self.setLayout(layout) self._toggle_collapse() def _toggle_collapse(self): if not self.arrowButton.isChecked(): self.arrowButton.setText("▶") self.contentFrameH.hide() else: self.arrowButton.setText("▼") self.contentFrameH.show() def setTitle(self, title: str): self.titleLabel.setText(title) def setContent(self, *content: QWidget): # the -1 is to avoid removing the spacerItem for i in reversed(range(self.contentFrameHLayout.count() - 1)): self.contentFrameHLayout.itemAt(i).widget().setParent(None) for w in content: w.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) self.contentFrameVLayout.addWidget(w) self.contentFrameHLayout.addWidget(self.contentFrameV) class infoWindowBase(QMainWindow): def __init__(self, parent=None): super().__init__(parent) self.centralWidget = QtWidgets.QWidget(self) self.centralWidget.setEnabled(True) self.centralWidget.setAutoFillBackground(False) self.centralWidget.setObjectName("centralWidget") self.primaryGridLayout = QtWidgets.QGridLayout(self.centralWidget) self.primaryGridLayout.setContentsMargins(0, 0, 0, 0) self.primaryGridLayout.setSpacing(0) self.primaryGridLayout.setObjectName("primaryGridLayout") self.scrollArea = QScrollArea(self) self.scrollArea.setWidgetResizable(True) self.layout = QVBoxLayout() self.layout.setSpacing(10) self.layout.setAlignment(Qt.AlignmentFlag.AlignTop) self.scrollWidget = QWidget(self) self.scrollWidget.setLayout(self.layout) self.scrollArea.setWidget(self.scrollWidget) comfyLayout = QVBoxLayout() # used to add content margins around scrollArea for better aesthetics comfyLayout.setContentsMargins(0, 10, 0, 10) comfyLayout.addWidget(self.scrollArea) comfyFrame = QFrame() comfyFrame.setLayout(comfyLayout) self.primaryGridLayout.addWidget(comfyFrame, 1, 0) self.setCentralWidget(self.centralWidget) class employeeInfo(infoWindowBase): def __init__(self, parent=None): super().__init__(parent) self._medicalData = QLabel("DATA HERE") self._medicalSection = collapsibleSection() self._initEmployeeData() def _initEmployeeData(self): # medical data setup medicalFrame = QFrame(self._medicalSection) medicalLayout = QVBoxLayout(medicalFrame) medicalLayout.addWidget(self._medicalData) medicalFrame.setLayout(medicalLayout) self._medicalSection.setTitle("Medical Data") self._medicalSection.setContent(medicalFrame) self.layout.addWidget(self._medicalSection) self.layout.addSpacing(20) app = QApplication(sys.argv) w = employeeInfo() w.show() app.exec()
解决方案
多余间距的根源是嵌套的子布局默认带有内容边距,只调整主布局的边距无法消除这些子布局的默认边距。需要对以下几处布局进行修改:
关键修改点
- 标题区域的布局
titleFrameLayout添加边距清零:titleFrameLayout.setContentsMargins(0, 0, 0, 0) - 内容水平布局
contentFrameHLayout添加边距清零:self.contentFrameHLayout.setContentsMargins(0, 0, 0, 0) - 内容垂直布局
contentFrameVLayout添加边距清零:self.contentFrameVLayout.setContentsMargins(0, 0, 0, 0) - 医疗数据区域的布局
medicalLayout添加边距清零:medicalLayout.setContentsMargins(0, 0, 0, 0)
修改后的完整代码
import sys from PyQt6 import QtWidgets from PyQt6.QtCore import Qt from PyQt6.QtWidgets import * class collapsibleSection(QFrame): def __init__(self, title="", *content: QWidget, parent=None): super().__init__(parent) self.arrowButton = QPushButton() self.titleLabel = QLabel(title) self.titleFrame = QFrame(self) self.setFrameStyle(QFrame.Shape.Box) self.setLineWidth(1) self.contentFrameH = QFrame(self) self.contentFrameHLayout = QHBoxLayout() # 清零内容水平布局边距 self.contentFrameHLayout.setContentsMargins(0, 0, 0, 0) self.contentFrameV = QFrame(self) self.contentFrameVLayout = QVBoxLayout() # 清零内容垂直布局边距 self.contentFrameVLayout.setContentsMargins(0, 0, 0, 0) self.init_ui(*content) def init_ui(self, *content: QWidget): layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) self.arrowButton.setCheckable(True) self.arrowButton.setFlat(True) self.arrowButton.setStyleSheet(""" QPushButton { border: none; background-color: transparent; } QPushButton:checked { background-color: transparent; } QPushButton:hover { background-color: none; } """) self.arrowButton.toggled.connect(self._toggle_collapse) self.arrowButton.setFixedWidth(16) self.arrowButton.setFixedHeight(16) self.titleLabel.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter) self.titleLabel.setStyleSheet("font-weight: bold;") titleFrameLayout = QHBoxLayout() # 清零标题布局边距 titleFrameLayout.setContentsMargins(0, 0, 0, 0) titleFrameLayout.addWidget(self.arrowButton) titleFrameLayout.addWidget(self.titleLabel) self.titleFrame.setLayout(titleFrameLayout) self.contentFrameH.setLayout(self.contentFrameHLayout) self.contentFrameV.setLayout(self.contentFrameVLayout) # set up visual indent for content in widget self.contentFrameHLayout.addSpacing(20) self.setContent(*content) layout.addWidget(self.titleFrame) layout.addWidget(self.contentFrameH) self.setFrameShape(QtWidgets.QFrame.Shape.NoFrame) self.setLayout(layout) self._toggle_collapse() def _toggle_collapse(self): if not self.arrowButton.isChecked(): self.arrowButton.setText("▶") self.contentFrameH.hide() else: self.arrowButton.setText("▼") self.contentFrameH.show() def setTitle(self, title: str): self.titleLabel.setText(title) def setContent(self, *content: QWidget): # the -1 is to avoid removing the spacerItem for i in reversed(range(self.contentFrameHLayout.count() - 1)): self.contentFrameHLayout.itemAt(i).widget().setParent(None) for w in content: w.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) self.contentFrameVLayout.addWidget(w) self.contentFrameHLayout.addWidget(self.contentFrameV) class infoWindowBase(QMainWindow): def __init__(self, parent=None): super().__init__(parent) self.centralWidget = QtWidgets.QWidget(self) self.centralWidget.setEnabled(True) self.centralWidget.setAutoFillBackground(False) self.centralWidget.setObjectName("centralWidget") self.primaryGridLayout = QtWidgets.QGridLayout(self.centralWidget) self.primaryGridLayout.setContentsMargins(0, 0, 0, 0) self.primaryGridLayout.setSpacing(0) self.primaryGridLayout.setObjectName("primaryGridLayout") self.scrollArea = QScrollArea(self) self.scrollArea.setWidgetResizable(True) self.layout = QVBoxLayout() self.layout.setSpacing(10) self.layout.setAlignment(Qt.AlignmentFlag.AlignTop) self.scrollWidget = QWidget(self) self.scrollWidget.setLayout(self.layout) self.scrollArea.setWidget(self.scrollWidget) comfyLayout = QVBoxLayout() # used to add content margins around scrollArea for better aesthetics comfyLayout.setContentsMargins(0, 10, 0, 10) comfyLayout.addWidget(self.scrollArea) comfyFrame = QFrame() comfyFrame.setLayout(comfyLayout) self.primaryGridLayout.addWidget(comfyFrame, 1, 0) self.setCentralWidget(self.centralWidget) class employeeInfo(infoWindowBase): def __init__(self, parent=None): super().__init__(parent) self._medicalData = QLabel("DATA HERE") self._medicalSection = collapsibleSection() self._initEmployeeData() def _initEmployeeData(self): # medical data setup medicalFrame = QFrame(self._medicalSection) medicalLayout = QVBoxLayout(medicalFrame) # 清零医疗数据布局边距 medicalLayout.setContentsMargins(0, 0, 0, 0) medicalLayout.addWidget(self._medicalData) medicalFrame.setLayout(medicalLayout) self._medicalSection.setTitle("Medical Data") self._medicalSection.setContent(medicalFrame) self.layout.addWidget(self._medicalSection) self.layout.addSpacing(20) app = QApplication(sys.argv) w = employeeInfo() w.show() app.exec()
说明
这些修改会清除所有嵌套布局的默认边距,从而消除标题与内容区域之间的多余间距。如果需要保留少量间距,可将setContentsMargins的参数调整为所需数值(例如(0,2,0,2))。
内容的提问来源于stack exchange,提问作者Zaria Burton
相关产品推荐
相关产品推荐

