Auf dieser Seite

Beispiel „Transformationen“

Das Beispiel „Transformationen“ veranschaulicht, wie Transformationen die Darstellung von Grafikprimitiven durch QPainter beeinflussen.

Anwendung, die verschiedene anpassbare Textumwandlungen anzeigt

Die Anwendung ermöglicht es dem Benutzer, die Darstellung einer Form zu beeinflussen, indem er die Verschiebung, Drehung und Skalierung des Koordinatensystems von „ QPainter “ ändert.

Das Beispiel besteht aus zwei Klassen und einer globalen Enumeration:

  • Die Klasse „ RenderArea “ steuert die Darstellung einer bestimmten Form.
  • Die Klasse „ Window “ ist das Hauptfenster der Anwendung.
  • Die Enumeration „ Operation “ beschreibt die verschiedenen in der Anwendung verfügbaren Transformationsoperationen.

Zunächst werfen wir einen kurzen Blick auf die Enumeration „ Operation “, anschließend betrachten wir die Klasse „ RenderArea “, um zu sehen, wie eine Form dargestellt wird. Abschließend sehen wir uns die Funktionen der Anwendung „Transformations“ an, die in der Klasse „ Window “ implementiert sind.

Transformationsoperationen

Normalerweise arbeitet das „ QPainter “ im eigenen Koordinatensystem des zugehörigen Geräts, bietet jedoch auch eine gute Unterstützung für Koordinatentransformationen.

Das Standardkoordinatensystem eines Zeichen-Geräts hat seinen Ursprung in der oberen linken Ecke. Die x-Werte nehmen nach rechts hin zu, die y-Werte nach unten hin. Mit der Funktion ` QPainter::scale()` können Sie das Koordinatensystem um einen bestimmten Versatz skalieren, mit der Funktion ` QPainter::rotate()` im Uhrzeigersinn drehen und mit der Funktion ` QPainter::translate()` verschieben (d. h. den Punkten einen bestimmten Versatz hinzufügen). Mit der Funktion ` QPainter::shear()` können Sie das Koordinatensystem zudem um den Ursprung verdrehen (sogenanntes „Shearing“).

Alle Transformationsoperationen wirken auf die Transformationsmatrix von „ QPainter “, die Sie mithilfe der Funktion „ QPainter::worldTransform()“ abrufen können. Eine Matrix transformiert einen Punkt in der Ebene in einen anderen Punkt. Weitere Informationen zur Transformationsmatrix finden Sie in der Dokumentation zu „Coordinate System “ und „ QTransform “.

enum Operation { NoTransformation, Translate, Rotate, Scale };

Die globale Enumeration „ Operation “ ist in der Datei „ renderarea.h “ deklariert und beschreibt die verschiedenen Transformationsoperationen, die in der Anwendung „Transformations“ verfügbar sind.

Definition der Klasse „RenderArea“

Die Klasse „ RenderArea “ erbt von „ QWidget “ und steuert die Darstellung einer bestimmten Form.

class RenderArea : public QWidget
{
    Q_OBJECT

public:
    RenderArea(QWidget *parent = nullptr);

    void setOperations(const QList<Operation> &operations);
    void setShape(const QPainterPath &shape);

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

protected:
    void paintEvent(QPaintEvent *event) override;

Wir deklarieren zwei öffentliche Funktionen, ` setOperations() ` und ` setShape()`, um die Form des Widgets „ RenderArea “ festlegen und das Koordinatensystem transformieren zu können, in dem die Form gerendert wird.

Wir implementieren die Funktionen ` minimumSizeHint()` und ` sizeHint()` von ` QWidget` neu, um dem Widget ` RenderArea ` eine angemessene Größe innerhalb unserer Anwendung zu geben, und wir implementieren den Ereignishandler ` QWidget::paintEvent()` neu, um die Form des Darstellungsbereichs unter Anwendung der vom Benutzer gewählten Transformationen zu zeichnen.

private:
    void drawCoordinates(QPainter &painter);
    void drawOutline(QPainter &painter);
    void drawShape(QPainter &painter);
    void transformPainter(QPainter &painter);

    QList<Operation> operations;
    QPainterPath shape;
    QRect xBoundingRect;
    QRect yBoundingRect;
};

Außerdem definieren wir mehrere Hilfsfunktionen, um die Form, den Umriss des Koordinatensystems und die Koordinaten zu zeichnen sowie den „Painter“ entsprechend den gewählten Transformationen zu transformieren.

Darüber hinaus verwaltet das Widget „ RenderArea “ eine Liste der aktuell angewendeten Transformationsoperationen, einen Verweis auf seine Form sowie einige Hilfsvariablen, die wir beim Rendern der Koordinaten verwenden werden.

Implementierung der Klasse „RenderArea“

Das Widget „ RenderArea “ steuert die Darstellung einer bestimmten Form, einschließlich der Transformationen des Koordinatensystems, durch die Neuimplementierung des Ereignis-Handlers „ QWidget::paintEvent()“. Zunächst werfen wir jedoch einen kurzen Blick auf den Konstruktor und auf die Funktionen, die den Zugriff auf das Widget „ RenderArea “ ermöglichen:

RenderArea::RenderArea(QWidget *parent)
    : QWidget(parent)
{
    QFont newFont = font();
    newFont.setPixelSize(12);
    setFont(newFont);

    QFontMetrics fontMetrics(newFont);
    xBoundingRect = fontMetrics.boundingRect(tr("x"));
    yBoundingRect = fontMetrics.boundingRect(tr("y"));
}

Im Konstruktor übergeben wir den Parameter „parent“ an die Basisklasse und passen die Schriftart an, die wir zum Darstellen der Koordinaten verwenden werden. Die Funktion „ QWidget::font()“ gibt die aktuell für das Widget eingestellte Schriftart zurück. Solange keine spezielle Schriftart festgelegt wurde oder nachdem „ QWidget::setFont()“ aufgerufen wurde, handelt es sich dabei entweder um eine spezielle Schriftart für die Widget-Klasse, die Schriftart des übergeordneten Elements oder (wenn dieses Widget ein Widget der obersten Ebene ist) um die Standardschriftart der Anwendung.

Nachdem wir sichergestellt haben, dass die Schriftgröße 12 Punkt beträgt, extrahieren wir mithilfe der Klasse ` QFontMetrics ` die Rechtecke, die die Koordinatenbuchstaben „x“ und „y“ umschließen.

QFontMetrics bietet Funktionen für den Zugriff auf die einzelnen Metriken der Schriftart, ihrer Zeichen sowie auf in der Schriftart gerenderte Zeichenketten. Die Funktion ` QFontMetrics::boundingRect()` gibt das umschließende Rechteck des angegebenen Zeichens relativ zum Punkt ganz links auf der Grundlinie zurück.

void RenderArea::setOperations(const QList<Operation> &operations)
{
    this->operations = operations;
    update();
}

void RenderArea::setShape(const QPainterPath &shape)
{
    this->shape = shape;
    update();
}

In den Funktionen ` setShape() ` und ` setOperations() ` aktualisieren wir das Widget ` RenderArea `, indem wir den neuen Wert oder die neuen Werte speichern und anschließend den Slot ` QWidget::update()` aufrufen, der ein Zeichenereignis zur Verarbeitung einplant, sobald Qt zur Hauptereignisschleife zurückkehrt.

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

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

Wir implementieren die Funktionen ` minimumSizeHint()` und ` sizeHint()` des ` QWidget` neu, um dem Widget ` RenderArea ` eine angemessene Größe innerhalb unserer Anwendung zuzuweisen. Die Standardimplementierungen dieser Funktionen geben eine ungültige Größe zurück, wenn für dieses Widget kein Layout vorhanden ist, und andernfalls jeweils die Mindestgröße bzw. die bevorzugte Größe des Layouts.

void RenderArea::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);
    painter.fillRect(event->rect(), QBrush(Qt::white));

    painter.translate(66, 66);

Der Ereignis-Handler „ paintEvent() “ empfängt die „paint“-Ereignisse des „ RenderArea “-Widgets. Ein „paint“-Ereignis ist eine Anforderung, das Widget ganz oder teilweise neu zu zeichnen. Es kann als Folge von „ QWidget::repaint()“ oder „ QWidget::update()“ auftreten, oder weil das Widget verdeckt war und nun wieder sichtbar ist, oder aus vielen anderen Gründen.

Zunächst erstellen wir ein „ QPainter “ für das „ RenderArea “-Widget. Der Render-Hinweis „ QPainter::Antialiasing “ gibt an, dass die Engine die Kanten der Primitive nach Möglichkeit glätten soll. Anschließend löschen wir den Bereich, der neu gezeichnet werden muss, mithilfe der Funktion „ QPainter::fillRect()“.

Außerdem verschieben wir das Koordinatensystem um einen konstanten Versatz, um sicherzustellen, dass die ursprüngliche Form mit einem angemessenen Rand gerendert wird.

    {
        QPainterStateGuard guard(&painter);
        transformPainter(painter);
        drawShape(painter);
    }

