【PyQt5】 用painter画一个时钟

使用pyqt5里的painter绘图函数画了一个时钟。使用库pyqt5、pyautogui。

代码编写

首先去walnutpi.com-pyqt5教程这里复制了一个基本代码,等下在他的基础上进行修改

qt技巧-坐标系平移

translate函数可以对坐标系原点进行平移。默认状态下左上角是(0,0),这里将窗口正中间作为(0,0)点,方便后续围绕窗口中心来绘制图形

        painter.translate( self.width()/2, self.height()/2) #坐标系平移

qt技巧-坐标系缩放

scale函数,可以设置一个坐标点所对应的实际屏幕像素点数量。

比如我调用scale(10,50)函数后,坐标(0,0)跟坐标(0,1)之间就相差了50个像素点距离。坐标(0,0)跟坐标(1,0)之间就相差了10个像素点距离。

我使用这段代码,后面如果使用画线函数绘制一条长度100的线,无论窗口大小怎么变动,这条线都会是窗口较短那条边的一半

        short_side = min( self.width(), self.height())
        painter.scale(short_side / 200.0, short_side / 200.0)

绘制指针

拿时钟绘制举例,大概流程如下

  1. save函数保存painter的状态
  2. rotate函数对坐标系进行整体旋转
  3. drawConvexPolygon函数来绘制多边形
  4. restore函数将painter重置回save时的状态,已经绘制好的图形不会变,这里是为了将坐标系回正。

首先定义指针的各个坐标点

# 时针形状
NEEDLE_HOUR_heigh = 20
NEEDLE_HOUR_width = 4
NEEDLE_HOUR_heigh_anoth = 6
Pointr_hour=[
    QPoint(int(NEEDLE_HOUR_width/2), NEEDLE_HOUR_heigh_anoth),
    QPoint(int(-NEEDLE_HOUR_width/2), NEEDLE_HOUR_heigh_anoth),
    QPoint(int(-NEEDLE_HOUR_width/2), -NEEDLE_HOUR_heigh),
    QPoint(0, -NEEDLE_HOUR_heigh-6),
    QPoint(int(NEEDLE_HOUR_width/2), -NEEDLE_HOUR_heigh),
]

然后在加入paintEvent内加入绘制部分

        # 时针
        painter.setPen(Qt.PenStyle.NoPen) 
        painter.setBrush( Color_hour_pointer )
        painter.save()
        painter.rotate(30.0 * (  datetime.now().hour  + datetime.now().minute / 60 ))
        painter.drawConvexPolygon(Pointr_hour)
        painter.restore()

绘制刻度线

跟绘制表针的套路一样,只是这里换成了用drawline

  1. save函数保存painter的状态
  2. rotate函数对坐标系进行旋转
  3. drawConvexPolygon函数来绘制多边形
  4. restore函数将painter重置回save时的状态
        # 表盘-分钟线
        painter.save()
        painter.setPen(Color_minute_line)
        for i in range(0, 360, 6):
            if i%30 !=0 :
                painter.drawLine(95, 0, 96, 0)
            painter.rotate(6) 
        painter.restore()

绘制表盘上的数字

  1. 使用setFont来设置字体大小
  2. 调用math库的sin cos函数,计算文本要放置的坐标
  3. 使用drawText函数来绘制文本
        # 表盘-数字
        painter.setPen( Color_hour_line )
        painter.setBrush( Color_hour_line )  
        tmp_font = QFont(painter.font())
        tmp_font.setPointSize(int(short_side/50))
        painter.setFont(tmp_font)
        size = painter.font().pointSize()
        for i in range(1, 13):
            if i == datetime.now().hour%12:
                painter.setPen( Color_hour_pointer )
                painter.setBrush( Color_hour_pointer )
            else :
                painter.setPen( Color_hour_line )
                painter.setBrush( Color_hour_line )
                
            mx =  80 * math.sin(math.radians(i*30)) - size / 2
            my =  80 * math.cos(math.radians(i*30)) - size / 2
            mx = int( mx )
            my = int( -my )
          
            # painter.drawLine(0, 0, mx, my)
            painter.drawText(mx, my, str(i))


定时触发重绘事件

