解析Qt自帶的Style示例

        在之前的QStyle類參考一文中我們介紹到實現自定義樣式有兩種方法:靜態方法和動態方法。我們先介紹靜態方法:也就是繼承已經存在的類,不是QStyle,通常是QCommonStyle或者是QWindowsStyle等等。然後實現其中的虛函數,重寫自己需要修改的部分代碼。至於選擇哪個基類來繼承完全取決用戶,通常選擇和自己所期望的最相近的類來繼承。這裏貼一個圖,主要是繼承層次的圖:


        其中的QGtkStyle就是通常GNOME環境下的,而QPlastiqueStyle則是和KDE環境最爲接近的。注意QMacStyle只有在Mac OS上纔有效果,因爲它的實現依賴相應的平臺。

       接下來解析一下Qt自帶的Style示例,示例程序是Qt十分好的學習資源,不可不看哦!

首先上效果圖:


                  圖一:自定義樣式效果圖


                              圖二:支持的樣式

         大家可以看到該程序在我的系統(Win7)上支持很多樣式,這裏就不一一截圖了,大家可以自己編譯示例程序體驗一下。

         接下來是源代碼,首先看一下工程的目錄結構:


        大家可以看到,實現該自定義樣式的類爲:NorwegianWoodStyle,而類WidgetGallery主要是用代碼實現整個界面佈局,並沒有什麼好介紹的。主要介紹一下NorwegianWoodStyle類。

1、首先是頭文件:

class NorwegianWoodStyle : public QMotifStyle
{
    Q_OBJECT

public:
    NorwegianWoodStyle() {}

    void polish(QPalette &palette);
    void polish(QWidget *widget);
    void unpolish(QWidget *widget);
    int pixelMetric(PixelMetric metric, const QStyleOption *option,
                    const QWidget *widget) const;
    int styleHint(StyleHint hint, const QStyleOption *option,
                  const QWidget *widget, QStyleHintReturn *returnData) const;
    void drawPrimitive(PrimitiveElement element, const QStyleOption *option,
                       QPainter *painter, const QWidget *widget) const;
    void drawControl(ControlElement control, const QStyleOption *option,
                     QPainter *painter, const QWidget *widget) const;

private:
    static void setTexture(QPalette &palette, QPalette::ColorRole role,
                           const QPixmap &pixmap);
    static QPainterPath roundRectPath(const QRect &rect);
};
        主要就是顯示了QMotifStyle類中的虛函數,而這些函數最初是QStyle類中的虛函數。下面介紹這幾個函數的功能:
(1)void polish(QPalette &palette)

     這是一個重載函數,功能就是改變調色板爲樣式指定的顏色調色板。

     該處具體實現爲:

//! [0]
void NorwegianWoodStyle::polish(QPalette &palette)
{
    QColor brown(212, 140, 95);
    QColor beige(236, 182, 120);
    QColor slightlyOpaqueBlack(0, 0, 0, 63);

    QPixmap backgroundImage(":/images/woodbackground.png");
    QPixmap buttonImage(":/images/woodbutton.png");
    QPixmap midImage = buttonImage;

    QPainter painter;
    painter.begin(&midImage);
    painter.setPen(Qt::NoPen);
    painter.fillRect(midImage.rect(), slightlyOpaqueBlack);
    painter.end();
//! [0]

//! [1]
    palette = QPalette(brown);

    palette.setBrush(QPalette::BrightText, Qt::white);
    palette.setBrush(QPalette::Base, beige);
    palette.setBrush(QPalette::Highlight, Qt::darkGreen);
    setTexture(palette, QPalette::Button, buttonImage);
    setTexture(palette, QPalette::Mid, midImage);
    setTexture(palette, QPalette::Window, backgroundImage);

    QBrush brush = palette.background();
    brush.setColor(brush.color().dark());

    palette.setBrush(QPalette::Disabled, QPalette::WindowText, brush);
    palette.setBrush(QPalette::Disabled, QPalette::Text, brush);
    palette.setBrush(QPalette::Disabled, QPalette::ButtonText, brush);
    palette.setBrush(QPalette::Disabled, QPalette::Base, brush);
    palette.setBrush(QPalette::Disabled, QPalette::Button, brush);
    palette.setBrush(QPalette::Disabled, QPalette::Mid, brush);
}
//! [1]

(2)void polish(QWidget *widget)

       這是一個重載函數,功能是:初始化給定窗口部件的外觀,該函數在每一個窗口完全創建之後的某個時刻並且是該窗口部件每一次創建後首次顯示之前調用。注意,默認的實現是不做任何事情。該函數可能的動作就是調用QWidget::setBackgroundMode()。不要調用該函數來設置例如窗口部件顯示位置之類的信息。重現實現這個函數使得我們獲得課改變窗口部件顯示方式的後門,但是Qt的樣式引擎很少需要實現這個函數,而是重新實現 drawItemPixmap(), drawItemText(),drawPrimitive(),等等函數來代替。

       此處的實現爲:

