Beispiel für Malerpfade (Painter Paths)

Das Beispiel "Malerpfade" zeigt, wie man mit Hilfe von Malerpfaden komplexe Formen für das Rendering erstellen kann.

Die Klasse QPainterPath bietet einen Container für Malvorgänge, mit dem grafische Formen konstruiert und wiederverwendet werden können.

Ein Malerpfad ist ein Objekt, das aus einer Reihe grafischer Bausteine (z. B. Rechtecke, Ellipsen, Linien und Kurven) besteht und zum Füllen, Umreißen und Ausschneiden verwendet werden kann. Der Hauptvorteil von Malerpfaden gegenüber normalen Zeichenoperationen besteht darin, dass komplexe Formen nur einmal erstellt werden müssen, sie aber viele Male gezeichnet werden können, indem nur die Funktion QPainter::drawPath() aufgerufen wird.

Das Beispiel besteht aus zwei Klassen:

  • Die Klasse RenderArea, die ein benutzerdefiniertes Widget ist, das einen einzelnen Malerpfad anzeigt.
  • Die Klasse Window ist das Hauptfenster der Anwendung, das mehrere RenderArea Widgets anzeigt und dem Benutzer die Möglichkeit gibt, die Füllung, den Stift, die Farbe und den Drehwinkel der Malerpfade zu verändern.

Zunächst wird die Klasse Window besprochen, dann wird die Klasse RenderArea näher betrachtet.

Definition der Fensterklasse

Die Klasse Window erbt von QWidget und ist das Hauptfenster der Anwendung, das mehrere RenderArea Widgets anzeigt und dem Benutzer die Möglichkeit gibt, die Füllung, den Stift, die Farbe und den Drehwinkel der Malpfade zu verändern.

class Window : public QWidget
{
    Q_OBJECT

public:
    Window();

private slots:
    void fillRuleChanged();
    void fillGradientChanged();
    void penColorChanged();

Wir deklarieren drei private Slots, um auf Benutzereingaben bezüglich Füllung und Farbe zu reagieren: fillRuleChanged(), fillGradientChanged() und penColorChanged().

Wenn der Benutzer die Stiftbreite und den Drehwinkel ändert, wird der neue Wert über das Signal QSpinBox::valueChanged() direkt an die RenderArea Widgets weitergegeben. Der Grund, warum wir Slots implementieren müssen, um die Füllung und die Farbe zu aktualisieren, ist, dass QComboBox kein ähnliches Signal zur Verfügung stellt, das den neuen Wert als Argument weitergibt; also müssen wir den neuen Wert oder die neuen Werte abrufen, bevor wir die RenderArea Widgets aktualisieren können.

private:
    void populateWithColors(QComboBox *comboBox);
    QVariant currentItemData(QComboBox *comboBox);

Wir deklarieren auch ein paar private Komfortfunktionen: populateWithColors() füllt ein gegebenes QComboBox mit Elementen, die den Farbnamen entsprechen, die Qt kennt, und currentItemData() gibt das aktuelle Element für ein gegebenes QComboBox zurück.

    QList<RenderArea*> renderAreas;
    QLabel *fillRuleLabel;
    QLabel *fillGradientLabel;
    QLabel *fillToLabel;
    QLabel *penWidthLabel;
    QLabel *penColorLabel;
    QLabel *rotationAngleLabel;
    QComboBox *fillRuleComboBox;
    QComboBox *fillColor1ComboBox;
    QComboBox *fillColor2ComboBox;
    QSpinBox *penWidthSpinBox;
    QComboBox *penColorComboBox;
    QSpinBox *rotationAngleSpinBox;
};

Dann deklarieren wir die verschiedenen Komponenten des Hauptfenster-Widgets. Wir deklarieren auch eine Konstante, die die Anzahl der RenderArea Widgets angibt.

Implementierung der Fensterklasse

Im Window -Konstruktor definieren wir die verschiedenen Malerpfade und erstellen die entsprechenden RenderArea -Widgets, die die grafischen Formen darstellen werden:

Window::Window()
{
    QPainterPath rectPath;
    rectPath.moveTo(20.0, 30.0);
    rectPath.lineTo(80.0, 30.0);
    rectPath.lineTo(80.0, 70.0);
    rectPath.lineTo(20.0, 70.0);
    rectPath.closeSubpath();

Wir konstruieren ein Rechteck mit scharfen Ecken mit Hilfe der Funktionen QPainterPath::moveTo() und QPainterPath::lineTo().

QPainterPath::moveTo() verschiebt den aktuellen Punkt zu dem als Argument übergebenen Punkt. Ein Malerpfad ist ein Objekt, das aus einer Reihe von grafischen Bausteinen, d.h. Unterpfaden, besteht. Durch das Verschieben des aktuellen Punktes wird auch ein neuer Unterpfad gestartet (wobei der zuvor aktuelle Pfad implizit geschlossen wird, wenn der neue gestartet wird). Die Funktion QPainterPath::lineTo() fügt eine gerade Linie vom aktuellen Punkt zum angegebenen Endpunkt hinzu. Nachdem die Linie gezeichnet wurde, wird der aktuelle Punkt so aktualisiert, dass er sich am Endpunkt der Linie befindet.

Zuerst verschieben wir den aktuellen Punkt und beginnen einen neuen Unterpfad, und wir zeichnen drei Seiten des Rechtecks. Dann rufen wir die Funktion QPainterPath::closeSubpath() auf, die eine Linie zum Anfang des aktuellen Teilpfades zeichnet. Ein neuer Teilpfad wird automatisch begonnen, wenn der aktuelle Teilpfad geschlossen wird. Der aktuelle Punkt des neuen Pfades ist (0, 0). Wir hätten auch QPainterPath::lineTo() aufrufen können, um die letzte Linie zu zeichnen, und dann explizit einen neuen Unterpfad mit der Funktion QPainterPath::moveTo() beginnen können.

QPainterPath Die Funktion QPainterPath::addRect() ist eine Komfortfunktion, die dem Pfad ein bestimmtes Rechteck als geschlossenen Unterpfad hinzufügt. Das Rechteck wird als eine Reihe von Linien im Uhrzeigersinn hinzugefügt. Die aktuelle Position des Malerpfads nach dem Hinzufügen des Rechtecks befindet sich an der linken oberen Ecke des Rechtecks.

    QPainterPath roundRectPath;
    roundRectPath.moveTo(80.0, 35.0);
    roundRectPath.arcTo(70.0, 30.0, 10.0, 10.0, 0.0, 90.0);
    roundRectPath.lineTo(25.0, 30.0);
    roundRectPath.arcTo(20.0, 30.0, 10.0, 10.0, 90.0, 90.0);
    roundRectPath.lineTo(20.0, 65.0);
    roundRectPath.arcTo(20.0, 60.0, 10.0, 10.0, 180.0, 90.0);
    roundRectPath.lineTo(75.0, 70.0);
    roundRectPath.arcTo(70.0, 60.0, 10.0, 10.0, 270.0, 90.0);
    roundRectPath.closeSubpath();

Dann konstruieren wir ein Rechteck mit abgerundeten Ecken. Wie zuvor verwenden wir die Funktionen QPainterPath::moveTo() und QPainterPath::lineTo(), um die Seiten des Rechtecks zu zeichnen. Um die abgerundeten Ecken zu erzeugen, verwenden wir die Funktion QPainterPath::arcTo().

QPainterPath::arcTo() erzeugt einen Bogen, der das angegebene Rechteck (angegeben durch QRect oder die Koordinaten des Rechtecks) einnimmt, am angegebenen Startwinkel beginnt und sich um die angegebenen Gradzahlen gegen den Uhrzeigersinn erstreckt. Die Winkel werden in Grad angegeben. Bögen im Uhrzeigersinn können mit negativen Winkeln angegeben werden. Die Funktion verbindet den aktuellen Punkt mit dem Startpunkt des Bogens, sofern sie nicht bereits verbunden sind.

    QPainterPath ellipsePath;
    ellipsePath.moveTo(80.0, 50.0);
    ellipsePath.arcTo(20.0, 30.0, 60.0, 40.0, 0.0, 360.0);

Wir verwenden auch die Funktion QPainterPath::arcTo(), um den Ellipsenpfad zu konstruieren. Zuerst verschieben wir den aktuellen Punkt und beginnen einen neuen Pfad. Dann rufen wir QPainterPath::arcTo() mit dem Startwinkel 0,0 und 360,0 Grad als letztem Argument auf und erstellen eine Ellipse.

Auch hier bietet QPainterPath eine Komfortfunktion ( QPainterPath::addEllipse()), die eine Ellipse innerhalb eines bestimmten begrenzenden Rechtecks erzeugt und sie dem Malerpfad hinzufügt. Wenn der aktuelle Unterpfad geschlossen ist, wird ein neuer Unterpfad begonnen. Die Ellipse besteht aus einer Kurve im Uhrzeigersinn, die bei Null Grad (der 3-Uhr-Position) beginnt und endet.

    QPainterPath piePath;
    piePath.moveTo(50.0, 50.0);
    piePath.arcTo(20.0, 30.0, 60.0, 40.0, 60.0, 240.0);
    piePath.closeSubpath();

Bei der Konstruktion des Kreisdiagrammpfades verwenden wir weiterhin eine Kombination der genannten Funktionen: Zuerst verschieben wir den aktuellen Punkt und beginnen einen neuen Unterpfad. Dann erstellen wir eine Linie von der Mitte des Diagramms zum Bogen und den Bogen selbst. Wenn wir den Unterpfad schließen, konstruieren wir implizit die letzte Linie zurück zum Mittelpunkt des Diagramms.

    QPainterPath polygonPath;
    polygonPath.moveTo(10.0, 80.0);
    polygonPath.lineTo(20.0, 10.0);
    polygonPath.lineTo(80.0, 30.0);
    polygonPath.lineTo(90.0, 70.0);
    polygonPath.closeSubpath();

Die Konstruktion eines Polygons ist äquivalent zur Konstruktion eines Rechtecks.

QPainterPath Die Funktion QPainterPath::addPolygon() fügt das angegebene Polygon dem Pfad als neuen Unterpfad hinzu. Die aktuelle Position, nachdem das Polygon hinzugefügt wurde, ist der letzte Punkt im Polygon.

    QPainterPath groupPath;
    groupPath.moveTo(60.0, 40.0);
    groupPath.arcTo(20.0, 20.0, 40.0, 40.0, 0.0, 360.0);
    groupPath.moveTo(40.0, 40.0);
    groupPath.lineTo(40.0, 80.0);
    groupPath.lineTo(80.0, 80.0);
    groupPath.lineTo(80.0, 40.0);
    groupPath.closeSubpath();

Dann erstellen wir einen Pfad, der aus einer Gruppe von Unterpfaden besteht: Zuerst verschieben wir den aktuellen Punkt und erstellen einen Kreis mit der Funktion QPainterPath::arcTo() mit dem Startwinkel 0,0 und 360 Grad als letztem Argument, wie wir es bei der Erstellung des Ellipsenpfades getan haben. Dann verschieben wir den aktuellen Punkt erneut, beginnen einen neuen Unterpfad und konstruieren drei Seiten eines Quadrats mit der Funktion QPainterPath::lineTo().

Wenn wir nun die Funktion QPainterPath::closeSubpath() aufrufen, wird die letzte Seite erstellt. Denken Sie daran, dass die Funktion QPainterPath::closeSubpath() eine Linie zum Anfang des aktuellen Unterpfades, d.h. des Quadrats, zieht.

QPainterPath Die Funktion QPainterPath::addPath() fügt dem Pfad, der die Funktion aufruft, einen bestimmten Pfad hinzu.

    QPainterPath textPath;
    QFont timesFont("Times", 50);
    timesFont.setStyleStrategy(QFont::ForceOutline);
    textPath.addText(10, 70, timesFont, tr("Qt"));

Beim Erstellen des Textpfads wird zunächst die Schriftart erstellt. Dann legen wir die Stilstrategie der Schriftart fest, die dem Algorithmus für den Schriftartenabgleich mitteilt, welche Art von Schriftarten verwendet werden soll, um eine geeignete Standardfamilie zu finden. QFont::ForceOutline erzwingt die Verwendung von Outline-Schriftarten.

Um den Text zu konstruieren, verwenden wir die Funktion QPainterPath::addText(), die den angegebenen Text dem Pfad als eine Reihe von geschlossenen Unterpfaden hinzufügt, die aus der angegebenen Schriftart erstellt werden. Die Unterpfade werden so positioniert, dass das linke Ende der Grundlinie des Textes an dem angegebenen Punkt liegt.

    QPainterPath bezierPath;
    bezierPath.moveTo(20, 30);
    bezierPath.cubicTo(80, 0, 50, 50, 80, 80);

Um den Bézier-Pfad zu erstellen, verwenden wir die Funktion QPainterPath::cubicTo(), die eine Bézier-Kurve zwischen dem aktuellen Punkt und dem angegebenen Endpunkt mit dem angegebenen Kontrollpunkt einfügt. Nach dem Hinzufügen der Kurve wird der aktuelle Punkt so aktualisiert, dass er sich am Endpunkt der Kurve befindet.

In diesem Fall unterlassen wir es, den Unterpfad zu schließen, so dass wir nur eine einfache Kurve haben. Es gibt aber immer noch eine logische Linie vom Endpunkt der Kurve zurück zum Anfang des Unterpfades; sie wird beim Füllen des Pfades sichtbar, wie man im Hauptfenster der Anwendung sehen kann.

    QPainterPath starPath;
    starPath.moveTo(90, 50);
    for (int i = 1; i < 5; ++i) {
        starPath.lineTo(50 + 40 * std::cos(0.8 * i * M_PI),
                        50 + 40 * std::sin(0.8 * i * M_PI));
    }
    starPath.closeSubpath();

Der endgültige Pfad, den wir konstruiert haben, zeigt, dass Sie QPainterPath verwenden können, um ziemlich komplexe Formen zu konstruieren, indem Sie nur die zuvor erwähnten Funktionen QPainterPath::moveTo(), QPainterPath::lineTo() und QPainterPath::closeSubpath() verwenden.

    renderAreas.push_back(new RenderArea(rectPath));
    renderAreas.push_back(new RenderArea(roundRectPath));
    renderAreas.push_back(new RenderArea(ellipsePath));
    renderAreas.push_back(new RenderArea(piePath));
    renderAreas.push_back(new RenderArea(polygonPath));
    renderAreas.push_back(new RenderArea(groupPath));
    renderAreas.push_back(new RenderArea(textPath));
    renderAreas.push_back(new RenderArea(bezierPath));
    renderAreas.push_back(new RenderArea(starPath));

Nachdem wir nun alle benötigten Malerpfade erstellt haben, erstellen wir für jeden ein entsprechendes RenderArea Widget. Zum Schluss stellen wir mit dem Makro Q_ASSERT() sicher, dass die Anzahl der Renderbereiche korrekt ist.

    fillRuleComboBox = new QComboBox;
    fillRuleComboBox->addItem(tr("Odd Even"), Qt::OddEvenFill);
    fillRuleComboBox->addItem(tr("Winding"), Qt::WindingFill);

    fillRuleLabel = new QLabel(tr("Fill &Rule:"));
    fillRuleLabel->setBuddy(fillRuleComboBox);

Dann erstellen wir die Widgets, die mit den Füllregeln der Malerpfade verbunden sind.

Es gibt zwei verfügbare Füllregeln in Qt: Die Qt::OddEvenFill Regel bestimmt, ob ein Punkt innerhalb der Form liegt, indem sie eine horizontale Linie von dem Punkt zu einer Stelle außerhalb der Form zieht und die Anzahl der Schnittpunkte zählt. Wenn die Anzahl der Schnittpunkte eine ungerade Zahl ist, befindet sich der Punkt innerhalb der Form. Diese Regel ist die Standardeinstellung.

Die Regel Qt::WindingFill bestimmt, ob ein Punkt innerhalb der Form liegt, indem sie eine horizontale Linie von dem Punkt zu einer Stelle außerhalb der Form zieht. Dann wird bestimmt, ob die Richtung der Linie an jedem Schnittpunkt nach oben oder unten zeigt. Die Windungszahl wird durch Summieren der Richtungen der einzelnen Schnittpunkte ermittelt. Wenn die Zahl ungleich Null ist, liegt der Punkt innerhalb der Form.

Die Regel Qt::WindingFill kann in den meisten Fällen als Schnittpunkt von geschlossenen Formen betrachtet werden.

    fillColor1ComboBox = new QComboBox;
    populateWithColors(fillColor1ComboBox);
    fillColor1ComboBox->setCurrentIndex(fillColor1ComboBox->findText("mediumslateblue"));

    fillColor2ComboBox = new QComboBox;
    populateWithColors(fillColor2ComboBox);
    fillColor2ComboBox->setCurrentIndex(fillColor2ComboBox->findText("cornsilk"));

    fillGradientLabel = new QLabel(tr("&Fill Gradient:"));
    fillGradientLabel->setBuddy(fillColor1ComboBox);

    fillToLabel = new QLabel(tr("to"));
    fillToLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);

    penWidthSpinBox = new QSpinBox;
    penWidthSpinBox->setRange(0, 20);

    penWidthLabel = new QLabel(tr("&Pen Width:"));
    penWidthLabel->setBuddy(penWidthSpinBox);

    penColorComboBox = new QComboBox;
    populateWithColors(penColorComboBox);
    penColorComboBox->setCurrentIndex(penColorComboBox->findText("darkslateblue"));

    penColorLabel = new QLabel(tr("Pen &Color:"));
    penColorLabel->setBuddy(penColorComboBox);

    rotationAngleSpinBox = new QSpinBox;
    rotationAngleSpinBox->setRange(0, 359);
    rotationAngleSpinBox->setWrapping(true);
    rotationAngleSpinBox->setSuffix(QLatin1String("\xB0"));

    rotationAngleLabel = new QLabel(tr("&Rotation Angle:"));
    rotationAngleLabel->setBuddy(rotationAngleSpinBox);