Bevor wir mit dem Rendern der Form beginnen, instanziieren wir ein ` QPainterStateGuard `, um den aktuellen „Painter-Zustand“ (d. h. den Zustand auf einen Stack zu schieben) einschließlich des aktuellen Koordinatensystems zu speichern, solange dieser im Gültigkeitsbereich ist. Der Grund für das Speichern des „Painter-Zustands“ liegt darin, dass der nachfolgende Aufruf der Funktion „ transformPainter() “ das Koordinatensystem entsprechend den aktuell gewählten Transformationsoperationen transformiert und wir eine Möglichkeit benötigen, zum ursprünglichen Zustand zurückzukehren, um den Umriss zu zeichnen.

Nach der Transformation des Koordinatensystems zeichnen wir die Form von ` RenderArea` und stellen anschließend den Malerzustand mithilfe der Funktion ` QPainter::restore()` wieder her (d. h., wir entfernen den gespeicherten Zustand vom Stapel).

    drawOutline(painter);

Anschließend zeichnen wir den Umriss des Quadrats.

    transformPainter(painter);
    drawCoordinates(painter);
}

Da die Koordinaten mit dem Koordinatensystem übereinstimmen sollen, in dem die Form gerendert wird, müssen wir die Funktion ` transformPainter() ` erneut aufrufen.

Die Reihenfolge der Zeichenvorgänge ist im Hinblick auf die gemeinsam genutzten Pixel entscheidend. Der Grund, warum wir die Koordinaten nicht zeichnen, wenn das Koordinatensystem bereits für die Darstellung der Form transformiert ist, sondern deren Darstellung stattdessen auf das Ende verschieben, ist, dass wir möchten, dass die Koordinaten über der Form und ihrem Umriss erscheinen.

Es ist diesmal nicht notwendig, den Zustand von ` QPainter ` zu speichern, da das Zeichnen der Koordinaten der letzte Zeichenvorgang ist.

void RenderArea::drawCoordinates(QPainter &painter)
{
    painter.setPen(Qt::red);

    painter.drawLine(0, 0, 50, 0);
    painter.drawLine(48, -2, 50, 0);
    painter.drawLine(48, 2, 50, 0);
    painter.drawText(60 - xBoundingRect.width() / 2,
                     0 + xBoundingRect.height() / 2, tr("x"));

    painter.drawLine(0, 0, 0, 50);
    painter.drawLine(-2, 48, 0, 50);
    painter.drawLine(2, 48, 0, 50);
    painter.drawText(0 - yBoundingRect.width() / 2,
                     60 + yBoundingRect.height() / 2, tr("y"));
}

void RenderArea::drawOutline(QPainter &painter)
{
    painter.setPen(Qt::darkGreen);
    painter.setPen(Qt::DashLine);
    painter.setBrush(Qt::NoBrush);
    painter.drawRect(0, 0, 100, 100);
}

void RenderArea::drawShape(QPainter &painter)
{
    painter.fillPath(shape, Qt::blue);
}

Die Funktionen „ drawCoordinates() “, „ drawOutline() “ und „ drawShape() “ sind Hilfsfunktionen, die aus dem Ereignis-Handler „ paintEvent() “ aufgerufen werden. Weitere Informationen zu den grundlegenden Zeichenoperationen von „ QPainter “ und zur Darstellung grundlegender Grafikprimitive finden Sie im Beispiel „Basic Drawing “.

void RenderArea::transformPainter(QPainter &painter)
{
    for (int i = 0; i < operations.size(); ++i) {
        switch (operations[i]) {
        case Translate:
            painter.translate(50, 50);
            break;
        case Scale:
            painter.scale(0.75, 0.75);
            break;
        case Rotate:
            painter.rotate(60);
            break;
        case NoTransformation:
        default:
            ;
        }
    }
}

Die Hilfsfunktion „ transformPainter() “ wird ebenfalls vom Ereignisbehandler „ paintEvent() “ aufgerufen und transformiert das Koordinatensystem des angegebenen „ QPainter “ entsprechend den vom Benutzer gewählten Transformationseinstellungen.

Definition der Fensterklasse

Die Klasse „ Window “ ist das Hauptfenster der Anwendung „Transformations“.

Die Anwendung zeigt vier „ RenderArea “-Widgets an. Das Widget ganz links rendert die Form im Standardkoordinatensystem von „ QPainter “, die anderen rendern die Form mit der gewählten Transformation zusätzlich zu allen Transformationen, die auf die „ RenderArea “-Widgets links davon angewendet wurden.

