为PyQt5自动扩展和收缩`QTextEdit`

问题描述 投票:0回答:1

我正在尝试在 PyQt5 中创建一个自定义

QTextEdit
小部件,该小部件会根据其内容自动扩展和收缩。当用户输入新行时,小部件的高度应增加;当行被删除时,小部件的高度应缩小。我有一些代码,但是当调整 TextEdit 大小时,窗口大小保持不变,因此 TextEdit 上方的小部件(应该具有动态大小)的大小会增加。我的MRE如下:

from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import sys

class App(QWidget):
    def __init__(self):
        super().__init__()

        self.layout = QVBoxLayout()
        self.layout.setSpacing(0)
        self.layout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(self.layout)

        self.init_ui()

    def init_ui(self):
        ### ChatWidget ###
        chat_widget = QWidget(self)
        chat_widget.setObjectName("chatbox")
        chat_widget.setStyleSheet("#chatbox {background-color: black; border: 0;}")

        chat_widget.scroll_area = QScrollArea(self)
        chat_widget.scroll_area.setWidgetResizable(True)
        chat_widget.scroll_area.setWidget(chat_widget)
        chat_widget.scroll_area.setStyleSheet("""
        QScrollArea {
        }
        QScrollBar:vertical {
            background-color: black;
            color: white;
        }
        """)

        chat_widget.setMinimumHeight(449)
        chat_widget.setMinimumWidth(300)
        chat_widget.scroll_area.setMinimumHeight(453)
        chat_widget.scroll_area.setMinimumWidth(333)

        self.layout.addWidget(chat_widget.scroll_area)
        ###

        ### CustomTextEdit ###
        self.text_edit = CustomTextEdit(5, self)
        self.layout.addWidget(self.text_edit)
        self.text_edit.setStyleSheet("""
        font-size: 12pt;
        """)
        QTimer().singleShot(5, self.text_edit.update_height)
        ###

class CustomTextEdit(QTextEdit):
    def __init__(self, max_lines=5, parent=None):
        super().__init__(parent)
        self.max_lines = max_lines
        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)
        self.document().contentsChanged.connect(self.update_height)

        self.setFixedHeight(self.fontMetrics().height())

    def update_height(self):
        document_height = self.document().size().height()
        line_height = self.fontMetrics().lineSpacing()

        text_layout = self.document().documentLayout()
        block = self.document().begin()
        num_lines = 0
        while block.isValid():
            layout = block.layout()
            num_lines += layout.lineCount()
            block = block.next()

        desired_height = document_height + self.contentsMargins().top() + self.contentsMargins().bottom()

        if num_lines > self.max_lines:
            self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        else:
            self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
            self.setFixedHeight(int(desired_height))

    def scroll_to_bottom(self):
        scrollbar = self.verticalScrollBar()
        scrollbar.setValue(scrollbar.maximum())

if __name__ == '__main__':
    app = QApplication(sys.argv)
    app_widget = App()
    app_widget.show()
    sys.exit(app.exec_())

示例 UI 图像

如果有人能提供帮助,我将不胜感激,谢谢!

尝试/目标/期望:我尝试使 TextEdit 随着行数的增加而调整大小。 问题:其他小部件正在调整大小/窗口未调整大小。

python python-3.x pyqt5 qtextedit
1个回答
0
投票

当子窗口小部件需要比以前更多的空间时,如果其他窗口小部件没有留下可用空间,通常会导致父窗口部件相应地增加其大小,最终上升到顶层窗口。这是为了确保所有小部件始终可见且可用。

当更改反而降低了尺寸要求时,这种情况不会发生,因为假设可以更好地利用空间,而不是试图任意减小总体尺寸。通常不会这样做,因为内部小部件和容器无法正确计算大小的减小,因为它们不知道导致减小的原因,它们只知道发生了调整大小:这可能是由用户调整大小引起的窗口,或通过另一个小部件更改其大小限制。

为了尝试将大小“重置”为最小值,我们最终可以尝试获得窗口的最小首选大小要求(

sizeHint()
),但只有在之后更改已被正确处理布局管理器(这就是调用
activate()
的原因)。

    def update_height(self):
        ...
            self.window().layout.activate()
            self.window().resize(
                self.window().width(), 
                self.window().sizeHint().height()
            )

但请注意,通常不鼓励这种大小更改。虽然尺寸的增加可能是合理的(UI must 能够显示其内容),但这两项更改实际上都应该考虑当前尺寸,以防用户手动调整窗口大小。

即使在原始代码中,执行所有这些操作的缺点也清晰可见:尝试使窗口比启动时稍高(几个像素),然后添加足够的文本以显示多行;窗户只会稍微增加其高度。现在,如果清除文本,大小将不会恢复到以前的大小。
如果您对新添加的内容执行相同的操作,则无论用户最终如何尝试调整窗口大小,窗口都将始终恢复到其最小大小。

这是仅在必要时且在特定条件下才应更改窗口大小的众多原因之一(内容中没有完全动态的更改)。虽然从技术上来说,以正确的方式实现你想要的并不是不可能的,但它总是需要遵循非常微妙的程序(检查父顶级窗口、大小限制、同级小部件等),这可能很难实现。此外,它通常被认为对用户来说很烦人,尤其是对于输入字段,并且实际上从用户体验的角度来看它没有提供任何真正的好处。 更合适的方法应该正确使用大小提示,特别是

minimumSizeHint()

sizeHint(),它们分别返回小部件的最小可能大小和首选大小:子小部件最终应提供
preferred
大小如果其他小部件需要更多空间,最终可能会缩小。 窗口不应该

因用户输入而调整大小,除非为了正确显示其所有内容是绝对必要的。

在您的情况下,这应该作为 QScrollArea 子类实现:

class ScrollArea(QScrollArea): def minimumSizeHint(self): return QSize(300, 350) def sizeHint(self): return QSize(300, 450) class App(QWidget): ... def init_ui(self): ... chat_widget.scroll_area = ScrollArea(self) self.setMinimumHeight(self.sizeHint().height())

上面将给出与您类似的初始结果,其好处是,如果输入区域需要更多空间,“消息区域”最终会缩小,然后如果需要较少的垂直空间,所有内容都会相应恢复。
进一步说明:

如果您使用

setFixedHeight()
    ,则无需设置大小策略;
  • chat_widget
  • 是滚动区域的子级,而不是相反;将
  • scroll_area
     作为其成员在概念上是错误的,因为它是相反的;
  • layout()
  • 是所有小部件的动态函数,永远不应该用“静态”成员覆盖,主要是因为它使其
    不可调用
    (在第一个示例中,
    self.window().layout()会更合适,它对任何级别的任何父母都有效,但这是不可能的,因为你把它变成了一个不可调用的参考);
    始终使用正确的父参数:大多数时候,如果小部件在构造后立即添加到布局管理器中,则不需要它,但如果您
    do
  • 提供它,它应该是正确的父参数:对于例如,
  • chat_widget = QWidget(self) 是不合适的,因为它将被重新设置为滚动区域的子级;
        
© www.soinside.com 2019 - 2024. All rights reserved.