重绘事件只在窗口初始化,窗口大小改变之类的关键点才会自动触发。时钟嘛,肯定要让他一秒触发一次。

# 定时触发重绘
window.timer = QTimer()  # 定时器
window.timer.timeout.connect(window.update)
window.timer.start(1000)  # 每1s 更新一次

点击窗口任意位置退出

我希望这个窗口全屏显示,但不想做丑死的退出按钮。还好窗口提供了一个鼠标点击事件,在这里面调用退出即可。

就像paintEvent一样,在window的类里面定义即可

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.close()

程序启动后将鼠标移动到右下角

鼠标太丑了,全屏黑色背景下太扎眼。这里调用一个库来强制移动鼠标位置。

import pyautogui
screen_width, screen_height = pyautogui.size()
pyautogui.moveTo(screen_width-1, screen_height-1)

最终代码

# -*- coding: utf-8 -*-

# pyQT5 For WalnutPi

from PyQt5 import QtCore, QtGui, QtWidgets

from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import QWidget
import math
from datetime import datetime
import pyautogui


Color_hour_line = QColor(100, 100, 100, 200)
Color_hour_pointer = QColor(255, 120, 0, 200)
Color_minute_line = QColor(200, 200, 200, 150)
Color_minute_pointer = QColor(0, 255, 0, 200)
Color_sec_pointer = QColor(200, 200, 0, 150)
Color_center = QColor(50, 50, 100, 255)

# 时针形状
NEEDLE_HOUR_heigh = 20
NEEDLE_HOUR_width = 4
NEEDLE_HOUR_heigh_anoth = 6
Pointr_hour=[
    QPoint(int(NEEDLE_HOUR_width/2), NEEDLE_HOUR_heigh_anoth),
    QPoint(int(-NEEDLE_HOUR_width/2), NEEDLE_HOUR_heigh_anoth),
    QPoint(int(-NEEDLE_HOUR_width/2), -NEEDLE_HOUR_heigh),
    QPoint(0, -NEEDLE_HOUR_heigh-6),
    QPoint(int(NEEDLE_HOUR_width/2), -NEEDLE_HOUR_heigh),
]

# 时针形状
NEEDLE_minute_heigh = 60
NEEDLE_minute_width = 4
NEEDLE_minute_heigh_anoth = 3
Pointr_minute=[
    QPoint(int(NEEDLE_minute_width/2), NEEDLE_minute_heigh_anoth),
    QPoint(int(-NEEDLE_minute_width/2), NEEDLE_minute_heigh_anoth),
    QPoint(int(-NEEDLE_minute_width/2), -NEEDLE_minute_heigh),
    QPoint(0, -NEEDLE_minute_heigh-6),
    QPoint(int(NEEDLE_minute_width/2), -NEEDLE_minute_heigh),
]

# 秒针形状
NEEDLE_sec_heigh = 97
NEEDLE_sec_width = 2
NEEDLE_sec_heigh_anoth = 6
Pointr_sec=[
    QPoint(int(NEEDLE_sec_width/2), NEEDLE_sec_heigh_anoth),
    QPoint(int(-NEEDLE_sec_width/2), NEEDLE_sec_heigh_anoth),
    QPoint(int(-NEEDLE_sec_width/2), -NEEDLE_sec_heigh),
    QPoint(int(NEEDLE_sec_width/2), -NEEDLE_sec_heigh),
]



class Window(QWidget):
    
    def __init__(self):
        super().__init__() #同时执行父对象QWidget的初始化程序
        self.setWindowTitle("WalnutPi Paint") # 设置窗口标题
        self.resize(480,320) # 设置窗口大小
        
        #窗口背景颜色设置
        self.setObjectName("Paint_Window")
        self.setStyleSheet("#Paint_Window{background-color: black}") #黑色
    
    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.close()

    def paintEvent(self,event):
        painter = QPainter(self)

        painter.translate( self.width()/2, self.height()/2) #坐标系平移
        short_side = min( self.width(), self.height())
        painter.scale(short_side / 200.0, short_side / 200.0)
        painter.setRenderHint(QPainter.RenderHint.Antialiasing)

        # 表盘-数字
        painter.setPen( Color_hour_line )
        painter.setBrush( Color_hour_line )  
        tmp_font = QFont(painter.font())
        tmp_font.setPointSize(int(short_side/50))
        painter.setFont(tmp_font)
        size = painter.font().pointSize()
        for i in range(1, 13):
            if i == datetime.now().hour%12:
                painter.setPen( Color_hour_pointer )
                painter.setBrush( Color_hour_pointer )
            else :
                painter.setPen( Color_hour_line )
                painter.setBrush( Color_hour_line )
                
            mx =  80 * math.sin(math.radians(i*30)) - size / 2
            my =  80 * math.cos(math.radians(i*30)) - size / 2
            mx = int( mx )
            my = int( -my )
          
            # painter.drawLine(0, 0, mx, my)
            painter.drawText(mx, my, str(i))


    
        # 时针
        painter.setPen(Qt.PenStyle.NoPen)
        painter.setBrush( Color_hour_pointer )
        painter.save()
        painter.rotate(30.0 * (  datetime.now().hour  + datetime.now().minute / 60 ))
        painter.drawConvexPolygon(Pointr_hour)
        painter.restore()

        # 分针
        painter.setPen(Qt.PenStyle.NoPen)
        painter.setBrush( Color_minute_pointer )
        painter.save()
        painter.rotate(6 * datetime.now().minute)
        painter.drawConvexPolygon(Pointr_minute)
        painter.restore()

        # 秒针
        painter.setPen(Qt.PenStyle.NoPen)
        painter.setBrush( Color_sec_pointer )
        
        painter.save()
        painter.rotate(6 * datetime.now().second)
        painter.drawConvexPolygon(Pointr_sec)
        painter.restore()

        # 中心圆
        painter.setPen(Color_center)
        painter.setBrush(Color_center)
        CENTER_r = 2
        painter.drawEllipse(-CENTER_r, -CENTER_r, CENTER_r*2, CENTER_r*2)


        # 表盘-小时线
        painter.save()
        painter.setPen(Color_hour_line)
        painter.rotate(-90)
        for i in range(0, 360, 30):
            painter.drawLine(88, 0, 96, 0)
            painter.rotate(30)
        painter.restore()

        # 表盘-当前小时线
        painter.save()
        painter.setPen( Color_hour_pointer )
        painter.rotate(datetime.now().hour % 12 * 30 - 90 )
        painter.drawLine(88, 0, 96, 0)
        painter.restore()

        # 表盘-分钟线
        painter.save()
        painter.setPen(Color_minute_line)
        for i in range(0, 360, 6):
            if i%30 !=0 :
                painter.drawLine(95, 0, 96, 0)
            painter.rotate(6) 
        painter.restore()

        # 表盘-当前分钟线
        painter.save()
        painter.setPen( Color_minute_pointer )
        painter.rotate(datetime.now().minute % 60 * 6 - 90 )
        painter.drawLine(90, 0, 96, 0)
        painter.restore()

        
        
#################
#   主程序代码   #
#################
import sys

#【可选代码】允许Thonny远程运行
import os
os.environ["DISPLAY"] = ":0.0"

#【可选代码】解决2K以上分辨率显示器显示缺失问题
QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling)

#主程序入口,构建窗口并显示
app = QtWidgets.QApplication(sys.argv)
window = Window() #构建窗口对象
# window.show() #显示窗口
window.showFullScreen() #全屏显示窗口

#【建议代码】允许终端通过ctrl+c中断窗口,方便调试
import signal
signal.signal(signal.SIGINT, signal.SIG_DFL)
timer = QtCore.QTimer()
timer.start(100)  # You may change this if you wish.
timer.timeout.connect(lambda: None)  # Let the interpreter run each 100 ms

# 定时触发重绘
window.timer = QTimer()  # 定时器
window.timer.timeout.connect(window.update)
window.timer.start(1000)  # 每1s 更新一次

# 将鼠标强制移动到右下角
screen_width, screen_height = pyautogui.size()
pyautogui.moveTo(screen_width-1, screen_height-1)


sys.exit(app.exec_()) #程序关闭时退出进程

2 Likes

新手友好贴

1 Like