class Window : public QWidget
{
    Q_OBJECT

public:
    Window();

public slots:
    void operationChanged();
    void shapeSelected(int index);

Wir deklarieren zwei öffentliche Slots, damit die Anwendung auf Benutzerinteraktionen reagieren und die angezeigten „ RenderArea “-Widgets entsprechend der vom Benutzer gewählten Transformationen aktualisieren kann.

Der Slot „ operationChanged() “ aktualisiert jedes der „ RenderArea “-Widgets unter Anwendung der aktuell ausgewählten Transformationsoperationen und wird immer dann aufgerufen, wenn der Benutzer die ausgewählten Operationen ändert. Der Slot „ shapeSelected() “ aktualisiert die Formen der „ RenderArea “-Widgets, sobald der Benutzer die bevorzugte Form ändert.

private:
    void setupShapes();

    enum { NumTransformedAreas = 3 };
    RenderArea *originalRenderArea;
    RenderArea *transformedRenderAreas[NumTransformedAreas];
    QComboBox *shapeComboBox;
    QComboBox *operationComboBoxes[NumTransformedAreas];
    QList<QPainterPath> shapes;
};

Außerdem deklarieren wir eine private Hilfsfunktion, ` setupShapes()`, die beim Erstellen des ` Window `-Widgets verwendet wird, und wir deklarieren Zeiger auf die verschiedenen Komponenten des Widgets. Wir entscheiden uns dafür, die verfügbaren Formen in einem ` QList ` aus ` QPainterPath`s zu speichern. Darüber hinaus deklarieren wir eine private Enumeration, die die Anzahl der angezeigten ` RenderArea `-Widgets zählt, mit Ausnahme des Widgets, das die Form im Standardkoordinatensystem von ` QPainter` rendert.

Implementierung der Fensterklasse

Im Konstruktor erstellen und initialisieren wir die Komponenten der Anwendung:

Window::Window()
{
    originalRenderArea = new RenderArea;

    shapeComboBox = new QComboBox;
    shapeComboBox->addItem(tr("Clock"));
    shapeComboBox->addItem(tr("House"));
    shapeComboBox->addItem(tr("Text"));
    shapeComboBox->addItem(tr("Truck"));

    QGridLayout *layout = new QGridLayout;
    layout->addWidget(originalRenderArea, 0, 0);
    layout->addWidget(shapeComboBox, 1, 0);

Zunächst erstellen wir das Widget „ RenderArea “, das die Form im Standardkoordinatensystem rendert. Außerdem erstellen wir das zugehörige „ QComboBox “, mit dem der Benutzer zwischen vier verschiedenen Formen wählen kann: einer Uhr, einem Haus, einem Text und einem LKW. Die Formen selbst werden am Ende des Konstruktors mithilfe der Hilfsfunktion „ setupShapes() “ erstellt.

    for (int i = 0; i < NumTransformedAreas; ++i) {
        transformedRenderAreas[i] = new RenderArea;

        operationComboBoxes[i] = new QComboBox;
        operationComboBoxes[i]->addItem(tr("No transformation"));
        operationComboBoxes[i]->addItem(tr("Rotate by 60\xC2\xB0"));
        operationComboBoxes[i]->addItem(tr("Scale to 75%"));
        operationComboBoxes[i]->addItem(tr("Translate by (50, 50)"));

        connect(operationComboBoxes[i], &QComboBox::activated,
                this, &Window::operationChanged);

        layout->addWidget(transformedRenderAreas[i], 0, i + 1);
        layout->addWidget(operationComboBoxes[i], 1, i + 1);
    }

Anschließend erstellen wir die Widgets „ RenderArea “, die ihre Formen mithilfe von Koordinatentransformationen darstellen. Standardmäßig wird die Operation „ No Transformation “ angewendet, d. h., die Formen werden im Standardkoordinatensystem dargestellt. Wir erstellen und initialisieren die zugehörigen „ QComboBox“ mit Elementen, die den verschiedenen Transformationsoperationen entsprechen, die durch die globale Enumeration „ Operation “ beschrieben werden.

Außerdem verbinden wir das Signal „ activated()“ der „ QComboBoxs“ mit dem Slot „ operationChanged() “, um die Anwendung zu aktualisieren, sobald der Benutzer die ausgewählten Transformationsoperationen ändert.

    setLayout(layout);
    setupShapes();
    shapeSelected(0);

    setWindowTitle(tr("Transformations"));
}

