• Qt 自定义组件风格说明(QStyle和paintEvent)


    https://www.devbean.net/2011/08/native-style-qt-3/

    https://blog.csdn.net/hyongilfmmm/article/details/83238938

    需要说明一点的是,组件的 style 是一个非常复杂的内容,仅在这里不可能全部讲解清楚。如果需要自定义组件 style,还是自己仔细阅读相关文档。另外,这部分牵扯的类很多,函数也很复杂,步步为营才是最好的对待方法。除非非常必要,还是建议不要轻易去碰 style 这部分。

    自定义 style,顾名思义,也就是自己实现样式。这里通常有两种实现方式:第一,重写 widget 的paintEvent()函数;第二,使用QStyle类。两种方式的侧重点有所不同:重写组件的paintEvent()函数,可以简单地实现某一类组件的样式;继承QStyle类,则可以实现对全部组件的一致性处理,例如,将程序中所有的 text 变成红色等。

    首先我们来看看重写paintEvent()函数。paintEvent()QWidget的一个函数,用于实现自身的绘制。一个组件显示到屏幕上,就是通过调用paintEvent()函数把它画出来。看看一个组件有多复杂,全部要使用QPainter提供的画点、画线的函数绘制出来,就知道这里的工作量了。当然也有偷懒的办法,就是重写paintEvent()的时候使用一张图片代替。我们这里就不讨论这种思路了,而是完全从代码开始。

    我们以QPushButton为例。这里,我们创建一个 button,这个 button 在点击时可以凹下显示。为了重写paintEvent()函数,我们必须继承QPushButton类。头文件很简单,暂且略去,下面只看paintEvent()这个函数:

    void MyPushButton::paintEvent(QPaintEvent *)
    {
        QStyleOptionButton option;
        option.initFrom(this);
        // qDebug() << option.state;
        option.state |= isDown() ? QStyle::State_Sunken : QStyle::State_Raised;
        // qDebug() << option.state;
        if (isDefault())
            option.features |= QStyleOptionButton::DefaultButton;
        option.text = text();
        option.icon = icon(); 
    
        QPainter painter(this);
        style()->drawControl(QStyle::CE_PushButton, &option, &painter, this);
    }

    尽管前面说过,我们需要重头绘制整个组件,但实际上,Qt 为我们提供了一系列方便的函数,用于绘制出各个组件的组成部分。这种绘制方式在将组合进行组合的情况下非常有用。例如,一个 combo box 实际上是一个 button 加上一个向下的三角形构成。那么,我不需要将整个 combo box 用像素画出来,而是借用 Qt 已有的组件进行绘制:画出一个 button 和一个三角形。所以,这里我们也使用类似的思路,让 Qt 绘制出组件的每个部分,而我们要做的就是修改参数,让它按照我们的参数绘制。

    如何调用 Qt 的组件绘制函数呢?这个绘制函数是QStyle类的成员。QWidget提供了style()函数,返回当前的QStyle对象。那么,我们就可以通过这个对象绘制。注意上面代码中最后一行,我们从这里看起。下面给出这个函数的签名:

     
    virtual void QStyle::drawControl ( ControlElement element,
    const QStyleOption * option,
    QPainter * painter,
    const QWidget * widget = 0 ) const = 0;
     

    尽管这是一个纯虚函数,但是类似于 Java 的 interface,我们可以直接使用style()返回的对象调用。这是一个很典型的 style 式的函数调用。翻看一下QStyle的声明,QStyle类提供了很多以 draw 打头的函数,用于绘制整个系统组件的绘制。这类 draw 函数一般会有四个参数:

    1. 一个 enum,用于指定要绘制哪个元素。这个 enum 在不同的 draw 函数中可能是不一样的。例如,在drawControl()中是QStyle::ControlElement,指的是组件;在drawPrimitive()中则是QStyle::PrimitiveElement,指的是组件的原始组成元素,例如焦点框,check box 的小勾等;
    2. QStyleOption对象指针。这个对象保存了 painter 绘制时所需要的所有数据信息,比如绘制大小、坐标、绘制文本等。不同的 element 可能对应着不同的QStyleOption的子类,这个在文档中可以找到;
    3. QPainter对象指针。系统即用这个 painter 进行绘制;
    4. QWidget对象指针,用于辅助绘制。

    回到代码,我们可以看到,在drawControl()函数的四个参数中,只有最后一个有默认值。也就是说,如果要调用这个函数,我们必须准备好参数数据。这就是在paintEvent()中,前面几行代码做在的工作。

    通过文档我们查到,QPushButton需要的是QStyleOptionButton作为第二个参数。于是,我们新建一个QStyleOptionButton对象。初始化调用initFrom(),也就是使用本对象设置一个初始值。QStyleOption有很多属性。比如QStyleOption::state指的是当前状态。例如,如果 button 被按下,也就是isDown()返回true的时候,我们将 state 设置为QStyle::State_Sunken,也就是凹下,否则则是QStyle::State_Raised。这样,我们就完成了设置。另外,还要根据需要设置别的属性,例如,如果isDefault()返回true时,我们需要设置option.features,这样才能绘制出默认的效果。text 和 icon 属性则是通过 button 自身函数获得。这样,我们完成对绘制数据的设置,就可以调用QStyle::drawControl()函数,将这个 button 绘制出来。

    这里注意一点是,对于QFlags对象,使用 = 赋值很可能不是你所期望的结果。QFlags实现的是 bitmap 位图,如果简单的使用 = 赋值,在赋值的同时会清除原有位的值。你可以将上面的option.state |= isDown() ? QStyle::State_Sunken : QStyle::State_Raised;修改为option.state = isDown() ? QStyle::State_Sunken : QStyle::State_Raised;,注意比较下前后两个 debug 输出的不同。

    调用QStyle::drawControl()函数时,第一个参数可以通过文档查到。这里的 CE_ 前缀实际就是 ControlElement 的意思。

    这样,我们就完成了一个简单的自定义 button。代码虽然简单,但是大体流程已经表现出来,剩下的就是去翻阅大量文档,仔细了解各个 draw 函数的使用,才能够做出满意的自定义组件效果。

    前面说的第一种自定义组件实现就简单说到这里。然后看看第二种,QStyle的实现。其实在上面,我们已经使用了QStyle。想必也能够想到,这里我们依旧要用到QStyle的各个 draw 函数,只不过这里我们不是简单的去调用它们,而是通过继承,将这些 draw 函数替换成我们自己的版本,达到自定义样式的目的。

    虽然我们可以直接继承QStyle来实现,但是这并不是一个好主意。因为QStyle这个类很复杂,几乎所有的函数都是纯虚函数,这要求我们必须一个个实现它们。有时候,我们并不需要自己实现所有功能,仅仅是做简单的修改。于是,从 4.6 版本开始,Qt 提供了一个专门的类,QProxyStyle。我们要做的就是继承QProxyStyle,覆盖我们感兴趣的函数即可。看下面一个简单的实例: 

     
    class MyProxyStyle : public QProxyStyle
    {
    public:
        void drawControl(ControlElement element,
                         const QStyleOption *option,
                         QPainter *painter,
                         const QWidget *widget) const;
    };
    
    void MyProxyStyle::drawControl(ControlElement element,
                                   const QStyleOption *option,
                                   QPainter *painter,
                                   const QWidget *widget) const
    {
        if(element == QStyle::CE_PushButtonLabel) {
            painter->drawText(option->rect, "fixed");
        } else {
            QProxyStyle::drawControl(element, option, painter, widget);
        }
    }
     

    MyProxyStyle覆盖了drawControl()函数,然后判断,如果是 button label 的话,绘制文本 “fixed”。可想而知,我们的QPushButton::setText()函数已经没有作用了,因为我们在绘制时没有使用这个属性,也就不会显示出来了。不管你设不设置,所有 button 的 text 都会是 fixed。如果要使用这个 style ,需要在运行前设置,例如:

    int main(int argc, char **argv)
    {
        QApplication app(argc, argv);
        app.setStyle(new MyProxyStyle);
        QPushButton button;
        button.setText("Hello, world!");
        button.show();
        return app.exec();
    }

    这样,我们就可以用我们自己的 style 显示组件了。

    就像前面所说,自定义 style 是一个相当复杂的话题,我们不可能在这里完全说明。不过,也正因为 Qt 提供了这种机制,也能够让我们可以比较轻松地实现自定义 style。

    坚持就是胜利
  • 相关阅读:
    高能天气——团队博客汇总
    高能天气——团队Scrum冲刺阶段-Day 1-领航
    20172303 2018-2019-1 《程序设计与数据结构》实验三报告
    高能天气——团队作业第二周
    20172303 2018-2019-1《程序设计与数据结构》深度优先遍历
    高能天气——团队作业第一周
    20172322 2018-2019-1《程序设计与数据结构》课程总结
    172322 2018-2019-1 《程序设计与数据结构》哈夫曼编码测试报告
    高能天气——团队Scrum冲刺阶段-Day 3
    172322 2018-2019-1 《程序设计与数据结构》实验三报告
  • 原文地址:https://www.cnblogs.com/whwywzhj/p/13542125.html
Copyright © 2020-2023  润新知