The QStyle API in Qt 4

Материал из Wiki.crossplatform.ru

Перейти к: навигация, поиск
Image:qt-logo_new.png Image:qq-title-article.png
Qt Quarterly | Выпуск 13 | Документация


by Jasmin Blanchette

Qt applications are given a native look and feel on every platform,either by emulating the native style or by using the system'stheming engine.Since 2.0, Qt also lets you customize the look and feelthrough its sophisticated style API, a feature that is especiallyuseful for Qt/Embedded since mobile devices usually requirecustomized user interfaces.

Содержание

In this article, we will retrace the history of the style APIfrom Qt 1 to Qt 4 and show how to get the most out of the latestAPI. By the end, you should understand how to write style-awarewidgets and the basics of how to create custom styles withQt 4.

[править] Qt 1: All You Need Is GUIStyle

The first version of Qt had no real support for stylesbeyond the infamous GUIStyle enum:

enum GUIStyle {
    MacStyle,     // obsolete
    WindowsStyle,
    Win3Style,    // obsolete
    PMStyle,      // obsolete
    MotifStyle
};

The GUI style would be set globally for the entireapplication using QApplication::setStyle() andcould be overridden for individual widgets usingQWidget::setStyle(). Qt's built-in widgets checkedthe value of QWidget::style() in their paint eventand tried to paint themselves correctly for the style,like this:

void QPushButton::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
 
    if (style() == MotifStyle) {
        // Motif painting code
    } else {
        // Windows painting code
    }
}

While this approach was sufficient to emulate the look and feel ofthe two platforms supported by Qt at the time (Unix/X11 and Windows95), it forced users who wanted to implement custom painting stylesto subclass all of Qt's built-in widgets, and even this didn't workwith Qt's built-in widgets that created child widgets, such as QFileDialog, QSplitter, and QWizard.

[править] Qt 2: QStyle Takes Over Widget Painting

The emergence of themable X11-based desktops prompted us to redesignthe style API for Qt 2.0. The new API substituted the QStyleabstract base class for the GUIStyle enum. Qt 2 initiallyincluded four styles: QWindowsStyle, QMotifStyle, QCDEStyle, and QPlatinumStyle.

QStyle provided virtual functions for drawingwidgets, getting sizes, and querying which part of awidget was hit when the user clicked on it. Eachbuilt-in Qt widget had its own set of functions. Forexample, here's the QStyle API dedicated to scroll bars:

enum ScrollControl {
    AddLine = 0x1,
    SubLine = 0x2,
    AddPage = 0x4,
    SubPage = 0x8,
    First = 0x10,
    Last = 0x20,
    Slider = 0x40,
    NoScroll = 0x80
};
 
virtual void scrollBarMetrics(const QScrollBar *bar,
        int &x, int &y, int &width, int &height);
 
virtual void drawScrollBarControls(QPainter *painter,
        const QScrollBar *bar, int sliderStart,
        uint controls, uint activeControls);
 
virtual void scrollBarPointOver(const QScrollBar *bar,
        int sliderStart, const QPoint &pos);

The paintEvent() of a class like QScrollBarwould then look like this:

void QScrollBar::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    style().drawScrollBarControls(&painter, this,
         sliderStart(), m_allControls, m_pressedControls);
}

This approach made it easy for our users to create their own widgetstyles, by subclassing QStyle or one of the predefined styles.This made it possible for KDE, the Qt-based K Desktop Environment, tosupport themes in their applications.

As we released Qt versions 2.1, 2.2, and 2.3, we discovered that themain problem with QStyle was that we couldn't add new virtualfunctions to QStyle, even if that was necessary to support newwidgets that were introduced during Qt 2's lifetime. For Qt 2, wesimply had to give up making new widgets stylable through QStyle.

[править] Qt 3: Enums Take Over QStyle

Qt 3.0 gave us the opportunity to redesign the QStyle API so thatit would be possible to add support for new widgets simply by addingenum values to QStyle. Adding values to existing enum typesnormally doesn't compromise binary compatibility.

The Qt 3 QStyle API distinguishes between threecategories of items it can draw: primitives, controlsand complex controls. Each category has an enum typelisting the different elements belonging to it, and aset of functions for drawing the elements orperforming hit tests.

