Как заставить текст в QLabel перепрыгивать на новую строку, если ему не хватает места?

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

  • Главный контейнер - main_box. Включает:
    • color_box - цветной контейнер;
    • scroll_box - контейнер, связанный с областью прокрутки. Включает:
      • Три цветных контейнера little_box;
      • box_h - горизонтальный контейнер. Включает:
        • box_v1 - вертикальный контейнер;
        • box_v2 - вертикальный контейнер.

В box_v1 находится picture - изображение с закругленными краями. Его получаю с помощью класса RoundArt.

В box_v2 находятся две надписи - label1 и label2.

введите сюда описание изображения

label2 нужна, чтобы сохранить форму макета, никак задействовать ее не нужно.

А вот label1 хотелось бы полностью переделать. Мне нужно, чтобы текст внутри label1 переходил на новую строку, если окно приложения становится слишком маленьким. Есть несколько условий:

  1. Если это возможно, текст располагается на одной строке.
  2. Если места становится слишком мало, текст частично переползает на вторую строку, пока ему это необходимо.
  3. Текст может располагаться максимум на двух строках. На третью переходить он не должен.
  4. Если двух строк становится мало, приложение должно обрезать текст тремя точками:

Было - looooong text >>> Стало - looo...

^^^ Текст стал непозволительно длинным, и приложение обрезало его

введите сюда описание изображения

введите сюда описание изображения


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

Для меня это очень сложно, но я не знаю, насколько это трудоемкая задача на самом деле. Может быть, это делается вручную на сотни строк кода, а может быть, для этого существует специальный виджет? Из-за этого я даже не знаю, насколько корректный задал вопрос. Возможно, я сейчас слишком много хочу.


Есть две причины, которые объясняют, почему мне это нужно.

Первая причина довольно очевидная - я хочу, чтобы текст было лучше видно.

Вторая причина заключается в том, что вместе с текстом становится не видно надписи в контейнерах little_box. Эти надписи я специально прижимаю к правому боку. Я думаю, что, когда для надписи label1 становится слишком мало места, перестает масштабироваться весь контейнер scroll_box, хотя контейнеры little_box еще могут уменьшаться.

Я немного потерялся и мог забыть что-нибудь очень важное. Пожалуйста, дайте знать, если что-то не объяснил или объяснил плохо

Пожалуйста, помогите мне.

from PyQt5 import QtCore, QtWidgets, QtGui

class RoundArt(QtWidgets.QLabel):
    clicked = QtCore.pyqtSignal()

    def __init__(self, picture, x, *args, **kwargs):
        super(RoundArt, self).__init__(*args, **kwargs)
        self.setFixedSize(x, x)
        self.x = x
        self.radius = 10 
        self.setPicture(picture)

    def setPicture(self, picture):
        target = QtGui.QPixmap(self.size())
        target.fill(QtCore.Qt.transparent)
        p = QtGui.QPixmap(picture).scaled(
            self.x, self.x, QtCore.Qt.KeepAspectRatioByExpanding,
            QtCore.Qt.SmoothTransformation
        )
        painter = QtGui.QPainter(target)
        painter.setRenderHint(QtGui.QPainter.Antialiasing, True)
        painter.setRenderHint(QtGui.QPainter.HighQualityAntialiasing, True)
        painter.setRenderHint(QtGui.QPainter.SmoothPixmapTransform, True)
        path = QtGui.QPainterPath()
        path.addRoundedRect(0, 0, self.width(), self.height(), self.radius, self.radius)
        painter.setClipPath(path)
        painter.drawPixmap(0, 0, p)
        self.setPixmap(target)
        painter.end()
        target = None

    def mouseReleaseEvent(self, event):
        self.clicked.emit()


class MyWindow(QtWidgets.QWidget):
    def __init__(self, parent = None):
        super().__init__(parent)
        self.resize(900, 600)

        main_box = QtWidgets.QGridLayout(self)

        scrollArea = QtWidgets.QScrollArea()
        container = QtWidgets.QWidget()
        scrollArea.setWidget(container)
        scrollArea.setWidgetResizable(True)
        scrollArea.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        scroll_box = QtWidgets.QVBoxLayout(container)
        main_box.addWidget(scrollArea, 0, 0)

        container = QtWidgets.QWidget()
        container.setFixedHeight(220)
        scroll_box.addWidget(container)
        box_h = QtWidgets.QHBoxLayout(container)

        picture = RoundArt('Fc414959288ga866.png', 200)
        box_v1 = QtWidgets.QVBoxLayout()
        box_v1.addWidget(picture)
        box_h.addLayout(box_v1)
        box_h.addSpacing(10)

        box_v2 = QtWidgets.QVBoxLayout()
        box_v2.addSpacing(10)

        label1 = QtWidgets.QLabel('very looooooooooooooong label')
        label1.setStyleSheet(qss_lb)
        label1.setFixedHeight(30)
        box_v2.addWidget(label1)
        box_v2.addSpacing(20)

        label2 = QtWidgets.QLabel('label')
        label2.setStyleSheet(qss_lb)
        label2.setFixedHeight(30)
        box_v2.addWidget(label2)
        box_v2.addSpacing(10)

        box_h.addLayout(box_v2)

        container = QtWidgets.QWidget()
        container.setStyleSheet('background: #3A4256;')
        container.setFixedWidth(200)
        main_box.addWidget(container, 0, 1)
        color_box = QtWidgets.QVBoxLayout(container)

        label = QtWidgets.QLabel('color box')
        label.setStyleSheet(qss_lb)
        color_box.addWidget(label, alignment = QtCore.Qt.AlignCenter)

        for i in range(0, 3):
            container = QtWidgets.QWidget()
            container.setStyleSheet('background: #3A4256;')
            container.setFixedHeight(50)
            little_box = QtWidgets.QHBoxLayout(container)
            label = QtWidgets.QLabel('label')
            little_box.addWidget(label, alignment = QtCore.Qt.AlignRight)
            scroll_box.addWidget(container)


qss_lb = '''QLabel {
                font: bold 25px;
            }'''

if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    window = MyWindow()
    window.setWindowTitle(' ')
    window.show()
    sys.exit(app.exec_())

Ответы (1 шт):

Автор решения: S. Nick

В общем, вам надо изучить:

QFontMetricsF Class -> https://doc.qt.io/qt-5/qfontmetricsf.html

from PyQt5 import QtCore, QtWidgets, QtGui

class RoundArt(QtWidgets.QLabel):
    clicked = QtCore.pyqtSignal()

    def __init__(self, picture, x, *args, **kwargs):
        super(RoundArt, self).__init__(*args, **kwargs)
        self.setFixedSize(x, x)
        self.x = x
        self.radius = 10 
        self.setPicture(picture)

    def setPicture(self, picture):
        target = QtGui.QPixmap(self.size())
        target.fill(QtCore.Qt.transparent)
        p = QtGui.QPixmap(picture).scaled(
            self.x, self.x, QtCore.Qt.KeepAspectRatioByExpanding,
            QtCore.Qt.SmoothTransformation
        )
        painter = QtGui.QPainter(target)
        painter.setRenderHint(QtGui.QPainter.Antialiasing, True)
        painter.setRenderHint(QtGui.QPainter.HighQualityAntialiasing, True)
        painter.setRenderHint(QtGui.QPainter.SmoothPixmapTransform, True)
        path = QtGui.QPainterPath()
        path.addRoundedRect(0, 0, self.width(), self.height(), self.radius, self.radius)
        painter.setClipPath(path)
        painter.drawPixmap(0, 0, p)
        self.setPixmap(target)
        painter.end()
        target = None

    def mouseReleaseEvent(self, event):
        self.clicked.emit()