//! [3]
void NorwegianWoodStyle::polish(QWidget *widget)
//! [3] //! [4]
{
    if (qobject_cast<QPushButton *>(widget)
            || qobject_cast<QComboBox *>(widget))
        widget->setAttribute(Qt::WA_Hover, true);
}
//! [4]


(3)int pixelMetric(PixelMetric metric, const QStyleOption *option,
                    const QWidget *widget) const

       功能:返回給定像素的公制值。指定的option和widget可以被用來計算公制值。一般情況下,widget參數都不會使用到。option參數可以通過qstyleoption_cast()函數轉換爲合適的類型。注意,option可能會爲0,即使是PixelMetrics可以利用它。查看下面的表來獲取合適的option轉換:



            一些像素公制是從widgets中調用的,而一些則僅僅是style在內部調用的。如果像素公制不是從widget調用的,那麼樣式的製作者就應該謹慎使用它。對於一些樣式而言,這也許是不適用的。

       看看該函數的具體實現:

//! [7]
int NorwegianWoodStyle::pixelMetric(PixelMetric metric,
//! [7] //! [8]
                                    const QStyleOption *option,
                                    const QWidget *widget) const
{
    switch (metric) {
    case PM_ComboBoxFrameWidth:
        return 8;
    case PM_ScrollBarExtent:
        return QMotifStyle::pixelMetric(metric, option, widget) + 4;
    default:
        return QMotifStyle::pixelMetric(metric, option, widget);
    }
}
//! [8]

         這裏附帶說一下PixelMetric,這是一個枚舉類型,主要是描述了像素公制可取的一些值,一個像素公制值是單個像素在在樣式中表現的尺寸。

        在本例中我們對默認值做出了一些修改,然後就實現了自定義的樣式,例如:

PM_ComboBoxFrameWidth默認值在Qt幫助文檔中如下定義:

QStyle::PM_ComboBoxFrameWidth 7 Frame width of a combo box, defaults to PM_DefaultFrameWidth.
也就是說默認值是7,而在本例中我們改爲8了,這就是之前說的實現虛函數,修改相應的部分實現就創建了自定義樣式。


(3)int styleHint(StyleHint hint, const QStyleOption *option,
                  const QWidget *widget, QStyleHintReturn *returnData) const

        功能:對指定的widget返回由option指定的樣式的hint,返回值爲整型。當查詢的widget需要返回更多的信息,而不僅僅是由styleHint()返回的整型值時,我們就可以使用returnData來返回這些額外的信息。

       這裏的具體實現爲:

//! [9]
int NorwegianWoodStyle::styleHint(StyleHint hint, const QStyleOption *option,
//! [9] //! [10]
                                  const QWidget *widget,
                                  QStyleHintReturn *returnData) const
{
    switch (hint) {
    case SH_DitherDisabledText:
        return int(false);
    case SH_EtchDisabledText:
        return int(true);
    default:
        return QMotifStyle::styleHint(hint, option, widget, returnData);
    }
}
//! [10]
       這裏也附帶介紹一下StyleHint的內容,這是一個枚舉類型,指定了style hints可以選取的值,一種style hint一般外觀和/感到提示。


       由上面的代碼和這裏的圖片可以看到這裏也做了一些修改。

(5)void drawPrimitive(PrimitiveElement element, const QStyleOption *option,
                       QPainter *painter, const QWidget *widget) const

      功能:用給定的option核painter來繪製element。關於原始元素以及它們相關的option可以參見Qt幫組文檔。

       這裏的實現爲:

