En esta página

Ejemplo de dibujo básico

El ejemplo de Dibujo Básico muestra cómo mostrar primitivas gráficas básicas en una variedad de estilos utilizando la clase QPainter.

QPainter realiza pintado de bajo nivel en widgets y otros dispositivos de pintado. La clase puede dibujar desde líneas simples hasta formas complejas como pasteles y acordes. También puede dibujar texto alineado y mapas de píxeles. Normalmente, dibuja en un sistema de coordenadas "natural", pero además puede realizar transformaciones de vista y de mundo.

Ventana con varias opciones para modificar el dibujo

El ejemplo proporciona un área de renderizado, mostrando la forma activa en ese momento, y permite al usuario manipular la forma renderizada y su apariencia utilizando los parámetros de QPainter: El usuario puede cambiar la forma activa (Shape), y modificar el lápiz (Pen Width, Pen Style, Pen Cap, Pen Join), el pincel (Brush Style) y los consejos de renderizado (Antialiasing) de QPainter. Además, el usuario puede rotar una forma (Transformations); entre bastidores utilizamos la capacidad de QPainter de manipular el sistema de coordenadas para realizar la rotación.

El ejemplo de Dibujo Básico consiste en dos clases:

  • RenderArea es un widget personalizado que renderiza múltiples copias de la forma activa en ese momento.
  • Window es la ventana principal de la aplicación que muestra un widget RenderArea además de varios widgets de parámetros.

Primero revisaremos la clase Window, luego echaremos un vistazo a la clase RenderArea.

Definición de la clase Window

La clase Window hereda de QWidget, y es la ventana principal de la aplicación que muestra un widget RenderArea además de varios widgets de parámetros.

class Window : public QWidget
{
    Q_OBJECT

public:
    Window();

private slots:
    void shapeChanged();
    void penChanged();
    void brushChanged();

private:
    RenderArea *renderArea;
    QLabel *shapeLabel;
    QLabel *penWidthLabel;
    QLabel *penStyleLabel;
    QLabel *penCapLabel;
    QLabel *penJoinLabel;
    QLabel *brushStyleLabel;
    QLabel *otherOptionsLabel;
    QComboBox *shapeComboBox;
    QSpinBox *penWidthSpinBox;
    QComboBox *penStyleComboBox;
    QComboBox *penCapComboBox;
    QComboBox *penJoinComboBox;
    QComboBox *brushStyleComboBox;
    QCheckBox *antialiasingCheckBox;
    QCheckBox *transformationsCheckBox;
};

Declaramos los diversos widgets, y tres ranuras privadas que actualizan el widget RenderArea: La ranura shapeChanged() actualiza el widget RenderArea cuando el usuario cambia la forma actualmente activa. Llamamos a la ranura penChanged() cuando cualquiera de los parámetros del lápiz QPainter cambia. Y la ranura brushChanged() actualiza el widget RenderArea cuando el usuario cambia el estilo del pincel del pintor.

Implementación de la clase Window

En el constructor creamos e inicializamos los distintos widgets que aparecen en la ventana principal de la aplicación.

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

    shapeComboBox = new QComboBox;
    shapeComboBox->addItem(tr("Polygon"), RenderArea::Polygon);
    shapeComboBox->addItem(tr("Rectangle"), RenderArea::Rect);
    shapeComboBox->addItem(tr("Rounded Rectangle"), RenderArea::RoundedRect);
    shapeComboBox->addItem(tr("Ellipse"), RenderArea::Ellipse);
    shapeComboBox->addItem(tr("Pie"), RenderArea::Pie);
    shapeComboBox->addItem(tr("Chord"), RenderArea::Chord);
    shapeComboBox->addItem(tr("Path"), RenderArea::Path);
    shapeComboBox->addItem(tr("Line"), RenderArea::Line);
    shapeComboBox->addItem(tr("Polyline"), RenderArea::Polyline);
    shapeComboBox->addItem(tr("Arc"), RenderArea::Arc);
    shapeComboBox->addItem(tr("Points"), RenderArea::Points);
    shapeComboBox->addItem(tr("Text"), RenderArea::Text);
    shapeComboBox->addItem(tr("Pixmap"), RenderArea::Pixmap);

    shapeLabel = new QLabel(tr("&Shape:"));
    shapeLabel->setBuddy(shapeComboBox);

