zoukankan      html  css  js  c++  java
  • 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的源码,目前对文本显示机制只有初步了解,还没有真正弄清,非常希望和感兴趣的朋友相互交流、学习。

  • 相关阅读:
    多个类定义attr属性重复的问题:Attribute "xxx" has already been defined
    好用的批量改名工具——文件批量改名工具V2.0 绿色版
    得到ImageView中drawable显示的区域的计算方法
    得到view坐标的各种方法
    实现类似于QQ空间相册的点击图片放大,再点后缩小回原来位置
    Material Designer的低版本兼容实现(五)—— ActivityOptionsCompat
    Android 自带图标库 android.R.drawable
    解决 Attempting to destroy the window while drawing!
    解决Using 1.7 requires compiling with Android 4.4 (KitKat); currently using API 4
    Material Designer的低版本兼容实现(四)—— ToolBar
  • 原文地址:https://www.cnblogs.com/buffer/p/1488614.html
Copyright © 2011-2022 走看看