Auf dieser Seite

Gestaltung eines Kontrollkästchens: Eine Schritt-für-Schritt-Anleitung

Diese Seite verfolgt den Weg eines Kontrollkästchens vom Empfang eines Paint-Ereignisses bis zum Abschluss des Zeichenvorgangs. Sie zeigt, wie das Widget seine Stiloption aufbaut, wie „ QCommonStyle “ das Element in Teile zerlegt und wie „ QProxyStyle “ einen dieser Teile ersetzt. Die meisten Widgets folgen derselben Struktur, daher funktioniert für sie dieselbe Lesestrategie: Finden Sie die vom Widget erstellte Stiloption, finden Sie die Elemente, die es zeichnet, und lesen Sie die „ QCommonStyle “-Implementierung dieser Elemente.

Das Widget erstellt eine Stiloption

QCheckBox und verwendet dabei „ QStyleOptionButton “. Seine Funktion „ initStyleOption() “ füllt die Option wie folgt aus (etwas vereinfacht):

    opt.initFrom(q);
    if (down)
        opt.state |= QStyle::State_Sunken;
    if (tristate && noChange)
        opt.state |= QStyle::State_NoChange;
    else
        opt.state |= checked ? QStyle::State_On : QStyle::State_Off;
    if (q->testAttribute(Qt::WA_Hover) && q->underMouse()) {
        if (hovering)
            opt.state |= QStyle::State_MouseOver;
        else
            opt.state &= ~QStyle::State_MouseOver;
    }
    opt.text = text;
    opt.icon = icon;
    opt.iconSize = q->iconSize();

QStyleOption::initFrom() legt die Informationen fest, die jedes Widget gemeinsam hat. Die Umsetzung läuft im Wesentlichen so ab:

    state = QStyle::State_None;
    if (widget->isEnabled())
        state |= QStyle::State_Enabled;
    if (widget->hasFocus())
        state |= QStyle::State_HasFocus;
    if (widget->window()->testAttribute(Qt::WA_KeyboardFocusChange))
        state |= QStyle::State_KeyboardFocusChange;
    if (widget->underMouse())
        state |= QStyle::State_MouseOver;
    if (widget->window()->isActiveWindow())
        state |= QStyle::State_Active;

    direction = widget->layoutDirection();
    rect = widget->rect();
    palette = widget->palette();
    fontMetrics = widget->fontMetrics();

State_Enabled wird gesetzt, wenn das Widget aktiviert ist, „ State_HasFocus “, wenn es den Fokus hat, „ State_KeyboardFocusChange “, wenn der Benutzer den Fokus zuletzt mit der Tastatur gewechselt hat, und „ State_Active “, wenn das Fenster des Widgets das aktive Fenster ist. „ State_MouseOver “ wird gesetzt, solange sich der Mauszeiger über dem Widget befindet. Zusätzlich zum Status speichert „ initFrom() “ die Layoutrichtung, das Rechteck des Widgets, seine Palette und seine Schriftmetriken in der Option.

QCheckBox und fügt dann seinen eigenen Status hinzu. „ State_Sunken “ wird gesetzt, solange der Benutzer auf das Kontrollkästchen klickt, unabhängig davon, ob es markiert ist oder nicht. „ State_NoChange “ wird für ein teilweise markiertes Dreistufen-Kontrollkästchen gesetzt; andernfalls spiegeln „ State_On “ oder „ State_Off “ den Markierungsstatus wider. Die Option enthält außerdem den Text, das Symbol und die Symbolgröße. „ QCheckBox “ behält „ State_MouseOver “ nur so lange bei, wie das Widget das Attribut „ WA_Hover “ besitzt, das in der Regel von einem Stil in „ polish()“ gesetzt wird.

Angenommen, der Benutzer klickt mit der Maus auf das markierte Kontrollkästchen im folgenden Screenshot, während es den Fokus hat. Der Screenshot zeigt außerdem ein Optionsfeld, das dieselbe Struktur mit „ PE_IndicatorRadioButton “ und „ CE_RadioButtonLabel “ verwendet:

Kontrollkästchen und Optionsfeld mit ihrer Anzeige, ihrer Beschriftung und ihren Fokusrechtecken, die in einer Legende umrandet und benannt sind

In diesem Moment weist die Option folgende Status-Flags auf:

