QCheckbox/QRadioButton line wrap
Asked Answered
B

5

12

I'm trying to have a multi-line checkbox/radiobutton with Qt using standard QCheckbox/QRadioButton.

I didn't find the direct solution since QRadioButton{wrap:true;} has no effect. The only thing possible would be to access to the QRadioButton->label->setLineWrap(true) but

  1. I'd like to do that from the designer
  2. not having to rewrite a widget

Any idea beside putting a QRadioButton and a QLabel next to each others?

Thx, Boris.

Butterwort answered 3/12, 2009 at 10:55 Comment(0)
F
12

That indeed is really annoying and cannot be solved without reimplementation: the label uses Qt::PlainText if I'm not mistaken. In our projects, the UI team solved this with two approaches.

Using the QRadioButton button as a title

Layout using the QRadioButton as a title

Rewrite the QRadioButton text in such a fashion that it has only a brief description of the option. Put a QLabel underneath it and add a longer description. We use a smaller font and a little indentation to make it look neat. This is used frequently in Mac OS X, for example.

Removing the text from the radio button

Layout using buddy labels

Relayout the UI so that each radio button is put on the left of a QLabel. Add the whole text to the QLabel and set the label as the radio buttons' buddy. This is the least functional approach, because clicking the label does not check the radio button. Also, alignment is not very good.

Frick answered 6/12, 2009 at 16:23 Comment(2)
Thanks for your answer. I solved my issue with the 2nd solution you explained. This is not very good in the layout but works... I really hope Qt will solve this issue in a close future! Thanks again anyway.Butterwort
It's possible to implement this in a clean way by handling the resizeEvent within a custom QRadioButton subclass, and to perform the necessary line wrapping there. See my answer for details.Nathannathanael
S
4

The only way I know (but it's not "layoutable") is putting \n sign into a string.

Strohben answered 3/12, 2009 at 14:23 Comment(0)
N
4

The problem

There unfortunately is no short and simple solution for this. There's a Qt feature request about this (see QTBUG-5370), but it's pending since 2009 (!), so it probably will never be implemented.

However, it's actually possible to implement this by overriding the QRadioButton::resizeEvent and to perform the necessary line wrapping there. In addition to that, we must also adjust the horizontal size policy and override the minimum size hint so that the layout of the radio button will be correct.

The complete implementation is given below.

LineWrappedRadioButton.h

#include <QRadioButton>

class LineWrappedRadioButton : public QRadioButton {
    Q_OBJECT
private:
    void wrapLines(int width);
protected:
    virtual void resizeEvent(QResizeEvent *event);
public:
    LineWrappedRadioButton(QWidget *parent = nullptr) : LineWrappedRadioButton(QString(), parent) { }
    LineWrappedRadioButton(const QString &text, QWidget *parent = nullptr);
    virtual QSize minimumSizeHint() const { return QSize(QRadioButton().minimumSizeHint().width(), sizeHint().height()); }
};

LineWrappedRadioButton.cpp

#include "LineWrappedRadioButton.h"
#include <QRadioButton>
#include <QResizeEvent>
#include <QStyle>

void LineWrappedRadioButton::wrapLines(int width) {
    QString word, line, result;
    for (QChar c : text().replace('\n', ' ') + ' ') {
        word += c;
        if (c.isSpace()) {
            if (!line.isEmpty() && fontMetrics().width(line + word.trimmed()) > width) {
                result += line.trimmed() + '\n';
                line = word;
            } else {
                line += word;
            }
            word.clear();
        }
    }
    result += line.trimmed();
    setText(result.trimmed());
}

void LineWrappedRadioButton::resizeEvent(QResizeEvent *event) {
    int controlElementWidth = sizeHint().width() - style()->itemTextRect(fontMetrics(), QRect(), Qt::TextShowMnemonic, false, text()).width();
    wrapLines(event->size().width() - controlElementWidth);
    QRadioButton::resizeEvent(event);
}

LineWrappedRadioButton::LineWrappedRadioButton(const QString &text, QWidget *parent) : QRadioButton(text, parent) {
    QSizePolicy policy = sizePolicy();
    policy.setHorizontalPolicy(QSizePolicy::Preferred);
    setSizePolicy(policy);
    updateGeometry();
}

main.cpp

#include "LineWrappedRadioButton.h"
#include <QApplication>

int main(int argc, char **argv) {
    QApplication app(argc, argv);
    LineWrappedRadioButton button("Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua.");
    button.show();
    return app.exec();
}

This will create a freely resizable window with a line wrapped radio button in it:

LineWrappedRadioButton screenshot

Nathannathanael answered 7/2, 2023 at 22:3 Comment(0)
L
3

I succeeded adding a layout and a label as a child for the radio button, and changing the vertical size policy to Preferred (instead of Fixed).

Unfortunately this didn't automatically make it react to the mouse hovers and clicks like the native label, but watch a hack: I setStyleSheet("border:none") for the radio button and it started working. Maybe that's some feature happening behind the scenes; I'd love to have some certainty.

And the indicator can be aligned using "QRadioButton::indicator{subcontrol-position:top left}" <- http://doc.trolltech.com/qq/qq20-qss.html

Lytton answered 29/12, 2009 at 11:46 Comment(0)
K
1

My solution:

#ifndef CHECKBOX_H
#define CHECKBOX_H

#include <QCheckBox>

#include <QHBoxLayout>
#include <QLabel>

class CheckBox : public QCheckBox
{
    Q_OBJECT
public:
   explicit CheckBox(QWidget *parent = 0);

   void setText(const QString & text);
   QSize sizeHint() const;
   bool hitButton(const QPoint &pos) const;

protected:
   void paintEvent(QPaintEvent *);

private:
   QHBoxLayout* _layout;
   QLabel*      _label;
};

#endif // CHECKBOX_H




#include "checkbox.h"

#include <QStylePainter>
#include <QStyleOption>

#define MARGIN 4 // hardcoded spacing acording to QCommonStyle implementation

CheckBox::CheckBox(QWidget *parent) : QCheckBox(parent)
{
    setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred, QSizePolicy::CheckBox));

    QStyleOptionButton opt;
    initStyleOption(&opt);

    QRect label_rect = style()->subElementRect(QStyle::SE_CheckBoxContents, &opt, this);

    _label = new QLabel(this);
    _label->setWordWrap(true);
    _label->setMouseTracking(true);
    //_label->setMinimumHeight(label_rect.height());

    _layout = new QHBoxLayout(this);
    _layout->setContentsMargins(label_rect.left()+MARGIN, MARGIN/2, MARGIN/2, MARGIN/2);
    _layout->setSpacing(0);
    _layout->addWidget(_label);

    setLayout(_layout);
}