Schließlich legen wir das Layout für das Anwendungsfenster mithilfe der Funktion ` QWidget::setLayout()` fest, erstellen die verfügbaren Formen mithilfe der privaten Hilfsfunktion ` setupShapes() ` und sorgen dafür, dass die Anwendung beim Start die Uhrenform anzeigt, indem wir den öffentlichen Slot ` shapeSelected() ` verwenden, bevor wir den Fenstertitel festlegen.

void Window::setupShapes()
{
    QPainterPath truck;
    QPainterPath clock;
    QPainterPath house;
    QPainterPath text;
    ...
    shapes.append(clock);
    shapes.append(house);
    shapes.append(text);
    shapes.append(truck);

    connect(shapeComboBox, &QComboBox::activated,
            this, &Window::shapeSelected);
}

Die Funktion ` setupShapes() ` wird vom Konstruktor aufgerufen und erstellt die ` QPainterPath `-Objekte, die die in der Anwendung verwendeten Formen darstellen. Einzelheiten zur Erstellung finden Sie in der Beispieldatei „ painting/transformations/window.cpp “. Die Formen werden in einer ` QList` gespeichert. Die Funktion ` QList::append()` fügt die angegebene Form am Ende der Liste ein.

Außerdem verbinden wir das Signal „ activated()“ des zugehörigen „ QComboBox “ mit dem Slot „ shapeSelected() “, um die Anwendung zu aktualisieren, wenn der Benutzer die bevorzugte Form ändert.

void Window::operationChanged()
{
    static const Operation operationTable[] = {
        NoTransformation, Rotate, Scale, Translate
    };

    QList<Operation> operations;
    for (int i = 0; i < NumTransformedAreas; ++i) {
        int index = operationComboBoxes[i]->currentIndex();
        operations.append(operationTable[index]);
        transformedRenderAreas[i]->setOperations(operations);
    }
}

Der öffentliche Slot „ operationChanged() “ wird immer dann aufgerufen, wenn der Benutzer die ausgewählten Operationen ändert.

Wir ermitteln die gewählte Transformationsoperation für jedes der transformierten „ RenderArea “-Widgets, indem wir das zugehörige „ QComboBoxes “ abfragen. Die transformierten „ RenderArea “-Widgets sollen die Form mit der durch die zugehörige Combobox festgelegten Transformation rendern , zusätzlich zu allen Transformationen, die auf die links davon liegenden „ RenderArea “-Widgets angewendet wurden. Aus diesem Grund fügen wir für jedes abgefragte Widget die zugehörige Operation an eine „ QList “ von Transformationen an, die wir auf das Widget anwenden, bevor wir zum nächsten übergehen.

void Window::shapeSelected(int index)
{
    QPainterPath shape = shapes[index];
    originalRenderArea->setShape(shape);
    for (int i = 0; i < NumTransformedAreas; ++i)
        transformedRenderAreas[i]->setShape(shape);
}

Der Slot „ shapeSelected() “ wird immer dann aufgerufen, wenn der Benutzer die bevorzugte Form ändert, wodurch die „ RenderArea “-Widgets mithilfe ihrer öffentlichen Funktion „ setShape() “ aktualisiert werden.

Zusammenfassung

Das Beispiel „Transformations“ zeigt, wie Transformationen die Art und Weise beeinflussen, in der „ QPainter “ Grafikprimitive rendert. Normalerweise arbeitet „ QPainter “ im geräteeigenen Koordinatensystem, bietet jedoch auch eine gute Unterstützung für Koordinatentransformationen. Mit der Anwendung „Transformations“ können Sie das Koordinatensystem von „ QPainter “ skalieren, drehen und verschieben. Die Reihenfolge, in der diese Transformationen angewendet werden, ist entscheidend für das Ergebnis.

Alle Transformationsoperationen wirken auf die Transformationsmatrix von ` QPainter`. Weitere Informationen zur Transformationsmatrix finden Sie in der Dokumentation zu „Koordinatensystem“ und „ QTransform “.

Die Qt-Referenzdokumentation enthält mehrere Beispiele zum Zeichnen. Dazu gehört das Beispiel „Affine Transformationen“, das die Fähigkeit von Qt demonstriert, Transformationen bei Zeichenoperationen durchzuführen. Das Beispiel ermöglicht es dem Benutzer zudem, mit den verschiedenen Transformationsoperationen zu experimentieren.

Beispielprojekt @ code.qt.io

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