警告

本节包含从C++到Python自动翻译的片段,可能包含错误。

模拟时钟#

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

../_images/analogclock-example.png

模拟时钟示例的屏幕截图

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

模拟时钟类定义#

《模拟时钟》类提供了一个时钟小部件,带有时针、分针和秒针,每秒自动更新一次。我们继承QWidget 类并重新实现标准 paintEvent() 函数来绘制时钟面

class AnalogClock(QWidget):

    Q_OBJECT
# public
    AnalogClock(QWidget parent = None)
# protected
    def paintEvent(event):

模拟时钟类实现#

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

def __init__(self, parent):
    super().__init__(parent)

    timer = QTimer(self)
    timer.timeout.connect(this, QOverload<>::of(&AnalogClock::update))
    timer.start(1000)
    setWindowTitle(tr("Analog Clock"))
    resize(200, 200)

paintEvent() 函数会在小部件内容需要更新时被调用。这发生在小部件首次显示时,以及在它被覆盖然后暴露时,同时当小部件的 update() 槽被调用时。由于我们将定时器的timeout() 信号连接到该槽,所以它至少每秒被调用一次。

在我们设置画家并绘制时钟之前,我们首先定义三个QPoints列表和三个QColors列表,将用于时针、分针和秒针。我们使用 palette() 函数来获取适合整个窗口的适当颜色,无论是在浅色模式下还是在深色模式下。时针和分针以前景色绘制,秒针以强调色绘制。

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

def paintEvent(self, arg__0):
QPoint hourHand[4] = {
    QPoint(5, 14),
    QPoint(-5, 14),
    QPoint(-4, -71),
    QPoint(4, -71)

QPoint minuteHand[4] = {
    QPoint(4, 14),
    QPoint(-4, 14),
    QPoint(-3, -89),
    QPoint(3, -89)

QPoint secondsHand[4] = {
   QPoint(1, 14),
   QPoint(-1, 14),
   QPoint(-1, -89),
   QPoint(1, -89)

hourColor = QColor(palette().color(QPalette.Text))
minuteColor = QColor(palette().color(QPalette.Text))
secondsColor = QColor(palette().color(QPalette.Accent))
side = qMin(width(), height())

使用QPainter在自定义小部件中绘制内容。画家可以用来在任何一个QPaintDevice上绘制,但通常与小部件一起使用,所以我们将小部件实例传递给画家的构造函数。

painter = QPainter(self)
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)

QPoint hourHand[4] = {
    QPoint(5, 14),
    QPoint(-5, 14),
    QPoint(-4, -71),
    QPoint(4, -71)

QPoint minuteHand[4] = {
    QPoint(4, 14),
    QPoint(-4, 14),
    QPoint(-3, -89),
    QPoint(3, -89)

QPoint secondsHand[4] = {
   QPoint(1, 14),
   QPoint(-1, 14),
   QPoint(-1, -89),
   QPoint(1, -89)

hourColor = QColor(palette().color(QPalette.Text))
minuteColor = QColor(palette().color(QPalette.Text))
secondsColor = QColor(palette().color(QPalette.Accent))
side = qMin(width(), height())
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)

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

绘图器负责处理画图事件过程中进行的所有转换,并确保一切正确绘制。让绘图器处理转换通常比手动计算只为了绘制自定义小部件的内容要容易。

../_images/analogclock-viewport.png

我们将画笔设置为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()
painter.save()

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

for i in range(0, 12):
    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 j in range(0, 60):
    painter.drawLine(92, 0, 96, 0)
    painter.rotate(6.0)

示例项目 @ code.qt.io