Primero creamos el widget RenderArea que renderizará la forma actualmente activa. Luego creamos el combobox Shape, y añadimos los elementos asociados (es decir, las diferentes formas que un QPainter puede dibujar).

    penWidthSpinBox = new QSpinBox;
    penWidthSpinBox->setRange(0, 20);
    penWidthSpinBox->setSpecialValueText(tr("0 (cosmetic pen)"));

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

QPainterEl lápiz de QPen es un objeto QPen; la clase define cómo un pintor debe dibujar las líneas y los contornos de las formas. Una pluma tiene varias propiedades: Anchura, estilo, tapa y unión.

La anchura de una pluma puede ser cero o mayor, pero la anchura más común es cero. Tenga en cuenta que esto no significa 0 píxeles, sino que implica que la forma se dibuja lo más suavemente posible aunque quizás no sea matemáticamente correcto.

Creamos un QSpinBox para el parámetro Pen Width.

    penStyleComboBox = new QComboBox;
    penStyleComboBox->addItem(tr("Solid"), static_cast<int>(Qt::SolidLine));
    penStyleComboBox->addItem(tr("Dash"), static_cast<int>(Qt::DashLine));
    penStyleComboBox->addItem(tr("Dot"), static_cast<int>(Qt::DotLine));
    penStyleComboBox->addItem(tr("Dash Dot"), static_cast<int>(Qt::DashDotLine));
    penStyleComboBox->addItem(tr("Dash Dot Dot"), static_cast<int>(Qt::DashDotDotLine));
    penStyleComboBox->addItem(tr("None"), static_cast<int>(Qt::NoPen));

    penStyleLabel = new QLabel(tr("&Pen Style:"));
    penStyleLabel->setBuddy(penStyleComboBox);

    penCapComboBox = new QComboBox;
    penCapComboBox->addItem(tr("Flat"), Qt::FlatCap);
    penCapComboBox->addItem(tr("Square"), Qt::SquareCap);
    penCapComboBox->addItem(tr("Round"), Qt::RoundCap);

    penCapLabel = new QLabel(tr("Pen &Cap:"));
    penCapLabel->setBuddy(penCapComboBox);

    penJoinComboBox = new QComboBox;
    penJoinComboBox->addItem(tr("Miter"), Qt::MiterJoin);
    penJoinComboBox->addItem(tr("Bevel"), Qt::BevelJoin);
    penJoinComboBox->addItem(tr("Round"), Qt::RoundJoin);

    penJoinLabel = new QLabel(tr("Pen &Join:"));
    penJoinLabel->setBuddy(penJoinComboBox);

El estilo de la pluma define el tipo de línea. El estilo por defecto es sólido (Qt::SolidLine). Establecer el estilo a ninguno (Qt::NoPen) le dice al pintor que no dibuje líneas o contornos. El límite de la pluma define cómo se dibujan los puntos finales de las líneas. Y la unión de lápiz define cómo se unen dos líneas cuando se dibujan múltiples líneas conectadas. La tapa y la unión sólo se aplican a líneas con una anchura de 1 píxel o superior.