//! [11]
void NorwegianWoodStyle::drawPrimitive(PrimitiveElement element,
//! [11] //! [12]
                                       const QStyleOption *option,
                                       QPainter *painter,
                                       const QWidget *widget) const
{
    switch (element) {
    case PE_PanelButtonCommand:
        {
            int delta = (option->state & State_MouseOver) ? 64 : 0;
            QColor slightlyOpaqueBlack(0, 0, 0, 63);
            QColor semiTransparentWhite(255, 255, 255, 127 + delta);
            QColor semiTransparentBlack(0, 0, 0, 127 - delta);

            int x, y, width, height;
            option->rect.getRect(&x, &y, &width, &height);
//! [12]

//! [13]
            QPainterPath roundRect = roundRectPath(option->rect);
//! [13] //! [14]
            int radius = qMin(width, height) / 2;
//! [14]

//! [15]
            QBrush brush;
//! [15] //! [16]
            bool darker;

            const QStyleOptionButton *buttonOption =
                    qstyleoption_cast<const QStyleOptionButton *>(option);
            if (buttonOption
                    && (buttonOption->features & QStyleOptionButton::Flat)) {
                brush = option->palette.background();
                darker = (option->state & (State_Sunken | State_On));
            } else {
                if (option->state & (State_Sunken | State_On)) {
                    brush = option->palette.mid();
                    darker = !(option->state & State_Sunken);
                } else {
                    brush = option->palette.button();
                    darker = false;
//! [16] //! [17]
                }
//! [17] //! [18]
            }
//! [18]

//! [19]
            painter->save();
//! [19] //! [20]
            painter->setRenderHint(QPainter::Antialiasing, true);
//! [20] //! [21]
            painter->fillPath(roundRect, brush);
//! [21] //! [22]
            if (darker)
//! [22] //! [23]
                painter->fillPath(roundRect, slightlyOpaqueBlack);
//! [23]

//! [24]
            int penWidth;
//! [24] //! [25]
            if (radius < 10)
                penWidth = 3;
            else if (radius < 20)
                penWidth = 5;
            else
                penWidth = 7;

            QPen topPen(semiTransparentWhite, penWidth);
            QPen bottomPen(semiTransparentBlack, penWidth);

            if (option->state & (State_Sunken | State_On))
                qSwap(topPen, bottomPen);
//! [25]

//! [26]
            int x1 = x;
            int x2 = x + radius;
            int x3 = x + width - radius;
            int x4 = x + width;

            if (option->direction == Qt::RightToLeft) {
                qSwap(x1, x4);
                qSwap(x2, x3);
            }

            QPolygon topHalf;
            topHalf << QPoint(x1, y)
                    << QPoint(x4, y)
                    << QPoint(x3, y + radius)
                    << QPoint(x2, y + height - radius)
                    << QPoint(x1, y + height);

            painter->setClipPath(roundRect);
            painter->setClipRegion(topHalf, Qt::IntersectClip);
            painter->setPen(topPen);
            painter->drawPath(roundRect);
//! [26] //! [32]

            QPolygon bottomHalf = topHalf;
            bottomHalf[0] = QPoint(x4, y + height);

            painter->setClipPath(roundRect);
            painter->setClipRegion(bottomHalf, Qt::IntersectClip);
            painter->setPen(bottomPen);
            painter->drawPath(roundRect);

            painter->setPen(option->palette.foreground().color());
            painter->setClipping(false);
            painter->drawPath(roundRect);

            painter->restore();
        }
        break;
//! [32] //! [33]
    default:
//! [33] //! [34]
        QMotifStyle::drawPrimitive(element, option, painter, widget);
    }
}
//! [34]

(6)void drawControl(ControlElement control, const QStyleOption *option,
                     QPainter *painter, const QWidget *widget) const

      功能:和上個函數類似。具體參見Qt幫組文檔。

      具體實現:

//! [35]
void NorwegianWoodStyle::drawControl(ControlElement element,
//! [35] //! [36]
                                     const QStyleOption *option,
                                     QPainter *painter,
                                     const QWidget *widget) const
{
    switch (element) {
    case CE_PushButtonLabel:
        {
            QStyleOptionButton myButtonOption;
            const QStyleOptionButton *buttonOption =
                    qstyleoption_cast<const QStyleOptionButton *>(option);
            if (buttonOption) {
                myButtonOption = *buttonOption;
                if (myButtonOption.palette.currentColorGroup()
                        != QPalette::Disabled) {
                    if (myButtonOption.state & (State_Sunken | State_On)) {
                        myButtonOption.palette.setBrush(QPalette::ButtonText,
                                myButtonOption.palette.brightText());
                    }
                }
            }
            QMotifStyle::drawControl(element, &myButtonOption, painter, widget);
        }
        break;
    default:
        QMotifStyle::drawControl(element, option, painter, widget);
    }
}
//! [36]

(7)兩個輔助函數,這裏代碼比較好懂,直接貼代碼了:

//! [37]
void NorwegianWoodStyle::setTexture(QPalette &palette, QPalette::ColorRole role,
//! [37] //! [38]
                                    const QPixmap &pixmap)
{
    for (int i = 0; i < QPalette::NColorGroups; ++i) {
        QColor color = palette.brush(QPalette::ColorGroup(i), role).color();
        palette.setBrush(QPalette::ColorGroup(i), role, QBrush(color, pixmap));
    }
}
//! [38]

//! [39]
QPainterPath NorwegianWoodStyle::roundRectPath(const QRect &rect)
//! [39] //! [40]
{
    int radius = qMin(rect.width(), rect.height()) / 2;
    int diam = 2 * radius;

    int x1, y1, x2, y2;
    rect.getCoords(&x1, &y1, &x2, &y2);

    QPainterPath path;
    path.moveTo(x2, y1 + radius);
    path.arcTo(QRect(x2 - diam, y1, diam, diam), 0.0, +90.0);
    path.lineTo(x1 + radius, y1);
    path.arcTo(QRect(x1, y1, diam, diam), 90.0, +90.0);
    path.lineTo(x1, y2 - radius);
    path.arcTo(QRect(x1, y2 - diam, diam, diam), 180.0, +90.0);
    path.lineTo(x1 + radius, y2);
    path.arcTo(QRect(x2 - diam, y2 - diam, diam, diam), 270.0, +90.0);
    path.closeSubpath();
    return path;
}
//! [40]



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章