PySide2 QTextEdit не адаптируется к собственному содержимому при использовании переноса. (Делаем окно чата)

PySide2 QTextEdit не меняет свой размер при размещении текста.

Я пытаюсь создать что-то вроде окна чата, где каждое сообщение - QTextEdit в режиме OnlyRead. Все «сообщения» помещаются в QScrollArea. Основная цель состоит в том, чтобы позволить окнам сообщений (окнам сообщений, как на экране ниже) подстраивать свой размер под контент. неправильный рабочий пример

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

который был скопирован много раз. Но это не делает то, что я хочу. Он создает фиксированные, не изменяемые по размеру окна сообщений QTextEdit.

Например, что я на самом деле имею в виду, если у нас есть сообщение из одного слова, виджет QTextEdit должен стать рамкой с одним штрихом с шириной сообщения. Если у нас есть сообщение, состоящее из нескольких предложений, виджет QTextEdit должен стать многострочным полем (уже расширенным по высоте, без необходимости прокручивать его внутри) с максимальной постоянной длиной (которую я выберу).

Далее пример с правильным отображением сообщений (хороший пример)

Что вы подразумеваете под «изменяемым размером»? Код, который вы предоставляете (который, кстати, полностью основан на мой собственный ответ), уже делает это: он изменяет размер содержимого на основе текста и доступного размера и, в конечном итоге, переносит текст, когда это необходимо.

musicamante 10.05.2022 23:34

@musicamante Финнали, я встретил тебя, автор примера. Пожалуйста, помогите с идеями т_т. Я на самом деле не понимаю, как QTextEdit может приспосабливаться к содержимому. В вашем примере QTextEdit просто добавляет ScrollArea и размеры к ширине всего окна, но мне нужно прекратить изменение размера сообщения, когда оно достигает 100 пикселей (например), и перенести слово на следующую строку, поэтому сообщение должно выглядеть как прямоугольник, который не расширяется на всю ширину окна и не превышает ширину 100 пикселей. Но когда у меня есть небольшое сообщение, которое будет меньше 100 пикселей, QTextEdit должен стать меньше, как и ширина сообщения.

Otaku 11.05.2022 01:44

@musicamante Где я могу найти информацию о переопределении функций MinimumSizeHint, SizeHint и ResizeEvent?

Otaku 11.05.2022 01:53

Если ваше единственное требование — ограничить ширину до 100 пикселей, измените возвращаемое значение minimumSizeHint() на return QtCore.QSize(100, height). Вся необходимая информация есть в документации: minimumSizeHint(), sizeHint() и resizeEvent(). Помните, что это все методы виртуальный, которые могут быть переопределены в подклассах, и каждый класс может переопределить их (что и происходит для QTextEdit и унаследованного QAbstractScrollArea).

musicamante 11.05.2022 02:51

Имейте в виду, что, как объясняется в комментариях к исходному вопросу, существуют важные проблемы, связанные с изменением размера элемента в соответствии с его содержимым, когда соотношение сторон содержимого может измениться на лету. Это обычная проблема с менеджерами компоновки (не только для Qt), поскольку размер намеки может меняться в зависимости от брат или сестра и родительских элементов (что происходит с областями прокрутки из-за того, что полосы прокрутки могут изменять размер внутренней области). видового экрана и в конечном итоге вызвать рекурсию). Не существует «окончательного» решения, вы должны найти свой собственный обходной путь в зависимости от ваших потребностей.

musicamante 11.05.2022 02:55

@musicamante большое спасибо за ваши ответы! Я попытался изменить возвращаемое значение MinimumSizeHint, но это не сработало. Все, что я могу сделать, это увеличить значение ширины, но не уменьшить. Он всегда остается на ширине окна и никогда не уменьшается.

Otaku 11.05.2022 15:26

@musicamante не знаю, но иногда мои комментарии пропадают. Кстати, я хотел сказать спасибо за вашу помощь. Я действительно ценю это. (у меня есть еще один вопрос. как я понял, нет возможности добавлять поля внутри QTextEdit из-за изменения его размера? может быть, мне следует поместить QTextEdit во фрейм и добавить поля в фрейм?)

Otaku 12.05.2022 04:35

Если вы хотите добавить поля, вы либо делаете это (и правильно) в таблице стилей, либо устанавливаете их для редактирования текста textDocument() через setDocumentMargin(). Обратите внимание, что добавление полей с таблицами стилей может изменить результат minimumSizeHint(), как объясняется в моем ответе.

musicamante 12.05.2022 05:29

@musicamante еще раз большое спасибо!!! он снова работает))) (извините за пинг в комментариях. я на самом деле не могу задавать вопросы за 4 дня на stackoverflow Т-Т. кстати, я хочу задать вам вопрос в последний (надеюсь) раз. можно ли создать прозрачное окно с эффектом размытия рабочего стола? Я пытался использовать QGraphicsBlurEffect с прозрачностью, но только содержимое окна приложения будет размыто вместо рабочего стола под приложением. Это действительно возможно сделать? (еще раз большое спасибо !!) )

Otaku 12.05.2022 19:50

Не напрямую из Qt, так как это полностью зависит от ОС, но см. этот github.com/Peticali/PythonBlurBehind

musicamante 12.05.2022 20:17

@musicamante омг. Это золотой самородок! На самом деле, это делает то, что я хочу. (есть некоторое отставание, но все равно отлично!) Большое спасибо!

Otaku 12.05.2022 22:33

Пожалуйста, не используйте комментарии для вопросов не по теме. Если вы еще не можете задавать новые вопросы, пожалуйста, подождите, пока не сможете. Кстати, я не могу ответить, просто взглянув на код, так как я не знаю, в чем вопрос.

musicamante 14.05.2022 20:14
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
12
46
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Чтобы реализовать самонастраивающееся сообщение, требуются некоторые меры предосторожности.

Как объяснялось в моем ответе и комментариях к связанный пост, вы должны учитывать сложную и тонкую связь между размерами макета и его требованиями, которая становится еще более сложной, поскольку макет текста не имеет фиксированного соотношения.

Основная проблема с установкой ширины на основе текста заключается в том, что он может изменить высоту, необходимую для его отображения, что может вызвать рекурсию, а поскольку размер основан на размере окна просмотра Текущий, результатом будет то, что minimumSizeHint() всегда будет возвращаться наименьший возможный размер после определенного количества рекурсивных вызовов.

Учитывая вышеизложенное, мы должны внести следующие изменения в исходный код:

  • область прокрутки всегда должна устанавливать максимальную ширину для своих виджетов сообщений, возможно, с указанным полем (для различия отправленных и полученных) всякий раз, когда изменяется размер представления;
  • виджеты должны быть добавлены в макет с аргументом выравнивание;
  • minimumSizeHint() необходимо заменить на:
    1. вычислить ширину текста предпочтительный (idealWidth() на основе максимального размера виджета;
    2. получить опорную высоту для этой ширины текста;
    3. установить ширину текста на ширину Текущий;
    4. сравниваем новую высоту документа с предыдущей, если они совпадают, значит, мы можем использовать новую ширину как максимальную ширину для подсказки (текст может быть короче), в противном случае мы используем исходную ширину текста, исходя из максимального размера ;

Обратите внимание, что есть несколько отличий от измененного кода вашей ссылки: самое главное, переписывание таблицы стилей не имеет большого смысла, а установка поля создает проблему со значением, возвращаемым 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())

Другие вопросы по теме