Beispiel für Transformationen
Das Transformations-Beispiel zeigt, wie Transformationen die Art und Weise beeinflussen, in der QPainter Grafikprimitive rendert.
Die Anwendung ermöglicht es dem Benutzer, das Rendering einer Form zu manipulieren, indem er die Translation, Rotation und Skalierung des Koordinatensystems von QPainter ändert.
Das Beispiel besteht aus zwei Klassen und einem globalen Enum:
- Die Klasse
RenderArea
steuert das Rendering einer bestimmten Form. - Die Klasse
Window
ist das Hauptfenster der Anwendung. - Die Aufzählung
Operation
beschreibt die verschiedenen in der Anwendung verfügbaren Transformationsoperationen.
Zunächst werfen wir einen kurzen Blick auf das Operation
enum, dann sehen wir uns die Klasse RenderArea
an, um zu sehen, wie eine Form gerendert wird. Abschließend werden wir einen Blick auf die Funktionen der Anwendung Transformations werfen, die in der Klasse Window
implementiert sind.
Transformationsoperationen
Normalerweise arbeitet QPainter mit dem eigenen Koordinatensystem des zugehörigen Geräts, bietet aber auch gute Unterstützung für Koordinatentransformationen.
Das Standardkoordinatensystem eines Malgeräts hat seinen Ursprung in der linken oberen Ecke. Die x-Werte steigen nach rechts und die y-Werte steigen nach unten. Sie können das Koordinatensystem mit der Funktion QPainter::scale() um einen bestimmten Offset skalieren, mit der Funktion QPainter::rotate() im Uhrzeigersinn drehen und mit der Funktion QPainter::translate() übersetzen (d. h. einen bestimmten Offset zu den Punkten hinzufügen). Mit der Funktion QPainter::shear() können Sie das Koordinatensystem auch um den Ursprung drehen (Scherung genannt).
Alle Transformationsoperationen wirken auf die Transformationsmatrix von QPainter, die Sie mit der Funktion QPainter::worldTransform() abrufen können. Eine Matrix transformiert einen Punkt in der Ebene in einen anderen Punkt. Weitere Informationen über die Transformationsmatrix finden Sie in der Dokumentation Koordinatensystem und QTransform.
enum Operation { NoTransformation, Translate, Rotate, Scale };
Das globale Operation
enum wird 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 das Rendering 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
festzulegen und das Koordinatensystem zu transformieren, in dem die Form gerendert wird.
Wir reimplementieren die Funktionen minimumSizeHint() und sizeHint() von QWidget, um dem RenderArea
Widget eine angemessene Größe innerhalb unserer Anwendung zu geben, und wir reimplementieren den QWidget::paintEvent() Event-Handler, um die Form des Renderbereichs unter Anwendung der vom Benutzer gewählten Transformation 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; };
Wir deklarieren auch mehrere Komfortfunktionen, um die Form, den Umriss des Koordinatensystems und die Koordinaten zu zeichnen und den Maler gemäß den gewählten Transformationen zu transformieren.
Darüber hinaus speichert das Widget RenderArea
eine Liste der aktuell angewendeten Transformationsoperationen, einen Verweis auf seine Form und einige Komfortvariablen, die wir beim Rendern der Koordinaten verwenden werden.
Implementierung der Klasse RenderArea
Das RenderArea
Widget steuert das Rendering einer bestimmten Form, einschließlich der Transformationen des Koordinatensystems, indem es den QWidget::paintEvent()-Ereignishandler reimplementiert. Doch zunächst werfen wir einen kurzen Blick auf den Konstruktor und die Funktionen, die den Zugriff auf das RenderArea
Widget 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 Parent-Parameter an die Basisklasse und passen die Schriftart an, die wir zum Rendern 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, ist dies entweder eine spezielle Schriftart für die Widget-Klasse, die Schriftart des Elternteils oder (wenn dieses Widget ein Widget der obersten Ebene ist) die Standard-Anwendungsschriftart.
Nachdem wir sichergestellt haben, dass die Schriftgröße 12 Punkt beträgt, extrahieren wir die Rechtecke, die die Koordinatenbuchstaben 'x' und 'y' einschließen, mit Hilfe der Klasse QFontMetrics.
QFontMetrics Die Klasse bietet Funktionen für den Zugriff auf die einzelnen Metriken der Schriftart, ihre Zeichen und für in der Schriftart gerenderte Zeichenketten. Die Funktion QFontMetrics::boundingRect() gibt das Begrenzungsrechteck des angegebenen Zeichens relativ zum äußersten linken Punkt 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, gefolgt von einem Aufruf des Slots QWidget::update(), der ein Malereignis zur Verarbeitung einplant, wenn 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() von QWidget neu, um dem RenderArea
Widget eine vernünftige Größe innerhalb unserer Anwendung zu geben. Die Standardimplementierungen dieser Funktionen geben eine ungültige Größe zurück, wenn es kein Layout für dieses Widget gibt, und geben andernfalls die Mindestgröße des Layouts bzw. die bevorzugte Größe zurück.
void RenderArea::paintEvent(QPaintEvent *event) { QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); painter.fillRect(event->rect(), QBrush(Qt::white)); painter.translate(66, 66);
Der paintEvent()
Event-Handler empfängt die Paint-Ereignisse des Widgets RenderArea
. Ein Malereignis ist eine Aufforderung, das Widget ganz oder teilweise neu zu malen. Dies kann als Ergebnis von QWidget::repaint() oder QWidget::update() geschehen, oder weil das Widget verdeckt war und nun aufgedeckt wurde, oder aus vielen anderen Gründen.
Zuerst erstellen wir ein QPainter für das RenderArea
Widget. Der Rendering-Hinweis QPainter::Antialiasing gibt an, dass die Engine die Kanten von Primitiven wenn möglich antialiasen sollte. Dann löschen wir den Bereich, der neu gezeichnet werden muss, mit der Funktion QPainter::fillRect().
Wir übersetzen auch das Koordinatensystem mit einem konstanten Offset, um sicherzustellen, dass die ursprüngliche Form mit einem geeigneten Rand gerendert wird.
painter.save(); transformPainter(painter); drawShape(painter); painter.restore();
Bevor wir mit dem Rendern der Form beginnen, rufen wir die Funktion QPainter::save() auf.
QPainter::save() speichert den aktuellen Zustand des Malers (d. h. schiebt den Zustand auf einen Stapel) einschließlich des aktuellen Koordinatensystems. Der Grund für die Speicherung des Malerstatus ist, dass der folgende Aufruf der Funktion transformPainter()
das Koordinatensystem in Abhängigkeit von den aktuell gewählten Transformationsoperationen transformiert, und wir brauchen eine Möglichkeit, zum ursprünglichen Zustand zurückzukehren, um die Kontur zu zeichnen.
Nach der Transformation des Koordinatensystems zeichnen wir die Form von RenderArea
und stellen dann den Zustand des Malers mit der Funktion QPainter::restore() wieder her (d. h. wir nehmen den gespeicherten Zustand vom Stapel).
drawOutline(painter);
Dann zeichnen wir den Umriss des Quadrats.
transformPainter(painter); drawCoordinates(painter); }
Da wir wollen, dass die Koordinaten mit dem Koordinatensystem übereinstimmen, in dem die Form gerendert wird, müssen wir einen weiteren Aufruf der Funktion transformPainter()
durchführen.
Die Reihenfolge der Zeichenoperationen ist im Hinblick auf die gemeinsam genutzten Pixel wichtig. Der Grund, warum wir die Koordinaten nicht rendern, wenn das Koordinatensystem bereits transformiert ist, um die Form zu rendern, sondern ihr Rendern auf das Ende verschieben, ist, dass wir wollen, dass die Koordinaten über der Form und ihrem Umriss erscheinen.
Es besteht keine Notwendigkeit, den Zustand QPainter zu speichern, da das Zeichnen der Koordinaten die letzte Maloperation 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 Komfortfunktionen, die vom Event-Handler paintEvent()
aufgerufen werden. Weitere Informationen zu den grundlegenden Zeichenoperationen von QPainter und zur Anzeige grundlegender Grafikprimitive finden Sie im Beispiel Grundlegendes Zeichnen.
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 Convenience-Funktion transformPainter()
wird ebenfalls vom Event-Handler paintEvent()
aufgerufen und transformiert das Koordinatensystem des angegebenen QPainter entsprechend der vom Benutzer gewählten Transformation.
Definition der Fensterklasse
Die Klasse Window
ist das Hauptfenster der Anwendung Transformations.
Die Anwendung zeigt vier RenderArea
Widgets an. Das Widget ganz links stellt die Form im Standardkoordinatensystem von QPainter dar, die anderen stellen die Form mit der gewählten Transformation dar, zusätzlich zu allen Transformationen, die auf die RenderArea
Widgets links von ihnen angewendet werden.
class Window : public QWidget { Q_OBJECT public: Window(); public slots: void operationChanged(); void shapeSelected(int index);
Wir deklarieren zwei öffentliche Slots, um die Anwendung in die Lage zu versetzen, auf Benutzerinteraktionen zu reagieren und die angezeigten RenderArea
Widgets entsprechend der vom Benutzer gewählten Transformation zu aktualisieren.
Der operationChanged()
Slot aktualisiert jedes der RenderArea
Widgets unter Anwendung der aktuell gewählten Transformationsoperationen und wird immer dann aufgerufen, wenn der Benutzer die ausgewählten Operationen ändert. Der shapeSelected()
Slot aktualisiert die Formen der RenderArea
Widgets, wenn 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; };
Wir deklarieren auch eine private Komfortfunktion, setupShapes()
, die bei der Konstruktion 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 einer QList von QPainterPathzu halten. Zusätzlich deklarieren wir ein privates Enum, das die Anzahl der angezeigten RenderArea
Widgets zählt, mit Ausnahme des Widgets, das die Form im Standardkoordinatensystem von QPainter wiedergibt.
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 darstellt. Außerdem erstellen wir das zugehörige QComboBox, das es dem Benutzer ermöglicht, zwischen vier verschiedenen Formen zu wählen: Eine Uhr, ein Haus, ein Text und ein Lastwagen. Die Formen selbst werden am Ende des Konstruktors mit der Komfortfunktion 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); }
Dann erstellen wir die RenderArea
Widgets, die ihre Formen mit Koordinatentransformationen wiedergeben werden. Standardmäßig ist die angewandte Operation No Transformation, d. h. die Formen werden im Standardkoordinatensystem gerendert. Wir erstellen und initialisieren die zugehörigen QComboBoxes mit Elementen, die den verschiedenen Transformationsoperationen entsprechen, die durch das globale Operation
enum beschrieben werden.
Außerdem verbinden wir das Signal QComboBoxes' activated() mit dem Slot operationChanged()
, um die Anwendung zu aktualisieren, wenn 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 mit der Funktion QWidget::setLayout() fest, konstruieren die verfügbaren Formen mit der privaten Komfortfunktion setupShapes()
und sorgen dafür, dass die Anwendung beim Start die Uhrform 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 Konstruktion 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.
Wir verbinden auch das Signal activated() des zugehörigen QComboBox mit dem shapeSelected()
Slot, 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 operationChanged()
Slot wird immer dann aufgerufen, wenn der Benutzer die ausgewählten Operationen ändert.
Wir rufen die gewählte Transformationsoperation für jedes der transformierten RenderArea
Widgets ab, indem wir das zugehörige QComboBoxes abfragen. Die transformierten RenderArea
Widgets sollen die Form mit der Transformation wiedergeben, die in der zugehörigen Combobox angegeben ist , zusätzlich zu allen Transformationen, die auf die RenderArea
Widgets links von ihnen angewendet werden. 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 mit dem nächsten fortfahren.
void Window::shapeSelected(int index) { QPainterPath shape = shapes[index]; originalRenderArea->setShape(shape); for (int i = 0; i < NumTransformedAreas; ++i) transformedRenderAreas[i]->setShape(shape); }
Der shapeSelected()
Slot wird immer dann aufgerufen, wenn der Benutzer die bevorzugte Form ändert, wobei die RenderArea
Widgets mit ihrer öffentlichen setShape()
Funktion aktualisiert werden.
Zusammenfassung
Das Transformations-Beispiel zeigt, wie Transformationen die Art und Weise beeinflussen, wie QPainter Grafik-Primitive rendert. Normalerweise arbeitet QPainter mit dem eigenen Koordinatensystem des Geräts, bietet aber auch gute Unterstützung für Koordinatentransformationen. Mit der Anwendung "Transformationen" können Sie das Koordinatensystem von QPainter skalieren, rotieren und verschieben. Die Reihenfolge, in der diese Transformationen angewendet werden, ist für das Ergebnis entscheidend.
Alle Transformationsoperationen wirken auf die Transformationsmatrix von QPainter. Weitere Informationen über die Transformationsmatrix finden Sie in der Dokumentation Koordinatensystem und QTransform.
Die Qt-Referenzdokumentation enthält mehrere Malbeispiele. Eines davon ist das Beispiel Affine Transformationen, das die Fähigkeit von Qt zeigt, Transformationen bei Maloperationen durchzuführen. Das Beispiel erlaubt es dem Benutzer auch, mit den verschiedenen Transformationsoperationen zu experimentieren.
© 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.