StatusflagGesetzt
State_SunkenJa
State_NoChangeNein
State_OnJa
State_OffNein
State_MouseOverJa
State_EnabledJa
State_HasFocusJa
State_KeyboardFocusChangeNein
State_ActiveJa

Das Widget fordert den Stil auf,

QCheckBox zeichnet sich selbst in ` paintEvent()` mit einem ` QStylePainter`, der die Zeichenfunktionen von ` QStyle` umschließt:

    QStylePainter p;
    QStyleOptionButton opt;
    initStyleOption(&opt);
    p.drawControl(QStyle::CE_CheckBox, opt);

Das ist alles, was das Widget tut. Alles andere geschieht im Stil.

QCommonStyle zerlegt das Element in Teile

QCommonStyle und verarbeitet „ CE_CheckBox “, indem es die Rechtecke seiner beiden Unterelemente „ SE_CheckBoxIndicator “ und „ SE_CheckBoxContents “ abfragt und in jedes davon ein Element zeichnet. Hat das Kontrollkästchen den Fokus, zeichnet es außerdem den Fokusrahmen:

    QStyleOptionButton subopt = *btn;
    subopt.rect = subElementRect(QStyle::SE_CheckBoxIndicator, btn, widget);
    proxy()->drawPrimitive(QStyle::PE_IndicatorCheckBox, &subopt, p, widget);
    subopt.rect = subElementRect(QStyle::SE_CheckBoxContents, btn, widget);
    proxy()->drawControl(QStyle::CE_CheckBoxLabel, &subopt, p, widget);
    if (btn->state & State_HasFocus) {
        QStyleOptionFocusRect fropt;
        fropt.QStyleOption::operator=(*btn);
        fropt.rect = subElementRect(QStyle::SE_CheckBoxFocusRect, btn, widget);
        proxy()->drawPrimitive(QStyle::PE_FrameFocusRect, &fropt, p, widget);
    }

Beachten Sie die Aufrufe von ` proxy() `. Ein Stil zeichnet seine untergeordneten Elemente immer über ` proxy()`, damit ein umgebendes ` QProxyStyle ` die Möglichkeit hat, diese zu überschreiben.

CE_CheckBoxLabel ist ebenfalls in ` QCommonStyle` implementiert:

    const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(opt);
    uint alignment = visualAlignment(btn->direction, Qt::AlignLeft | Qt::AlignVCenter);
    if (!proxy()->styleHint(SH_UnderlineShortcut, btn, widget))
        alignment |= Qt::TextHideMnemonic;
    QPixmap pix;
    QRect textRect = btn->rect;
    if (!btn->icon.isNull()) {
        const auto dpr = p->device()->devicePixelRatio();
        pix = btn->icon.pixmap(btn->iconSize, dpr,
                                btn->state & State_Enabled ? QIcon::Normal : QIcon::Disabled);
        proxy()->drawItemPixmap(p, btn->rect, alignment, pix);
        if (btn->direction == Qt::RightToLeft)
            textRect.setRight(textRect.right() - btn->iconSize.width() - 4);
        else
            textRect.setLeft(textRect.left() + btn->iconSize.width() + 4);
    }
    if (!btn->text.isEmpty()){
        proxy()->drawItemText(p, textRect, alignment | Qt::TextShowMnemonic,
            btn->palette, btn->state & State_Enabled, btn->text, QPalette::WindowText);
    }

visualAlignment() passt die Ausrichtung an die Layoutrichtung an. Der Stil zeichnet das Symbol, falls vorhanden, und verkleinert das verbleibende Textrechteck entsprechend. ` drawItemText` () zeichnet den Text unter Berücksichtigung der Ausrichtung, der Layoutrichtung und des Mnemonik-Codes und wählt die Textfarbe anhand der Palette aus. Das Zeichnen einer Beschriftung umfasst viele Details, die von der Basisklasse gut gehandhabt werden, sodass ein benutzerdefinierter Stil dies selten neu implementieren muss.

Ein Proxy-Stil ersetzt den Indikator

Der Indikator, PE_IndicatorCheckBox, ist der Punkt, an dem sich Stile unterscheiden, daher ist dies der Teil, der ersetzt werden muss. Die folgende Unterklasse von QProxyStyle zeichnet einen abgerundeten Indikator anhand der Palettenfarben aus der Option und leitet alle anderen Elemente an den Basisstil weiter:

