模拟时钟

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

模拟时钟示例截图

此示例还演示了如何使用 QPainter 的转换和缩放功能简化自定义控件的绘制。

模拟时钟类定义

AnalogClock 类提供了一个带有时针、分针和秒针的时钟控件,每秒自动更新一次。我们继承自 QWidget 并重写了标准的 paintEvent() 函数来绘制时钟面。

class AnalogClock : public QWidget
{
    Q_OBJECT

public:
    AnalogClock(QWidget *parent = nullptr);

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

模拟时钟类实现

当构建控件时,我们设置一个一秒定时器来跟踪当前时间,并将其连接到标准的 update() 槽,以便在定时器发出 timeout() 信号时更新时钟面。最后,我们调整控件的大小以便在合理的尺寸下显示。

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

每当控件内容需要更新时都会调用 paintEvent() 函数。这发生在控件首次显示时,以及当控件被覆盖然后再次显示时,但也是在控件调用 update() 槽时执行的。由于我们将定时器的 timeout() 信号连接到了这个槽,所以它至少每秒会被调用一次。

在我们设置画笔和绘制时钟之前,我们首先定义三个用于时针、分针和秒针的 QPoint 列表和三个 QColor。我们使用 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());

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

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

我们使用 QPainter::setRenderHint() 在 QPainter::Antialiasing 开启抗锯齿。这使得斜线的绘制更加平滑。

    painter.setRenderHint(QPainter::Antialiasing);

翻译将坐标系移动到小部件的中心,并且缩放操作确保后续的绘图操作可以在小部件内缩放。我们使用一个缩放因子,允许我们使用在-100和100之间的x和y坐标,并确保它们位于小部件最短边长度内。

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

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

绘制器负责在绘制事件期间进行的所有转换,并确保一切绘制正确。让绘制器处理转换通常比手动计算仅为了绘制自定义小部件的内容更容易。

我们设置画笔为Qt::NoPen,因为我们不想有任何轮廓,并使用与显示小时适当的颜色填充的实心画刷。填充多边形和其他几何形状时使用画刷。

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

我们首先绘制时针,使用一个公式来顺时针旋转坐标系一定角度,该角度由当前的小时和分钟确定。这意味着手将按所需量顺时针旋转。在旋转之前和之后保存和恢复转换矩阵,因为我们希望在放置分钟手时无需考虑到任何先前的旋转。

    painter.save();
    painter.rotate(30.0 * ((time.hour() + time.minute() / 60.0)));
    painter.drawConvexPolygon(hourHand, 4);
    painter.restore();

我们按照与时针相同的颜色在时钟边缘绘制每个小时的标记。我们绘制每个标记然后旋转坐标系,以便绘制下一个标记的绘制器准备就绪。

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

秒针以类似时针的方式旋转并绘制。

    painter.setBrush(minuteColor);

    painter.save();
    painter.rotate(6.0 * time.minute());
    painter.drawConvexPolygon(minuteHand, 4);
    painter.restore();

对于秒针,我们以相同的方式进行操作,并添加两个圆形作为视觉亮点。

    painter.setBrush(secondsColor);

    painter.save();
    painter.rotate(6.0 * time.second());
    painter.drawConvexPolygon(secondsHand, 4);
    painter.drawEllipse(-3, -3, 6, 6);
    painter.drawEllipse(-5, -68, 10, 10);
    painter.restore();

最后,我们在时钟边缘绘制标记,表示分钟和秒。这次我们将其绘制为线条,因此将画笔设置为相应的颜色。

    painter.setPen(minuteColor);

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

示例项目 @ code.qt.io

© 2024 The Qt Company Ltd. 本文件中的文档贡献是相应所有者的版权。本文件提供的文档是根据由自由软件基金会发布的GNU自由文档许可版本1.3的条款许可的。Qt及其相关标志是The Qt Company Ltd.在芬兰和/或世界其他国家的商标。所有其他商标均为其所有者的财产。