Wir erstellen auch die anderen Widgets im Zusammenhang mit der Füllung, dem Stift und dem Drehwinkel.

    connect(fillRuleComboBox, &QComboBox::activated,
            this, &Window::fillRuleChanged);
    connect(fillColor1ComboBox, &QComboBox::activated,
            this, &Window::fillGradientChanged);
    connect(fillColor2ComboBox, &QComboBox::activated,
            this, &Window::fillGradientChanged);
    connect(penColorComboBox, &QComboBox::activated,
            this, &Window::penColorChanged);

    for (RenderArea *area : std::as_const(renderAreas)) {
        connect(penWidthSpinBox, &QSpinBox::valueChanged,
                area, &RenderArea::setPenWidth);
        connect(rotationAngleSpinBox, &QSpinBox::valueChanged,
                area, &RenderArea::setRotationAngle);
    }

Wir verbinden die Signale der Comboboxen activated() mit den zugehörigen Slots in der Klasse Window, während wir die Signale der Spinboxen valueChanged() direkt mit den entsprechenden Slots des Widgets RenderArea verbinden.

    QGridLayout *topLayout = new QGridLayout;

    int i = 0;
    for (RenderArea *area : std::as_const(renderAreas)) {
        topLayout->addWidget(area, i / 3, i % 3);
        ++i;
    }

    QGridLayout *mainLayout = new QGridLayout;
    mainLayout->addLayout(topLayout, 0, 0, 1, 4);
    mainLayout->addWidget(fillRuleLabel, 1, 0);
    mainLayout->addWidget(fillRuleComboBox, 1, 1, 1, 3);
    mainLayout->addWidget(fillGradientLabel, 2, 0);
    mainLayout->addWidget(fillColor1ComboBox, 2, 1);
    mainLayout->addWidget(fillToLabel, 2, 2);
    mainLayout->addWidget(fillColor2ComboBox, 2, 3);
    mainLayout->addWidget(penWidthLabel, 3, 0);
    mainLayout->addWidget(penWidthSpinBox, 3, 1, 1, 3);
    mainLayout->addWidget(penColorLabel, 4, 0);
    mainLayout->addWidget(penColorComboBox, 4, 1, 1, 3);
    mainLayout->addWidget(rotationAngleLabel, 5, 0);
    mainLayout->addWidget(rotationAngleSpinBox, 5, 1, 1, 3);
    setLayout(mainLayout);

