In this example, we will learn how to use coordinate transformation and a timer to create a real-time clock display.
To create our first graphical clock display, let's follow these steps:
mainwindow.ui
and remove the menu bar, tool bar, and status bar.mainwindow.h
and include the following headers:#include <QTime> #include <QTimer> #include <QPainter>
paintEvent()
function, like so:public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
virtual void paintEvent(QPaintEvent *event);
mainwindow.cpp
, create three arrays to store the shapes of the hour hand, minute hand, and second hand, where each of the arrays contains three sets of coordinates:void MainWindow::paintEvent(QPaintEvent *event) { static const QPoint hourHand[3] = { QPoint(4, 4), QPoint(-4, 4), QPoint(0, -40) }; static const QPoint minuteHand[3] = { QPoint(4, 4), QPoint(-4, 4), QPoint(0, -70) }; static const QPoint secondHand[3] = { QPoint(2, 2), QPoint(-2, 2), QPoint(0, -90) }; }
int side = qMin(width(), height()); QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); painter.translate(width() / 2, height() / 2); painter.scale(side / 250.0, side / 250.0);
for
loop. Each dial is rotated by an increment of 6 degrees, so 60 dials would complete a full circle. Also, the dial at every 5 minutes will look slightly longer:for (int i = 0; i < 60; ++i) { if ((i % 5) != 0) painter.drawLine(92, 0, 96, 0); else painter.drawLine(86, 0, 96, 0); painter.rotate(6.0); }
QTime time = QTime::currentTime(); // Draw hour hand painter.save(); painter.rotate((time.hour() * 360) / 12); painter.setPen(Qt::NoPen); painter.setBrush(Qt::black); painter.drawConvexPolygon(hourHand, 3); painter.restore(); // Draw minute hand painter.save(); painter.rotate((time.minute() * 360) / 60); painter.setPen(Qt::NoPen); painter.setBrush(Qt::black); painter.drawConvexPolygon(minuteHand, 3); painter.restore(); // Draw second hand painter.save(); painter.rotate((time.second() * 360) / 60); painter.setPen(Qt::NoPen); painter.setBrush(Qt::black); painter.drawConvexPolygon(secondHand, 3); painter.restore();
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); QTimer* timer = new QTimer(this); timer->start(1000); connect(timer, SIGNAL(timeout()), this, SLOT(update())); }
Each of the arrays contain three QPoint
data, which form the shape of an elongated triangle. The arrays are then passed to the painter and rendered as a convex polygon using the drawConvexPolygon()
function.
Before drawing each of the clock hands, we use painter.save()
to save the state of the QPainter
object and then proceed with drawing the hand using coordinate transformation. Once we're done with the drawing, we restore the painter to its previous state by calling painter.restore()
. This function will undo all the transformations before painter.restore()
so that the next clock hand will not inherit the transformations of the previous one. Without using painter.save()
and painter.restore()
, we will have to manually change back the position, rotation, and scale before drawing the next hand.
A good example of not using painter.save()
and painter.restore()
is when drawing the dials. Since each dial's rotation is an increment of 6 degrees from the previous one, we don't need to save the painter's state at all. We just have to call painter.rotate(6.0)
in a loop and each dial will inherit the previous dial's rotation. We also use a modulus operator (%
) to check whether the unit represented by the dial can be divided by 5. If it can, then we draw it slightly longer.
Without using a timer to constantly call the update()
slot, the clock will not function properly. This is because paintEvent()
will not be called by Qt when there is no change to the state of the parent widget, which in this case is the main window. Therefore, we need to manually tell Qt that we need to refresh the graphics by calling update()
every second.
We used the painter.setRenderHint(QPainter::Antialiasing)
function to enable anti-aliasing when rendering the clock. Without anti-aliasing, the graphics will look very jagged and pixelated:
The QPainter
class uses the coordinate system to determine the position and size of the graphics before rendering them on screen. This information can be altered to make the graphics appear at a different position, rotation, and size. This process of altering the coordinate information of a graphic is what we called coordinate transformation. There are several types of transformation, among them are translation, rotation, scaling and shearing:
Qt uses a coordinate system that has its origin at the top-left corner, meaning the x values increase to the right and the y values increase downwards. This coordinate system might be different from the coordinate system used by the physical device, such as a computer screen. Qt handles this automatically by using the QPaintDevice
class, which maps Qt's logical coordinates to the physical coordinates.
QPainter
provides four transform operations to perform different types of transformation:
QPainter::translate()
: Offset the graphic's position by a given set of unitsQPainter::rotate()
: Rotate the graphics around the origin in a clockwise directionQPainter::scale()
: Offset the graphic's size by a given factorQPainter::shear()
: Twist the graphic's coordinate system around the origin18.119.235.79