PyQt6 auto-resize QTextEdit to fit content 问题

PyQt6 auto-resize QTextEdit to fit content problem

我在 QScrollArea 的 QVBoxLayout 中有一堆 QTextEdit。

文本通常会变得很长,水平 space 受设计限制,QTextEdit 自动将文本换行成多行,这很好。

我想自动调整 QTextEdit 的大小以适合换行的文本,文本本身总是在一行中,而换行的文本可以有多行,我希望 QTextEdits 适合换行的高度.

经过几个小时 Google 的搜索,我找到了一个解决方案,但它没有按预期工作,有时底部会多出一行,我会 post下面的示例代码:

from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *

font = QFont('Noto Serif', 9)

class Editor(QTextEdit):
    doubleClicked = pyqtSignal(QTextEdit)
    def __init__(self):
        super().__init__()
        self.setReadOnly(True)
        self.setFont(font)
        self.textChanged.connect(self.autoResize)
        self.margins = self.contentsMargins()
        self.setAttribute(Qt.WidgetAttribute.WA_DontShowOnScreen)
    @pyqtSlot(QMouseEvent)
    def mouseDoubleClickEvent(self, e: QMouseEvent) -> None:
        self.doubleClicked.emit(self)
    
    def autoResize(self):
        self.show()
        height = int(self.document().size().height() + self.margins.top() + self.margins.bottom())
        self.setFixedHeight(height)

class Window(QMainWindow):
    def __init__(self):
        super().__init__()
        self.resize(405, 720)
        frame = self.frameGeometry()
        center = self.screen().availableGeometry().center()
        frame.moveCenter(center)
        self.move(frame.topLeft())
        self.centralwidget = QWidget(self)
        self.vbox = QVBoxLayout(self.centralwidget)
        self.scrollArea = QScrollArea(self.centralwidget)
        self.scrollArea.setWidgetResizable(True)
        self.scrollAreaWidgetContents = QWidget()
        self.scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
        self.verticalLayout = QVBoxLayout(self.scrollAreaWidgetContents)
        self.verticalLayout.setAlignment(Qt.AlignmentFlag.AlignTop)
        self.scrollArea.setWidget(self.scrollAreaWidgetContents)
        self.scrollArea.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop)
        self.vbox.addWidget(self.scrollArea)
        self.setCentralWidget(self.centralwidget)

def addItems():
    items = [
        "L'Estro Armonico No. 9 in D Major\u2014III. Allegro", 
        "L'Estro Armonico No. 6 in A Minor\u2014III. Presto",
        "L'Estro Armonico No. 6 in A Minor\u2014I. Allegro",
        "L'Estro Armonico No. 1 in D major\u2014I. Allegro",
        "12 Concertos Op.3 \u2014 L'estro Armonico \u2014 Concerto No. 6 In A Minor For Solo Violin RV 356\u2014Presto",
        "Ultimate Mozart\u2014 The Essential Masterpieces",
        "Serenade in G K.525 Eine kleine Nachtmusik\u20141. Allegro",
        "Vivaldi\u2014 L'estro Armonico",
        "Academy of St. Martin in the Fields",
        "Are You With Me \u2014 Reality"
        ]
    for i in items:
        textbox = Editor()
        textbox.setText(i)
        window.verticalLayout.addWidget(textbox)


app = QApplication([])
window = Window()
window.show()
addItems()
app.exec()

请注意,您需要 Noto Serif 才能正确 运行(当然您可以替换它),当然您还需要 PyQt6。

示例中,前七个文本框底部都有一个额外的空行,后三个没有。

是什么导致了额外的线以及如何删除它?

更新:

我必须设置Qt.WidgetAttribute.WA_DontShowOnScreen,因为如果我不设置它,调用QTextEdit 的.show() 将导致QTextEdit 出现在屏幕中间并迅速消失,这很烦人。

我不得不调用.show() 因为调用document().size() 没有show(),值将全部为0,我不知道为什么会这样。

问题出在您尝试过早设置高度。事实上,如果您在 show() 之后添加一个 print(self.show()),您会看到所有内容都将显示默认大小(可能是 256x192)。

这取决于两个方面:

  1. 当一个小部件第一次显示时,它还没有完全“映射”在OS window 管理中,因此它会根据许多方面使用默认大小;
  2. 您在将文本添加到布局之前设置文本,因此 QTextEdit 将不知道父级所需的大小;

然后又出现了一个问题:如果window调整了大小,内容将不会适应,直到文本被改变。

为了根据内容正确设置垂直高度,您应该设置文档的 textWidth,并在每次调整小部件大小时调用 autoResize

class Editor(QTextEdit):
    def __init__(self):
        super().__init__()
        self.setReadOnly(True)
        self.setFont(font)
        self.textChanged.connect(self.autoResize)

    def autoResize(self):
        self.document().setTextWidth(self.viewport().width())
        margins = self.contentsMargins()
        height = int(self.document().size().height() + margins.top() + margins.bottom())
        self.setFixedHeight(height)

    def resizeEvent(self, event):
        self.autoResize()

注意:

  • 边距应该动态访问,而不是存储在 __init__;
  • mouseDoubleClickEvent 是一个在鼠标事件上调用的函数,它不是(也不应该)是一个槽,所以使用 pyqtSlot 装饰器是没有意义的;
  • 虽然对于“主”布局(如滚动区域内容的布局)在概念上没有问题,但设置布局的对齐方式不会设置其项目的对齐方式,而只会设置布局的对齐方式;虽然结果 经常 相同,但在实践中却大不相同(因此结果并不 总是 相同,尤其是如果将更多布局添加到相同的父布局);
  • 双击文本字段非常常用于高级 selection(通常,select 光标下的单词),并选择阻止此类操作(从而更改已知 UI 惯例) 应该仔细考虑;