Wir fügen die RenderArea Widgets in ein separates Layout ein, das wir dann zusammen mit den übrigen Widgets in das Hauptlayout einfügen.

    fillRuleChanged();
    fillGradientChanged();
    penColorChanged();
    penWidthSpinBox->setValue(2);

    setWindowTitle(tr("Painter Paths"));
}

Schließlich initialisieren wir die RenderArea Widgets, indem wir die Slots fillRuleChanged(), fillGradientChanged() und penColorChanged() aufrufen und die anfängliche Stiftbreite und den Fenstertitel festlegen.

void Window::fillRuleChanged()
{
    Qt::FillRule rule = (Qt::FillRule)currentItemData(fillRuleComboBox).toInt();

    for (RenderArea *area : std::as_const(renderAreas))
        area->setFillRule(rule);
}

void Window::fillGradientChanged()
{
    QColor color1 = qvariant_cast<QColor>(currentItemData(fillColor1ComboBox));
    QColor color2 = qvariant_cast<QColor>(currentItemData(fillColor2ComboBox));

    for (RenderArea *area : std::as_const(renderAreas))
        area->setFillGradient(color1, color2);
}

void Window::penColorChanged()
{
    QColor color = qvariant_cast<QColor>(currentItemData(penColorComboBox));

    for (RenderArea *area : std::as_const(renderAreas))
        area->setPenColor(color);
}

