最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

qt - How to enable QLabel character wrapping(Not Word Wrapping) in QWidget C++? - Stack Overflow

programmeradmin3浏览0评论

I tried the word Wrap option in QLabel, but it wraps second word of "Hello World" on next line when size of label shrinks, I need to wrap the single word like "Hello" on character basis when size shrinks and when size expand it should be normal.

Is there is any option available to do this?

I tried the word Wrap option in QLabel, but it wraps second word of "Hello World" on next line when size of label shrinks, I need to wrap the single word like "Hello" on character basis when size shrinks and when size expand it should be normal.

Is there is any option available to do this?

Share Improve this question asked Mar 11 at 11:27 Adil AhmedAdil Ahmed 717 bronze badges 6
  • There is no such option. If you want this behavior you will have to implement your own QLabel subclass. – Botje Commented Mar 11 at 12:02
  • can you give me some hints, so I can try it to achieve this – Adil Ahmed Commented Mar 11 at 12:09
  • I don't have any hints to give beyond "go read the QLabel source code" – Botje Commented Mar 11 at 12:13
  • If text flags to be used, that's not QLabel, that's a QStaticText. QLabel source wouldn't help, it uses different and much simpler approach – Swift - Friday Pie Commented Mar 11 at 12:46
  • It is a weird behaviour you want to have, and you will need to code it yourself. The first idea I can think of is to loop on all characters of your string and measure each letter size in pixel (using QFontMetrics), probably need to count the letter-spacing somehow... and insert a newline character when the total width of the characters has the QLabel width. – ymoreau Commented Mar 11 at 13:32
 |  Show 1 more comment

3 Answers 3

Reset to default 2

The following is untested but will, I think, give you roughly what you're looking for.

class label: public QLabel {
  using super = QLabel;
public:
  explicit label (const QString &text, QWidget *parent = nullptr)
    : super(text, parent)
    {}
protected:
  virtual void paintEvent (QPaintEvent *event) override
    {
      QPainter painter(this);
      QTextOption opt;

      /*
       * Let the QPainter know it's allowed to wrap anywhere.
       */
      opt.setWrapMode(QTextOption::WrapAnywhere);
      painter.drawText(rect(), text(), opt);
    }
};

Premise: what you ask cannot be accomplished in a completely reliable way, especially when dealing with complex layouts. Layout management of widgets having variable content sizes is not easy to achieve, and text layout certainly falls in that category.

When using rich text content, QLabel automatically "installs" a private QWidgetTextControl on itself, used to manage the display and layout of rich text, by means of an internal QTextDocument which provides more text wrap options other than the common word wrap.

The creation of that text control can be enforced, even using plain text, by explicitly setting the textFormat property to Qt::RichText.

By doing this, we can access the internal QTextDocument through findChild() and set the wrapMode of the default QTextOption when necessary, which is primarily when painting happens.
A lucky benefit of forcing the wrap mode when painting is that, when that event is called, the resize has already happened, which makes it possible to also transparently use text interaction flags, also allowing text selection.

Note that the text control always resets the wrap mode when the contents change or the label is resized; this also affects the size hint, and would prevent resizing the label to a smaller width. While we could explicitly set a minimum width or set an Ignore size policy, it wouldn't be completely appropriate when used in a layout.

In order to work around this, we need to override both size hint functions of QWidget:

  • minimumSizeHint() should return an arbitrary minimum size, based on the font metrics; the smallest width and height that would theoretically allow all the text to be shown, with or without wrapping;
  • sizeHint() of QLabel is based on minimumSizeHint(), therefore we need to override it and return the default implementation (not our own); in this way the layout may know the preferred size of the label;

Here things become tricky, though: what would be a valid minimum size? In theory, it could be the maximum width of the narrower character in the text, so that "Hello world" could also be displayed as one letter per line, but what would be the minimum height? We cannot obviously use the line height multiplied the number of characters, otherwise the label will require too much vertical space that will probably be unnecessary. Since we don't know how much the label would be "stretched", nor how many lines some text would eventually required on those cases, we need an arbitrary height.

QLabel does that by choosing an arbitrary size based on the text contents (using an approximation of the golden ratio considering the font metrics), but we cannot use that. This means that an arbitrary minimum height must be decided, possibly based on individual cases.

In the following example, I just used a very simplistic approach: just return the width of the "M" letter, and the height of two lines. You may probably need to change that depending on the scenario, possibly providing some function that tells how many characters/lines should be considered for the minimum size hint.

I only use Qt from Python, and I implemented the above in that language, therefore I used an online AI conversion to help me getting an approximate (and not 100% valid) C++ code for the subclass:

class WrapEverywhereLabel : public QLabel {
    Q_OBJECT

public:
    WrapEverywhereLabel(QWidget *parent = nullptr) : QLabel(parent) {
        this->setTextFormat(Qt::RichText);
        this->_document = this->findChild<QTextDocument *>();
    }

private:
    QTextDocument *_document;

    void _ensureWrapMode() {
        QTextOption opt = this->_document->defaultTextOption();
        if (opt.wrapMode() != QTextOption::WrapAnywhere) {
            opt.setWrapMode(QTextOption::WrapAnywhere);
            this->_document->setDefaultTextOption(opt);
        }
    }

    QSize minimumSizeHint() const override {
        return fontMetrics().size(0, 'M\n');
    }

    QSize sizeHint() const override {
        return QLabel::minimumSizeHint();
    }

    void paintEvent(QPaintEvent *event) override {
        if (this->width() < QLabel::minimumSizeHint().width()) {
            _ensureWrapMode();
        }
        QLabel::paintEvent(event);
    }
};

Note: I am well aware of the ban on AI contents on StackOverflow. The above is intended only as a possible reference to write the real implementation, not to be used as is. I could've written some pseudo code, but it would've been a bit pointless.

For reference, this is how I implemented the above in Python:

class WrapEverywhereLabel(QLabel):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setTextFormat(Qt.RichText)
        self._document = self.findChild(QTextDocument)

    def _ensureWrapMode(self):
        opt = self._document.defaultTextOption()
        if opt.wrapMode() != QTextOption.WrapAnywhere:
            opt.setWrapMode(QTextOption.WrapAnywhere)
            self._document.setDefaultTextOption(opt)

    def minimumSizeHint(self):
        return self.fontMetrics().size(0, 'M\n')

    def sizeHint(self):
        return super().minimumSizeHint()

    def paintEvent(self, event):
        if self.width() < super().minimumSizeHint().width():
            self._ensureWrapMode()
        super().paintEvent(event)

Be aware that you may face some layout management issues, because there is no way for Qt to get an appropriate size hint when wrapping is enabled. This does not depend on my approach, but is a known Qt layout issue that cannot be fixed in arbitrary ways and depends on how windowed layout management works, which is also the reason for which it's difficult to get resizable widgets having a fixed aspect ratio.

If you set the wordWrap property of your QLabel to true, then it indeed looks like you can only have long sentences break between words.

This is easily solved with an old programming trick, i.e. inserting zero-width spaces between all the letters where you want to allow a line break.
This effectively turns single words into multiple words for your computer but as your are dealing with zero-zidth spaces, words that remain on 1 line appear completely normal to human beings looking at their screens.

There are rules as to where words can wrap to the next line but as for all rules, there are exceptions. Chances are you want to use an hyphenation dictionary so your program is aware of these exceptions.
If you find the dictionary to be too big (this one has about 380k words) and with a little bit of work, you could even:

  1. Implement the general rule.
  2. Browse all the words from the dictionary to filter out those that follow the rule.
  3. Include the remaining words into your program via a resource file.

All this works well for QLabel where you do not have to worry about copying text.

If copying text must be supported (e.g. with QTextEdit) you would probably want to either (untested):

  • Work with the zero-width spaces as described above and override createMimeDataFromSelection.
    This would be suitable if you want total control on where the line breaks can be done.
  • Stay standard by changing the wordWrapMode of your control.
发布评论

评论列表(0)

  1. 暂无评论