记录一次失败的控件编写经历——initStyleOption居然不是虚函数

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/czyt1988/article/details/84075418

概述

最近写一个Ribbon控件,Ribbon控件上的编辑控件都是图标加文字加控件模式,如Word的的这个combobox控件:

在这里插入图片描述

它是由图标和文件以及控件本身组成,但普通的QComboBox只是一个combobox,当然也可以使用QLabel+QComboBox组合控件来实现,但加入了QLabel有点多余,于是就想重写一个QComboBox,在绘制控件时把图标和文本绘制出来

思路

QWidget会保存一个icon和一个windowTitle,因此不需要给类新加任何变量保存图标和文字,只需在绘制时,把图标和文字绘制好后在旁边绘制combobox。

使用QStylePainter对标准控件进行重绘

重绘控件只要继承paintEvent函数即可,在paintEvent函数里可以对控件进行任意绘制,但是为了可以使用同一的绘制风格和能应用QSS,正确的绘制应该通过样式进行重绘,QComboBox的绘制信息都保存在QStyleOptionComboBox中,Qt大部分标准控件都会有一个函数initStyleOption,此函数根据当前的状态初始化QStyleOptionComboBox的内容。

QStyleOptionComboBox继承于QStyleOptionQStyleOption这个类是负责保存窗口状态信息的类,里面有窗口的大小rect,调色板palette,字体相关fontMetrics,已经状态state,因此想让combobox绘制时右移一点,只需要改动QStyleOptionComboBox::rect的大小即可

Qt源码QComboBoxpaintEvent如下(Qt5.7 源码):

/*!
    \reimp
*/
void QComboBox::paintEvent(QPaintEvent *)
{
    QStylePainter painter(this);
    painter.setPen(palette().color(QPalette::Text));

    // draw the combobox frame, focusrect and selected etc.
    QStyleOptionComboBox opt;
    initStyleOption(&opt);
    painter.drawComplexControl(QStyle::CC_ComboBox, opt);

    // draw the icon and text
    painter.drawControl(QStyle::CE_ComboBoxLabel, opt);
}

绘制控件主要通过painter.drawComplexControl(QStyle::CC_ComboBox, opt);此句进行绘制,需要改变绘制的结果,只需要改变initStyleOption的输出即可。

修正的QComboBox代码如下:
头文件

class SARibbonComboBox : public QComboBox
{
public:
    SARibbonComboBox(QWidget *parent = Q_NULLPTR);
protected:
    void paintEvent(QPaintEvent *e);
    void initStyleOption(QStyleOptionComboBox *option) const;
private:
    mutable int m_iconEnd;
    mutable int m_textWidth;
};

这里m_iconEnd个m_textWidth使用mutable修饰是为了在const函数里使用,其实不用这两个变量记录也行,就是在paintEvent绘制时麻烦一点。

cpp文件:

void SARibbonComboBox::initStyleOption(QStyleOptionComboBox *option) const
{
    QComboBox::initStyleOption(option);
    //修正图标
    int w = 0;
    QIcon icon = windowIcon();
    const int widgetHeight = height();
    if(!icon.isNull())
    {
        QSize iconSize = icon.actualSize(QSize(widgetHeight,widgetHeight),
                                (option->state & QStyle::State_Enabled) ? QIcon::Normal : QIcon::Disabled,
                                (option->state & QStyle::State_Selected) ? QIcon::On : QIcon::Off  );
        w += iconSize.width();
        w += 4;
        m_iconEnd = w;
    }
    //绘制文字
    QString text = windowTitle();
    if(!text.isEmpty())
    {
        int textWidth = option->fontMetrics.width(text);
        if(textWidth > (option->rect.width() - widgetHeight - w))
        {
            textWidth = option->rect.width() - widgetHeight - w;
            text = option->fontMetrics.elidedText(text,Qt::ElideRight,textWidth);
        }
        if(textWidth > 0)
        {
            w += textWidth;
            w += 2;
        }
        m_textWidth = textWidth;
    }
    option->rect.adjust(w,0,0,0);
}


void SARibbonComboBox::paintEvent(QPaintEvent *e)
{
    Q_UNUSED(e);
    QStylePainter painter(this);
    painter.setPen(palette().color(QPalette::Text));

    // draw the combobox frame, focusrect and selected etc.
    QStyleOptionComboBox opt;
    initStyleOption(&opt);
    //绘制图标
    QIcon icon = windowIcon();
    const int widgetHeight = height();
    if(!icon.isNull())
    {
        //绘制图标
        icon.paint(&painter,0,0,widgetHeight,widgetHeight,Qt::AlignCenter
                  ,(opt.state & QStyle::State_Enabled) ? QIcon::Normal : QIcon::Disabled,
                  (opt.state & QStyle::State_Selected) ? QIcon::On : QIcon::Off);
    }
    //绘制文字
    QString text = windowTitle();
    if(!text.isEmpty())
    {
            painter.drawItemText(QRect(m_iconEnd,0,m_textWidth,opt.rect.height())
                                ,Qt::AlignLeft|Qt::AlignVCenter
                                ,opt.palette
                                ,opt.state & QStyle::State_Enabled
                                ,text
                                );

    }


    painter.drawComplexControl(QStyle::CC_ComboBox, opt);

    // draw the icon and text
    painter.drawControl(QStyle::CE_ComboBoxLabel, opt);
}

思路很简单,就是计算好图标和文字的长度,把QStyleOptionrect进行右偏移
显示的结果:

在这里插入图片描述

一切看起来很美好
但是等设置为编辑模式就出了问题

在这里插入图片描述

发现编辑框并没有根据QStyleOption的rect进行绘制,查找QComboBox的源代码发现,编辑模式会生成一个QLineEdit作为编辑框,尺寸是由initStyleOption获取

void QComboBox::setEditable(bool editable)
{
    Q_D(QComboBox);
    if (isEditable() == editable)
        return;

    QStyleOptionComboBox opt;
    initStyleOption(&opt);
    if (editable) {
        if (style()->styleHint(QStyle::SH_ComboBox_Popup, &opt, this)) {
            d->viewContainer()->updateScrollers();
            view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
        }
        QLineEdit *le = new QLineEdit(this);
        setLineEdit(le);
    } else {
        if (style()->styleHint(QStyle::SH_ComboBox_Popup, &opt, this)) {
            d->viewContainer()->updateScrollers();
            view()->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
        }
        setAttribute(Qt::WA_InputMethodEnabled, false);
        d->lineEdit->hide();
        d->lineEdit->deleteLater();
        d->lineEdit = 0;
    }

    d->updateDelegate();
    d->updateFocusPolicy();

    d->viewContainer()->updateTopBottomMargin();
    if (!testAttribute(Qt::WA_Resized))
        adjustSize();
}

非常可惜的是,initStyleOption不是虚函数!!!,这样,setEditable函数并不会调用重载版本的initStyleOption函数

猜你喜欢

转载自blog.csdn.net/czyt1988/article/details/84075418