Creamos QComboBoxes para cada uno de los parámetros Pen Style, Pen Cap y Pen Join, y añade los elementos asociados (es decir, los valores de los enums Qt::PenStyle, Qt::PenCapStyle y Qt::PenJoinStyle respectivamente).

    brushStyleComboBox = new QComboBox;
    brushStyleComboBox->addItem(tr("Linear Gradient"),
            static_cast<int>(Qt::LinearGradientPattern));
    brushStyleComboBox->addItem(tr("Radial Gradient"),
            static_cast<int>(Qt::RadialGradientPattern));
    brushStyleComboBox->addItem(tr("Conical Gradient"),
            static_cast<int>(Qt::ConicalGradientPattern));
    brushStyleComboBox->addItem(tr("Texture"), static_cast<int>(Qt::TexturePattern));
    brushStyleComboBox->addItem(tr("Solid"), static_cast<int>(Qt::SolidPattern));
    brushStyleComboBox->addItem(tr("Horizontal"), static_cast<int>(Qt::HorPattern));
    brushStyleComboBox->addItem(tr("Vertical"), static_cast<int>(Qt::VerPattern));
    brushStyleComboBox->addItem(tr("Cross"), static_cast<int>(Qt::CrossPattern));
    brushStyleComboBox->addItem(tr("Backward Diagonal"), static_cast<int>(Qt::BDiagPattern));
    brushStyleComboBox->addItem(tr("Forward Diagonal"), static_cast<int>(Qt::FDiagPattern));
    brushStyleComboBox->addItem(tr("Diagonal Cross"), static_cast<int>(Qt::DiagCrossPattern));
    brushStyleComboBox->addItem(tr("Dense 1"), static_cast<int>(Qt::Dense1Pattern));
    brushStyleComboBox->addItem(tr("Dense 2"), static_cast<int>(Qt::Dense2Pattern));
    brushStyleComboBox->addItem(tr("Dense 3"), static_cast<int>(Qt::Dense3Pattern));
    brushStyleComboBox->addItem(tr("Dense 4"), static_cast<int>(Qt::Dense4Pattern));
    brushStyleComboBox->addItem(tr("Dense 5"), static_cast<int>(Qt::Dense5Pattern));
    brushStyleComboBox->addItem(tr("Dense 6"), static_cast<int>(Qt::Dense6Pattern));
    brushStyleComboBox->addItem(tr("Dense 7"), static_cast<int>(Qt::Dense7Pattern));
    brushStyleComboBox->addItem(tr("None"), static_cast<int>(Qt::NoBrush));

    brushStyleLabel = new QLabel(tr("&Brush:"));
    brushStyleLabel->setBuddy(brushStyleComboBox);

La clase QBrush define el patrón de relleno de las formas dibujadas por un QPainter. El estilo de pincel por defecto es Qt::NoBrush. Este estilo indica al pintor que no rellene las formas. El estilo estándar de relleno es Qt::SolidPattern.

Creamos un QComboBox para el parámetro Brush Style, y añadimos los elementos asociados (es decir, los valores del enum Qt::BrushStyle ).

    otherOptionsLabel = new QLabel(tr("Options:"));
    antialiasingCheckBox = new QCheckBox(tr("&Antialiasing"));

El antialiasing es una función que "suaviza" los píxeles para crear líneas más uniformes y menos dentadas, y puede aplicarse utilizando las sugerencias de renderizado de QPainter. QPainter::RenderHints se utilizan para especificar indicadores a QPainter que pueden ser respetados o no por un motor determinado.

Simplemente creamos un QCheckBox para la opción Antialiasing.

    transformationsCheckBox = new QCheckBox(tr("&Transformations"));

La opción Transformations implica una manipulación del sistema de coordenadas que aparecerá como si la forma renderizada estuviera rotada en tres dimensiones.

Utilizamos las funciones QPainter::translate(), QPainter::rotate() y QPainter::scale() para implementar esta característica representada en la ventana principal de la aplicación por un simple QCheckBox.

    connect(shapeComboBox, &QComboBox::activated,
            this, &Window::shapeChanged);
    connect(penWidthSpinBox, &QSpinBox::valueChanged,
            this, &Window::penChanged);
    connect(penStyleComboBox, &QComboBox::activated,
            this, &Window::penChanged);
    connect(penCapComboBox, &QComboBox::activated,
            this, &Window::penChanged);
    connect(penJoinComboBox, &QComboBox::activated,
            this, &Window::penChanged);
    connect(brushStyleComboBox, &QComboBox::activated,
            this, &Window::brushChanged);
    connect(antialiasingCheckBox, &QAbstractButton::toggled,
            renderArea, &RenderArea::setAntialiased);
    connect(transformationsCheckBox, &QAbstractButton::toggled,
            renderArea, &RenderArea::setTransformed);

A continuación, conectamos los widgets de parámetros con sus ranuras asociadas mediante la función estática QObject::connect(), asegurándonos de que el widget RenderArea se actualiza cada vez que el usuario cambia la forma, o cualquiera de los otros parámetros.

    QGridLayout *mainLayout = new QGridLayout;
    mainLayout->setColumnStretch(0, 1);
    mainLayout->setColumnStretch(3, 1);
    mainLayout->addWidget(renderArea, 0, 0, 1, 4);
    mainLayout->addWidget(shapeLabel, 2, 0, Qt::AlignRight);
    mainLayout->addWidget(shapeComboBox, 2, 1);
    mainLayout->addWidget(penWidthLabel, 3, 0, Qt::AlignRight);
    mainLayout->addWidget(penWidthSpinBox, 3, 1);
    mainLayout->addWidget(penStyleLabel, 4, 0, Qt::AlignRight);
    mainLayout->addWidget(penStyleComboBox, 4, 1);
    mainLayout->addWidget(penCapLabel, 3, 2, Qt::AlignRight);
    mainLayout->addWidget(penCapComboBox, 3, 3);
    mainLayout->addWidget(penJoinLabel, 2, 2, Qt::AlignRight);
    mainLayout->addWidget(penJoinComboBox, 2, 3);
    mainLayout->addWidget(brushStyleLabel, 4, 2, Qt::AlignRight);
    mainLayout->addWidget(brushStyleComboBox, 4, 3);
    mainLayout->addWidget(otherOptionsLabel, 5, 0, Qt::AlignRight);
    mainLayout->addWidget(antialiasingCheckBox, 5, 1, 1, 1, Qt::AlignRight);
    mainLayout->addWidget(transformationsCheckBox, 5, 2, 1, 2, Qt::AlignRight);
    setLayout(mainLayout);

    shapeChanged();
    penChanged();
    brushChanged();
    antialiasingCheckBox->setChecked(true);

    setWindowTitle(tr("Basic Drawing"));
}

Por último, añadimos los distintos widgets a un diseño y llamamos a las ranuras shapeChanged(), penChanged() y brushChanged() para inicializar la aplicación. También activamos el antialiasing.

void Window::shapeChanged()
{
    RenderArea::Shape shape = RenderArea::Shape(shapeComboBox->itemData(
            shapeComboBox->currentIndex(), IdRole).toInt());
    renderArea->setShape(shape);
}

La ranura shapeChanged() se llama cada vez que el usuario cambia la forma activa.

Primero recuperamos la forma que el usuario ha elegido utilizando la función QComboBox::itemData(). Esta función devuelve los datos de la función dada en el índice dado en el combobox. Usamos QComboBox::currentIndex() para recuperar el índice de la forma, y el rol está definido por el enum Qt::ItemDataRole; IdRole es un alias de Qt::UserRole.

Tenga en cuenta que Qt::UserRole es sólo el primer rol que puede utilizarse para fines específicos de la aplicación. Si necesita almacenar diferentes datos en el mismo índice, puede utilizar diferentes roles simplemente incrementando el valor de Qt::UserRole, por ejemplo: 'Qt::UserRole + 1' y 'Qt::UserRole + 2'. Sin embargo, es una buena práctica de programación dar a cada rol su propio nombre: 'miPrimerRol = Qt::UserRole + 1' y 'miSegundoRol = Qt::UserRole + 2'. Aunque en este ejemplo concreto sólo necesitamos un único rol, añadimos la siguiente línea de código al principio del archivo window.cpp.

const int IdRole = Qt::UserRole;

La función QComboBox::itemData() devuelve los datos como QVariant, así que tenemos que convertir los datos a RenderArea::Shape. Si no hay datos para el rol dado, la función devuelve QMetaType::UnknownType.

Al final llamamos a la ranura RenderArea::setShape() para actualizar el widget RenderArea.

void Window::penChanged()
{
    int width = penWidthSpinBox->value();
    Qt::PenStyle style = Qt::PenStyle(penStyleComboBox->itemData(
            penStyleComboBox->currentIndex(), IdRole).toInt());
    Qt::PenCapStyle cap = Qt::PenCapStyle(penCapComboBox->itemData(
            penCapComboBox->currentIndex(), IdRole).toInt());
    Qt::PenJoinStyle join = Qt::PenJoinStyle(penJoinComboBox->itemData(
            penJoinComboBox->currentIndex(), IdRole).toInt());

    renderArea->setPen(QPen(Qt::blue, width, style, cap, join));
}

Llamamos a la ranura penChanged() cada vez que el usuario cambia alguno de los parámetros del lápiz. De nuevo utilizamos la función QComboBox::itemData() para recuperar los parámetros, y luego llamamos a la ranura RenderArea::setPen() para actualizar el widget RenderArea.

void Window::brushChanged()
{
    Qt::BrushStyle style = Qt::BrushStyle(brushStyleComboBox->itemData(

La ranura brushChanged() es llamada cada vez que el usuario cambia el parámetro del pincel que recuperamos usando la función QComboBox::itemData() como antes.

    if (style == Qt::LinearGradientPattern) {
        QLinearGradient linearGradient(0, 0, 100, 100);
        linearGradient.setColorAt(0.0, Qt::white);
        linearGradient.setColorAt(0.2, Qt::green);
        linearGradient.setColorAt(1.0, Qt::black);
        renderArea->setBrush(linearGradient);

Si el parámetro del pincel es un relleno de gradiente, se requieren acciones especiales.

La clase QGradient se utiliza en combinación con QBrush para especificar rellenos de degradado. Qt soporta actualmente tres tipos de rellenos de degradado: lineal, radial y cónico. Cada uno de ellos está representado por una subclase de QGradient: QLinearGradient, QRadialGradient y QConicalGradient.

Así que si el estilo del pincel es Qt::LinearGradientPattern, primero creamos un objeto QLinearGradient con área de interpolación entre las coordenadas pasadas como argumentos al constructor. Las posiciones se especifican mediante coordenadas lógicas. Luego establecemos los colores del gradiente usando la función QGradient::setColorAt(). Los colores se definen utilizando puntos de parada que están compuestos por una posición (entre 0 y 1) y un QColor. El conjunto de puntos de parada describe cómo debe rellenarse el área del gradiente. Un degradado puede tener un número arbitrario de puntos de parada.

Al final llamamos a la ranura RenderArea::setBrush() para actualizar el pincel del widget RenderArea con el objeto QLinearGradient.

    } else if (style == Qt::RadialGradientPattern) {
        QRadialGradient radialGradient(50, 50, 50, 70, 70);
        radialGradient.setColorAt(0.0, Qt::white);
        radialGradient.setColorAt(0.2, Qt::green);
        radialGradient.setColorAt(1.0, Qt::black);
        renderArea->setBrush(radialGradient);
    } else if (style == Qt::ConicalGradientPattern) {
        QConicalGradient conicalGradient(50, 50, 150);
        conicalGradient.setColorAt(0.0, Qt::white);
        conicalGradient.setColorAt(0.2, Qt::green);
        conicalGradient.setColorAt(1.0, Qt::black);
        renderArea->setBrush(conicalGradient);

Un patrón similar de acciones, como el utilizado para QLinearGradient, se utiliza en los casos de Qt::RadialGradientPattern y Qt::ConicalGradientPattern.

La única diferencia son los argumentos que se pasan al constructor: En el caso del constructor de QRadialGradient el primer argumento es el centro, y el segundo el radio del gradiente radial. El tercer argumento es opcional, pero puede utilizarse para definir el punto focal del gradiente dentro del círculo (el punto focal por defecto es el centro del círculo). En cuanto al constructor QConicalGradient, el primer argumento especifica el centro de la cónica, y el segundo especifica el ángulo de inicio de la interpolación.

    } else if (style == Qt::TexturePattern) {
        renderArea->setBrush(QBrush(QPixmap(":/images/brick.png")));

Si el estilo del pincel es Qt::TexturePattern creamos un QBrush a partir de un QPixmap. Luego llamamos a la ranura RenderArea::setBrush() para actualizar el widget RenderArea con el pincel recién creado.

    } else {
        renderArea->setBrush(QBrush(Qt::green, style));
    }
}

De lo contrario, simplemente creamos un pincel con el estilo dado y un color verde, y luego llamamos a la ranura RenderArea::setBrush() para actualizar el widget RenderArea con el pincel recién creado.

Definición de la clase RenderArea

La clase RenderArea hereda de QWidget, y renderiza múltiples copias de la forma actualmente activa utilizando un QPainter.

class RenderArea : public QWidget
{
    Q_OBJECT

public:
    enum Shape { Line, Points, Polyline, Polygon, Rect, RoundedRect, Ellipse, Arc,
                 Chord, Pie, Path, Text, Pixmap };

    explicit RenderArea(QWidget *parent = nullptr);

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

public slots:
    void setShape(Shape shape);
    void setPen(const QPen &pen);
    void setBrush(const QBrush &brush);
    void setAntialiased(bool antialiased);
    void setTransformed(bool transformed);

protected:
    void paintEvent(QPaintEvent *event) override;

private:
    Shape shape;
    QPen pen;
    QBrush brush;
    bool antialiased;
    bool transformed;
    QPixmap pixmap;
};

Primero definimos un enum público Shape para contener las diferentes formas que pueden ser renderizadas por el widget (es decir, las formas que pueden ser renderizadas por un QPainter). A continuación, reimplementamos el constructor y dos de las funciones públicas de QWidget: minimumSizeHint() y sizeHint().

También reimplementamos la función QWidget::paintEvent() para poder dibujar la forma activa en ese momento según los parámetros especificados.

Declaramos varias ranuras privadas: La ranura setShape() cambia la forma de RenderArea, las ranuras setPen() y setBrush() modifican el lápiz y el pincel del widget, y las ranuras setAntialiased() y setTransformed() modifican las propiedades respectivas del widget.

Implementación de la clase RenderArea

En el constructor inicializamos algunas de las variables del widget.

RenderArea::RenderArea(QWidget *parent)
    : QWidget(parent)
{
    shape = Polygon;
    antialiased = false;
    transformed = false;
    pixmap.load(":/images/qt-logo.png");

    setBackgroundRole(QPalette::Base);
    setAutoFillBackground(true);
}

Establecemos su forma en Polygon, su propiedad antialiased en false y cargamos una imagen en la variable pixmap del widget. Al final establecemos el rol de fondo del widget, definiendo el pincel de palette del widget que se usará para renderizar el fondo. QPalette::Base es típicamente blanco.

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

El RenderArea hereda QWidget's sizeHint propiedad que contiene el tamaño recomendado para el widget. Si el valor de esta propiedad es un tamaño no válido, no se recomienda ningún tamaño.

La implementación por defecto de la función QWidget::sizeHint() devuelve un tamaño no válido si no hay diseño para el widget, y devuelve el tamaño preferido del diseño en caso contrario.

Nuestra reimplementación de la función devuelve un QSize con una anchura de 400 píxeles y una altura de 200 píxeles.

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

RenderArea también hereda la propiedad minimumSizeHint de QWidget que contiene el tamaño mínimo recomendado para el widget. De nuevo, si el valor de esta propiedad es un tamaño no válido, no se recomienda ningún tamaño.

La implementación por defecto de QWidget::minimumSizeHint() devuelve un tamaño no válido si no hay diseño para el widget, y devuelve el tamaño mínimo del diseño en caso contrario.

Nuestra reimplementación de la función devuelve un QSize con una anchura de 100 píxeles y una altura de 100 píxeles.

void RenderArea::setShape(Shape shape)
{
    this->shape = shape;
    update();
}

void RenderArea::setPen(const QPen &pen)
{
    this->pen = pen;
    update();
}

void RenderArea::setBrush(const QBrush &brush)
{
    this->brush = brush;
    update();
}

Las ranuras públicas setShape(), setPen() y setBrush() son llamadas cada vez que queremos modificar la forma, el lápiz o el pincel de un widget RenderArea. Establecemos la forma, el lápiz o el pincel de acuerdo con el parámetro de la ranura, y llamamos a QWidget::update() para que los cambios sean visibles en el widget RenderArea.

La ranura QWidget::update() no provoca un repintado inmediato, sino que programa un evento de pintura para procesarlo cuando Qt vuelva al bucle de eventos principal.

void RenderArea::setAntialiased(bool antialiased)
{
    this->antialiased = antialiased;
    update();
}

void RenderArea::setTransformed(bool transformed)
{
    this->transformed = transformed;
    update();
}

Con las ranuras setAntialiased() y setTransformed() cambiamos el estado de las propiedades según el parámetro de la ranura, y llamamos a la ranura QWidget::update() para hacer visibles los cambios en el widget RenderArea.

void RenderArea::paintEvent(QPaintEvent * /* event */)
{
    static const QPoint points[4] = {
        QPoint(10, 80),
        QPoint(20, 10),
        QPoint(80, 30),
        QPoint(90, 70)
    };

    QRect rect(10, 20, 80, 60);

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

    int startAngle = 20 * 16;
    int arcLength = 120 * 16;

A continuación reimplementamos la función QWidget::paintEvent(). Lo primero que hacemos es crear los objetos gráficos que necesitaremos para dibujar las distintas formas.

Creamos un vector de cuatro QPoints. Utilizamos este vector para renderizar las formas Points, Polyline y Polygon. A continuación, creamos un QRect, que define un rectángulo en el plano y que utilizaremos como rectángulo delimitador de todas las formas, excepto Path y Pixmap.

También creamos un QPainterPath. La clase QPainterPath proporciona un contenedor para las operaciones de pintado, lo que permite construir y reutilizar formas gráficas. Un trazado de pintado es un objeto compuesto por una serie de bloques de construcción gráficos, como rectángulos, elipses, líneas y curvas. Para obtener más información sobre la clase QPainterPath, consulte el ejemplo Trayectorias de pintor. En este ejemplo, creamos una trayectoria de pintor compuesta por una línea recta y una curva de Bézier.

Además definimos un ángulo inicial y una longitud de arco que utilizaremos al dibujar las formas Arc, Chord y Pie.

    QPainter painter(this);
    painter.setPen(pen);
    painter.setBrush(brush);
    if (antialiased)
        painter.setRenderHint(QPainter::Antialiasing, true);

Creamos un QPainter para el widget RenderArea, y establecemos la pluma y el pincel del pintor de acuerdo con la pluma y el pincel de RenderArea. Si la opción del parámetro Antialiasing está marcada, también configuramos las pistas de renderizado del pintor. QPainter::Antialiasing indica que el motor debe antialiasear los bordes de las primitivas si es posible.

    for (int x = 0; x < width(); x += 100) {
        for (int y = 0; y < height(); y += 100) {
            QPainterStateGuard guard(&painter);
            painter.translate(x, y);

Finalmente, renderizamos las múltiples copias de la forma de RenderArea. El número de copias depende del tamaño del widget RenderArea, y calculamos sus posiciones usando dos bucles for y la altura y anchura de los widgets.

Para cada copia, primero guardamos el estado actual del pintor instanciando un QPainterStateGuard. A continuación, trasladamos el sistema de coordenadas, mediante la función QPainter::translate(), a la posición determinada por las variables de los bucles for. Si omitimos esta traslación del sistema de coordenadas, todas las copias de la forma se renderizarán una encima de otra en el ángulo superior izquierdo del widget RenderArea.

            if (transformed) {
                painter.translate(50, 50);
                painter.rotate(60.0);
                painter.scale(0.6, 0.9);
                painter.translate(-50, -50);
            }

Si la opción del parámetro Transformations está marcada, hacemos una traslación adicional del sistema de coordenadas antes de rotar el sistema de coordenadas 60 grados en el sentido de las agujas del reloj utilizando la función QPainter::rotate() y reducir su tamaño utilizando la función QPainter::scale(). Al final traducimos el sistema de coordenadas a donde estaba antes de rotarlo y escalarlo.

Ahora, al renderizar la forma, aparecerá como si hubiera sido rotada en tres dimensiones.

            switch (shape) {
            case Line:
                painter.drawLine(rect.bottomLeft(), rect.topRight());
                break;
            case Points:
                painter.drawPoints(points, 4);
                break;
            case Polyline:
                painter.drawPolyline(points, 4);
                break;
            case Polygon:
                painter.drawPolygon(points, 4);
                break;
            case Rect:
                painter.drawRect(rect);
                break;
            case RoundedRect:
                painter.drawRoundedRect(rect, 25, 25, Qt::RelativeSize);
                break;
            case Ellipse:
                painter.drawEllipse(rect);
                break;
            case Arc:
                painter.drawArc(rect, startAngle, arcLength);
                break;
            case Chord:
                painter.drawChord(rect, startAngle, arcLength);
                break;
            case Pie:
                painter.drawPie(rect, startAngle, arcLength);
                break;
            case Path:
                painter.drawPath(path);
                break;
            case Text:
                painter.drawText(rect,
                                 Qt::AlignCenter,
                                 tr("Qt by\nThe Qt Company"));
                break;
            case Pixmap:
                painter.drawPixmap(10, 10, pixmap);
            }

A continuación, identificamos la forma de RenderArea, y la renderizamos utilizando la función de dibujo asociada QPainter:

Antes de empezar a renderizar, guardamos el estado actual del pintor (empujamos el estado a una pila). La razón de esto es que calculamos la posición de cada copia de forma relativa al mismo punto en el sistema de coordenadas. Al trasladar el sistema de coordenadas, perdemos el conocimiento de este punto a menos que guardemos el estado actual del pintor antes de iniciar el proceso de traslación.

        }
    }

    painter.setRenderHint(QPainter::Antialiasing, false);
    painter.setPen(palette().dark().color());
    painter.setBrush(Qt::NoBrush);
    painter.drawRect(QRect(0, 0, width() - 1, height() - 1));
}

Entonces, cuando terminamos de renderizar una copia de la forma, el estado original del pintor es restaurado por QPainterStateGuard, que llama a la función QPainter::restore(). De esta forma, nos aseguramos de que la siguiente copia de la forma se renderizará en la posición correcta.

Podríamos volver a trasladar el sistema de coordenadas utilizando QPainter::translate() en lugar de guardar el estado del pintor. Pero dado que además de traducir el sistema de coordenadas (cuando la opción del parámetro Transformation está marcada) rotamos y escalamos el sistema de coordenadas, la solución más sencilla es guardar el estado actual del pintor.

Proyecto de ejemplo @ 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.