Die privaten Slots werden implementiert, um den oder die neuen Werte aus den zugehörigen Comboboxen abzurufen und die RenderArea-Widgets zu aktualisieren.

Zunächst wird der neue Wert bzw. werden die neuen Werte mithilfe der privaten Funktion currentItemData() und der Vorlagenfunktion qvariant_cast() ermittelt. Dann rufen wir den zugehörigen Slot für jedes der RenderArea Widgets auf, um die Malerpfade zu aktualisieren.

void Window::populateWithColors(QComboBox *comboBox)
{
    const QStringList colorNames = QColor::colorNames();
    for (const QString &name : colorNames)
        comboBox->addItem(name, QColor(name));
}

Die Funktion populateWithColors() füllt die angegebene Combobox mit Elementen, die den Farbnamen entsprechen, von denen Qt weiß, dass sie von der statischen Funktion QColor::colorNames() bereitgestellt werden.

QVariant Window::currentItemData(QComboBox *comboBox)
{
    return comboBox->itemData(comboBox->currentIndex());
}

Die currentItemData() Funktion gibt einfach das aktuelle Element der angegebenen Combobox zurück.

RenderArea Klassendefinition

Die Klasse RenderArea erbt von QWidget und ist ein benutzerdefiniertes Widget, das einen einzelnen Malerpfad anzeigt.

