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

如何实现自适应内容的QPlainTextEdit?PyQt5布局适配问题

PyQt5 实现QPlainTextEdit内容自适应高度并联动上方组件调整

问题背景

运行下方最小可复现代码时,调整窗口大小会触发MainWindow.resizeEvent(),动态修改QPlainTextEdit的文本内容。需求如下:

  • QPlainTextEdit根据内容自动调整高度,恰好完整容纳文本,无底部空白
  • 联动上方的HorizontalSplitter自动调整尺寸,不改变主窗口大小,避免触发resizeEvent导致无限循环

最小可复现代码(MRE)

import sys, random
from PyQt5 import QtWidgets, QtCore, QtGui

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        main_splitter = MainSplitter(self)
        self.setCentralWidget(main_splitter)
        main_splitter.setOrientation(QtCore.Qt.Vertical)
        main_splitter.setStyleSheet('background-color: red; border: 1px solid pink;');
        
        # top component of vertical splitter: a QFrame to hold horizontal splitter and breadcrumbs QPlainTextEdit
        top_frame = QtWidgets.QFrame()
        top_frame_layout = QtWidgets.QVBoxLayout()
        top_frame.setLayout(top_frame_layout)
        top_frame.setStyleSheet('background-color: green; border: 1px solid green;');
        main_splitter.addWidget(top_frame)
        
        # top component of top_frame: horizontal splitter
        h_splitter = HorizontalSplitter()
        h_splitter.setStyleSheet('background-color: cyan; border: 1px solid orange;')
        top_frame_layout.addWidget(h_splitter)

        # bottom component of top_frame: QPlainTextEdit (for "breadcrumbs")
        self.breadcrumbs_pte = BreadcrumbsPTE(top_frame)
        top_frame_layout.addWidget(self.breadcrumbs_pte)
        self.text = 'some plain text '
        self.breadcrumbs_pte.setPlainText(self.text * 50)
        self.breadcrumbs_pte.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
        self.breadcrumbs_pte.setStyleSheet('background-color: orange; border: 1px solid blue;');
        self.breadcrumbs_pte.setMinimumWidth(300)
        self.breadcrumbs_pte.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)

        # bottom component of vertical splitter: a QFrame
        bottom_panel = BottomPanel(main_splitter)
        bottom_panel.setStyleSheet('background-color: magenta; border: 1px solid cyan;')
        main_splitter.addWidget(bottom_panel)

    def resizeEvent(self, *args):
        print('resize event...')
        n_repeat = random.randint(10, 50)
        self.breadcrumbs_pte.setPlainText(self.text * n_repeat)
        super().resizeEvent(*args)

class BreadcrumbsPTE(QtWidgets.QPlainTextEdit):
    def sizeHint(self):
        return QtCore.QSize(500, 100)

class MainSplitter(QtWidgets.QSplitter):
    def sizeHint(self):
        return QtCore.QSize(40, 150)

class HorizontalSplitter(QtWidgets.QSplitter):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setOrientation(QtCore.Qt.Horizontal)

        self.left_panel = LeftPanel()
        self.left_panel.setStyleSheet('background-color: yellow; border: 1px solid black;');
        self.addWidget(self.left_panel)
        self.left_panel.setMinimumHeight(150)

        right_panel = QtWidgets.QFrame()
        right_panel.setStyleSheet('background-color: black; border: 1px solid blue;');
        self.addWidget(right_panel)

        # to achieve 66%-33% widths ratio
        self.setStretchFactor(0, 20)
        self.setStretchFactor(1, 10)
         
    def sizeHint(self):
        return self.left_panel.sizeHint()
    
class LeftPanel(QtWidgets.QFrame):
    def sizeHint(self):
        return QtCore.QSize(150, 250)

class BottomPanel(QtWidgets.QFrame):
    def sizeHint(self):
        return QtCore.QSize(30, 180)

app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()

解决方案

关键修改逻辑

  1. 固定BottomPanel高度:简化垂直Splitter的空间分配逻辑,避免主窗口尺寸波动
  2. 重写BreadcrumbsPTE的sizeHint():精确计算内容所需高度,包含滚动条和边距
  3. 设置布局拉伸因子:让HorizontalSplitter自动占据TopFrame剩余空间
  4. 触发局部布局更新:修改文本后仅更新QPlainTextEdit的几何信息,不触发主窗口resize

修改后的完整代码

