Basic Drawing Example

QPainter在小部件和其他绘画设备上执行低级绘画。 该类可以绘制从简单的线条到复杂的形状(如派和弦)的所有内容。 它还可以绘制对齐的文本和像素图。 通常,它绘制“自然”坐标系,但它还可以进行视图和世界变换。

The example provides a render area, displaying the currently active shape, and lets the user manipulate the rendered shape and its appearance using the QPainter parameters: The user can change the active shape (Shape), and modify the QPainter's pen (Pen Width, Pen Style, Pen Cap, Pen Join), brush (Brush Style) and render hints (Antialiasing). 另外,用户可以旋转形状(Transformations);在幕后,我们使用QPainter的能力来操纵坐标系以执行旋转。

基本绘图示例包含两个类:

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

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

Window Class Definition

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的任何一个pen参数更改时,我们称为penChanged()插槽。 当用户更改画家的画笔样式时,brushChanged()插槽会更新RenderArea小部件。

Window Class Implementation

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


  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类定义了画家应该如何绘制线条和形状轮廓。 一支笔具有几个属性:宽度,样式,cap和join。

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

We create a QSpinBox for the Pen Width parameter.


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

The pen style defines the line type. The default style is solid (Qt::SolidLine). Setting the style to none (Qt::NoPen) tells the painter to not draw lines or outlines. The pen cap defines how the end points of lines are drawn. And the pen join defines how two lines join when multiple connected lines are drawn. The cap and join only apply to lines with a width of 1 pixel or greater.

We create QComboBoxes for each of the Pen Style, Pen Cap and Pen Join parameters, and adds the associated items (i.e the values of the Qt::PenStyle, Qt::PenCapStyle and Qt::PenJoinStyle enums respectively).


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

The QBrush class defines the fill pattern of shapes drawn by a QPainter. The default brush style is Qt::NoBrush. This style tells the painter to not fill shapes. The standard style for filling is Qt::SolidPattern.

We create a QComboBox for the Brush Style parameter, and add the associated items (i.e. the values of the Qt::BrushStyle enum).


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

Antialiasing is a feature that "smoothes" the pixels to create more even and less jagged lines, and can be applied using QPainter's render hints. QPainter::RenderHints are used to specify flags to QPainter that may or may not be respected by any given engine.

We simply create a QCheckBox for the Antialiasing option.


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

The Transformations option implies a manipulation of the coordinate system that will appear as if the rendered shape is rotated in three dimensions.

We use the QPainter::translate(), QPainter::rotate() and QPainter::scale() functions to implement this feature represented in the main application window by a simple QCheckBox.


      connect(shapeComboBox, SIGNAL(activated(int)),
              this, SLOT(shapeChanged()));
      connect(penWidthSpinBox, SIGNAL(valueChanged(int)),
              this, SLOT(penChanged()));
      connect(penStyleComboBox, SIGNAL(activated(int)),
              this, SLOT(penChanged()));
      connect(penCapComboBox, SIGNAL(activated(int)),
              this, SLOT(penChanged()));
      connect(penJoinComboBox, SIGNAL(activated(int)),
              this, SLOT(penChanged()));
      connect(brushStyleComboBox, SIGNAL(activated(int)),
              this, SLOT(brushChanged()));
      connect(antialiasingCheckBox, SIGNAL(toggled(bool)),
              renderArea, SLOT(setAntialiased(bool)));
      connect(transformationsCheckBox, SIGNAL(toggled(bool)),
              renderArea, SLOT(setTransformed(bool)));

Then we connect the parameter widgets with their associated slots using the static QObject::connect() function, ensuring that the RenderArea widget is updated whenever the user changes the shape, or any of the other parameters.


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

Finally, we add the various widgets to a layout, and call the shapeChanged(), penChanged(), and brushChanged() slots to initialize the application. We also turn on antialiasing.


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

The shapeChanged() slot is called whenever the user changes the currently active shape.

