QLabel setMinimumHeight After Custom WordWrap Qt.TextWrapAnywhere PyQt5(完全响应With/Without Emoji)

QLabel setMinimumHeight After Custom WordWrap Qt.TextWrapAnywhere PyQt5 ( Full responsive With/Without Emoji )

我想要 Qt.TextWrapAnywhere 用于布局中的 QLabel。

我跟着This instruction.My 代码也是一样的给一个最小的代码

from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPainter
from PyQt5.QtWidgets import QApplication, QLabel, QMainWindow, QStyleOption, QVBoxLayout, QWidget, QStyle

class SuperQLabel(QLabel):
    def __init__(self, *args, **kwargs):
        super(SuperQLabel, self).__init__(*args, **kwargs)

        self.textalignment = Qt.AlignLeft | Qt.TextWrapAnywhere
        self.isTextLabel = True
        self.align = None

    def paintEvent(self, event):

        opt = QStyleOption()
        opt.initFrom(self)
        painter = QPainter(self)

        self.style().drawPrimitive(QStyle.PE_Widget, opt, painter, self)

        self.style().drawItemText(painter, self.rect(),
                                  self.textalignment, self.palette(), True, self.text())


class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        self.resize(100, 200)

        self.label = QLabel()
        self.label.setWordWrap(True)
        self.label.setText("11111111111111111111\n2222222211111111")

        self.slabel = SuperQLabel()
        self.slabel.setMinimumWidth(10)
        self.slabel.setText("111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111")

        self.centralwidget = QWidget()
        self.setCentralWidget(self.centralwidget)

        self.mainlayout = QVBoxLayout()
        self.mainlayout.addWidget(self.label)
        self.mainlayout.addWidget(self.slabel)

        self.centralwidget.setLayout(self.mainlayout)


if __name__ == "__main__":
    import sys
    app = QApplication(sys.argv)
    w = MainWindow()
    w.show()
    sys.exit(app.exec_())

我稍微更改了代码 self.slabel.setMinimumWidth(10) 否则根据宽度调整标签大小将不起作用。

根据 width.But 完美地包装文本问题是在考虑高度时 self.label = QLabel() 正常 QLabel 自动调整高度根据满足于布局。

例如,如果我添加一个 \n 和意味着 Qlabel 必须显示 2 行的文本。

但是有了这个新的自定义标签,例如self.slabel = SuperQLabel() 只要在布局中有 space 的高度,包装就很好。 我想我必须使用 setminimumHeight() 但不知道如何在自定义包装后获得正确的高度。

经过一些研究,我成功修复了它。 有窍门

这是完全响应式 With/Without 表情符号

from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPainter,QFontMetrics,QFont
from PyQt5.QtWidgets import QApplication, QLabel, QMainWindow, QStyleOption, QVBoxLayout, QWidget, QStyle
import math
class SuperQLabel(QLabel):
    def __init__(self, *args, **kwargs):
        super(SuperQLabel, self).__init__(*args, **kwargs)

        self.textalignment = Qt.AlignLeft | Qt.TextWrapAnywhere
        self.isTextLabel = True
        self.align = None

    def paintEvent(self, event):

        opt = QStyleOption()
        opt.initFrom(self)
        painter = QPainter(self)

        self.style().drawPrimitive(QStyle.PE_Widget, opt, painter, self)

        self.style().drawItemText(painter, self.rect(),
                                  self.textalignment, self.palette(), True, self.text())
        
        fm=QFontMetrics(self.font())
        #To get unicode in Text if using Emoji(Optional)
        string_unicode = self.text().encode("unicode_escape").decode()
        ##To remove emoji/unicode from text while calculating
        string_encode = self.text().encode("ascii", "ignore")
        string_decode = string_encode.decode()
        #If Unicode/Emoji is Used
        if string_unicode.count("\U0001") > 0:
            height=fm.boundingRect(self.rect(),Qt.TextWordWrap,string_decode).height()+1
            # +1 is varrying according to Different font .SO set different value and test.
        else:
            height=fm.boundingRect(self.rect(),Qt.TextWordWrap,string_decode).height()
        row=math.ceil(fm.horizontalAdvance(self.text())/self.width())
        self.setMinimumHeight(row*height)
class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        self.resize(100, 200)

        self.label = QLabel()
        self.label.setWordWrap(True)
        self.label.setStyleSheet("background:red;")
        self.label.setText("11111111111111111111\n2222222211111111")
        self.emoji_font = QFont("Segoe UI Emoji",15,0,False)
        self.emoji_font.setBold(True)
        self.slabel = SuperQLabel()
        self.slabel.setMinimumWidth(10)
        self.slabel.setStyleSheet("background:green;")
        self.slabel.setFont(self.emoji_font)
        ########### Plain Text ######################
        # self.slabel.setText("111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111")
        ####################    Or Using Emoji ############
        self.slabel.setText("111111111111111111111ABCDDWAEQQ1111111111111111111111111111wqewqgdfgdfhyhtyhy11111111111111111111111111111111111111")
        
        self.centralwidget = QWidget()
        self.setCentralWidget(self.centralwidget)

        self.mainlayout = QVBoxLayout()
        self.mainlayout.addWidget(self.label)
        self.mainlayout.addWidget(self.slabel)

        self.centralwidget.setLayout(self.mainlayout)


if __name__ == "__main__":
    import sys
    app = QApplication(sys.argv)
    w = MainWindow()
    w.show()
    sys.exit(app.exec_())

只要标签显示在滚动区域(不会对顶层布局造成问题),更好的解决方案是使用 QTextEdit 子类,配置如下:

  • readOnly 必须为真;
  • 滚动条被禁用;
  • 垂直尺寸政策必须是 Preferred(而不是 Expanding);
  • minimumSizeHint()sizeHint() 都应该使用内部 QTextDocument 来 return 适当的高度,最小默认宽度;
  • 任何大小或内容的变化都必须触发updateGeometry(),这样父布局就会知道提示已经改变并且可以再次计算几何图形;
  • 提示必须包括可能的滚动区域装饰(QFrame);

这允许避免 paintEvent() 覆盖,并由于 QTextDocument 提供的功能提供更好和更容易的大小机制实现,同时将递归的可能性降至可接受的水平。

class WrapLabel(QtWidgets.QTextEdit):
    def __init__(self, text=''):
        super().__init__(text)
        self.setStyleSheet('''
            WrapLabel {
                border: 1px outset palette(dark);
                border-radius: 8px;
                background: palette(light);
            }
        ''')
        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):
        doc = self.document().clone()
        doc.setTextWidth(self.viewport().width())
        height = doc.size().height()
        height += self.frameWidth() * 2
        return QtCore.QSize(50, height)

    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.messages = []

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

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

        for i in range(1, 11):
            QtCore.QTimer.singleShot(1000 * i, lambda:
                self.addMessage('1' * randrange(100, 250)))

    def addMessage(self, text):
        self.widget().layout().addWidget(WrapLabel(text))
        QtCore.QTimer.singleShot(0, self.scrollToBottom)

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

更新:HTML 和 QTextDocument

当使用 setHtml()setDocument() 时,源可能有 pre 格式的文本,不允许换行。为避免这种情况,有必要遍历所有 QTextBlocks of the document, get their QTextBlockFormat, check the nonBreakableLines() property and eventually set it to False and set the format back with a QTextCursor.

class WrapLabel(QtWidgets.QTextEdit):
    def __init__(self, text=None):
        super().__init__()
        if isinstance(text, str):
            if Qt.mightBeRichText(text):
                self.setHtml(text)
            else:
                self.setPlainText(text)
        elif isinstance(text, QtGui.QTextDocument):
            self.setDocument(text)
        # ...

    def setHtml(self, html):
        doc = QtGui.QTextDocument()
        doc.setHtml(html)
        self.setDocument(doc)

    def setDocument(self, doc):
        doc = doc.clone()
        tb = doc.begin() # start a QTextBlock iterator
        while tb.isValid():
            fmt = tb.blockFormat()
            if fmt.nonBreakableLines():
                fmt.setNonBreakableLines(False)
                # create a QTextCursor for the current text block,
                # then set the updated format to override the wrap
                tc = QtGui.QTextCursor(tb)
                tc.setBlockFormat(fmt)
            tb = tb.next()
        super().setDocument(doc)

但是请注意,当使用具有预定义宽度或最小宽度的对象时,这还不够:图像和表格。结果将是,如果对象大于可用 space,它将在右侧裁剪(或从 RightToLeft 文本布局的左侧裁剪)。