基本绘图示例

基本绘图示例显示了如何使用 QPainter 类以多种样式显示基本图形原语。

QPainter 对小部件和其他绘图设备执行低级绘图。该类可以绘制从简单线条到复杂形状(如圆弧和弦)的一切。它还可以绘制对齐文本和位图。通常,它以“自然”坐标系绘制,但它还可以进行视图和世界转换。

示例提供了一个渲染区域,显示当前活动的形状,并允许用户使用 QPainter 参数操纵渲染形状及其外观:用户可以更改活动形状(形状),并修改 QPainter 的笔(笔宽笔风格笔帽笔连接),画刷(画刷样式)和渲染提示(抗锯齿)。此外,用户可以旋转形状(变换);在幕后,我们使用 QPainter 操作坐标系统的能力来执行旋转。

基本绘图示例包含两个类

  • RenderArea 是一个自定义小部件,用于渲染当前活动的形状的多个副本。
  • Window 是应用程序的主要窗口,除了显示几个参数小部件外,还显示 RenderArea 小部件。

首先我们将回顾 Window 类,然后我们将看看 RenderArea 类。

窗口类定义

窗口类继承自 QWidget,是应用程序的主要窗口,除显示几个参数小部件外,还显示 RenderArea 小部件。

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;
};

我们声明了各种小部件,并声明了三个私有槽来更新 RenderArea 小部件:当用户更改当前活动形状时,shapeChanged() 槽更新 RenderArea 小部件。当 QPainter 的任何笔参数更改时,我们调用 penChanged() 槽。当用户更改画家的画刷样式时,brushChanged() 槽更新 RenderArea 小部件。

窗口类实现

在构造函数中,我们创建了主应用程序窗口中出现的各种小部件。

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);

首先,我们创建了一个 RenderArea 小部件,它将渲染当前活动的形状。然后,我们创建了一个 形状 组合框,并添加了相关项目(即 QPainter 可以绘制的不同形状)。

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

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

QPainter 的笔是一个 QPen 对象;QPen 类定义了画家如何绘制线条和形状的轮廓。笔有几个属性:宽度、样式、帽和连接。

钢笔的宽度可以是或更大,但最常见的宽度是零。请注意,这并不意味着0像素,而意味着形状尽可能平滑地绘制,尽管可能不是数学上完全正确。

我们为Pen Width参数创建了一个QSpinBox

    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);

钢笔样式定义了线型。默认样式是实线(Qt::SolidLine)。将样式设置为无(Qt::NoPen)告诉画家不绘制线或轮廓。钢笔帽定义了线端点绘制的方式。而钢笔连接定义了绘制多个连接线时两条线如何结合。只有宽度为1像素或更宽的线条,帽和连接才起作用。

我们为每个Pen StylePen CapPen Join参数创建了QComboBox,并添加了相关的项(即Qt::PenStyleQt::PenCapStyleQt::PenJoinStyle枚举的值分别)。

    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);

QBrush类定义了由QPainter绘制的形状的填充模式。默认画刷样式是Qt::NoBrush。这种样式告诉画家不要填充形状。填充的标准样式是Qt::SolidPattern

我们为Brush Style参数创建了一个QComboBox,并添加了相关的项(即Qt::BrushStyle枚举的值)。

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

抗锯齿是一种特征,它“平滑”像素以创建更均匀且更少的锯齿状线条,并且可以使用QPainter的渲染提示应用。使用QPainter::RenderHints指定QPainter可能或可能不会被特定引擎尊重的标志。

我们简单地创建了一个Antialiasing选项的QCheckBox

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

Transformations选项意味着对坐标系统进行操作,使得绘制的形状看起来像在三维空间中旋转。

我们使用QPainter::translate()、QPainter::rotate()和QPainter::scale()函数来实现这一功能,通过在主应用程序窗口中以简单的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);

然后,我们使用静态的QObject::connect()函数连接参数小部件及其关联的槽,确保当用户更改形状或任何其他参数时,RenderArea小部件会更新。

    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"));
}

最后,我们将各种小部件添加到布局中,并调用shapeChanged()、penChanged()和brushChanged()槽来初始化应用程序。我们还打开了抗锯齿。

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

当用户更改当前活动形状时,会调用shapeChanged()槽。

首先,我们使用QComboBox::itemData()函数检索用户已选择的形状。该函数返回组合框中给定索引在给定作用域中的数据。我们使用QComboBox::currentIndex()检索形状的索引,作用域由Qt::ItemDataRole枚举定义;IdRoleQt::UserRole的别名。

请注意,Qt::UserRole仅是用于应用程序特定目的的第一个可以使用的角色。如果您需要在同一个索引中存储不同的数据,可以通过简单地增加Qt::UserRole的值来使用不同的角色,例如:'Qt::UserRole + 1'和'Qt::UserRole + 2'。然而,为每个角色指定自己的名称是一种良好的编程习惯:'myFirstRole = Qt::UserRole + 1'和'mySecondRole = Qt::UserRole + 2'。即便在这特定示例中我们只需要一个角色,我们也将以下代码行添加到window.cpp文件的开始部分。

const int IdRole = Qt::UserRole;

QComboBox::itemData()函数返回数据作为一个QVariant,所以我们需要将数据转换为RenderArea::Shape。如果没有给定角色的数据,该函数返回QVariant::Invalid。

最后,我们调用RenderArea::setShape()槽来更新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));
}

每当用户更改任一画笔参数时,我们都会调用penChanged()槽。同样,我们使用QComboBox::itemData()函数来检索参数,然后调用RenderArea::setPen()槽来更新RenderArea小部件。

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

每当用户更改刷子参数时,调用brushChanged()槽,与我们之前使用QComboBox::itemData()函数检索参数的方式相同。

    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);

如果刷子参数是渐变填充,则需要执行特殊操作。

QGradientQBrush结合使用来指定渐变填充。Qt当前支持三种渐变填充类型:线性、径向和圆锥形。这些都由QGradient的子类表示:QLinearGradientQRadialGradientQConicalGradient

因此,如果刷子样式是Qt::LinearGradientPattern,我们首先创建一个包含用于构造函数中传入的坐标之间的插值区域的QLinearGradient对象。位置使用逻辑坐标指定。然后,我们使用QGradient::setColorAt()函数设置渐变的颜色。颜色使用停止点来定义,这些停止点由一个介于0和1之间的位置和一个QColor组成。停止点的集合描述了如何填充渐变区域。一个渐变可以有任意数量的停止点。

最后,我们调用RenderArea::setBrush()槽,更新RenderArea小部件的刷子为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);

QLinearGradient类似的操作模式也用于Qt::RadialGradientPatternQt::ConicalGradientPattern的情况。

唯一的区别是传递给构造函数的参数:对于QRadialGradient构造函数,第一个参数是中心,第二个参数是径向渐变的半径。第三个参数是可选的,但可以用来定义圆内的渐变焦点(默认焦点是圆心)。对于QConicalGradient构造函数,第一个参数指定圆锥的中心,第二个参数指定插值开始的角度。

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

如果画笔样式是 Qt::TexturePattern,我们将根据一个 QPixmap 创建一个 QBrush。然后我们调用 RenderArea::setBrush() 插槽来更新 RenderArea 小部件,并使用新创建的画笔。

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

否则,我们简单地创建一个给定样式和绿色的画笔,然后调用 RenderArea::setBrush() 插槽来更新 RenderArea 小部件,并使用新创建的画笔。

RenderArea 类定义

RenderArea 类继承自 QWidget,并且使用 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;
};

首先,我们定义一个公共 Shape 枚举来保存小部件可以渲染的不同形状(即可以由 QPainter 渲染的形状)。然后,我们重写构建函数以及 QWidget 的两个公共函数:minimumSizeHint() 和 sizeHint()。

我们还重写了 QWidget::paintEvent() 函数,以便能根据指定的参数绘制当前活动形状。

我们声明了几个私有插槽:setShape() 插槽更改 RenderArea 的形状,setPen()setBrush() 插槽修改小部件的笔和画笔,而 setAntialiased()setTransformed() 插槽修改相应的小部件属性。

RenderArea 类实现

在构建函数中,我们初始化了一些小部件的变量。

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

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

我们将其形状设为 Polygon,将抗锯齿属性设为 false,并将图像加载到小部件的 pixmap 变量中。最后,我们设置小部件的背景角色,定义用于渲染背景的从小部件的 palette 中获取的画笔。通常 QPalette::Base 是白色。

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

RenderArea 继承了 QWidgetsizeHint 属性,该属性持有小部件的推荐大小。如果此属性的值是无效大小,则不推荐大小。

QWidget::sizeHint() 函数的默认实现如果小部件没有布局,则返回一个无效大小,如果有,则返回布局的首选大小。