First we retrieve the shape the user has chosen using the QComboBox::itemData() function. This function returns the data for the given role in the given index in the combobox. We use QComboBox::currentIndex() to retrieve the index of the shape, and the role is defined by the Qt::ItemDataRole enum; IdRole is an alias for Qt::UserRole.

Note that Qt::UserRole is only the first role that can be used for application-specific purposes. If you need to store different data in the same index, you can use different roles by simply incrementing the value of Qt::UserRole, for example: 'Qt::UserRole + 1' and 'Qt::UserRole + 2'. However, it is a good programming practice to give each role their own name: 'myFirstRole = Qt::UserRole + 1' and 'mySecondRole = Qt::UserRole + 2'. Even though we only need a single role in this particular example, we add the following line of code to the beginning of the window.cpp file.


  const int IdRole = Qt::UserRole;

The QComboBox::itemData() function returns the data as a QVariant, so we need to cast the data to RenderArea::Shape. If there is no data for the given role, the function returns QVariant::Invalid.

In the end we call the RenderArea::setShape() slot to update the 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));
  }

We call the penChanged() slot whenever the user changes any of the pen parameters. Again we use the QComboBox::itemData() function to retrieve the parameters, and then we call the RenderArea::setPen() slot to update the RenderArea widget.


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

The brushChanged() slot is called whenever the user changes the brush parameter which we retrieve using the QComboBox::itemData() function as before.


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

If the brush parameter is a gradient fill, special actions are required.

The QGradient class is used in combination with QBrush to specify gradient fills. Qt currently supports three types of gradient fills: linear, radial and conical. Each of these is represented by a subclass of QGradient: QLinearGradient, QRadialGradient and QConicalGradient.

So if the brush style is Qt::LinearGradientPattern, we first create a QLinearGradient object with interpolation area between the coordinates passed as arguments to the constructor. The positions are specified using logical coordinates. Then we set the gradient's colors using the QGradient::setColorAt() function. The colors is defined using stop points which are composed by a position (between 0 and 1) and a QColor. The set of stop points describes how the gradient area should be filled. A gradient can have an arbitrary number of stop points.

In the end we call RenderArea::setBrush() slot to update the RenderArea widget's brush with the QLinearGradient object.


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

A similar pattern of actions, as the one used for QLinearGradient, is used in the cases of Qt::RadialGradientPattern and Qt::ConicalGradientPattern.

The only difference is the arguments passed to the constructor: Regarding the QRadialGradient constructor the first argument is the center, and the second the radial gradient's radius. The third argument is optional, but can be used to define the focal point of the gradient inside the circle (the default focal point is the circle center). Regarding the QConicalGradient constructor, the first argument specifies the center of the conical, and the second specifies the start angle of the interpolation.


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

If the brush style is Qt::TexturePattern we create a QBrush from a QPixmap. Then we call RenderArea::setBrush() slot to update the RenderArea widget with the newly created brush.


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

Otherwise we simply create a brush with the given style and a green color, and then call RenderArea::setBrush() slot to update the RenderArea widget with the newly created brush.

RenderArea Class Definition

The RenderArea class inherits QWidget, and renders multiple copies of the currently active shape using a QPainter.


  class RenderArea : public QWidget
  {
      Q_OBJECT

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

      RenderArea(QWidget *parent = 0);

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

First we define a public Shape enum to hold the different shapes that can be rendered by the widget (i.e the shapes that can be rendered by a QPainter). Then we reimplement the constructor as well as two of QWidget's public functions: minimumSizeHint() and sizeHint().

We also reimplement the QWidget::paintEvent() function to be able to draw the currently active shape according to the specified parameters.

We declare several private slots: The setShape() slot changes the RenderArea's shape, the setPen() and setBrush() slots modify the widget's pen and brush, and the setAntialiased() and setTransformed() slots modify the widget's respective properties.

RenderArea Class Implementation

In the constructor we initialize some of the widget's variables.


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

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

We set its shape to be a Polygon, its antialiased property to be false and we load an image into the widget's pixmap variable. In the end we set the widget's background role, defining the brush from the widget's palette that will be used to render the background. QPalette::Base is typically white.


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

The RenderArea inherits QWidget's sizeHint property holding the recommended size for the widget. If the value of this property is an invalid size, no size is recommended.

The default implementation of the QWidget::sizeHint() function returns an invalid size if there is no layout for the widget, and returns the layout's preferred size otherwise.

Our reimplementation of the function returns a QSize with a 400 pixels width and a 200 pixels height.


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

RenderArea also inherits QWidget's minimumSizeHint property holding the recommended minimum size for the widget. Again, if the value of this property is an invalid size, no size is recommended.

The default implementation of QWidget::minimumSizeHint() returns an invalid size if there is no layout for the widget, and returns the layout's minimum size otherwise.

Our reimplementation of the function returns a QSize with a 100 pixels width and a 100 pixels height.


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

The public setShape(), setPen() and setBrush() slots are called whenever we want to modify a RenderArea widget's shape, pen or brush. We set the shape, pen or brush according to the slot parameter, and call QWidget::update() to make the changes visible in the RenderArea widget.

The QWidget::update() slot does not cause an immediate repaint; instead it schedules a paint event for processing when Qt returns to the main event loop.


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

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

With the setAntialiased() and setTransformed() slots we change the state of the properties according to the slot parameter, and call the QWidget::update() slot to make the changes visible in the RenderArea widget.


  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;

Then we reimplement the QWidget::paintEvent() function. The first thing we do is to create the graphical objects we will need to draw the various shapes.

We create a vector of four QPoints. We use this vector to render the Points, Polyline and Polygon shapes. Then we create a QRect, defining a rectangle in the plane, which we use as the bounding rectangle for all the shapes excluding the Path and the Pixmap.

We also create a QPainterPath. The QPainterPath class provides a container for painting operations, enabling graphical shapes to be constructed and reused. A painter path is an object composed of a number of graphical building blocks, such as rectangles, ellipses, lines, and curves. For more information about the QPainterPath class, see the Painter Paths example. In this example, we create a painter path composed of one straight line and a Bezier curve.

In addition we define a start angle and an arc length that we will use when drawing the Arc, Chord and Pie shapes.


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

We create a QPainter for the RenderArea widget, and set the painters pen and brush according to the RenderArea's pen and brush. If the Antialiasing parameter option is checked, we also set the painter's render hints. QPainter::Antialiasing indicates that the engine should antialias edges of primitives if possible.


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

Finally, we render the multiple copies of the RenderArea's shape. The number of copies is depending on the size of the RenderArea widget, and we calculate their positions using two for loops and the widgets height and width.

For each copy we first save the current painter state (pushes the state onto a stack). Then we translate the coordinate system, using the QPainter::translate() function, to the position determined by the variables of the for loops. If we omit this translation of the coordinate system all the copies of the shape will be rendered on top of each other in the top left cormer of the RenderArea widget.


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

If the Transformations parameter option is checked, we do an additional translation of the coordinate system before we rotate the coordinate system 60 degrees clockwise using the QPainter::rotate() function and scale it down in size using the QPainter::scale() function. In the end we translate the coordinate system back to where it was before we rotated and scaled it.

Now, when rendering the shape, it will appear as if it was rotated in three dimensions.


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

Then, when we are finished rendering a copy of the shape we can restore the original painter state, with its associated coordinate system, using the QPainter::restore() function. In this way we ensure that the next shape copy will be rendered in the correct position.

We could translate the coordinate system back using QPainter::translate() instead of saving the painter state. But since we in addition to translating the coordinate system (when the Transformation parameter option is checked) both rotate and scale the coordinate system, the easiest solution is to save the current painter state.

Files:

Images: