I want Qt.TextWrapAnywhere for my QLabel in a Layout.
I followed This instruction.My code is also same to give a minimal code
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_())
I changed little bit that code self.slabel.setMinimumWidth(10) otherwise resizing Label according to width wont work.
It is perfectly wrapping the text according to width.But the Problem is when height is considered self.label = QLabel() Normal QLabel auto adjust height according to content with layout.
For example if i add one \n with text that means Qlabel must show 2 lines.
But with this new Custom Label e.g.self.slabel = SuperQLabel() wrapping is good as long as there is space for height in layout. I think i have to use setminimumHeight() but dont know how to get proper height after custom wrapping.
As long as the label is shown in a scroll area (which will not create issues with the top level layout), a better solution is to use a QTextEdit subclass, with the following configuration:
readOnly
must be True;Preferred
(and not Expanding
);minimumSizeHint()
and sizeHint()
should use the internal QTextDocument to return a proper height, with a minimum default width;updateGeometry()
so that the parent layout will know that the hint has changed and geometries could be computed again;This allows avoiding the paintEvent()
override, and provide a better and easier implementation of the size mechanism due to the features provided by QTextDocument, while minimizing the possibility of recursion to an acceptable level.
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())
When using setHtml()
and setDocument()
, the source could have pre
formatted text that doesn't allow wrapping. To avoid that, it's necessary to iterate through all 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)
Be aware, though, that this could not be enough whenever objects with predefined or minimum width are used: images and tables. The result will be that if the object is larger than the available space, it will be cropped on its right (or left for RightToLeft text layouts).