• Linux下程序开发:用QT创建新风格


    1.Qt的风格

    a) Qt简介

    Qt是一个跨平台的C++图形用户界面应用程序开发库,使用Qt可以开发出高质量的图形用户接口,它是完全面向对象的、易于扩展且允许真正的组件编程。Qt获得了很大的成功,特别是它的信号-槽机制是非常值得研究的通信机制,它也是 Linux发行版标准组件KDE(K Desktop Enviroment)的基础。

    b) 风格机制

    Qt的风格机制实现了不同平台上的图形用户接口(GUI)的观感(look and feel),例如Windows平台上通常使用Windows或Windows-xp风格,而Unix平台上通常使用Motif、CDE风格。

    下图显示了Qt中与风格相关的类的继承关系

    QStyle是所有风格类的基类,它控制着所有的部件(widget即windows编程中的控件)的界面风格或观感,它定义了大量的枚举类型和十几个函数。枚举类型表示界面上的不同元素(如组合框中的按钮,按钮的边框等);函数控制图形用户界面的绘制,但大多数函数基本上只是一些声明而没有函数实现,他们的实现在QCommonStyle、QWindowStyle、QMotifStyle及其子类中。QStyle只实现了3个函数drawItem(), itemRect(), visualRect()。

    drawItem(): 负责绘制文本和象素图。

    itemRect(): 返回文本或图像所占的区域。

    visualRect(): 返回逻辑坐标,这个函数使Qt实现right-to-left风格(阿文、维文传统是文本从右向左显示,因此控件布局也是从右向左)。如下图所示:

    可以看到菜单、工具条是右对齐、单选框的按钮在右边

    c) 创建新风格的步骤

    在Qt中实现一种新风格的步骤很简单:只需选择一个风格类(如QCommonStyle或QStyle)作为父类,然后实现感兴趣的函数即可。难点在于函数的实现。

    选择父类:可以选择QStyle, QCommonStyle, QWindowStyle, QMotifStyle以及他们的子类的任意一个作为父类。通常可以选择QWindowsStyle或QMotifStyle,也可以选择 QCommonStyle甚至是QStyle,但是工作量会比较大,因为很多界面的细节需要自己实现。

    重新实现必要的函数:想修改界面风格的哪部分,就重新实现与绘制那部分相关的函数,下面解释一下我们要重载的QStyle中的几个函数,这几个函数控制着图形用户界面上不同元素的布局和观感。

      
              
    1)void drawPrimitive( PrimitiveElement pe,
               QPainter *p,
                const QRect & r,
                const QColorGroup & cg,
                SFlags flags = Style_Default,
                const QStyleOption &opt = QStyleOption::Default ) ;



    功能:绘制基本图形元素,如QSpinBox中的带箭头的按钮等。

    参数: PrimitiveElement pe: 这个枚举型变量表示将要绘制的基本图形界面元素(这里说的基本图形用户界面元素指GUI中不可再分的一个原子元素,如组合框 中的这个绘有黑色三角形的按钮,spinBox中的按钮 

    QPainter *p:指向QPainter类的指针,Qt中的所有绘制操作不管是绘制文本、图形还是图像都由这个类来处理。

    QRect &r: 表示一个矩形区域,Qt在这个区域中绘制基本界面元素(PrimitiveElement).

    QColorGroup &cg: QColorGroup表示一个部件(widget)的颜色组(color group),color group含有部件绘制自己时使用的各种颜色,譬如前景色背景色等。下图展示了color group中的各种颜色属性

    SFlags flags: 控制如何绘制图形界面元素的标志。

    QStyleOption &opt: 绘制不同的部件(widget)时会需要不同的参数,如绘制面板(panel)可能需要线宽作为额外参数而绘制焦点矩形(focus rect)可能需要背景色作为额外参数,所以Qt专门提供了一个类QStyleOption来封装不同的widget可能需要的不同的参数,opt指向这样一个类的对象。

      
      2)void drawComplexControl( ComplexControl control,
       QPainter *p,
       const QWidget *widget,
       const QRect &r,
       const QColorGroup &cg,
       SFlags flags = Style_Default,
       SCFlags controls = QStyle::SC_All,
       SCFlags active = QStyle::SC_None,
       const QStyleOption& opt = QStyleOption::Default)
          

    功能:绘制复杂控制部件(widget)如SpinWidget,comboBox,slider,listView等

    参数:

    ComplexControl control:是一个枚举量,表示将要绘制的复杂控制部件(widget)如组合框、列表框等。

    QPainter *p:指向QPainter的指针,Qt中的所有绘制操作不管是绘制文本、图形还是图像都由这个类来处理。

    QWidget *widget:指向QWdget或其子类的指针,可以根据上面control的值转变(cast)成合适的类型,例如如果要绘制 QSpinWidget,那么control取值为CC_SpinWidget,而widget指向一个QSpinWidget(QWidget的子类) 的实例(instance)。使用这个变量可以访问QSpinWidget的成员函数和成员变量,譬如可以调用QSpinWidget的sizeHint 函数获得这个部件的缺省大小(一个矩形空间)。

    QRect &r: 表示一个矩形区域,Qt在这个区域中绘制控件或其子部件。

    QColorGroup &cg: QColorGroup表示一个部件(widget)的颜色组(color group),color group含有部件绘制自己时使用的各种颜色,譬如前景色背景色等。

    SFlags flags: 控制如何绘制图形界面元素的标志

    SCFlags controls表示绘制复杂控制部件control的哪个子部件,缺省为SC_All,即绘制整个control而不是其某个子部件(注意control, controls是两个不同的参数)

    QStyleOption &opt: 在绘制不同的部件时可能需要不同的额外的参数,这个变量在绘制不同的widget时提供不同的信息。

      
       3) QRect querySubControlMetrics(ComplexControl control,
       const QWidget* widget,
       SubControl sc,
       const QStyleOption& = QStyleOption::Default)
      

    功能:获取子部件的坐标和尺寸信息。这个函数控制着一个复杂控件的布局,重载这个函数可以使的组合框的下拉按钮绘制在左边 而不是默认的右边。

    参数:

    ComplexControl control: 枚举量,表示将要绘制的复杂控制部件(widget)如组合框、列表框等。

    QWidget *widget:指向QWidget或其子类的指针,可以根据上面control的值转变(cast)成合适的类型,例如如果要绘制 QSpinWidget,那么control取值为CC_SpinWidget,而widget指向一个QSpinWidget(QWidget的子类) 的实例。使用这个变量可以访问QSpinWidget的成员函数和成员变量,譬如可以调用QSpinWidget的sizeHint函数获得这个部件的缺省大小(一个矩形空间)。

    SubControl sc:枚举量,一个复杂部件可能由多个的子部件组成,使用sc变量说明要获取那个子部件的坐标和尺寸信息。

    QStyleOption &opt: 计算不同部件的尺寸时可能需要不同的额外信息,QStyleOption封装了这些信息。


    2.创建新风格

    下面用一个例子来介绍一下创建新风格的整个过程,在编程之前,先看一下最终的结果是什么样的。(在Qt内部QSpinBox类是通过QSpinWidget实现的)

    默认风格的效果:

    使用新风格的效果:

    可以看到在新风格中我们的SpinBox有了垂直显示的效果。下面我们按上面说明的步骤来创建一种新的风格。

    1)选择基类:我们选择QWindowsStyle类作为我们新风格类的基类,当然也可以选择QMotifStyle,在这个例子种也可以选择QCommonStyle。一般不建议选择QCommonStyle作为基类,因为 QCommonStyle只实现了一部分界面部件,如果要实现一个完整的风格类,我们需要重新写很多代码。

    2)重载相关的函数:在这个例程中我们只修改了spinBox的风格,实现这个部件(widget)只与QStyle类的三个函数drawPrimitive, drawComplexControl, qureySubControlMerics相关,所以我们只需重载这三个函数的相关部分代码.下面对代码中的关键部分做一下注释,不重要的部分省略了。详细的代码可以从后面下载。

    绘制spinbox中按钮的函数:


    void CustomStyle::drawPrimitive( PrimitiveElement pe,
    QPainter * p,
    const QRect & r,
    const QColorGroup & cg,
    SFlags flags,
    const QStyleOption & opt ) const
    {
    /*PE_SpinWidgetUp,PE_SpinWidgetDown表示spinBox中的下按钮和上按钮,
    下面的代码使得这两个按钮中的三角形分别向左和向右*/
    if ((pe == PE_SpinWidgetUp) || (pe == PE_SpinWidgetDown)){
    int fw = pixelMetric( PM_DefaultFrameWidth, 0 );//fw表示边框宽度,默认为2
    QRect br; //spinBox上按钮的边界矩形不是spinBox的边界矩形。
    br.setRect( r.x() + fw, r.y() + fw, r.width() - fw*2,
    r.height() - fw*2 );
    p->fillRect( br, cg.brush( QColorGroup::Button ) );
    int x = r.x(), y = r.y(), w = r.width(), h = r.height();
    int sw = w-4;
    int sh = sw/2 + 2; // Must have empty row at foot of arrow
    int sx = x + w / 2 - sw / 2 - 1;
    int sy = y + h / 2 - sh / 2 - 1;

    QPointArray a;
    /* 设置三角形的三个点的坐标,修改这三个点可以使得QSpinBox上按钮里的三角型呈现任意的形状,
    下面的设置使得三角形表示的箭头分别向左和向右。*/
    if ( pe == PE_SpinWidgetDown )
    a.setPoints( 3, 0, sh/2, sw-1, 1, sw-1, sh-1 );
    else
    a.setPoints( 3, 0, 1, 0, sh-1, sw-1, sh/2 );
    ...........
    p->drawPolygon( a ); //绘制三角形
    }else if((pe == PE_ButtonBevel) || (pe == PE_ButtonCommand) || (pe == PE_ButtonTool)
    || (pe == PE_ButtonDropDown) || (pe == PE_HeaderSection))
    { //绘制按钮的各种效果使得看起来凸起或凹下。
    qDrawShadePanel(p, r, cg, flags & (Style_Sunken | Style_Down | Style_On),
    1, &cg.brush(QColorGroup::Button));
    }else{
    /*对于其他基本图形元素(PrimitiveElement)的绘制我们不作处理只是简单的调用父类的函数。*/
    QWindowsStyle::drawPrimitive( pe, p, r, cg, flags, opt);
    }
    }

    绘制整个spinBox的函数:


    void CustomStyle::drawComplexControl( ComplexControl control,
    QPainter *p,
    const QWidget *widget,
    const QRect &r,
    const QColorGroup &cg,
    SFlags flags,
    SCFlags controls,
    SCFlags active,
    const QStyleOption& opt ) const
    {
    //下面的代码使得spinWidget呈现垂直显示的风格而不是通常的水平显示
    if (control == CC_SpinWidget) {
    const QSpinWidget * sw = (const QSpinWidget *) widget;
    //绘制向上按钮部分,controls默认为SC_All,即绘制整个spinwidget
    if ( controls & SC_SpinWidgetUp ) {

    if ( sw->buttonSymbols() == QSpinWidget::PlusMinus )
    pe = PE_SpinWidgetPlus; // 使用加减号




    else
    pe = PE_SpinWidgetUp; // 使用三角形




    QRect re = sw->upRect();
    QColorGroup ucg = sw->isUpEnabled() ? cg : sw->palette().disabled();
    drawPrimitive(PE_ButtonBevel, p, re, ucg, flags); //绘制按钮的边框
    drawPrimitive(pe, p, re, ucg, flags); //绘制按钮
    }
    //绘制向左按钮部分。
    if ( controls & SC_SpinWidgetDown ) {
    /*与绘制向下按钮相似*/
    }
    }else{//不处理spinbox之外的其他复杂控制部件,调用父类函数处理
    QWindowsStyle::drawComplexControl(control, p, widget, r, cg, flags, controls, active, opt);
    }
    }

    获取部件(widget)中各个子部件布局信息的函数,这个函数控制着一个widget的外观


    QRect CustomStyle::querySubControlMetrics( ComplexControl control,
    const QWidget *widget,
    SubControl sc,
    const QStyleOption &opt ) const
    {
    if(control == CC_SpinWidget){
    int fw = pixelMetric( PM_SpinBoxFrameWidth, widget);
    /*QSize 定义二维对象的大小,也就是宽和高. 坐标类型是QCOORD定义为int)*/
    QSize bs; //此处bs表示每个按钮的大小,因为有两个按钮所以下面除以2.
    bs.setWidth(widget->width()/2 -fw);
    if(bs.width() < 8) bs.setWidth(8);
    /*按钮高度设置为QMIN{按钮宽度的1.6倍, 部件高度的四分之一}
    bs.setHeight( QMIN(bs.width()*8/5, widget->height() / 4) );
    bs = bs.expandedTo( QApplication::globalStrut() );

    int x = fw;
    int y, ly, ry;
    y = widget->height() - x - bs.height();
    ly = fw;
    ry = y - fw;
    //下面定义了QSpinWidget的各个子部件的坐标位置.
    switch ( sc ) {
    case SC_SpinWidgetUp:
    //返回向右按钮的坐标信息
    return QRect(x + bs.width(), y, bs.width(), bs.height());
    case SC_SpinWidgetDown:
    //返回向左按钮的坐标信息
    return QRect(x, y, bs.width(), bs.height());
    case SC_SpinWidgetButtonField:
    //返回两个按钮的总区域大小
    return QRect(x, y, widget->width() - 2*fw, bs.height());
    case SC_SpinWidgetEditField:
    /*返回可编辑框的坐标信息*/
    return QRect(fw, ly, widget->width() - 2*fw, ry);
    case SC_SpinWidgetFrame:
    //返回整个spinBox的坐标信息
    return widget->rect();
    default:
    break;
    }
    }else{//其它部件的布局信息调用父类的函数来处理。
    return QWindowsStyle::querySubControlMetrics(control,widget,sc,opt );
    }
    return QRect();
    }





    3.使用新风格

    有两种方法使用新风格,一种是作为插件,一种是在应用程序里直接使用。作为插件的风格可以在不用修改代码、不用重新编译的情况下使用新风格。由于本文着重介绍如何创建风格所以我们使用第一种方法。这种方法很简单,只需在应用程序中包含相应风格类的头文件,然后把main()函数第一句可执行代码设置为QApplication::setStyle(new MyStyle())即可。

    下面我们用一个小例子来看看效果。


    #i nclude <qapplication.h>
    #i nclude <qspinbox.h>
    #i nclude "customstyle.h"
    int main( int argc, char **argv )
    {
    QApplication::setStyle(new CustomStyle()); //使用新风格类来绘制界面。
    QApplication a( argc, argv );
    QSpinBox spin( 0, 15 );
    spin.resize( 20, 100 );
    a.setMainWidget( &spin );
    spin.show();
    return a.exec();
    }

    然后编译运行即可看到效果。

    Ps. qt中编译使用qmake,步骤为

    • 创建源程序
    • 同一目录下运行qmake -project
    • qmake
    • make
    • 运行可执行程序。



    4.进一步工作

    1)默认大小:细心的朋友可能看到上面的代码中有一句:spin.resize( 20, 100 ),这一句设置spinbox的长度为20象素,宽度为100个象素。如果没有这一句的话,显示的结果会一团糟,两个按钮几乎看不到



    ,因为qt默认的显示是水平显示而根本没有考虑垂直显示的情况。

    如果想让spinbox在默认情况下看起来长度窄而宽度高需要修改QSpinBox类中的sizeHint函数,这个函数功能是设置部件(widget)的默认尺寸。在qt中几乎每个GUI部件类都有sizeHint这个函数来设置它自己的默认的长和宽。

    文本垂直显示:在此例中虽然控件spinbox达到了垂直显示的效果,但是文本仍旧是水平显示的,因此要达到真正的垂直显示需要了解qt的文本显示机制。这些工作是很有意义的,因为有些民族(如蒙文)的语言传统就是垂直显示的,而现在没有一个真正满足这种需求的系统。笔者现在正在看qt-x11- free-3.2.2的源码,目前对文本显示机制只有初步了解,还没有真正弄清,非常希望和感兴趣的朋友相互交流、学习。

  • 相关阅读:
    docker 可视化面板工具 protainer
    Docker 镜像及容器命令
    linux 系统上通过docker容器技术部署Nginx,whereis nginx
    Linux CentOS7 系统下安装及卸载Docker 及 配置阿里云镜像加速 及 Docker C/S架构
    Docker数据卷 Volume 挂载,容器路径与linux主机路径的一致性映射,mysql 实战
    什么是 JWT JSON WEB TOKEN ,理论部分
    2022 年最受瞩目的新特性 CSS @layer 到底是个啥?
    一道有意思的 CSS 面试题,FizzBu​​zz ~
    巧用 backgroundclip 实现超强的文字动效
    节点基于资源压力要驱逐pod时,pod的状态是什么?
  • 原文地址:https://www.cnblogs.com/buffer/p/1488614.html
Copyright © 2020-2023  润新知