import sys, random
from PyQt5 import QtWidgets, QtCore, QtGui

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        main_splitter = MainSplitter(self)
        self.setCentralWidget(main_splitter)
        main_splitter.setOrientation(QtCore.Qt.Vertical)
        main_splitter.setStyleSheet('background-color: red; border: 1px solid pink;');
        
        # top component of vertical splitter: a QFrame to hold horizontal splitter and breadcrumbs QPlainTextEdit
        top_frame = QtWidgets.QFrame()
        top_frame_layout = QtWidgets.QVBoxLayout()
        top_frame.setLayout(top_frame_layout)
        top_frame.setStyleSheet('background-color: green; border: 1px solid green;');
        main_splitter.addWidget(top_frame)
        
        # top component of top_frame: horizontal splitter
        h_splitter = HorizontalSplitter()
        h_splitter.setStyleSheet('background-color: cyan; border: 1px solid orange;')
        top_frame_layout.addWidget(h_splitter)
        # 设置拉伸因子,让水平splitter占据剩余空间
        top_frame_layout.setStretchFactor(h_splitter, 1)

        # bottom component of top_frame: QPlainTextEdit (for "breadcrumbs")
        self.breadcrumbs_pte = BreadcrumbsPTE(top_frame)
        top_frame_layout.addWidget(self.breadcrumbs_pte)
        self.text = 'some plain text '
        self.breadcrumbs_pte.setPlainText(self.text * 50)
        self.breadcrumbs_pte.setStyleSheet('background-color: orange; border: 1px solid blue;');
        self.breadcrumbs_pte.setMinimumWidth(300)
        self.breadcrumbs_pte.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
        # 关闭自动调整策略,改用自定义sizeHint
        self.breadcrumbs_pte.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustIgnored)

        # bottom component of vertical splitter: a QFrame,固定高度
        bottom_panel = BottomPanel(main_splitter)
        bottom_panel.setStyleSheet('background-color: magenta; border: 1px solid cyan;')
        bottom_panel.setFixedHeight(180)  # 固定高度,简化垂直splitter空间分配
        main_splitter.addWidget(bottom_panel)

    def resizeEvent(self, *args):
        print('resize event...')
        n_repeat = random.randint(10, 50)
        self.breadcrumbs_pte.setPlainText(self.text * n_repeat)
        # 修改文本后触发局部布局更新
        self.breadcrumbs_pte.updateGeometry()
        super().resizeEvent(*args)

class BreadcrumbsPTE(QtWidgets.QPlainTextEdit):
    def sizeHint(self):
        # 计算内容所需高度:文档内容高度 + 滚动条宽度 + 边距
        doc = self.document()
        content_height = doc.size().height()
        # 获取滚动条宽度(适配系统样式)
        scrollbar_width = self.verticalScrollBar().sizeHint().width()
        # 加上上下边距
        margins = self.contentsMargins()
        total_height = content_height + margins.top() + margins.bottom() + scrollbar_width
        # 宽度保持现有设置
        return QtCore.QSize(self.width(), total_height)

class MainSplitter(QtWidgets.QSplitter):
    def sizeHint(self):
        return QtCore.QSize(800, 600)  # 设置合理初始窗口大小

class HorizontalSplitter(QtWidgets.QSplitter):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setOrientation(QtCore.Qt.Horizontal)

        self.left_panel = LeftPanel()
        self.left_panel.setStyleSheet('background-color: yellow; border: 1px solid black;');
        self.addWidget(self.left_panel)
        self.left_panel.setMinimumHeight(150)

        right_panel = QtWidgets.QFrame()
        right_panel.setStyleSheet('background-color: black; border: 1px solid blue;');
        self.addWidget(right_panel)

        # to achieve 66%-33% widths ratio
        self.setStretchFactor(0, 20)
        self.setStretchFactor(1, 10)
         
    def sizeHint(self):
        return self.left_panel.sizeHint()
    
class LeftPanel(QtWidgets.QFrame):
    def sizeHint(self):
        return QtCore.QSize(150, 250)

class BottomPanel(QtWidgets.QFrame):
    def sizeHint(self):
        return QtCore.QSize(30, 180)

app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()

效果验证

  • QPlainTextEdit高度会随文本内容自动调整,恰好完整显示所有内容,无底部空白
  • 上方的HorizontalSplitter会自动填充TopFrame剩余空间,尺寸随QPlainTextEdit高度变化联动调整
  • 主窗口大小保持稳定,修改文本仅触发内部布局更新,不会触发resizeEvent无限循环

内容的提问来源于stack exchange,提问作者mike rodent

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 09:50:13