class MyWindow(QtWidgets.QWidget):
    def __init__(self, parent = None):
        super().__init__(parent)
        self.resize(900, 600)

        main_box = QtWidgets.QGridLayout(self)

        scrollArea = QtWidgets.QScrollArea()
        container = QtWidgets.QWidget()
        scrollArea.setMinimumWidth(390)                                               # +++
        scrollArea.setMinimumHeight(235)                                              # +++

        scrollArea.setWidget(container)
        scrollArea.setWidgetResizable(True)
        scrollArea.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        scroll_box = QtWidgets.QVBoxLayout(container)
        main_box.addWidget(scrollArea, 0, 0)

        container = QtWidgets.QWidget()
        container.setFixedHeight(220)
        scroll_box.addWidget(container)
        box_h = QtWidgets.QHBoxLayout(container)

        picture = RoundArt('head3.png', 200)
        box_v1 = QtWidgets.QVBoxLayout()
        box_v1.addWidget(picture)
        box_h.addLayout(box_v1)
        box_h.addSpacing(10)

        box_v2 = QtWidgets.QVBoxLayout()
        box_v2.addSpacing(10)


# +++ vvv
        self.text = 'я не могу сказать про максимум, т.к. текст излекается из тегов MP3-файла, '\
                    'а в тегах может находится строка любой длины.'

        self.label1 = QtWidgets.QLabel(self.text)                                # +++ text
        self.label1.setWordWrap(True)                                            # +++    
#        self.label1.setStyleSheet(qss_lb)
        self.label1.resize(418, 55)                                              # +++
        self.label1.setFixedHeight(55)                                           # 30 -> 55
        self.label1.setMinimumWidth(130)                                         # +++
        self.label1.setFont(QtGui.QFont("Consolas", 20, QtGui.QFont.Bold))       # +++
# +++ ^^^        
        box_v2.addWidget(self.label1)
        box_v2.addSpacing(20)

        label2 = QtWidgets.QLabel('label')
        label2.setStyleSheet(qss_lb)
        label2.setFixedHeight(30)
        box_v2.addWidget(label2)
        box_v2.addSpacing(10)

        box_h.addLayout(box_v2)

        container = QtWidgets.QWidget()
        container.setStyleSheet('background: #3A4256;')
        container.setFixedWidth(200)
        main_box.addWidget(container, 0, 1)
        color_box = QtWidgets.QVBoxLayout(container)

        label = QtWidgets.QLabel('color box')
        label.setStyleSheet(qss_lb)
        color_box.addWidget(label, alignment = QtCore.Qt.AlignCenter)

        for i in range(0, 3):
            container = QtWidgets.QWidget()
            container.setStyleSheet('background: #3A4256;')
            container.setFixedHeight(50)
            little_box = QtWidgets.QHBoxLayout(container)
            label = QtWidgets.QLabel('label')
            little_box.addWidget(label, alignment = QtCore.Qt.AlignRight)
            scroll_box.addWidget(container)

# ++ vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
    def resizeEvent(self, event):
        super(MyWindow, self).resizeEvent(event)
        w = self.label1.size().width()

        # https://doc.qt.io/qt-5/qfontmetricsf.html#averageCharWidth
        # Возвращает среднюю ширину глифов в шрифте.
        fw = self.label1.fontMetrics().averageCharWidth() 

        x = w * 2 // fw - 12                        # 12 - какое-то корректирующее число

        self.label1.setText(self.text[:x] + ' ...')

# ++ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^


qss_lb = '''QLabel {
                font: bold 23px;
            }'''

if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    window = MyWindow()
    window.setWindowTitle(' ')
    window.show()
    sys.exit(app.exec_())

введите сюда описание изображения


Update

Есть одна деталь - три точки ... никуда не уходят, если тексту хватает места. Я забыл уточнить это в вопросе. Буду очень рад узнать, как это поправить, если вы знаете решение, и вам не трудно.

Замените:

self.label1.setText(self.text[:x] + ' ...')

на что-то такое:

    if (w * 2 // fw) > (len(self.text) ):       
        self.label1.setText(self.text)
    else:
        self.label1.setText(self.text[:x] + ' ...')
→ Ссылка