You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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()

解决方案

多余间距的根源是嵌套的子布局默认带有内容边距,只调整主布局的边距无法消除这些子布局的默认边距。需要对以下几处布局进行修改:

关键修改点

  1. 标题区域的布局titleFrameLayout添加边距清零:
    titleFrameLayout.setContentsMargins(0, 0, 0, 0)
    
  2. 内容水平布局contentFrameHLayout添加边距清零:
    self.contentFrameHLayout.setContentsMargins(0, 0, 0, 0)
    
  3. 内容垂直布局contentFrameVLayout添加边距清零:
    self.contentFrameVLayout.setContentsMargins(0, 0, 0, 0)
    
  4. 医疗数据区域的布局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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 22:54:57