Let's take QScrollBar as our example again. Ascroll bar is a complex control because it containssub-controls that the user can click. The enum valueassociated with QScrollBar is calledCC_ScrollBar:

enum ComplexControl {
    CC_SpinWidget,
    CC_ComboBox,
    CC_ScrollBar,
    CC_Slider,
    CC_ToolButton,
    CC_TitleBar,
    CC_ListView,
    CC_CustomBase = 0xF0000000
};

The various sub-controls of a scroll bar are also identified by enumvalues. Here's the portion of the SubControl enum that deals withscroll bars:

enum SubControl {
    ...
    SC_ScrollBarAddLine = 0x1,
    SC_ScrollBarSubLine = 0x2,
    SC_ScrollBarAddPage = 0x4,
    SC_ScrollBarSubPage = 0x8,
    SC_ScrollBarFirst = 0x10,
    SC_ScrollBarLast = 0x20,
    SC_ScrollBarSlider = 0x40,
    SC_ScrollBarGroove = 0x80,
    ...
};

The API for drawing complex controls consists of the following fourfunctions.

virtual void drawComplexControl(ComplexControl control,
        QPainter *painter, const QWidget *widget,
        const QRect &rect, const QColorGroup &colorGroup,
        SFlags style, SCFlags subControls,
        SCFlags activeSubControls,
        const QStyleOption &option) const;
 
virtual void drawComplexControlMask(
        ComplexControl control, QPainter *painter,
        const QWidget *widget, const QRect &rect,
        const QStyleOption &option) const;
 
virtual QRect querySubControlMetrics(
        ComplexControl control, const QWidget *widget,
        SubControl subControl,
        const QStyleOption &option) const;
 
virtual SubControl querySubControl(ComplexControl control,
        const QWidget *widget, const QPoint &pos,
        const QStyleOption &option) const;

This API proved its worth, as no fewer than 61 enum values were addedto QStyle during the lifetime of Qt 3. These values werenecessary to support QToolBox, which was added in Qt 3.2, and tocontrol more attributes of existing Qt widgets.

The API also proved to be powerful enough to accommodate two newstyles: QWindowsXPStyle and QMacStyle. Each of these stylesuses the native theming engine on its target platform.

center

The main disadvantage of the Qt 3 API compared to Qt 2 is that sometype safety is lost. All widgets are passed around as QWidget,and the style function has to cast it to the appropriate typebased on the value of the ComplexControl parameter. There's alsothe risk that the caller might pass the wrong widget type. Still,this was an acceptable price to pay for the flexibility provided byenums.

[править] Qt 4: Styles and Widgets Are Decoupled

As we started to work on Qt 4, we didn't expect to have to changeQStyle significantly. The API was fairly mature and allowed bothour users and ourselves to do what we wanted to do. Yet, one of ourdaring moves in preparation for Qt 4, the Qt library split, forced usto go back to the drawing board again.


The Library Split

In Qt 4, different widgets are provided by differentlibraries. In particular, compatibility widgets suchas Q3ListBox and Q3IconView need to bestylable. At the same time, QStyle subclassesshouldn't have to link against the Qt3Supportlibrary---after all, the whole point of providinga compatibility class in a separate library is to avoidbloat in Qt 4 applications that don't use it.

To avoid this undesirable dependency, we had to pushthe ideas developed for Qt 3 to their logicalconclusion. While Qt 3 eliminated the Qt widget typesfrom the header files by using enums, Qt 4 would haveto eliminate the Qt widget types from theimplementation files as well. QStyle subclassesshould no longer have to cast the QWidget pointerto a specific type to extract information relating tothe widget to draw. Instead, all the informationshould be passed in the QStyleOption parameter.

This decoupling makes it possible to draw widget typeswithout linking against them. Everything thatQStyle subclasses need to know about a widget isavailable as function parameters.


Consequences of QStyle's API

Once again, we will take QScrollBar as anillustration of the QStyle API. Scroll bars arestill treated as complex controls; theComplexControl and SubControl enum types fromQt 3 still exist unchanged. What has changed is thename and parameter list of the QStyle functions:

virtual void drawComplexControl(ComplexControl control,
        const QStyleOptionComplex *option,
        QPainter *painter, const QWidget *widget) const;
 