class CheckBoxStyle : public QProxyStyle
{
public:
    using QProxyStyle::QProxyStyle;

    void drawPrimitive(PrimitiveElement element, const QStyleOption *option,
                       QPainter *painter, const QWidget *widget) const override;
};
void CheckBoxStyle::drawPrimitive(PrimitiveElement element, const QStyleOption *option,
                                  QPainter *painter, const QWidget *widget) const
{
    if (element != PE_IndicatorCheckBox) {
        QProxyStyle::drawPrimitive(element, option, painter, widget);
        return;
    }

    const bool enabled = option->state & State_Enabled;
    const QPalette::ColorGroup group = !enabled ? QPalette::Disabled
                                     : option->state & State_Active ? QPalette::Active
                                                                    : QPalette::Inactive;
    const QPalette &palette = option->palette;
    const QRect rect = option->rect.adjusted(1, 1, -1, -1);

    painter->save();
    painter->setRenderHint(QPainter::Antialiasing);

    // Frame and background: highlight the frame while the mouse hovers over
    // the indicator, and darken the background while it's pressed.
    QColor frameColor = palette.color(group, QPalette::Mid);
    if (enabled && (option->state & State_MouseOver))
        frameColor = palette.color(group, QPalette::Highlight);
    const QPalette::ColorRole fillRole =
            option->state & State_Sunken ? QPalette::Mid : QPalette::Base;
    painter->setPen(frameColor);
    painter->setBrush(palette.brush(group, fillRole));
    painter->drawRoundedRect(rect, 2, 2);

    // Check mark for State_On, a filled square for the partially checked
    // State_NoChange, nothing for State_Off.
    const QRect inner = rect.adjusted(3, 3, -3, -3);
    if (option->state & State_On) {
        painter->setPen(QPen(palette.color(group, QPalette::Text), 2));
        painter->drawLine(inner.left(), inner.center().y(),
                          inner.center().x(), inner.bottom());
        painter->drawLine(inner.center().x(), inner.bottom(),
                          inner.right(), inner.top());
    } else if (option->state & State_NoChange) {
        painter->fillRect(inner, palette.brush(group, QPalette::Text));
    }

    painter->restore();
}

Die Implementierung liest alle Informationen aus der Option aus. Sie wählt die Farbgruppe aus „ State_Enabled “ und „ State_Active “ aus, hebt den Rahmen hervor, solange „ State_MouseOver “ gesetzt ist, dunkelt den Hintergrund ab, solange „ State_Sunken “ gesetzt ist, und zeichnet ein Häkchen für „ State_On “ oder ein gefülltes Quadrat für „ State_NoChange “. Da alle Farben aus der Palette stammen, folgt der Indikator der Anwendungspalette und funktioniert sowohl in hellen als auch in dunklen Farbschemata. Außerdem speichert und stellt er den Painter wieder her, sodass die Basisklasse den Painter in dem erwarteten Zustand vorfindet.

Der gleiche Indikator erscheint überall dort, wo der Stil „ PE_IndicatorCheckBox “ zeichnet: in Kontrollkästchen, in ankreuzbaren Gruppenfeldern und in Elementansichten, die ihn für Häkchen verwenden. Diese Wiederverwendbarkeit ist der Grund für die Existenz von Primitelementen.

Um den Stil zu verwenden, installieren Sie ihn, bevor die Anwendung ihre Widgets erstellt:

int main(int argc, char *argv[])
{
    QApplication::setStyle(new CheckBoxStyle);
    QApplication app(argc, argv);
    QCheckBox box("Send me updates");
    box.show();
    return app.exec();
}

Wenden Sie dieselbe Vorgehensweise auf andere Widgets an

Um zu erfahren, wie ein Widget gezeichnet wird, müssen Sie nicht den gesamten Code lesen. Es reicht in der Regel aus, zu wissen, welche Stilelemente das Widget zeichnet, welche Zustände es setzt und was seine Stiloption enthält. Das Widget erstellt eine Option und ruft den Stil ein- oder mehrmals auf; der Stil zeichnet die Elemente, die das Widget anfordert. Die Widget-Stilreferenz listet genau das für jedes Widget auf.

Siehe auch „Wie ein Stil ein Widget zeichnet“, „Widget-Stil-Referenz“, „ QProxyStyle “ und „ QStyleOptionButton “.

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.