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.
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;
}
;
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.
Window::
Window()
{
renderArea =
new
RenderArea;
shapeComboBox =
new
QComboBox;
shapeComboBox-&
gt;addItem(tr("Polygon"
), RenderArea::
Polygon);
shapeComboBox-&
gt;addItem(tr("Rectangle"
), RenderArea::
Rect);
shapeComboBox-&
gt;addItem(tr("Rounded Rectangle"
), RenderArea::
RoundedRect);
shapeComboBox-&
gt;addItem(tr("Ellipse"
), RenderArea::
Ellipse);
shapeComboBox-&
gt;addItem(tr("Pie"
), RenderArea::
Pie);
shapeComboBox-&
gt;addItem(tr("Chord"
), RenderArea::
Chord);
shapeComboBox-&
gt;addItem(tr("Path"
), RenderArea::
Path);
shapeComboBox-&
gt;addItem(tr("Line"
), RenderArea::
Line);
shapeComboBox-&
gt;addItem(tr("Polyline"
), RenderArea::
Polyline);
shapeComboBox-&
gt;addItem(tr("Arc"
), RenderArea::
Arc);
shapeComboBox-&
gt;addItem(tr("Points"
), RenderArea::
Points);
shapeComboBox-&
gt;addItem(tr("Text"
), RenderArea::
Text);
shapeComboBox-&
gt;addItem(tr("Pixmap"
), RenderArea::
Pixmap);
shapeLabel =
new
QLabel(tr("&Shape:"
));
shapeLabel-&
gt;setBuddy(shapeComboBox);
First we create the RenderArea widget that will render the currently active shape. Then we create the Shape combobox, and add the associated items (i.e. the different shapes a QPainter can draw).
penWidthSpinBox =
new
QSpinBox;
penWidthSpinBox-&
gt;setRange(0
, 20
);
penWidthSpinBox-&
gt;setSpecialValueText(tr("0 (cosmetic pen)"
));
penWidthLabel =
new
QLabel(tr("Pen &Width:"
));
penWidthLabel-&
gt;setBuddy(penWidthSpinBox);
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.
We create a QSpinBox for the Pen Width parameter.
penStyleComboBox =
new
QComboBox;
penStyleComboBox-&
gt;addItem(tr("Solid"
), static_cast
&
lt;int
&
gt;(Qt::
SolidLine));
penStyleComboBox-&
gt;addItem(tr("Dash"
), static_cast
&
lt;int
&
gt;(Qt::
DashLine));
penStyleComboBox-&
gt;addItem(tr("Dot"
), static_cast
&
lt;int
&
gt;(Qt::
DotLine));
penStyleComboBox-&
gt;addItem(tr("Dash Dot"
), static_cast
&
lt;int
&
gt;(Qt::
DashDotLine));
penStyleComboBox-&
gt;addItem(tr("Dash Dot Dot"
), static_cast
&
lt;int
&
gt;(Qt::
DashDotDotLine));
penStyleComboBox-&
gt;addItem(tr("None"
), static_cast
&
lt;int
&
gt;(Qt::
NoPen));
penStyleLabel =
new
QLabel(tr("&Pen Style:"
));
penStyleLabel-&
gt;setBuddy(penStyleComboBox);
penCapComboBox =
new
QComboBox;
penCapComboBox-&
gt;addItem(tr("Flat"
), Qt::
FlatCap);
penCapComboBox-&
gt;addItem(tr("Square"
), Qt::
SquareCap);
penCapComboBox-&
gt;addItem(tr("Round"
), Qt::
RoundCap);
penCapLabel =
new
QLabel(tr("Pen &Cap:"
));
penCapLabel-&
gt;setBuddy(penCapComboBox);
penJoinComboBox =
new
QComboBox;
penJoinComboBox-&
gt;addItem(tr("Miter"
), Qt::
MiterJoin);
penJoinComboBox-&
gt;addItem(tr("Bevel"
), Qt::
BevelJoin);
penJoinComboBox-&
gt;addItem(tr("Round"
), Qt::
RoundJoin);
penJoinLabel =
new
QLabel(tr("Pen &Join:"
));
penJoinLabel-&
gt;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-&
gt;addItem(tr("Linear Gradient"
),
static_cast
&
lt;int
&
gt;(Qt::
LinearGradientPattern));
brushStyleComboBox-&
gt;addItem(tr("Radial Gradient"
),
static_cast
&
lt;int
&
gt;(Qt::
RadialGradientPattern));
brushStyleComboBox-&
gt;addItem(tr("Conical Gradient"
),
static_cast
&
lt;int
&
gt;(Qt::
ConicalGradientPattern));
brushStyleComboBox-&
gt;addItem(tr("Texture"
), static_cast
&
lt;int
&
gt;(Qt::
TexturePattern));
brushStyleComboBox-&
gt;addItem(tr("Solid"
), static_cast
&
lt;int
&
gt;(Qt::
SolidPattern));
brushStyleComboBox-&
gt;addItem(tr("Horizontal"
), static_cast
&
lt;int
&
gt;(Qt::
HorPattern));
brushStyleComboBox-&
gt;addItem(tr("Vertical"
), static_cast
&
lt;int
&
gt;(Qt::
VerPattern));
brushStyleComboBox-&
gt;addItem(tr("Cross"
), static_cast
&
lt;int
&
gt;(Qt::
CrossPattern));
brushStyleComboBox-&
gt;addItem(tr("Backward Diagonal"
), static_cast
&
lt;int
&
gt;(Qt::
BDiagPattern));
brushStyleComboBox-&
gt;addItem(tr("Forward Diagonal"
), static_cast
&
lt;int
&
gt;(Qt::
FDiagPattern));
brushStyleComboBox-&
gt;addItem(tr("Diagonal Cross"
), static_cast
&
lt;int
&
gt;(Qt::
DiagCrossPattern));
brushStyleComboBox-&
gt;addItem(tr("Dense 1"
), static_cast
&
lt;int
&
gt;(Qt::
Dense1Pattern));
brushStyleComboBox-&
gt;addItem(tr("Dense 2"
), static_cast
&
lt;int
&
gt;(Qt::
Dense2Pattern));
brushStyleComboBox-&
gt;addItem(tr("Dense 3"
), static_cast
&
lt;int
&
gt;(Qt::
Dense3Pattern));
brushStyleComboBox-&
gt;addItem(tr("Dense 4"
), static_cast
&
lt;int
&
gt;(Qt::
Dense4Pattern));
brushStyleComboBox-&
gt;addItem(tr("Dense 5"
), static_cast
&
lt;int
&
gt;(Qt::
Dense5Pattern));
brushStyleComboBox-&
gt;addItem(tr("Dense 6"
), static_cast
&
lt;int
&
gt;(Qt::
Dense6Pattern));
brushStyleComboBox-&
gt;addItem(tr("Dense 7"
), static_cast
&
lt;int
&
gt;(Qt::
Dense7Pattern));
brushStyleComboBox-&
gt;addItem(tr("None"
), static_cast
&
lt;int
&
gt;(Qt::
NoBrush));
brushStyleLabel =
new
QLabel(tr("&Brush:"
));
brushStyleLabel-&
gt;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, QOverload&
lt;int
&
gt;::
of(&
amp;QComboBox::
activated),
this
, &
amp;Window::
shapeChanged);
connect(penWidthSpinBox, QOverload&
lt;int
&
gt;::
of(&
amp;QSpinBox::
valueChanged),
this
, &
amp;Window::
penChanged);
connect(penStyleComboBox, QOverload&
lt;int
&
gt;::
of(&
amp;QComboBox::
activated),
this
, &
amp;Window::
penChanged);
connect(penCapComboBox, QOverload&
lt;int
&
gt;::
of(&
amp;QComboBox::
activated),
this
, &
amp;Window::
penChanged);
connect(penJoinComboBox, QOverload&
lt;int
&
gt;::
of(&
amp;QComboBox::
activated),
this
, &
amp;Window::
penChanged);
connect(brushStyleComboBox, QOverload&
lt;int
&
gt;::
of(&
amp;QComboBox::
activated),
this
, &
amp;Window::
brushChanged);
connect(antialiasingCheckBox, &
amp;QAbstractButton::
toggled,
renderArea, &
amp;RenderArea::
setAntialiased);
connect(transformationsCheckBox, &
amp;QAbstractButton::
toggled,
renderArea, &
amp;RenderArea::
setTransformed);
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-&
gt;setColumnStretch(0
, 1
);
mainLayout-&
gt;setColumnStretch(3
, 1
);
mainLayout-&
gt;addWidget(renderArea, 0
, 0
, 1
, 4
);
mainLayout-&
gt;addWidget(shapeLabel, 2
, 0
, Qt::
AlignRight);
mainLayout-&
gt;addWidget(shapeComboBox, 2
, 1
);
mainLayout-&
gt;addWidget(penWidthLabel, 3
, 0
, Qt::
AlignRight);
mainLayout-&
gt;addWidget(penWidthSpinBox, 3
, 1
);
mainLayout-&
gt;addWidget(penStyleLabel, 4
, 0
, Qt::
AlignRight);
mainLayout-&
gt;addWidget(penStyleComboBox, 4
, 1
);
mainLayout-&
gt;addWidget(penCapLabel, 3
, 2
, Qt::
AlignRight);
mainLayout-&
gt;addWidget(penCapComboBox, 3
, 3
);
mainLayout-&
gt;addWidget(penJoinLabel, 2
, 2
, Qt::
AlignRight);
mainLayout-&
gt;addWidget(penJoinComboBox, 2
, 3
);
mainLayout-&
gt;addWidget(brushStyleLabel, 4
, 2
, Qt::
AlignRight);
mainLayout-&
gt;addWidget(brushStyleComboBox, 4
, 3
);
mainLayout-&
gt;addWidget(otherOptionsLabel, 5
, 0
, Qt::
AlignRight);
mainLayout-&
gt;addWidget(antialiasingCheckBox, 5
, 1
, 1
, 1
, Qt::
AlignRight);
mainLayout-&
gt;addWidget(transformationsCheckBox, 5
, 2
, 1
, 2
, Qt::
AlignRight);
setLayout(mainLayout);
shapeChanged();
penChanged();
brushChanged();
antialiasingCheckBox-&
gt;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-&
gt;itemData(
shapeComboBox-&
gt;currentIndex(), IdRole).toInt());
renderArea-&
gt;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-&
gt;value();
Qt::
PenStyle style =
Qt::
PenStyle(penStyleComboBox-&
gt;itemData(
penStyleComboBox-&
gt;currentIndex(), IdRole).toInt());
Qt::
PenCapStyle cap =
Qt::
PenCapStyle(penCapComboBox-&
gt;itemData(
penCapComboBox-&
gt;currentIndex(), IdRole).toInt());
Qt::
PenJoinStyle join =
Qt::
PenJoinStyle(penJoinComboBox-&
gt;itemData(
penJoinComboBox-&
gt;currentIndex(), IdRole).toInt());
renderArea-&
gt;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-&
gt;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-&
gt;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-&
gt;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-&
gt;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-&
gt;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-&
gt;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 &
amp;pen);
void
setBrush(const
QBrush &
amp;brush);
void
setAntialiased(bool
antialiased);
void
setTransformed(bool
transformed);
protected
: