pyside2 qtextedit在使用包装时不会适应自己的内容。 (制作聊天窗口)

发布于 2025-01-28 05:14:53 字数 995 浏览 5 评论 0原文

pyside2 qtextedit在放置文本时不会更改其自身的大小。

我正在尝试创建诸如聊天窗口的内容,每个消息 - qtextedit在仅在读模式下。所有放置在QSCrollarea中的“消息”。主要目标是让消息框(下面的屏幕上的消息盒子样)调整其尺寸至内容。 错误的工作示例

我尝试了此代码 https://ru.stackoverflow.com /QUASIOTS/1408239/务务 - lC/l C/д°ххххх-й-ч-ч-в-st-qt-pyqt/1408264#1408264

已被复制了很多次。但这没有我想要的。它创建了固定的,没有可重大的QTEXTEDIT消息框。

例如,我的实际意思是,如果我们有一个单词消息,则QTEXTEDIT小部件必须成为一个单程盒,并具有消息的宽度。如果我们有一个多句子消息,则QTEXTEDIT小部件必须成为一个多冲程框(已经扩展了高度,而无需在内部滚动),并具有最大的恒定长度(我会选择)。

接下来是显示正确显示的示例 (很好的例子)

The PySide2 QTextEdit doesn't change it's own size when the text is placed in.

I'm trying to create something like chat window, where every message - QTextEdit in OnlyRead mode. All the 'messages' placed in QScrollArea. The main goal is to let message-boxes (message-boxeslike on the screen below) adjust their size to content.
wrong working example

I tried this code
https://ru.stackoverflow.com/questions/1408239/Как-сделать-двухсторонний-чат-в-qt-pyqt/1408264#1408264

which has been copy-pasted lots of times. But it doesn't do what i want. It creates a fixed, no resizable QTextEdit message-boxes.

As example what i actually mean, if we have a single-word message, QTextEdit widget must become a single stroke box, with width of the message. If we have a multi-sentences message, QTextEdit widget must become a multi-stroke box (already expanded in height, without the need to scroll it inside), with maximum constant length(which i ll choose).

Next is the example with correct messages displaying
(good example)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

栩栩如生 2025-02-04 05:14:53

为了实施自调整的消息,需要一些预防措施。

正如我在答案中解释的那样,对相关帖子,您必须考虑布局及其尺寸之间的复杂而微妙的关系要求,这变得更加复杂,因为A 文本布局没有固定比率。

基于文本设置宽度的主要问题是它可以更改显示它所需的高度,这可能会导致递归,并且由于大小基于 current viewport大小,因此结果是MinimumSizeHint()将始终在一定量的递归调用后返回最小的大小。

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

  • 滚动区域必须始终为其消息窗口小部件设置最大宽度,每当调整视图大小时,可能具有指定的边距(用于发送/接收的区别);
  • 必须使用 Arignment 参数将小部件添加到布局中;
  • 必须更改MINVIMMIMSIZEHINT()至:
    1. 计算首选文本宽度( >基于小部件的最大尺寸;
    2. 获取该文本宽度的参考高度;
    3. 将文本宽度设置为当前宽度;
    4. 将新文档的高度与上一个文档进行比较,如果它们是相同的,则意味着我们可以将新宽度用作提示的最大宽度(文本可以更短),否则,我们使用基于初始文本宽度基于最大尺寸;

;与链接的修改代码有所不同:最重要的是,重写样式表并没有很大的意义,设置保证金会在> frame> 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())

In order to implement a self-adjusting message, some precautions are required.

As explained in my answer and comments to the related post, you must consider the complex and delicate relation between the dimensions of a layout and its requirement, which becomes even more complex as a text layout doesn't have a fixed ratio.

The main problem with setting the width based on the text is that it can change the height required to display it, which can cause a recursion, and since the size is based on the current viewport size, the result is that the minimumSizeHint() will always return the smallest possible size after a certain amount of recursive calls.

Considering the above, we must do the following changes to my original code:

  • the scroll area must always set a maximum width to its message widgets, possibly with a specified margin (for sent/received distinction) whenever the view is resized;
  • widgets must be added to the layout with an alignment argument;
  • the minimumSizeHint() must be changed to:
    1. compute the preferred text width (idealWidth() based on the maximum size of the widget;
    2. get the reference height for that text width;
    3. set the text width to the current width;
    4. compare the new document height with the previous one, if they are the same it means that we can use the new width as maximum width for the hint (the text can be shorter), otherwise we use the initial text width based on the maximum size;

Note that there are a couple of differences from the modified code of your link: most importantly, rewriting the stylesheet doesn't make a lot of sense, and setting the margin creates an issue with the value returned by frameWidth() (that's why they subtracted 100 from the document height); that is certainly not a good choice, as the margin should be set within the layout.

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())
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文