QCheckbox/QRadioButton换行

12
我正在尝试使用标准的QCheckbox/QRadioButton在Qt中实现多行复选框/单选按钮。
我没有找到直接的解决方案,因为QRadioButton{wrap:true;}没有效果。 唯一可能的方法是访问QRadioButton->label->setLineWrap(true),但是
  1. 我希望从设计师那里做到这一点
  2. 而不必重写一个小部件
除了将QRadioButton和QLabel放在一起之外,还有什么其他的想法吗?
谢谢,Boris。
5个回答

12

这确实很烦人,不能通过重新实现来解决:如果我没记错的话,该标签使用了Qt::PlainText。在我们的项目中,UI团队采用了两种方法来解决这个问题。

使用QRadioButton按钮作为标题

使用QRadioButton作为标题的布局

重新编写QRadioButton文本,使其仅包含选项的简要说明。在其下方添加一个QLabel并添加更长的说明。我们使用较小的字体和一些缩进来使它看起来整洁。例如,在Mac OS X中经常使用此方法。

从单选按钮中删除文本

使用伴侣标签进行布局

重新设计UI,使每个单选按钮放在一个QLabel的左边。将整个文本添加到QLabel中,并将该标签设置为单选按钮的伙伴。这是最不实用的方法,因为单击标签不会选中单选按钮。而且,对齐不是很好。


1
谢谢你的回答。我用你解释的第二种方法解决了我的问题。虽然布局不是很好,但它能够工作... 我真的希望Qt在不久的将来能够解决这个问题!无论如何还是非常感谢你。 - Boris Gougeon
1
可以通过在自定义的QRadioButton子类中处理resizeEvent并在那里执行必要的换行来以清晰的方式实现此操作。有关详细信息,请参见我的答案。 - emkey08

4
我知道的唯一方法(但它无法进行“布局”)是将 \n 符号放入字符串中。

4

问题

很遗憾,这个问题没有简短而简单的解决方案。有一个关于Qt的功能请求(请参见QTBUG-5370),但它已经挂起了13年了,所以它可能永远不会被实现。

因此,我们必须自己实现这个功能。清晰的方法是在自定义的QRadioButton子类中处理resizeEvent,以便在那里执行必要的换行操作。除此之外,您还必须使用setSizePolicy并覆盖单选按钮的minimumSizeHint

下面给出了LineWrappedRadioButton的完整实现。

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();
}

使用方法

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();
}

这将产生以下结果:

LineWrappedRadioButton screenshot


3
我成功地将一个布局和一个标签作为单选按钮的子级添加,并将垂直大小策略更改为首选(而不是固定)。
不幸的是,这并没有自动使它像本机标签一样响应鼠标悬停和点击,但是有个技巧:我为单选按钮设置了setStyleSheet("border:none"),然后它开始工作了。也许这是在幕后发生的一些功能;我很想有些确定性。
指示器可以使用 "QRadioButton::indicator{subcontrol-position:top left}" 进行对齐 <- http://doc.trolltech.com/qq/qq20-qss.html

1

我的解决方案:

#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
}

解决方案的开头很好。然而,计算焦点矩形的计算算法应该得到改进(即"fropt.rect = style()->subElementRect(QStyle::SE_CheckBoxFocusRect, &opt, this);"),因为焦点矩形会与多行文本混淆,而不是将其包围。为了确保系统能够为这种问题绘制出漂亮的焦点矩形,您需要为这样的矩形设置适当的高度,因为多行文本具有不同的高度。 - Vaidotas Strazdas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接