Qt Documentation

Basic Drawing Example

QPainter performs low-level painting on widgets and other paint devices. The class can draw everything from simple lines to complex shapes like pies and chords. It can also draw aligned text and pixmaps. Normally, it draws in a "natural" coordinate system, but it can in addition do view and world transformation.

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). In addition the user can rotate a shape (Transformations); behind the scenes we use QPainter's ability to manipulate the coordinate system to perform the rotation.

The Basic Drawing example consists of two classes:

RenderArea is a custom widget that renders multiple copies of the currently active shape.

Window is the application's main window displaying a RenderArea widget in addition to several parameter widgets.

First we will review the Window class, then we will take a look at the RenderArea class.

Window Class Definition

The Window class inherits QWidget, and is the application's main window displaying a RenderArea widget in addition to several parameter widgets.

We declare the various widgets, and three private slots updating the RenderArea widget: The shapeChanged() slot updates the RenderArea widget when the user changes the currently active shape. We call the penChanged() slot when either of the QPainter's pen parameters changes. And the brushChanged() slot updates the RenderArea widget when the user changes the painter's brush style.

Window Class Implementation

In the constructor we create and initialize the various widgets appearing in the main application window.

QPainter's pen is a QPen object; the QPen class defines how a painter should draw lines and outlines of shapes. A pen has several properties: Width, style, cap and join.

A pen's width can be zero or greater, but the most common width is zero. Note that this doesn't mean 0 pixels, but implies that the shape is drawn as smoothly as possible although perhaps not mathematically correct.

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.

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

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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 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.

Before we started rendering, we saved the current painter state (pushes the state onto a stack). The rationale for this is that we calculate each shape copy's position relative to the same point in the coordinate system. When translating the coordinate system, we lose the knowledge of this point unless we save the current painter state before we start the translating process.

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.