virtual void drawComplexControlMask(
        ComplexControl control,
        const QStyleOptionComplex *option,
        QPainter *painter, const QWidget *widget) const;
 
virtual SubControl hitTestComplexControl(
        ComplexControl control,
        const QStyleOptionComplex *option,
        const QPoint &pos, const QWidget *widget) const;
 
virtual QRect subControlRect(ComplexControl control,
        const QStyleOptionComplex *option,
        SubControl subControl,
        const QWidget *widget) const;

The widget's state is entirely defined by the option parameter.The widget parameter is optional (its default value is 0); it isprovided to make porting custom styles to Qt 4 easier and to allowoccasional hacks.

The new design has one unforeseen advantage: It is now easier to makecustom widgets stylable. If you want to design a custom widget thatresembles a QTabBar, you can now invokeQStyle::{|1s}drawControl() with CE_TabBarTab without having topass a QTabBar object to QStyle.


The QStyleOption Class Hierarchy

The option parameter is a pointer to aQStyleOptionComplex. For most complex controltype, there exists a QStyleOptionComplex subclassthat provides additional information for the specifictype. For QScrollBar (and QSlider), thesubclass is QStyleOptionSlider.

The QStyleOption classes are simple C++ structs with public datamembers. For example, here's how to draw a scroll bar:

void QScrollBar::paintEvent(QPaintEvent *)
{
    QStyleOptionSlider option;
    option.init(this);
    option.subControls = QStyle::SC_All;
    option.activeSubControls = m_pressedControl;
    option.orientation = orientation();
    option.minimum = minimum();
    ...
    option.upsideDown = invertedAppearance();
 
    QPainter painter(this);
    style().drawComplexControl(QStyle::CC_ScrollBar,
                               &option, &painter, this);
}

The last three lines can be made slightly shorter byusing the QStylePainter convenience class:

QStylePainter painter(this);
painter.drawComplexControl(QStyle::CC_ScrollBar, option);

The code inside the drawComplexControl()reimplementation looks like this:

void XyzStyle::drawComplexControl(ComplexControl control,
        const QStyleOptionComplex *option,
        QPainter *painter, const QWidget *widget) const
{
    switch (control) {
    case CC_ScrollBar:
        if (const QStyleOptionSlider *sliderOpt =
            qstyleoption_cast<const QStyleOptionSlider *>(option)) {
            // draw a scroll bar
        }
        break;
    ...
    }
}

The qstyleoption_cast<T>() function is new in Qt 4. Like standard C++'sdynamic_cast<T>() construct, it attempts to cast its argumentdown the inheritance hierarchy, and returns a null pointer if theactual object isn't of the right type. For example:

QStyleOption *slider = new QStyleOptionSlider;
QStyleOption *frame = new QStyleOptionFrame;
 
qstyleoption_cast<QStyleOptionSlider *>(slider);  // returns slider
qstyleoption_cast<QStyleOptionSlider *>(frame);   // returns 0

qstyleoption_cast<T>() works on subclasses ofQStyleOption. Unlike dynamic_cast<T>(), it works acrossdynamic library boundaries and doesn't require the compiler tosupport RTTI (run-time type identification).


Binary Compatibility and Extensibility

Since QStyleOption and its subclasses provide direct access totheir data members for efficiency reasons, the problem of binarycompatibility arises again. What happens if Trolltech wants to addmembers to these structs?

Fortunately, there's a solution. The day we need to add a data memberto a QStyleOption subclass, we will solve the issue byintroducing a new subclass with a V2 suffix, standing for version2. Existing styles will still work as before, because the version 2class will inherit the version 1 class; but new styles will useqstyleoption_cast<T>() to find out whether the option object is V2 ornot. For example:

if (const QStyleOptionSlider *sliderOpt =
       qstyleoption_cast<const QStyleOptionSlider *>(option)) {
    int extraMember = 0;
    if (const QStyleOptionSlider *sliderOptV2 =
           qstyleoption_cast<const QStyleOptionSliderV2 *>(option))
        extraMember = sliderOptV2->extraMember;
 
    // draw a scroll bar
}

With luck, Qt 5 will be released before we need to implement thissolution, auspiciously delivering us from the subjugation of binarycompatibility.