我们对该函数的重写返回一个宽度为 400 像素、高度为 200 像素的 QSize

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

RenderArea 也继承了 QWidgetminimumSizeHint 属性,该属性持有小部件的推荐最小大小。如果此属性的值是无效大小,则不推荐大小。

QWidget::minimumSizeHint() 函数的默认实现如果小部件没有布局,则返回一个无效大小,如果有,则返回布局的最小大小。

我们对该函数的重写返回一个宽度为 100 像素、高度为 100 像素的 QSize

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();
}

当我们要修改 RenderArea 小部件的形状、笔或画笔时,会调用公共 setShape()setPen()setBrush() 插槽。我们根据插槽参数设置形状、笔或画笔,并调用 QWidget::update() 使在 RenderArea 小部件中可见更改。

QWidget::update()槽函数不会立即触发重绘;相反,它在Qt返回主事件循环时安排一个绘制事件进行处理。

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

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

使用setAntialiased()setTransformed()槽函数,根据槽函数参数更改属性状态,并调用QWidget::update()槽函数以便在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;

然后,我们重写QWidget::paintEvent()函数。我们首先需要创建用于绘制各种形状的图形对象。

我们创建了一个包含四个QPoint的向量。我们使用这个向量绘制折线多边形形状。然后我们创建一个QRect,在平面上定义一个矩形,我们将其用作所有形状的边界矩形,但不包括路径位图

我们还创建了一个QPainterPath。QPainterPath类提供了一个绘制操作的容器,允许绘制图形形状并进行重用。绘图路径是一个由多个图形构建块组成的对象,例如矩形、椭圆形、线和曲线。有关QPainterPath类的更多信息,请参见绘图路径示例。在此示例中,我们创建了一个由一条直线和贝塞尔曲线组成的绘图路径。

此外,我们定义了起点角度和圆弧长度,这将用于绘制圆弧圆弧扇形形状。

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

我们为RenderArea组件创建了一个QPainter,并设置画笔和画刷,使其与RenderArea的画笔和画刷一致。如果勾选了抗锯齿参数选项,我们还会设置画家的绘制提示。《a href="qpainter.html#RenderHint-enum" translate="no">QPainter::Antialiasing表示如果可能,引擎应该抗锯齿原语边缘。

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

最后,我们渲染多份RenderArea的形状副本。副本的数量取决于RenderArea组件的大小,我们使用两个for循环和组件的高度和宽度来计算它们的位置。

对于每个副本,我们首先保存当前的画家状态(将状态压入堆栈中)。然后,我们使用QPainter::translate()函数将坐标系转换到由for循环变量确定的的位置。如果我们省略坐标系转换,所有形状的副本都将绘制在RenderArea组件的左上角重叠。

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

如果勾选了变换参数选项,我们在使用QPainter::rotate()函数顺时针旋转坐标系60度和使用QPainter::scale()函数缩小尺寸之前,对坐标系进行额外的转换。最后,我们将坐标系转换回我们旋转和缩放它之前的位置。

现在,在绘制形状时,它将看起来像是在三维中旋转了。

            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);
            }

接下来,我们识别RenderArea的形状,并使用相关的QPainter绘图函数进行渲染

在我们开始渲染之前,我们保存了当前绘图器状态(将状态推入栈中)。之所以这样做是因为我们计算每个形状副本的位置是相对于坐标系中相同点的。当平移坐标系时,如果我们不在开始平移过程之前保存当前的绘图器状态,就会失去对这个点的认识。

            painter.restore();
        }
    }

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

然后,当我们完成形状副本的渲染后,可以使用QPainter::restore() 函数来恢复原始绘图器状态,其中包括其关联的坐标系。这样,我们就能确保下一个形状副本将被渲染在正确的位置。

我们可以使用QPainter::translate() 来平移坐标系而不是保存绘图器状态。但是,因为我们不仅平移了坐标系(当勾选变换参数选项时)还旋转和缩放了坐标系,所以最简单的解决方案是保存当前的绘图器状态。

示例项目 @ code.qt.io

© 2024 Qt公司有限公司。本文件中所包含的文档贡献归其各自的拥有者所有。本文件中的文档是根据自由软件基金会发布的GNU自由文档许可协议版本1.3的条款授权使用的。Qt及其相关标徽是芬兰及其它世界各地的Qt公司有限公司的商标。所有其他商标均属于其各自的所有者。