Qt---自定義界面之QStyle

最近想學習下Qt的自定義界面,所以花了點時間看了下QStyle,,,,結果很難受,這一塊涉及到一大塊GUI的具體實現方式,看得我很頭疼。想看第一手資料而且英語功底不錯的能夠直接上qt文檔,下面我會以易懂的方式簡單講解下。html

1. Qt控件結構簡介

首先咱們要來說講GUI控件結構,這裏以QComboBox爲例:git

widget-structure combobox-pic

一個完整的控件由一種或多種GUI元素構成:github

  • Complex Control Element。
  • Primitive Element。
  • Control Element。

1.1 Complex Control Element

Complex control elements contain sub controls. Complex controls behave differently depending on where the user handles them with the mouse and which keyboard keys are pressed.api

Complex Control Elements(簡稱CC)包含子控件。根據用戶對鼠標和鍵盤的不一樣處理,CC控件的表現也不一樣。上圖中的QComboBox僅包含一個CC控件CC_ComboBox,該複雜控件又包含三個子控件(SC,Sub Control)SC_ComboBoxFrame、SC_ComboBoxArrow、SC_ComboBoxEditField。學習

1.2 Control Element

A control element performs an action or displays information to the user.code

控件元素與用戶交互相關,例如PushButton、CheckBox等等。QComboBox只有一個CE_ComboBoxLabel用以在ComboBox左側展現當前選中或者正在編輯的文字。orm

1.3 Primitive Element

Primitive elements are GUI elements that are common and often used by several widgets.htm

主元素表明那些公共的GUI元素,主要用於GUI控件複用。例如PE_FrameFocusRect這個主元素就進場被多種控件用來繪製輸入焦點。QComboBox包含兩個主元素PE_IndicatorArrowDown、PE_FrameFocusRect。blog

2. QStyle、QProxyStyle、QStyleFactory簡介

QStyle是一套抽象接口,它定義了實現界面控件外觀的一系列api而且不能用來被實例化:接口

  • virtual void drawComplexControl(...) 繪製複雜元素。
  • virtual void drawControl(...) 繪製控件元素。
  • virtual void drawPrimitive(...) 繪製主元素。
  • ...
  • virtual QSize sizeFromContent(...) 獲取控件大小。
  • virtual QRect subControlRect(...) 獲取子控件位置及大小。
  • virtual QRect subElementRect(...) 獲取子元素位置及大小。

QProxyStyle實現了QStyle全部的抽象接口,而且默認保持系統風格,在Linux、Windows、Mac系統上樣式以下:

system-look

QStyleFactory類提供了當前可應用的全部QStyle風格實現,在Windows系統上我得到以下幾種風格(具體結果見最後一小節):

  1. Windows
  2. WindowsXp
  3. WindowsVista
  4. Fusion

咱們能夠經過QStyleFactory::keys()和QStyleFactory::create()來獲取這些可用的風格而且設置到須要的QWidget上用以改變GUI風格。

3. 自定義QComboBox Style

這裏咱們經過實現一個QStyle來自定義QComboBox的樣式。

這個自定義的QComboBox樣式分爲兩部分,箭頭區域和非箭頭區域。非箭頭區域包含CE_ComboBoxLabel和SC_CombBoxListBoxPopup。因爲QStyle不負責繪製下拉框(由delegate繪製),咱們只能更改下拉框的位置和大小(這裏咱們不作改變)。 箭頭區域包含背景區和PE_IndicatorArrowDown。

箭頭區域咱們用一個輻射漸變來繪製背景,而且在鼠標Hover或者按下的時候更改漸變的顏色來重繪,中間的下拉箭頭咱們複用QProxyStyle的實現來完成。

void CustomeStyle::drawArrowArea(const QStyleOptionComplex *option,
                             QPainter *painter,
                             const QWidget *widget) const
{
    QRect arrowBoxRect = option->rect;
    arrowBoxRect.adjust(option->rect.width() * 0.8, 0, 0, 0);

    auto arrowAreaColor = Qt::darkCyan;
    m_arrowAreaHovered = arrowBoxRect.contains(widget->mapFromGlobal(QCursor::pos()));
    if (option->state & State_MouseOver && m_arrowAreaHovered)
        arrowAreaColor = Qt::cyan;
    else if (option->state & State_On && m_arrowAreaHovered)
        arrowAreaColor = Qt::darkMagenta;

    QRadialGradient gradient(arrowBoxRect.center(),
                            arrowBoxRect.width());
    gradient.setColorAt(1.0, arrowAreaColor);
    painter->fillRect(arrowBoxRect, QBrush(gradient));


    auto arrowDownOption = *option;
    auto adjustPixel = arrowBoxRect.width() * 0.2;
    arrowDownOption.rect = arrowBoxRect.adjusted(adjustPixel,
                                                adjustPixel,
                                                -adjustPixel,
                                                -adjustPixel);
    drawPrimitive(PE_IndicatorArrowDown, &arrowDownOption, painter, widget);
}

非肩頭區域即CE_ComboBoxLabel,咱們用4種顏色的線性漸變來繪製,同箭頭區域同樣她也會根據當前的狀態更改漸變顏色來增長交互效果:

auto comboBoxOption = qstyleoption_cast<const QStyleOptionComboBox*>(option);
if (comboBoxOption == nullptr)
    return;

QColor gradientColors[] = {
    Qt::yellow,
    Qt::green,
    Qt::blue,
    Qt::red
};
QColor penColor = Qt::white;
if (option->state & State_MouseOver && !m_arrowAreaHovered) {
    for (auto& color : gradientColors)
        color.setAlpha(80);
    penColor.setAlpha(80);
} else if (option->state & State_On && !m_arrowAreaHovered) {
    for (auto& color : gradientColors)
        color = color.darker(300);
    penColor = penColor.darker(300);
}

QRect labelRect = comboBoxOption->rect;
labelRect.adjust(0, 0, -(labelRect.width() * 0.2), 0);

QLinearGradient linearGradient(labelRect.topLeft(), labelRect.bottomRight());
for (int i = 0; i < 4; ++i) {
    linearGradient.setColorAt(0.25 *i, gradientColors[i]);
}

painter->fillRect(labelRect, QBrush(linearGradient));

painter->setPen(QPen(penColor));
painter->drawText(labelRect, comboBoxOption->currentText, QTextOption(Qt::AlignCenter));

4. 實現效果

style1 style2 style3 style4 style5 style6

完整代碼見連接

5. 總結

QStyle優勢:

  • 統一風格。特定類型的控件效果都統一,若是要多處用到同一種類型的控件,用QStyle會比較方便。

QStyle缺點:

  • 實現涉及Qt GUI控件結構細節,涉及知識面太多太雜。
  • 只有Qt控件使用了QStyle,系統及第三方實現的控件不保證有效。
  • 實現起來太複雜,不如重寫QWidget的paintEvent配合其餘事件來實現靈活。

下期咱們介紹Qt的Style Sheet。

相關文章
相關標籤/搜索