class RenderArea : public QWidget
{
    Q_OBJECT

public:
    explicit RenderArea(const QPainterPath &path, QWidget *parent = nullptr);

    QSize minimumSizeHint() const override;
    QSize sizeHint() const override;

public slots:
    void setFillRule(Qt::FillRule rule);
    void setFillGradient(const QColor &color1, const QColor &color2);
    void setPenWidth(int width);
    void setPenColor(const QColor &color);
    void setRotationAngle(int degrees);

protected:
    void paintEvent(QPaintEvent *event) override;

Wir deklarieren mehrere öffentliche Slots, die den zugehörigen Malerpfad des Widgets RenderArea aktualisieren. Außerdem reimplementieren wir die Funktionen QWidget::minimumSizeHint() und QWidget::sizeHint(), um dem Widget RenderArea eine vernünftige Größe innerhalb unserer Anwendung zu geben, und wir reimplementieren den Event-Handler QWidget::paintEvent(), um seinen Painter-Pfad zu zeichnen.

private:
    QPainterPath path;
    QColor fillColor1;
    QColor fillColor2;
    int penWidth;
    QColor penColor;
    int rotationAngle;
};

Jede Instanz der Klasse RenderArea hat eine QPainterPath, ein paar Füllfarben, eine Stiftbreite, eine Stiftfarbe und einen Rotationswinkel.

Implementierung der Klasse RenderArea

Der Konstruktor nimmt ein QPainterPath als Argument (zusätzlich zu dem optionalen QWidget parent):

RenderArea::RenderArea(const QPainterPath &path, QWidget *parent)
    : QWidget(parent), path(path)
{
    penWidth = 1;
    rotationAngle = 0;
    setBackgroundRole(QPalette::Base);
}

Im Konstruktor wird das Widget RenderArea mit dem Parameter QPainterPath initialisiert und die Stiftbreite und der Rotationswinkel werden festgelegt. Wir setzen auch die Widgets background role; QPalette::Base ist normalerweise weiß.

QSize RenderArea::minimumSizeHint() const
{
    return QSize(50, 50);
}

QSize RenderArea::sizeHint() const
{
    return QSize(100, 100);
}

Dann reimplementieren wir die Funktionen QWidget::minimumSizeHint() und QWidget::sizeHint(), um dem Widget RenderArea eine angemessene Größe in unserer Anwendung zu geben.

void RenderArea::setFillRule(Qt::FillRule rule)
{
    path.setFillRule(rule);
    update();
}

void RenderArea::setFillGradient(const QColor &color1, const QColor &color2)
{
    fillColor1 = color1;
    fillColor2 = color2;
    update();
}

void RenderArea::setPenWidth(int width)
{
    penWidth = width;
    update();
}

void RenderArea::setPenColor(const QColor &color)
{
    penColor = color;
    update();
}

void RenderArea::setRotationAngle(int degrees)
{
    rotationAngle = degrees;
    update();
}

Die verschiedenen öffentlichen Slots aktualisieren den Malerpfad des Widgets RenderArea, indem sie die zugehörige Eigenschaft setzen und die Funktion QWidget::update() aufrufen, die eine Neuzeichnung des Widgets mit den neuen Rendering-Einstellungen erzwingt.

Der QWidget::update()-Slot bewirkt keine sofortige Neuzeichnung; stattdessen wird ein Malereignis für die Verarbeitung geplant, wenn Qt zur Hauptereignisschleife zurückkehrt.

void RenderArea::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);

Ein Malereignis ist eine Aufforderung, das gesamte Widget oder Teile davon neu zu malen. Die Funktion paintEvent() ist ein Event-Handler, der neu implementiert werden kann, um die Malereignisse des Widgets zu empfangen. Wir implementieren den Ereignishandler neu, um den Malpfad des Widgets RenderArea darzustellen.

Zuerst erstellen wir eine QPainter für die RenderArea Instanz und setzen die Render-Hinweise des Painters. Die QPainter::RenderHints werden verwendet, um Flags für QPainter zu spezifizieren, die von einer bestimmten Engine beachtet werden können oder auch nicht. QPainter::Antialiasing gibt an, dass die Engine die Kanten von Primitiven wenn möglich anti-alias machen soll, d.h. zusätzliche Pixel um die ursprünglichen herum setzen soll, um die Kanten zu glätten.

    painter.scale(width() / 100.0, height() / 100.0);
    painter.translate(50.0, 50.0);
    painter.rotate(-rotationAngle);
    painter.translate(-50.0, -50.0);

Dann skalieren wir das Koordinatensystem von QPainter, um sicherzustellen, dass der Malerpfad in der richtigen Größe gerendert wird, d. h. dass er mit dem RenderArea Widget wächst, wenn die Größe der Anwendung geändert wird. Als wir die verschiedenen Malerpfade konstruiert haben, wurden sie alle innerhalb eines Quadrats mit einer Breite von 100 Pixeln gerendert, was RenderArea::sizeHint() entspricht. Die Funktion QPainter::scale() skaliert das Koordinatensystem um die aktuelle Breite und Höhe des Widgets RenderArea geteilt durch 100.

Wenn wir nun sicher sind, dass der Malerpfad die richtige Größe hat, können wir das Koordinatensystem übersetzen, um den Malerpfad um den Mittelpunkt des RenderArea Widgets zu drehen. Nachdem wir die Drehung durchgeführt haben, müssen wir daran denken, das Koordinatensystem wieder zurückzudrehen.

    painter.setPen(QPen(penColor, penWidth, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
    QLinearGradient gradient(0, 0, 0, 100);
    gradient.setColorAt(0.0, fillColor1);
    gradient.setColorAt(1.0, fillColor2);
    painter.setBrush(gradient);
    painter.drawPath(path);
}

Dann setzen wir den Stift von QPainter mit den Rendering-Einstellungen der Instanz. Wir erstellen ein QLinearGradient und setzen seine Farben entsprechend den Füllfarben des RenderArea Widgets. Schließlich setzen wir den Pinsel von QPainter(der Farbverlauf wird automatisch in einen QBrush umgewandelt) und zeichnen den Pfad des RenderArea Widgets mit der Funktion QPainter::drawPath().

Beispielprojekt @ code.qt.io

© 2025 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.