基本绘图示例

基本绘图示例展示了如何使用QPainter 类以各种样式显示基本图形基元。

QPainter 该类可在 widget 和其他绘制设备上执行低级绘制。该类可以绘制从简单线条到派和和弦等复杂形状的所有图形。它还能绘制对齐文本和像素图。通常情况下,它以 "自然 "坐标系绘制,但也可以进行视图和世界变换。

该示例提供了一个显示当前活动形状的渲染区域,用户可以使用QPainter 参数操作渲染后的形状及其外观:用户可以更改活动形状 (Shape),并修改QPainter 的笔 (Pen Width,Pen Style,Pen Cap,Pen Join)、画笔 (Brush Style) 和渲染提示 (Antialiasing)。此外,用户还可以旋转形状 (Transformations) ;在幕后,我们使用QPainter 的坐标系操作能力来执行旋转。

基本绘图示例由两个类组成:

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

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

窗口类定义

Window 类继承于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 部件,它将呈现当前活动的形状。然后,我们创建Shape 组合框,并添加相关项(即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或更大,但最常用的宽度是 0。请注意,这并不意味着 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 widget。

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

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

QGradient 类与QBrush 结合使用,用于指定渐变填充。Qt 目前支持三种类型的渐变填充:线性、径向和锥形。每种类型都由QGradientQLinearGradient,QRadialGradientQConicalGradient 的子类来表示。

因此,如果画笔样式为Qt::LinearGradientPattern ,我们首先要创建一个QLinearGradient 对象,并在作为参数传递给构造函数的坐标之间设置插值区域。位置使用逻辑坐标指定。然后,我们使用QGradient::setColorAt() 函数设置渐变色。颜色是用停止点定义的,停止点由一个位置(0 和 1 之间)和一个QColor 组成。停止点集合描述了渐变区域的填充方式。一个梯度可以有任意数量的停止点。

最后,我们调用RenderArea::setBrush() 槽,用QLinearGradient 对象更新RenderArea widget 的画笔。

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

Qt::RadialGradientPatternQt::ConicalGradientPattern 的操作模式与QLinearGradient 类似。

唯一不同的是传给构造函数的参数:QRadialGradient 构造函数的第一个参数是中心,第二个参数是径向梯度的半径。第三个参数是可选参数,但可用于定义圆内梯度的焦点(默认焦点为圆心)。关于QConicalGradient 构造函数,第一个参数指定锥形的中心,第二个参数指定插值的起始角度。

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

如果画笔样式是Qt::TexturePattern ,我们会从QPixmap 创建一个QBrush 。然后,我们会调用RenderArea::setBrush() slot,用新创建的画笔更新RenderArea widget。

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

否则,我们只需用给定的样式和绿色创建一个画笔,然后调用RenderArea::setBrush() 插槽,用新创建的画笔更新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(QWidget *parent)
    : QWidget(parent)
{
    shape = Polygon;
    antialiased = false;
    transformed = false;
    pixmap.load(":/images/qt-logo.png");

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

我们将其形状设置为Polygon ,将其反锯齿属性设置为 false,并将一张图片加载到 widget 的 pixmap 变量中。最后,我们设置 widget 的背景角色,从 widget 的palette 中定义用于渲染背景的笔刷。QPalette::Base 通常为白色。

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

RenderArea 继承了QWidgetsizeHint 属性,该属性持有 widget 的推荐尺寸。如果该属性的值为无效尺寸,则不推荐任何尺寸。

QWidget::sizeHint() 函数的默认实现是,如果窗口部件没有布局,则返回无效尺寸,否则返回布局的首选尺寸。

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

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

RenderArea 此外,我们还继承了 的 属性,该属性包含 widget 的推荐最小尺寸。同样,如果该属性的值为无效尺寸,则不推荐任何尺寸。QWidget minimumSizeHint

QWidget::minimumSizeHint() 的默认实现是,如果 widget 没有布局,则返回无效尺寸,否则返回布局的最小尺寸。

我们对该函数的重新实现将返回一个宽度为 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 widget 中可见。

QWidget::update() 插槽不会导致立即重新绘制;相反,它会调度一个绘制事件,以便在 Qt XML 返回主事件循环时进行处理。

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() 函数。我们首先要做的是创建绘制各种形状所需的图形对象。

我们创建一个包含四个QPoints 的矢量。我们使用该矢量来渲染PointsPolylinePolygon 形状。然后,我们创建一个QRect ,在平面上定义一个矩形,作为除PathPixmap 之外的所有图形的边界矩形。

我们还创建了一个QPainterPath 。该QPainterPath 类为绘制操作提供了一个容器,可用于构建和重复使用图形。绘画路径是一个由多个图形构件(如矩形、椭圆、直线和曲线)组成的对象。有关QPainterPath 类的更多信息,请参阅 "画家路径"示例。在本例中,我们创建了一条由直线和贝塞尔曲线组成的画家路径。

此外,我们还定义了一个起始角度和一个弧长,在绘制ArcChordPie 图形时,我们将使用这两个角度和弧长。

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

我们为RenderArea 小工具创建QPainter ,并根据RenderArea 的笔和画笔设置画笔和笔刷。如果选中Antialiasing 参数选项,我们还会设置绘制器的渲染提示。QPainter::Antialiasing 表示引擎应尽可能对基元的边缘进行反锯齿处理。

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

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

对于每个副本,我们首先通过实例化QPainterStateGuard 保存当前的绘制状态。然后,我们使用QPainter::translate() 函数将坐标系平移到由for 循环变量决定的位置。如果我们省略了坐标系的平移,那么所有形状的副本都将在RenderArea widget 的左上角相互渲染。

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

如果选中Transformations 参数选项,在使用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.setRenderHint(QPainter::Antialiasing, false);
    painter.setPen(palette().dark().color());
    painter.setBrush(Qt::NoBrush);
    painter.drawRect(QRect(0, 0, width() - 1, height() - 1));
}

然后,当我们完成形状副本的渲染后,会通过QPainterStateGuard 调用QPainter::restore() 函数来恢复原来的绘制状态。这样,我们就能确保下一个形状副本将以正确的位置呈现。

我们可以使用QPainter::translate() 将坐标系平移回来,而不是保存绘制器状态。但由于除了平移坐标系(选中Transformation 参数选项时),我们还可以旋转和缩放坐标系,因此最简单的解决方案就是保存当前的绘制状态。

示例项目 @ 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.