Styles and Style Aware Widgets

Styles (classes that inherit QStyle) draw on behalf of widgets and encapsulate the look and feel of a GUI. The QStyle class is an abstract base class that encapsulates the look and feel of a GUI. Qt's built-in widgets use it to perform nearly all of their drawing, ensuring that they look exactly like the equivalent native widgets.

Qt comes with a selection of built-in styles. Certain styles are only available on specific platforms. Custom styles are made available as plugins or by creating an instance of a specific style class with QStyleFactory::create() and setting it with QApplication::setStyle().

Customizing a Style

In order to customize an existing style, inherit QProxyStyle and reimplement the desired virtual methods. QProxyStyle allows one to specify a certain base style, or it will automatically use the application style when the base style is left unspecified. The former gives a full control on the base style and works best if the customization expects a certain style behavior, whereas the latter provides a platform agnostic way to customize the application style that defaults to the native platform style.

Implementing a Custom Style

QCommonStyle provides a convenient base for full custom style implementations. The approach is same than with QProxyStyle, but inherit QCommonStyle instead and reimplement the appropriate virtual methods. Implementing a full custom style is somewhat involved, and we therefore provide this overview. We give a step-by-step walkthrough of how to style individual Qt widgets. We will examine the QStyle virtual functions, member variables, and enumerations.

The part of this document that does not concern the styling of individual widgets is meant to be read sequentially because later sections tend to depend on earlier ones. The description of the widgets can be used for reference while implementing a style. However, you may need to consult the Qt source code in some cases. The sequence in the styling process should become clear after reading this document, which will aid you in locating relevant code.

To develop style aware widgets (i.e., widgets that conform to the style in which they are drawn), you need to draw them using the current style. This document shows how widgets draw themselves and which possibilities the style gives them.

Classes for Widget Styling

These classes are used to customize an application's appearance and style.

  • QStylePainter: The QStylePainter class is a convenience class for drawing QStyle elements inside a widget.

The QStyle Implementation

The API of QStyle contains functions that draw the widgets, static helper functions to do common and difficult tasks (e.g., calculating the position of slider handles) and functions to do the various calculations necessary while drawing (e.g., for the widgets to calculate their size hints). The style also helps some widgets with the layout of their contents. In addition, it creates a QPalette that contains QBrushes to draw with.

QStyle draws graphical elements; an element is a widget or a widget part like a push button bevel, a window frame, or a scroll bar. Most draw functions now take four arguments:

  • an enum value specifying which graphical element to draw

  • a QStyleOption specifying how and where to render that element

  • a QPainter that should be used to draw the element

  • a QWidget on which the drawing is performed (optional)

When a widget asks a style to draw an element, it provides the style with a QStyleOption, which is a class that contains the information necessary for drawing. Thanks to QStyleOption, it is possible to make QStyle draw widgets without linking in any code for the widget. This makes it possible to use QStyle's draw functions on any paint device, i.e., you can draw a combobox on any widget, not just on a QComboBox.

The widget is passed as the last argument in case the style needs it to perform special effects (such as animated default buttons on macOS), but it isn't mandatory.

In the course of this section, we will look at the style elements, the style options, and the functions of QStyle. Finally, we describe how the palette is used.

Items in item views are drawn by delegates in Qt. The item view headers are still drawn by the style. Qt's default delegate, QStyledItemDelegate, draws its items partially through the current style; it draws the check box indicators and calculates bounding rectangles for the elements of which the item consists. In this document, we only describe how to implement a QStyle subclass. If you wish to add support for other datatypes than those supported by the QStyledItemDelegate, you need to implement a custom delegate. Note that delegates must be set programmatically for each individual widget (i.e., default delegates cannot be provided as plugins).

The Style Elements

A style element is a graphical part of a GUI. A widget consists of a hierarchy (or tree) of style elements. For instance, when a style receives a request to draw a push button (from QPushButton, for example), it draws a label (text and icon), a button bevel, and a focus frame. The button bevel, in turn, consists of a frame around the bevel and two other elements, which we will look at later. Below is a conceptual illustration of the push button element tree. We will see the actual tree for QPushButton when we go through the individual widgets.

Image non disponible

Widgets are not necessarily drawn by asking the style to draw only one element. Widgets can make several calls to the style to draw different elements. An example is QTabWidget, which draws its tabs and frame individually.

There are three element types: primitive elements, control elements, and complex control elements. The elements are defined by the ComplexControl, ControlElement, and PrimitiveElement enums. The values of each element enum has a prefix to identify their type: CC_ for complex elements, CE_ for control elements, and PE_ for primitive elements. We will in the following three sections see what defines the different elements and see examples of widgets that use them.

The QStyle class description contains a list of these elements and their roles in styling widgets. We will see how they are used when we style individual widgets.

Primitive Elements

Primitive elements are GUI elements that are common and often used by several widgets. Examples of these are frames, button bevels, and arrows for spin boxes, scroll bars, and combo boxes. Primitive elements cannot exist on their own: they are always part of a larger construct. They take no part in the interaction with the user, but are passive decorations in the GUI.

Control Elements

A control element performs an action or displays information to the user. Examples of control elements are push buttons, check boxes, and header sections in tables and tree views. Control elements are not necessarily complete widgets such as push buttons, but can also be widget parts such as tab bar tabs and scroll bar sliders. They differ from primitive elements in that they are not passive, but fill a function in the interaction with the user. Controls that consist of several elements often use the style to calculate the bounding rectangles of the elements. The available sub elements are defined by the SubElement enum. This enum is only used for calculating bounding rectangles; sub elements are not graphical elements to be drawn like primitive, control, and complex elements.

Complex Control Elements

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. This is dependent on which sub control (if any) the mouse is over or pressed on. Examples of complex controls are scroll bars and combo boxes. With a scroll bar, you can use the mouse to move the slider and press the line up and line down buttons. The available sub controls are defined by the SubControl enum.

In addition to drawing, the style needs to provide the widgets with information on which sub control (if any) a mouse press was made on. For instance, a QScrollBar needs to know if the user pressed the slider, the slider groove, or one of the buttons.

Note that sub controls are not the same as the control elements described in the previous section. You cannot use the style to draw a sub control; the style will only calculate the bounding rectangle in which the sub control should be drawn. It is common, though, that complex elements use control and primitive elements to draw their sub controls, which is an approach that is frequently used by the built-in styles in Qt and also the Java style. For instance, the Java style uses PE_IndicatorCheckBox to draw the check box in group boxes (which is a sub control of CC_GroupBox). Some sub controls have an equivalent control element, e.g., the scroll bar slider (SC_SCrollBarSlider and CE_ScrollBarSlider).

Other QStyle Tasks

The style elements and widgets, as mentioned, use the style to calculate bounding rectangles of sub elements and sub controls. Pixel metrics, which are style-dependent sizes in screen pixels, are also used for measurements when drawing. The available rectangles and pixel metrics are represented by three enums in QStyle: SubElement, SubControl, and PixelMetric. Values of the enums can easily by identified as they start with SE_, SC_ and PM_.

The style also contains a set of style hints, which is represented as values in the StyleHint enum. All widgets do not have the same functionality and look in the different styles. For instance, when the menu items in a menu do not fit in a single column on the screen, some styles support scrolling while others draw more than one column to fit all items.

A style usually has a set of standard images (such as a warning, a question, and an error image) for message boxes, file dialogs, etc. QStyle provides the StandardPixmap enum. Its values represent the standard images. Qt's widgets use these, so when you implement a custom style you should supply the images used by the style that is being implemented.

The style calculates the spacing between widgets in layouts. There are two ways the style can handle these calculations. You can set the PM_LayoutHorizontalSpacing and PM_LayoutVerticalSpacing, which is the way the Java style does it (through QCommonStyle). Alternatively, you can implement QStyle::layoutSpacing() and QStyle::layoutSpacingImplementation() if you need more control over this part of the layout. In these functions you can calculate the spacing based on control types (QSizePolicy::ControlType) for different size policies (QSizePolicy::Policy) and also the style option for the widget in question.

Style Options

The sub-classes of QStyleOption contain all information necessary to style the individual elements. Style options are instantiated - usually on the stack - and filled out by the caller of the QStyle function. Depending on what is drawn the style will expect different a different style option class. For example, the QStyle::PE_FrameFocusRect element expects a QStyleOptionFocusRect argument, and it's possible to create custom subclasses that a custom style can use. The style options keep public variables for performance reasons.

The widgets can be in a number of different states, which are defined by the State enum. Some of the state flags have different meanings depending on the widget, but others are common for all widgets like State_Disabled. It is QStyleOption that sets the common states with QStyleOption::initFrom(); the rest of the states are set by the individual widgets.

Most notably, the style options contain the palette and bounding rectangles of the widgets to be drawn. Most widgets have specialized style options. QPushButton and QCheckBox, for instance, use QStyleOptionButton as their style option, which contains the text, icon, and the size of their icon. The exact contents of all options are described when we go through the individual widgets.

When reimplementing QStyle functions that take a QStyleOption parameter, you often need to cast the QStyleOption to a subclass (e.g., QStyleOptionFocusRect). For safety, you can use qstyleoption_cast() to ensure that the pointer type is correct. If the object isn't of the right type, qstyleoption_cast() returns nullptr. For example:

 
Sélectionnez
const QStyleOptionFocusRect *focusRectOption =
        qstyleoption_cast<const QStyleOptionFocusRect *>(option);
if (focusRectOption) {
    ...
}

The following code snippet illustrates how to use QStyle to draw the focus rectangle from a custom widget's paintEvent():

 
Sélectionnez
void MyWidget::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    ...

    QStyleOptionFocusRect option(1);
    option.init(this);
    option.backgroundColor = palette().color(QPalette::Window);

    style().drawPrimitive(QStyle::PE_FrameFocusRect, &option, &painter,
                          this);
}

The next example shows how to derive from an existing style to customize the look of a graphical element:

 
Sélectionnez
class CustomStyle : public QProxyStyle
{
    Q_OBJECT

public:
    CustomStyle();
    ~CustomStyle() {}

    void drawPrimitive(PrimitiveElement element, const QStyleOption *option,
                       QPainter *painter, const QWidget *widget) const override;
};

void CustomStyle::drawPrimitive(PrimitiveElement element, const QStyleOption *option,
                                QPainter *painter, const QWidget *widget) const
{
    if (element == PE_IndicatorSpinUp || element == PE_IndicatorSpinDown) {
        QPolygon points(3);
        int x = option->rect.x();
        int y = option->rect.y();
        int w = option->rect.width() / 2;
        int h = option->rect.height() / 2;
        x += (option->rect.width() - w) / 2;
        y += (option->rect.height() - h) / 2;

        if (element == PE_IndicatorSpinUp) {
            points[0] = QPoint(x, y + h);
            points[1] = QPoint(x + w, y + h);
            points[2] = QPoint(x + w / 2, y);
        } else { // PE_SpinBoxDown
            points[0] = QPoint(x, y);
            points[1] = QPoint(x + w, y);
            points[2] = QPoint(x + w / 2, y + h);