PySide2 QTextEdit 在使用换行时不会调整到它自己的内容。 (正在聊天 window)

PySide2 QTextEdit doesn't adjust to it's own content when using wrapping. (Making chat window)

当放置文本时,PySide2 QTextEdit 不会改变它自己的大小。

我正在尝试创建类似于聊天 window 的内容,其中每条消息 - QTextEdit 都处于 OnlyRead 模式。所有'messages'都放在QScrollArea中。主要目标是让消息框(下面屏幕上的消息框)根据内容调整大小。 wrong working example

我试过这段代码 https://ru.whosebug.com/questions/1408239/Как-сделать-двухсторонний-чат-в-qt-pyqt/1408264#1408264

已被复制粘贴多次。但它没有做我想要的。它创建一个固定的、不可调整大小的 QTextEdit 消息框。

举个例子,我的实际意思是,如果我们有一个单字消息,QTextEdit 小部件必须变成一个单笔划框,具有消息的宽度。如果我们有一个多句消息,QTextEdit 小部件必须成为一个多笔划框(高度已经展开,不需要在里面滚动),最大恒定长度(我会选择)。

接下来是显示正确信息的例子 (good example)

为了实现 self-adjusting 消息,需要采取一些预防措施。

正如我在 的回答和评论中所解释的那样,您必须考虑布局尺寸与其要求之间复杂而微妙的关系,这变得更加复杂 文本布局 没有固定比例。

根据文本设置宽度的主要问题是它可以更改显示它所需的高度,这会导致递归,并且由于大小是基于 当前视口大小,结果是minimumSizeHint()在一定数量的递归调用后总是return可能的最小尺寸。

考虑到上述情况,我们必须对我的原始代码进行以下更改:

  • 每当调整视图大小时,滚动区域必须始终为其消息小部件设置最大宽度,可能具有指定的边距(用于 sent/received 区别);
  • 必须使用 alignment 参数将小部件添加到布局;
  • minimumSizeHint()必须改为:
    1. 计算 首选 文本宽度(idealWidth() 基于小部件的最大尺寸;
    2. 获取该文本宽度的参考高度;
    3. 将文本宽度设置为当前宽度;
    4. 将新的文档高度与之前的文档高度进行比较,如果相同则表示我们可以使用新的宽度作为提示的最大宽度(文本可以更短),否则我们使用初始文本宽度基于最大尺寸;

请注意,与您的 link 修改后的代码有几点不同:最重要的是,重写样式表没有多大意义,并且设置边距会产生值问题returned by frameWidth()(这就是他们从文档高度中减去 100 的原因);这当然不是一个好的选择,因为边距应该在布局内设置。

class WrapLabel(QtWidgets.QTextEdit):
    def __init__(self, text=''):
        super().__init__(text)
        self.setReadOnly(True)
        self.setSizePolicy(QtWidgets.QSizePolicy.Preferred, 
            QtWidgets.QSizePolicy.Maximum)
        self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.textChanged.connect(self.updateGeometry)

    def minimumSizeHint(self):
        margin = self.frameWidth() * 2
        doc = self.document().clone()
        doc.setTextWidth(self.maximumWidth())
        idealWidth = doc.idealWidth()
        idealHeight = doc.size().height()
        doc.setTextWidth(self.viewport().width())
        if doc.size().height() == idealHeight:
            idealWidth = doc.idealWidth()
        return QtCore.QSize(
            max(50, idealWidth + margin), 
            doc.size().height() + margin)

    def sizeHint(self):
        return self.minimumSizeHint()

    def resizeEvent(self, event):
        super().resizeEvent(event)
        self.updateGeometry()


class ChatTest(QtWidgets.QScrollArea):
    def __init__(self):
        super().__init__()
        self.margin = 100
        self.marginRatio = .8
        self.messages = []

        container = QtWidgets.QWidget()
        self.setWidget(container)
        self.setWidgetResizable(True)

        layout = QtWidgets.QVBoxLayout(container)
        layout.addStretch()
        self.resize(480, 360)

        letters = 'abcdefghijklmnopqrstuvwxyz       '
        for i in range(1, 11):
            msg = ''.join(choice(letters) for i in range(randrange(10, 250)))
            QtCore.QTimer.singleShot(500 * i, lambda msg=msg, i=i:
                self.addMessage(msg, i & 1))

    def addMessage(self, text, sent=False):
        message = WrapLabel(text)
        message.setStyleSheet('''
            WrapLabel {{
                border: 1px outset palette(dark);
                border-radius: 8px;
                background: {};
            }}
        '''.format(
            '#fff8c7' if sent else '#ceffbd')
        )
        self.messages.append(message)
        self.widget().layout().addWidget(message, 
            alignment=QtCore.Qt.AlignRight if sent else QtCore.Qt.AlignLeft)
        QtCore.QTimer.singleShot(0, self.scrollToBottom)

    def scrollToBottom(self):
        QtWidgets.QApplication.processEvents()
        self.verticalScrollBar().setValue(
            self.verticalScrollBar().maximum())

    def resizeEvent(self, event):
        sb = self.verticalScrollBar()
        atMaximum = sb.value() == sb.maximum()
        maxWidth = max(self.width() * self.marginRatio, 
            self.width() - self.margin) - sb.sizeHint().width()
        for message in self.messages:
            message.setMaximumWidth(maxWidth)
        super().resizeEvent(event)
        if atMaximum:
            sb.setValue(sb.maximum())