模拟时钟

模拟时钟示例展示了如何绘制自定义 widget 的内容。

模拟时钟示例截图

该示例还演示了如何使用QPainter 的变换和缩放功能更轻松地绘制自定义 widget。

模拟时钟类定义

AnalogClock 类提供了一个带有时针、分针和秒针的时钟部件,每秒都会自动更新。我们对QWidget 进行子类化,并重新实现标准的paintEvent() 函数来绘制钟面:

class AnalogClock : public QWidget
{
    Q_OBJECT

public:
    AnalogClock(QWidget *parent = nullptr);

protected:
    void paintEvent(QPaintEvent *event) override;
};

模拟时钟类的实现

在构建 widget 时,我们设置了一个一秒计时器来记录当前时间,并将其连接到标准update() 槽,这样当计时器发出timeout() 信号时,钟面就会更新。最后,我们调整 widget 的大小,使其以合理的尺寸显示。

AnalogClock::AnalogClock(QWidget *parent)
    : QWidget(parent)
{
    QTimer *timer = new QTimer(this);
    connect(timer, &QTimer::timeout, this, QOverload<>::of(&AnalogClock::update));
    timer->start(1000);

    setWindowTitle(tr("Analog Clock"));
    resize(200, 200);
}

每当需要更新 widget 的内容时,就会调用paintEvent() 函数。这种情况会在 widget 首次显示、覆盖和暴露时发生,但也会在调用 widget 的update() 槽时执行。由于我们将定时器的timeout() 信号连接到了这个槽,因此它每秒至少会被调用一次。

在设置绘画器和绘制时钟之前,我们首先要定义三个QPoints 列表和三个QColors,分别用于时针、分针和秒针。我们使用palette() 函数获得适合窗口其余部分的颜色,包括浅色和深色模式。时针和分针使用前景色,秒针使用重点色。

我们还要确定窗口小部件最短边的长度,以便将钟面放入窗口小部件中。在开始绘制之前,确定当前时间也很有用。

void AnalogClock::paintEvent(QPaintEvent *)
{
    static const QPoint hourHand[4] = {
        QPoint(5, 14),
        QPoint(-5, 14),
        QPoint(-4, -71),
        QPoint(4, -71)
    };
    static const QPoint minuteHand[4] = {
        QPoint(4, 14),
        QPoint(-4, 14),
        QPoint(-3, -89),
        QPoint(3, -89)
    };

    static const QPoint secondsHand[4] = {
       QPoint(1, 14),
       QPoint(-1, 14),
       QPoint(-1, -89),
       QPoint(1, -89)
    };

    const QColor hourColor(palette().color(QPalette::Text));
    const QColor minuteColor(palette().color(QPalette::Text));
    const QColor secondsColor(palette().color(QPalette::Accent));

    int side = qMin(width(), height());

自定义 widget 的内容通过QPainter 绘制。绘制器可用于在任何QPaintDevice 上绘制,但通常与 widget 一起使用,因此我们将 widget 实例传递给绘制器的构造函数。

    QPainter painter(this);
    QTime time = QTime::currentTime();

我们使用QPainter::Antialiasing 调用QPainter::setRenderHint() 来打开抗锯齿功能。这将使对角线的绘制更加平滑。

    painter.setRenderHint(QPainter::Antialiasing);

平移操作会将原点移动到 widget 的中心,而缩放操作则会确保接下来的绘制操作按比例缩放,以适应 widget 的大小。我们使用的缩放因子可以让我们使用 -100 和 100 之间的 x 坐标和 y 坐标,并确保这些坐标位于 widget 最短边的长度范围内。

    painter.translate(width() / 2, height() / 2);
    painter.scale(side / 200.0, side / 200.0);

为了简化代码,我们将绘制一个固定大小的钟面,并对其进行定位和缩放,使其位于 widget 的中心。

绘制器会处理绘制事件中的所有变换,并确保一切绘制正确。让绘制器处理变换通常比仅为绘制自定义 widget 的内容而执行手动计算要简单得多。

我们将画笔设置为Qt::NoPen ,因为我们不需要任何轮廓,我们使用的是纯色画笔,颜色适合显示小时数。笔刷用于填充多边形和其他几何图形。

    painter.setPen(Qt::NoPen);
    painter.setBrush(hourColor);

我们首先绘制时针,使用的公式是将坐标系逆时针旋转一个由当前小时和分钟决定的度数。这意味着指针将按要求的角度顺时针旋转。我们通过实例化QPainterStateGuard 保存并还原旋转前后的变换矩阵,因为我们希望在放置分针时无需考虑之前的任何旋转。

    {
        QPainterStateGuard guard(&painter);
        painter.rotate(30.0 * ((time.hour() + time.minute() / 60.0)));
        painter.drawConvexPolygon(hourHand, 4);
    }

我们在时钟边缘为每个小时绘制与时针颜色相同的标记。绘制完每一个标记后,我们都要旋转坐标系,以便绘制下一个标记。

    for (int i = 0; i < 12; ++i) {
        painter.drawRect(73, -3, 16, 6);
        painter.rotate(30.0);
    }

分针的旋转和绘制方法与时针类似。

    painter.setBrush(minuteColor);

    {
        QPainterStateGuard guard(&painter);
        painter.rotate(6.0 * time.minute());
        painter.drawConvexPolygon(minuteHand, 4);
    }

秒针的绘制方法与时针相同,并添加两个圆圈作为视觉亮点。

    painter.setBrush(secondsColor);

    {
        QPainterStateGuard guard(&painter);
        painter.rotate(6.0 * time.second());
        painter.drawConvexPolygon(secondsHand, 4);
        painter.drawEllipse(-3, -3, 6, 6);
        painter.drawEllipse(-5, -68, 10, 10);
    }

最后,我们在时钟边缘画上标记,表示分针和秒针。这次我们将它们画成线条,并将笔设置为相应的颜色。

    painter.setPen(minuteColor);

    for (int j = 0; j < 60; ++j) {
        painter.drawLine(92, 0, 96, 0);
        painter.rotate(6.0);
    }

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