void CheckBox::setText(const QString & text)
{
    _label->setText(text);
    QCheckBox::setText(text);
}

void CheckBox::paintEvent(QPaintEvent *)
{
    QStylePainter p(this);
    QStyleOptionButton opt;
    initStyleOption(&opt);

    QStyleOptionButton subopt = opt;
    subopt.rect = style()->subElementRect(QStyle::SE_CheckBoxIndicator, &opt, this);
    subopt.rect.moveTop(opt.rect.top()+MARGIN/2); // align indicator to top

    style()->proxy()->drawPrimitive(QStyle::PE_IndicatorCheckBox, &subopt, &p, this);

    if (opt.state & QStyle::State_HasFocus)
    {
        QStyleOptionFocusRect fropt;
        fropt.QStyleOption::operator=(opt);
        fropt.rect = style()->subElementRect(QStyle::SE_CheckBoxFocusRect, &opt, this);
        style()->proxy()->drawPrimitive(QStyle::PE_FrameFocusRect, &fropt, &p, this);
    }
}

QSize CheckBox::sizeHint() const
{
    return QSize(); // will be calculated by layout
}

bool CheckBox::hitButton(const QPoint &pos) const
{
    QStyleOptionButton opt;
    initStyleOption(&opt);
    return opt.rect.contains(pos); // hit all button
}
Ketose answered 3/9, 2015 at 13:13 Comment(1)
Beginning of the solution is quite fine. However, calculation algorithm calculating focus rectangle should be improved (i.e. "fropt.rect = style()->subElementRect(QStyle::SE_CheckBoxFocusRect, &opt, this);"), since focus rectangle will mess up with the multi-line text, not surround it. To ensure nice focus rectangle will get drawn by the system for such issue, you need to set proper height for such rectangle, since multi-line text has a varying height.Eckenrode

© 2022 - 2024 — McMap. All rights reserved.