一个python小作业没想到引出这么多麻烦。要求是实现类似IPHONE手机得秒表计时,精确到小数点后两位(毫秒级),大概就是这种形式00:00.00,由分到毫秒。
看到有得人用wx.Timer设置定时器,但都是以秒为最小单位得,参数设置为毫秒时,如timer.Start(10),10ms时就会发现计时器跑一分钟比正常时间慢了6s左右。
又想到用time.time方法来,但是使用了while循环,页面刷新太快导致wx主界面卡死了,然后就一筹莫展。
最后用了线程,把time.time方法放到线程里执行,只传回参数来更新页面。这样初步是可以了,但是还有几个后续问题。
一个是还是会慢一些,大概会慢6s,但是可以通过修改代码解决,干脆不用tiem.time方法,只用while循环加sleep,每次时间加0.01s,但是sleep(0.009),少睡的0.001用于跑程序,因为1分钟大概误差6s,也就是6000ms,1分钟,程序更新时间的代码运行60000ms/10ms次,也就是6000次,6000次误差6000ms,每次误差1ms,也就是0.001s,所以sleep(0.01-0.001)。这样就弥补了误差,和正常时间无差。代码中有用来测试误差的备注代码,显示01:00.00时,时间戳结果为59.9820001125,这是不一定的,有时为59.99等,还是不错的精度(关于时间最关心的就是精度了,暂时最精确的时间是原子钟,为什么说暂时呢,因为原子钟过几十亿年也会产生1s误差,但是已经是人类能得到的最精确的时间了,机器会存在误差,关于时间精度的极限:没有最准,只有更准,这是我个人对于时间精度的理解)
第二个是使用wx.statictext控件来更新时间的话会导致有闪烁,具体原因不太清楚,可能是控件界面更新太快了,试了下几种解决方法后干脆改成led显示,led数字也是wx自带的,解决了这个问题。
代码中有一些按钮的转换逻辑,以及添加了背景图片,以及利用sqlite3保存了数据,判断了计时上限,判断了保存状态等等等这些细节,可以自己看情况修改。
下面直接上代码,基本都有注释,但是使用线程传递数据这点需要自己好好理解。
下面展示完整代码。
https://blog.youkuaiyun.com/weixin_43141119/article/details/105239008 这个是不使用线程的其他做法 欢迎移步
(关于时间上的区别 我的做法显示的是连续的时间 但是会损失了精度 这个做法时间精确 但是显示的却是不连续的时间)
#coding = utf-8
import wx
import wx.gizmos as gizmos
import time
import threading
import os
import sqlite3
#返回一个唯一的事件类型ID赋值给EVT_RESULT_ID
EVT_RESULT_ID = wx.NewId()
#自定义一个事件类型,用于线程向主界面传递数据
class ResultEvent(wx.PyEvent):
def __init__(self, data):
wx.PyEvent.__init__(self)
self.SetEventType(EVT_RESULT_ID)
self.data = data
#自定义事件绑定函数,类似wx自带的wx.EVT_BUTTON
def EVT_RESULT(win, func):
win.Connect(-1, -1, EVT_RESULT_ID, func)
#定义线程类
class WorkerThread(threading.Thread):
def __init__(self, notify_window):
threading.Thread.__init__(self)
self._notify_window = notify_window
self.resume_reset = 0#用于区分继续和重启线程
self.__flag = threading.Event()#控制线程暂停或启动
self.start()
#启动线程,循环计时
def run(self):
t = 0
#测试一分钟误差代码
#starttime = time.time()
while True:
t += 10
mm = int((t/1000) / 60)
ss = int((t/1000) % 60)
xx = int((t%1000) / 10)
text_time = "{:02d}:{:02d}.{:02d}".format(mm,ss,xx)
#if mm == 01 and ss == 00 and xx == 00:
#测试一分钟误差代码
#print (time.time() - starttime)
#self.__flag.clear()
if mm == 59 and ss == 59 and xx == 99:
self.__flag.clear()
#向MainFrame传递时间
wx.PostEvent(self._notify_window, ResultEvent(text_time))
time.sleep(0.009)
#重置秒表
if self.resume_reset == 1:
wx.PostEvent(self._notify_window, ResultEvent("00:00.00"))
t = 0.0
self.__flag.clear()#重置后,暂停线程
self.__flag.wait()
#暂停线程
def pause(self):
self.__flag.clear()
#继续线程
def resume(self):
self.resume_reset = 0
self.__flag.set()
#重启线程
def reset(self):
self.resume_reset = 1
self.__flag.set()
#主界面类
class MainFrame(wx.Frame):
def __init__(self, parent, id):
#定义界面大小
wx.Frame.__init__(self, parent, -1, 'StopWatch', size=(520,535))
self.SetMaxSize((520,535))
self.SetMinSize((400,400))
#设置背景图
panel = wx.Panel(self)
temp=wx.Image("clock.jpg")
self.bgImage=wx.StaticBitmap(panel, -1, wx.Bitmap(temp))
#LED时间显示
self.stime = gizmos.LEDNumberCtrl(panel, -1,wx.DefaultPosition, (300,75))
st = "{:02d}:{:02d}.{:02d}".format(0, 0, 0)
self.stime.SetValue(st)
self.stime.SetBackgroundColour("black")
self.stime.SetForegroundColour("red")
#计次显示
self.count_time = wx.TextCtrl(self.bgImage, 0, value="", style=wx.ALIGN_LEFT|wx.TE_READONLY|wx.TE_MULTILINE)
self.count_time.SetForegroundColour("Black")
self.count_time.SetBackgroundColour("White")
font = wx.Font(15, wx.DECORATIVE, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, 'Monaco')
self.count_time.SetFont(font)
#设置按钮
self.startButton = wx.Button(self.bgImage, -1, u'启动')
self.resetButton = wx.Button(self.bgImage, -1, u'重置')
self.saveButton = wx.Button(self.bgImage, -1, u'保存')
self.checkButton = wx.Button(self.bgImage, -1, u'查看')
#绑定事件
EVT_RESULT(self,self.OnResult)#自定义事件
self.Bind(wx.EVT_BUTTON, self.OnStart, self.startButton)
self.Bind(wx.EVT_BUTTON, self.OnReset, self.resetButton)
self.Bind(wx.EVT_BUTTON, self.OnSave, self.saveButton)
self.Bind(wx.EVT_BUTTON, self.OnCheck, self.checkButton)
#界面布局
bsizer_top = wx.BoxSizer(wx.VERTICAL)
bsizer_center = wx.BoxSizer(wx.HORIZONTAL)
bsizer_bottom = wx.BoxSizer(wx.VERTICAL)
#top
bsizer_top.Add(self.stime, proportion=0, flag=wx.ALIGN_CENTER, border = 5 )
#center
bsizer_center.Add(self.startButton,proportion=1,flag=wx.ALIGN_LEFT|wx.ALL,border =10)
bsizer_center.Add(self.resetButton,proportion=1,flag=wx.ALIGN_CENTER|wx.ALL,border =10)
bsizer_center.Add(self.saveButton,proportion=1,flag=wx.ALIGN_RIGHT|wx.ALL,border =10)
bsizer_center.Add(self.checkButton,proportion=1,flag=wx.ALIGN_RIGHT|wx.ALL,border =10)
#bottom
bsizer_bottom.Add(self.count_time,proportion=1 ,flag = wx.EXPAND|wx.ALL,border =5 )
#all
bsizer_all = wx.BoxSizer(wx.VERTICAL)
bsizer_all.Add(bsizer_top, proportion=0, flag=wx.EXPAND|wx.ALL, border=5)
bsizer_all.Add(bsizer_center, proportion=0, flag=wx.EXPAND|wx.ALL, border=5)
bsizer_all.Add(bsizer_bottom, proportion=1, flag=wx.EXPAND|wx.ALL, border=5)
panel.SetSizer(bsizer_all)
#设置透明度
self.SetTransparent(225)
#用于转换按钮显示
self.stop_start = 0
#用于判断是否保存
self.saved = 0
#响应启动,暂停按钮
def OnStart(self, event):
if self.stop_start == 0:
self.worker = WorkerThread(self)
self.stop_start = 1
if self.stop_start == 1:
self.startButton.SetLabel(u"停止")
self.resetButton.SetLabel(u"计次")
self.worker.resume()
self.stop_start = 2
else:
self.startButton.SetLabel(u"启动")
self.resetButton.SetLabel(u"重置")
self.worker.pause()
self.stop_start = 1
#响应重置,计次按钮
def OnReset(self, event):
if self.stop_start == 2:
self.count_time.AppendText("{}\r\n".format(self.stime.GetValue()))
self.stop_start = 2
self.saved = 1
else:
self.count_time.Clear()
self.worker.reset()
#响应保存按钮
def OnSave(self, event):
#使用SQLITE3
if self.count_time.GetValue() != "" and self.saved == 0:
dlg = wx.MessageDialog(None, u"不要重复保存", u"重复保存", wx.OK)
if dlg.ShowModal() == wx.ID_OK:
dlg.Destroy
if self.count_time.GetValue() != "" and self.saved == 1:
if not os.path.isfile("myTiming.db"):
conn = sqlite3.connect("myTiming.db")
cursor = conn.cursor()
sql = """create table mytb(
id integer primary key autoincrement,
data text,
timing text)"""
cursor.execute(sql)
else:
conn = sqlite3.connect("myTiming.db")
cursor = conn.cursor()
sql = """insert into mytb(data, timing)
values
(?, ?)"""
data = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
timing = self.count_time.GetValue()
cursor.execute(sql, (data,timing))
conn.commit()
cursor.close()
conn.close()
self.saved = 0
dlg = wx.MessageDialog(None, u"保存成功", u"保存成功", wx.OK)
if dlg.ShowModal() == wx.ID_OK:
dlg.Destroy()
if self.count_time.GetValue() == "":
dlg = wx.MessageDialog(None, u"暂无任何数据", u"保存失败", wx.OK)
if dlg.ShowModal() == wx.ID_OK:
dlg.Destroy()
#响应查询按钮
def OnCheck(self, event):
#使用SQLITE3
if os.path.isfile("myTiming.db"):
conn = sqlite3.connect("myTiming.db")
cursor = conn.cursor()
sql = """select * from mytb
"""
results = cursor.execute(sql)
results_all = results.fetchall()
print(results_all)
for r in results_all:
self.count_time.AppendText("\n".join(r[1:]))
cursor.close()
conn.close()
#响应自定义事件,接收线程传递的数据,并显示
def OnResult(self, event):
self.stime.SetValue("{}".format(event.data))
if event.data == "59:59.99":
self.startButton.Disable()
self.resetButton.Disable()
self.saveButton.Disable()
self.checkButton.Disable()
#窗口主程序类
class MainApp(wx.App):
def OnInit(self):
self.frame = MainFrame(None, -1)
self.frame.Show(True)
return True
if __name__ == '__main__':
app = MainApp()
app.MainLoop()
以下是运行截图:启动和重置按钮运行时会变为停止和计次按钮。生成的计次时间可以保存和查看。生成的数据库文件在当前文件夹下myTiming.db。(背景图路径也为当前文件夹)具体为什么这样设计 可以参考IPHONE秒表
初始界面:

程序运行时截图:

点击三次计次,记录下三次时间。

查看刚才保存的计次时间:

使用的图片:clock.jpg 这个只是背景图片 并不是程序运行的主界面

本文介绍了如何使用Python的wxPython库创建一个精确到毫秒的秒表计时器,通过线程解决界面卡死问题,并通过调整sleep时间减少计时误差。此外,还涉及wx.statictext控件闪烁的解决方案以及使用sqlite3保存计时数据。文章提供了详细的代码实现和运行截图。
317

被折叠的 条评论
为什么被折叠?



