惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
人人都是产品经理
人人都是产品经理
Cisco Talos Blog
Cisco Talos Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
V
V2EX
博客园 - 三生石上(FineUI控件)
Martin Fowler
Martin Fowler
WordPress大学
WordPress大学
D
Docker
S
SegmentFault 最新的问题
博客园 - 聂微东
美团技术团队
Apple Machine Learning Research
Apple Machine Learning Research
月光博客
月光博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Last Week in AI
Last Week in AI
M
MIT News - Artificial intelligence
F
Fortinet All Blogs
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
The GitHub Blog
The GitHub Blog
GbyAI
GbyAI
L
LangChain Blog
Vercel News
Vercel News
博客园 - 叶小钗
MongoDB | Blog
MongoDB | Blog
Stack Overflow Blog
Stack Overflow Blog
H
Help Net Security
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The Cloudflare Blog
Engineering at Meta
Engineering at Meta
T
Threat Research - Cisco Blogs
T
Threatpost
Scott Helme
Scott Helme
T
Tailwind CSS Blog
Latest news
Latest news
Stack Overflow Blog
Stack Overflow Blog
Blog — PlanetScale
Blog — PlanetScale
The Register - Security
The Register - Security
罗磊的独立博客
P
Proofpoint News Feed
腾讯CDC
S
Schneier on Security
雷峰网
雷峰网
A
About on SuperTechFans
T
Tenable Blog
F
Full Disclosure
Cyberwarzone
Cyberwarzone
博客园_首页
有赞技术团队
有赞技术团队
K
Kaspersky official blog

文章列表

埃氮幂の命名空间 博客一周年记 P13020 [GESP202506 八级] 遍历计数 题解 Moonlark(Nonebot2+Python)命令式聊天机器人插件开发记录 使用Github Actions定制Github个人主页 P3435 [POI 2006] OKR-Periods of Words 题解 Qt Troubleshoot(三) Qt Troubleshoot(二) Qt Troubleshoot (一) 自己动手写三维引擎(一) 自己动手写二维物理引擎(一) 埃氮幂の命名空间 埃氮幂の命名空间 Markdown与外挂标签语法测试 Hexo博客0氪建站记录(下) Hexo博客0氪建站记录(中) Hexo博客0氪建站记录(上)
埃氮幂の命名空间
2024-12-08 · via

前言

我使用Qt制作了一个时钟应用,现在来分享一下实现的过程。

这个小项目包括了四个部分:

  • 时钟(显示当前时间)
  • 秒表(正向计时器,支持记录时间)
  • 倒计时
  • 闹钟

各种准备工作

导入

制作这个应用,我使用了一大堆QtWidgets组件。同时,还有至关重要的time模块。

PYTHON
from PySide6.QtWidgets import (QApplication,QWidget,QPushButton,QGroupBox,QLabel,
                                QTableWidget,QTableWidgetItem,QProgressBar,QScrollArea,
                                QDialog,QGridLayout,QVBoxLayout,QHBoxLayout,QSpinBox,QLineEdit,QComboBox,QSystemTrayIcon,QCheckBox)
from PySide6.QtGui import QIcon,QFont
from PySide6.QtCore import QObject,QTimer,Qt,QSize
import time

然后,建立一个QApplication,定义一些常量。

PYTHON
qapp=QApplication([])
icon=QIcon('../media/images/1.png') #窗口图标
widgetslist=[] #每个模式对应一个窗格
musicUrlList=['../media/audio/1.wav'] #闹铃音乐
musicNameList=['音乐1'] #音乐名称

menubuttonnamelist=['时钟','秒表','倒计时','闹钟']
menubuttonlist=[]
mode=0 #当前的模式

#下面这段永远放在最后
qapp.exec()

建立窗口

建立一个类,继承QtWidgets.QWidget

PYTHON
class Window(QWidget):
    def __init__(self,width,height,title,icon):
        super().__init__()
        self.setWindowTitle(QObject.tr(title))
        self.resize(width,height)
        self.setWindowIcon(icon)

实例化窗口对象。

PYTHON
mainWindow=Window(1000,600,'时钟',icon)
mainWindow.show()

功能窗格与切换按钮

我在窗口左侧做了四个按钮,分别对应四种功能。点击按钮就可以切换窗格的显示状态。

PYTHON
#定义按钮类
class MenuButton(QPushButton):
    def __init__(self,parent,num):
        super().__init__(parent)
        self.num=num
        self.clicked.connect(self.toggleMode)
    def toggleMode(self):
        global mode
        widgetslist[mode].hide()
        widgetslist[self.num].show()
        mode=self.num
#显示
for i in range(len(menubuttonnamelist)):
    button=MenuButton(mainWindow,i)
    button.setText(menubuttonnamelist[i])
    button.move(10,10+i*30)
    button.show()
    menubuttonlist.append(button)
    group=QGroupBox(parent=mainWindow,title=menubuttonnamelist[i])
    group.resize(890,580)
    group.move(100,10)
    widgetslist.append(group)

窗格尺寸自适应

当缩放窗口时,窗格的大小不会改变,因此我们要自动设置窗格的尺寸。

Qt中,QWidget被缩放大小就会自动执行resizeEvent函数,重写其内容即可。

PYTHON
# class Window(QWidget):
    def resizeEvent(self,event):
        size=self.size()
        for i in widgetslist:
            i.resize(size.width()-110,size.height()-20)

时间显示

这是整个项目最最简单的一部分。

组件

PYTHON
nowtime=time.strftime('%H:%M:%S')
labelTime=QLabel(widgetslist[0])
font=QFont('Arian',100,10)
labelTime.setFont(font)
labelTime.setText(nowtime)

更新显示

除了刷新时间,还设置了窗口自适应

PYTHON
def update():
    size1_1=widgetslist[0].size()
    nowtime=time.strftime('%H:%M:%S')
    labelTime.setText(nowtime)
    labelTime.move(size1_1.width()/2-size2.width()/2,size1_1.height()/2-size2.height()/2)

timer=QTimer()
timer.start(0.05)
timer.timeout.connect(update)

效果

image

正向计时器

这是第二个功能,需要实现一个可以开始、暂停的计时器,计时状态下可以记录,非计时状态下可以清零。

创建组件

实现它,需要一个文本显示,三个按钮,以及一个表格。

PYTHON
recorder=QLabel(widgetslist[1])
recorder.setText('00:00:00.000')
recorder.setFont(font)
startStopButton=QPushButton('开始',widgetslist[1])
recordButton=QPushButton('记录',widgetslist[1])
recordButton.setDisabled(True)
clearButton=QPushButton('清零',widgetslist[1])
clearButton.setDisabled(True)
recordTable=QTableWidget(0,3,widgetslist[1])
recordTable.setHorizontalHeaderLabels(['序次','间隔','总计'])
recordTable.verticalHeader().setVisible(False)
recordTable.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
recordTable.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)

变量

PYTHON
startTimeStamp=0
lastEndStamp=0
lastRecordStamp=0
running=False
timeDelta=0
  • startTimeStamp:计时开始的时间戳
  • lastEndStamp:上次暂停时的时间戳
  • lastRecordStamp:上次记录时的时间戳,用于计算记录间隔
  • running:是否正在计时
  • timeDelta:暂停后,将产生时间差,在计算当前时间是要减去这个时间差 ## 获取与格式化时间戳

量已经齐全,下一步就是获取当前的时间。获取时间可以使用下面简单的公式: $$ res=now-startTimeStamp-timeDelta $$ 这个公式要使用多次,所以封装成函数:

PYTHON
def getTime():
    global startTimeStamp,timeDelta
    return time.time()-startTimeStamp-timeDelta

获取到时间之后,将时间戳直接展示给用户肯定不现实,因此将其转换成时间字符串。同时,要传入一个参数,表示取整还是精确到小数点后三位。(后续倒计时和闹钟都不取后三位。)

PYTHON
def formatTime(data,round=False):
    return '{:0>2d}'.format(int(data//3600))+':'+'{:0>2d}'.format((int(data)%3600)//60)+':'+'{:0>2d}'.format(int(data)%60)+('.'+'{:0>3d}'.format(int((data%1)*1000)) if not round else '')

计时控制

刚刚我创建了三个按钮组件,现在在要对应地写着三个按钮被点击是执行的函数。

开始和停止

这个函数可以控制running变量,以及做出对应的操作:

  • 设置按钮位置;
  • 改变timeDelta
  • 设置按钮
PYTHON
def startStop():
    global running,startTimeStamp,lastEndStamp,timeDelta,lastRecordStamp
    if running:
        lastEndStamp=time.time()
        startStopButton.setText('开始')
    else:
        if startTimeStamp==0:
            startTimeStamp=lastEndStamp=time.time()
        timeDelta+=time.time()-lastEndStamp
        startStopButton.setText('停止')
    recordButton.setDisabled(running)
    running=not running
    clearButton.setDisabled(running)

记录

记录按钮被按下时,就在表格中插入一行,第一列是次数,第二列是间隔,第三列是总计时间。最后,还要设置这次记录的时间戳,以计算这一次到下一次记录的时间间隔。

PYTHON
def writeRecord():
    global lastRecordStamp
    recordTable.insertRow(0)
    recordTable.setItem(0,0,QTableWidgetItem(str(recordTable.rowCount())))
    if recordTable.rowCount()>1:
        recordTable.setItem(0,1,QTableWidgetItem(formatTime(getTime()-lastRecordStamp)))
    recordTable.setItem(0,2,QTableWidgetItem(formatTime(getTime())))
    lastRecordStamp=getTime()

清除记录

清除,要把各种变量都初始化,设置时间文本的文字也为初始的。因为表格控件没有提供清空函数,所以只能逐行删除直至表格为空为止。

PYTHON
def clearRecord():
    global startTimeStamp,lastEndStamp,timeDelta,recorder
    startTimeStamp=lastEndStamp=timeDelta=0
    recorder.setText('00:00:00.000')
    clearButton.setDisabled(True)
    while(recordTable.rowCount()>0):
        recordTable.removeRow(0)

连接按钮与函数

使用按钮控件.clicked.connect(函数名)就可以在按钮被点击时调用函数。

PYTHON
startStopButton.clicked.connect(startStop)  
recordButton.clicked.connect(writeRecord)     
clearButton.clicked.connect(clearRecord)

更新显示

窗口的自适应需要一些计算。

PYTHON
# def update():
    global startTimeStamp,timeDelta
    # size1_1=widgetslist[0].size()
    # nowtime=time.strftime('%H:%M:%S')
    # labelTime.setText(nowtime)
    # labelTime.move(size1_1.width()/2-size2.width()/2,size1_1.height()/2-size2.height()/2)
    
    size1_2=widgetslist[1].size()
    size2=labelTime.size()
    size3=recorder.size()
    size4=startStopButton.size()
    size5=recordButton.size()
    size6=clearButton.size()
    recorder.move(size1_2.width()/2-size3.width()/2,10)
    startStopButton.move(size1_2.width()/2-size5.width()/2-10-size4.width(),size1_2.height()-10-size4.height())
    recordButton.move(size1_2.width()/2-size5.width()/2,size1_2.height()-10-size5.height())
    clearButton.move(size1_2.width()/2+size5.width()/2+10,size1_2.height()-10-size6.height())
    recordTable.resize(size1_2.width()-200,size1_2.height()-320)
    size7=recordTable.size()
    recordTable.move(size1_2.width()/2-size7.width()/2,220)
    recordTable.setColumnWidth(0,40)
    recordTable.setColumnWidth(1,size7.width()/2-20)
    recordTable.setColumnWidth(2,size7.width()/2-20)
    if running:
        stampDelta=getTime()
        rec=formatTime(stampDelta)
        recorder.setText(rec)

效果

image

倒计时

倒计时不像正向计时那么简单,需要支持一些倒计时同时计时。这就需要建立一个倒计时类来完成这个任务。

创建组件

PYTHON
newCountDown=QPushButton('新建',widgetslist[2])
deleteCountDown=QPushButton('编辑',widgetslist[2])
countDownScrollArea=QScrollArea(widgetslist[2])
countDownScrollArea.move(10,30)
countDownScrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
countDownScrollAreaWidget=QWidget()
countDownScrollArea.setWidget(countDownScrollAreaWidget)
countDownList=[]

还是一些按钮,但是多了一个滚动区,这样可以滚动显示倒计时控件。

创建倒计时类

我将在QtWidgets.QGroupBox的基础上制作这个倒计时组件。

实例化

PYTHON
class CountDown(QGroupBox):
    def __init__(self,parent,timelength,title,music):
        super().__init__(parent)
        self.timelength=timelength
        self.titlestr=title
        self.music=music
        self.restTime=0
        self.startTimeStamp=0
        self.lastEndStamp=0
        self.timeDelta=0
        self.running=False

启动

实例化方法内不能穿件组件,所以单独建一个启动函数,在对象实例化之后使用。 按钮绑定的函数后续都会定义。

PYTHON
# class CountDown(QGroupBox):
    def launch(self):
        self.resize(400,300)
        self.restTime=self.timelength
        self.timelabel=QLabel(formatTime(self.restTime),self)
        font=QFont('Arian',40,10)
        self.timelabel.setFont(font)
        self.progressbar=QProgressBar(self)
        self.progressbar.setTextVisible(False)
        self.progressbar.setRange(0,10000)
        self.startStopButton=QPushButton('开始',self)
        self.startStopButton.clicked.connect(self.startStop)
        self.clearButton=QPushButton('还原',self)
        self.clearButton.clicked.connect(self.reset)
        self.clearButton.setDisabled(True)
        self.editButton=None
        self.deleteButton=None
        self.mode=True

其他函数

包括:

  • 切换模式(开始与清除/编辑与删除)
  • 开始停止
  • 更新显示
  • 清除
  • 编辑
  • 倒计时结束任务栏通知
  • 删除
PYTHON
# class CountDown(QGroupBox)
    def toggleMode(self):
        if self.mode:
            self.startStopButton.hide()
            self.clearButton.hide()
            self.editButton=QPushButton('编辑',self)
            self.editButton.clicked.connect(self.edit)
            self.editButton.show()
            self.deleteButton=QPushButton('删除',self)
            self.deleteButton.clicked.connect(self.deleteSelf)
            self.deleteButton.show()
        else:
            self.startStopButton.show()
            self.clearButton.show()
            self.editButton.hide()
            self.editButton=None
            self.deleteButton.hide()
            self.deleteButton=None
        self.mode=not self.mode
    def updating(self):
        self.setTitle(self.titlestr)
        selfsize=self.size()
        buttonsize=self.startStopButton.size()
        progressbarsize=self.progressbar.size()
        if abs(self.timelength-max(0,time.time()-self.startTimeStamp-self.timeDelta))<0.0005:
            self.showMessage()
            self.startStop()
            self.timelabel.setText('00:00:00:000')
        self.restTime=max(0,self.timelength-max(0,time.time()-self.startTimeStamp-self.timeDelta))
        if self.running:
            self.timelabel.setText(formatTime(self.restTime))
            self.progressbar.setValue(self.restTime/self.timelength*10000)
            self.progressbar.update()
        labelsize=self.timelabel.size()
        self.timelabel.move(selfsize.width()/2-labelsize.width()/2,selfsize.height()/2-labelsize.height()/2)
        self.progressbar.resize(selfsize.width()-20,20)
        self.progressbar.move(10,selfsize.height()-10-buttonsize.height()-10-progressbarsize.height())
        if self.mode:
            self.startStopButton.move(selfsize.width()/2-5-buttonsize.width(),selfsize.height()-10-buttonsize.height())
            self.clearButton.move(selfsize.width()/2+5,selfsize.height()-10-buttonsize.height())
        else:
            self.editButton.move(selfsize.width()/2-5-buttonsize.width(),selfsize.height()-10-buttonsize.height())
            self.deleteButton.move(selfsize.width()/2+5,selfsize.height()-10-buttonsize.height())
    def startStop(self):
        if self.running:
            self.lastEndStamp=time.time()
            self.startStopButton.setText('开始')
        else:
            if self.startTimeStamp==0:
                self.startTimeStamp=self.lastEndStamp=time.time()
            self.timeDelta+=time.time()-self.lastEndStamp
            self.startStopButton.setText('停止')
        self.running=not self.running
        self.clearButton.setDisabled(self.running)
    def reset(self):
        self.startTimeStamp=self.lastEndStamp=self.timeDelta=0
        self.timelabel.setText(formatTime(self.timelength))
        self.clearButton.setDisabled(True)
    def edit(self):
        editor=CountDownEdit(self.timelength,self.titlestr,self.music)
        editor.editData()
        self.timelength=editor.length
        self.restTime=self.timelength
        self.titlestr=editor.titlestr
        self.music=editor.music
    def showMessage(self):
        self.trayicon=QSystemTrayIcon()
        self.trayicon.setIcon(icon)
        self.trayicon.show()
        self.trayicon.showMessage('计时器到了','倒计时'+self.titlestr+'计时已完成。',icon)
    def deleteSelf(self):
        countDownList.remove(self)
        self.deleteLater()
        del self

倒计时编辑类

刚刚的edit()函数里,就有一个CountDownEdit。当用户编辑一个倒计时时,就要打开一个倒计时编辑窗口,因此,我单独再建一个类,打开一个QtWidgets.QDialog

PYTHON
class CountDownEdit(QDialog):
    def __init__(self,length,title,music):
        super().__init__()
        self.confirmed=False
        self.length=length
        self.titlestr=title
        self.music=music
    def editData(self):
        self.setWindowTitle('倒计时编辑')
        self.vlayout=QVBoxLayout()
        self.gridlayout=QGridLayout()
        self.hlayout=QHBoxLayout()
        self.gridlayout.setSpacing(10)
        self.label1=QLabel('时长')
        self.label2=QLabel('标题')
        self.label3=QLabel('音乐')
        self.content1=QHBoxLayout()
        self.content1_1=QSpinBox()
        self.content1_1.setMinimum(0)
        self.content1_1.setMaximum(99)
        self.content1_1.setValue(self.length//3600)
        self.content1_2=QLabel(':')
        self.content1_3=QSpinBox()
        self.content1_3.setMinimum(0)
        self.content1_3.setMaximum(59)
        self.content1_3.setValue((self.length%3600)//60)
        self.content1_4=QLabel(':')
        self.content1_5=QSpinBox()
        self.content1_5.setMinimum(0)
        self.content1_5.setMaximum(59)
        self.content1_5.setValue(self.length%60)
        self.content1.addWidget(self.content1_1)
        self.content1.addWidget(self.content1_2)
        self.content1.addWidget(self.content1_3)
        self.content1.addWidget(self.content1_4)
        self.content1.addWidget(self.content1_5)
        self.content2=QLineEdit()
        self.content2.setText(self.titlestr)
        self.content3=QComboBox()
        self.content3.addItems(musicNameList)
        self.content3.setCurrentIndex(self.music)
        self.gridlayout.addWidget(self.label1,0,0)
        self.gridlayout.addWidget(self.label2,1,0)
        self.gridlayout.addWidget(self.label3,2,0)
        self.gridlayout.addLayout(self.content1,0,1)
        self.gridlayout.addWidget(self.content2,1,1)
        self.gridlayout.addWidget(self.content3,2,1)
        self.confirm=QPushButton('确定')
        self.cancel=QPushButton('取消')
        self.confirm.clicked.connect(self.confirming)
        self.cancel.clicked.connect(self.close)
        self.hlayout.addWidget(self.confirm)
        self.hlayout.addWidget(self.cancel)
        self.vlayout.addLayout(self.gridlayout)
        self.vlayout.addLayout(self.hlayout)
        self.setLayout(self.vlayout)
        self.show()
        self.exec()
    def confirming(self):
        self.confirmed=True
        self.length=self.content1_1.value()*3600+self.content1_3.value()*60+self.content1_5.value()
        self.titlestr=self.content2.text()
        self.music=self.content3.currentIndex()
        self.close()

按钮绑定函数

接着,给刚才两个按钮创建并绑定函数。

PYTHON
def onNewCountDown():
    global countDownList
    countDownEdit=CountDownEdit(0,'未命名',0)
    countDownEdit.editData()
    if countDownEdit.confirmed:
        countDown=CountDown(countDownScrollAreaWidget,countDownEdit.length,countDownEdit.titlestr,countDownEdit.music)
        countDown.launch()
        countDown.show()
        countDownList.append(countDown)
def onDeleteCountDown():
    global countDownList
    for i in countDownList:
        i.toggleMode()
newCountDown.clicked.connect(onNewCountDown)
deleteCountDown.clicked.connect(onDeleteCountDown)

更新显示

没错,又要更新

PYTHON
# def update():
    size1_3=widgetslist[2].size()
    size8=newCountDown.size()
    size9=deleteCountDown.size()
    newCountDown.move(size1_3.width()/2-5-size8.width(),size1_3.height()-10-size8.height())
    deleteCountDown.move(size1_3.width()/2+5,size1_3.height()-10-size9.height())
    countDownScrollArea.resize(size1_3.width()-20,size1_3.height()-50-size8.height())
    size10=countDownScrollArea.size()
    size11=QSize(400,300)
    numPerRow=(size10.width()-size11.width())//(size11.width()+10)+1
    widthPerRow=numPerRow*size11.width()+(numPerRow-1)*10
    countDownScrollAreaWidget.resize(size10.width(),10+((len(countDownList))//numPerRow+1)*(10+size11.height()))
    for i in range(len(countDownList)):
        countDownList[i].move(size10.width()/2-widthPerRow/2+(i%numPerRow)*(10+size11.width()),10+(i//numPerRow)*(10+size11.height()))
        countDownList[i].updating()

文件读写

这个时钟应用还是支持保存和读取计时器的。实现就是文件读写。

读入

这个是程序每次打开就执行的。

PYTHON
# deleteCountDown.clicked.connect(onDeleteCountDown)
with open('../data/countdown.txt','r',encoding='utf-8') as f:
    for i in f.readlines():
        timelength,title,music=i.split(':/:')
        countDown=CountDown(countDownScrollAreaWidget,int(timelength),title,int(music))
        countDown.launch()
        countDown.show()
        countDownList.append(countDown)

# def update():

保存

这个就是程序每次关闭执行的。 让我们回到程序一开始:

PYTHON
# class Window(QWidget):
    def closeEvent(self,event):
        global countDownList,alarmList
        with open('../data/countdown.txt','w',encoding='utf-8') as f:
            for i in countDownList:
                f.write(str(i.timelength)+':/:'+i.titlestr+':/:'+str(i.music)+'\n')

效果

image

闹钟

闹钟的程序,就是把倒计时复制了一遍改的,所以直接给个代码

主要代码

PYTHON
newAlarm=QPushButton('新建',widgetslist[3])
deleteAlarm=QPushButton('编辑',widgetslist[3])
alarmScrollArea=QScrollArea(widgetslist[3])
alarmScrollArea.move(10,30)
alarmScrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
alarmScrollAreaWidget=QWidget()
alarmScrollArea.setWidget(alarmScrollAreaWidget)
alarmList=[]
class Alarm(QGroupBox):
    def __init__(self,parent,timelength,title,music,repeat,description,running):
        super().__init__(parent)
        self.timelength=timelength
        self.titlestr=title
        self.music=music
        self.running=running
        self.repeat=repeat
        self.description=description
    def launch(self):
        self.resize(400,300)
        self.restTime=self.timelength
        self.timelabel=QLabel(formatTime(self.restTime,round=True),self)
        font=QFont('Arian',40,10)
        self.timelabel.setFont(font)
        self.descriptionLabel=QLabel(self)
        self.descriptionLabel.setWordWrap(True)
        self.descriptionLabel.setAlignment(Qt.AlignmentFlag.AlignTop|Qt.AlignmentFlag.AlignLeft)
        self.repetitionLabel=QLabel(self)
        self.startStopButton=QPushButton('关闭' if self.running else '开启',self)
        self.startStopButton.clicked.connect(self.startStop)
        self.editButton=None
        self.deleteButton=None
        self.mode=True
    def toggleMode(self):
        if self.mode:
            self.startStopButton.hide()
            self.editButton=QPushButton('编辑',self)
            self.editButton.clicked.connect(self.edit)
            self.editButton.show()
            self.deleteButton=QPushButton('删除',self)
            self.deleteButton.clicked.connect(self.deleteSelf)
            self.deleteButton.show()
        else:
            self.startStopButton.show()
            self.editButton.hide()
            self.editButton=None
            self.deleteButton.hide()
            self.deleteButton=None
        self.mode=not self.mode
    def updating(self):
        hours,minutes,seconds=list(map(int,time.strftime('%H %M %S').split()))
        if abs(self.timelength-(hours*3600+minutes*60+seconds))<0.0005 and self.running:
            self.showMessage()
            self.startStop()
        self.setTitle(self.titlestr)
        selfsize=self.size()
        buttonsize=self.startStopButton.size()
        self.timelabel.setText(formatTime(self.timelength,round=True))
        labelsize=self.timelabel.size()
        self.timelabel.move(selfsize.width()/2-labelsize.width()/2,30)

        self.descriptionLabel.setText(self.description)
        self.descriptionLabel.setFixedSize(selfsize.width()-10,200)
        labelsize=self.descriptionLabel.size()
        self.descriptionLabel.move(selfsize.width()/2-labelsize.width()/2,150)
        text=['星期一','星期二','星期三','星期四','星期五','星期六','星期日']
        list1=[]
        for i in range(7):
            if self.repeat[i]:
                list1.append(text[i])
        self.repetitionLabel.setText(','.join(list1))
        self.repetitionLabel.setFixedSize(selfsize.width()-10,20)
        labelsize=self.repetitionLabel.size()
        self.repetitionLabel.move(selfsize.width()/2-labelsize.width()/2,100)
        if self.mode:
            self.startStopButton.move(selfsize.width()/2-buttonsize.width()/2,selfsize.height()-10-buttonsize.height())
        else:
            self.editButton.move(selfsize.width()/2-5-buttonsize.width(),selfsize.height()-10-buttonsize.height())
            self.deleteButton.move(selfsize.width()/2+5,selfsize.height()-10-buttonsize.height())
    def startStop(self):
        if self.running:
            self.startStopButton.setText('开启')
        else:
            self.startStopButton.setText('关闭')
        self.running=not self.running
    def edit(self):
        editor=AlarmEdit(self.timelength,self.titlestr,self.music,self.repeat,self.description,self.running)
        editor.editData()
        self.timelength=editor.length
        self.restTime=self.timelength
        self.titlestr=editor.titlestr
        self.music=editor.music
        self.repeat=editor.repeat
        self.description=editor.description
        self.running=editor.running
    def showMessage(self):
        self.trayicon=QSystemTrayIcon()
        self.trayicon.setIcon(icon)
        self.trayicon.show()
        self.trayicon.showMessage('闹钟'+formatTime(self.timelength,round=True),self.description,icon)
    def deleteSelf(self):
        alarmList.remove(self)
        self.deleteLater()
        del self
class AlarmEdit(QDialog):
    def __init__(self,length,title,music,repeat,description,running):
        super().__init__()
        self.confirmed=False
        self.length=length
        self.titlestr=title
        self.music=music
        self.repeat=repeat
        self.description=description
        self.running=running
    def editData(self):
        self.setWindowTitle('闹钟编辑')
        self.vlayout=QVBoxLayout()
        self.gridlayout=QGridLayout()
        self.hlayout=QHBoxLayout()
        self.gridlayout.setSpacing(10)
        self.label1=QLabel('时间')
        self.label2=QLabel('标题')
        self.label3=QLabel('音乐')
        self.label4=QLabel('描述')
        self.label5=QLabel('重复')
        self.content1=QHBoxLayout()
        self.content1_1=QSpinBox()
        self.content1_1.setMinimum(0)
        self.content1_1.setMaximum(23)
        self.content1_1.setValue(self.length//3600)
        self.content1_2=QLabel(':')
        self.content1_3=QSpinBox()
        self.content1_3.setMinimum(0)
        self.content1_3.setMaximum(59)
        self.content1_3.setValue((self.length%3600)//60)
        self.content1_4=QLabel(':')
        self.content1_5=QSpinBox()
        self.content1_5.setMinimum(0)
        self.content1_5.setMaximum(59)
        self.content1_5.setValue(self.length%60)
        self.content1.addWidget(self.content1_1)
        self.content1.addWidget(self.content1_2)
        self.content1.addWidget(self.content1_3)
        self.content1.addWidget(self.content1_4)
        self.content1.addWidget(self.content1_5)
        self.content2=QLineEdit()
        self.content2.setText(self.titlestr)
        self.content3=QComboBox()
        self.content3.addItems(musicNameList)
        self.content3.setCurrentIndex(self.music)
        self.content4=QLineEdit()
        self.content4.setText(self.description)
        self.content5=QVBoxLayout()
        text=['星期一','星期二','星期三','星期四','星期五','星期六','星期日']
        self.content5s=[QCheckBox(text=i) for i in text]
        for i in self.content5s:
            self.content5.addWidget(i)
            p=self.content5s.index(i)
            i.setChecked(self.repeat[p])
        self.gridlayout.addWidget(self.label1,0,0)
        self.gridlayout.addWidget(self.label2,1,0)
        self.gridlayout.addWidget(self.label3,2,0)
        self.gridlayout.addWidget(self.label4,3,0)
        self.gridlayout.addWidget(self.label5,4,0)
        self.gridlayout.addLayout(self.content1,0,1)
        self.gridlayout.addWidget(self.content2,1,1)
        self.gridlayout.addWidget(self.content3,2,1)
        self.gridlayout.addWidget(self.content4,3,1)
        self.gridlayout.addLayout(self.content5,4,1)
        self.confirm=QPushButton('确定')
        self.cancel=QPushButton('取消')
        self.confirm.clicked.connect(self.confirming)
        self.cancel.clicked.connect(self.close)
        self.hlayout.addWidget(self.confirm)
        self.hlayout.addWidget(self.cancel)
        self.vlayout.addLayout(self.gridlayout)
        self.vlayout.addLayout(self.hlayout)
        self.setLayout(self.vlayout)
        self.show()
        self.exec()
    def setRepeat(self,p):
        self.repeat[p]=not self.repeat[p]
    def confirming(self):
        self.confirmed=True
        self.length=self.content1_1.value()*3600+self.content1_3.value()*60+self.content1_5.value()
        self.titlestr=self.content2.text()
        self.music=self.content3.currentIndex()
        self.description=self.content4.text()
        self.repeat=[i.isChecked() for i in self.content5s]
        self.close()
def onNewAlarm():
    global alarmList
    alarmEdit=AlarmEdit(0,'未命名',0,[False,False,False,False,False,False,False],'',True)
    alarmEdit.editData()
    if alarmEdit.confirmed:
        alarm=Alarm(alarmScrollAreaWidget,alarmEdit.length,alarmEdit.titlestr,alarmEdit.music,alarmEdit.repeat,alarmEdit.description,alarmEdit.running)
        alarm.launch()
        alarm.show()
        alarmList.append(alarm)
def onDeleteAlarm():
    global alarmList
    for i in alarmList:
        i.toggleMode()
newAlarm.clicked.connect(onNewAlarm)
deleteAlarm.clicked.connect(onDeleteAlarm)
with open('../data/alarm.txt','r',encoding='utf-8') as f:
    for i in f.readlines():
        timelength,title,music,repeat1,repeat2,repeat3,repeat4,repeat5,repeat6,repeat7,description,running1=i.split(':/:')
        alarm=Alarm(alarmScrollAreaWidget,int(timelength),title,int(music),list(map(bool,map(int,[repeat1,repeat2,repeat3,repeat4,repeat5,repeat6,repeat7]))),description,bool(int(running1)))
        alarm.launch()
        alarm.show()
        alarmList.append(alarm)

其他

别忘了还需要更新显示,关闭时的文件写入

PYTHON
# update():
    size8=newAlarm.size()
    size9=deleteAlarm.size()
    newAlarm.move(size1_3.width()/2-5-size8.width(),size1_3.height()-10-size8.height())
    deleteAlarm.move(size1_3.width()/2+5,size1_3.height()-10-size9.height())
    alarmScrollArea.resize(size1_3.width()-20,size1_3.height()-50-size8.height())
    size10=alarmScrollArea.size()
    size11=QSize(400,300)
    numPerRow=(size10.width()-size11.width())//(size11.width()+10)+1
    widthPerRow=numPerRow*size11.width()+(numPerRow-1)*10
    alarmScrollAreaWidget.resize(size10.width(),10+((len(alarmList))//numPerRow+1)*(10+size11.height()))
    for i in range(len(alarmList)):
        alarmList[i].move(size10.width()/2-widthPerRow/2+(i%numPerRow)*(10+size11.width()),10+(i//numPerRow)*(10+size11.height()))
        alarmList[i].updating()
PYTHON
# class Window(QWidget):
    # def closeEvent(self,event):
        with open('../data/alarm.txt','w',encoding='utf-8') as f:
            for i in alarmList:
                f.write(str(i.timelength)+':/:'+i.titlestr+':/:'+str(i.music)+':/:'+':/:'.join(map(str,map(int,i.repeat)))+':/:'+i.description+':/:'+str(int(i.running))+'\n')

效果

image

结语

这个项目,虽然简单,但是代码有亿点点多。所以写这篇文章也写了很多。希望这个项目可以对学习Qt的小伙伴起到一些引导性作用。 其实,写这么长真的很不容易。 拜拜!See you next time!