如何实现自适应内容的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_()
解决方案
关键修改逻辑
- 固定BottomPanel高度:简化垂直Splitter的空间分配逻辑,避免主窗口尺寸波动
- 重写BreadcrumbsPTE的
sizeHint():精确计算内容所需高度,包含滚动条和边距 - 设置布局拉伸因子:让HorizontalSplitter自动占据TopFrame剩余空间
- 触发局部布局更新:修改文本后仅更新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
相关产品推荐
相关产品推荐

