近日,公司供电不稳定,时不时断电,对台式机很不友好。在经历了两次早晨一来发现电脑前一晚被强行断电关机的不愉快后,我决定写一个晚上可以自动把电脑休眠的工具。
最终成品图如下:
下面是施工过程:
一、任务目标与分析
任务目标:
- 每晚8点自动运行
- 执行休眠动作前倒计时提醒
- 可以取消休眠,可以把倒计时加时,可以重置倒计时时间
- 倒计时以界面的方式出现(而不是命令行),且置顶+不可关闭,窗体可拖动
分析:
-
自动运行使用win的任务计划程序
-
程序使用python3.6编写,界面使用Python 的标准 Tk GUI 工具包的接口tkinter。tkinter官方文档
大神详细讲解
二、界面设计/功能设计
1、界面大小与常见的右下角悬浮窗类似。
2、界面有三个按钮对应三个小功能,分别是“重置“、”加时“、”取消“。
3、为了不影响正常工作,界面应置顶且有一定的透明度,界面不在焦点时应把透明度进一步调低。界面应可拖动。
4、为了美观,应把python运行的命令行隐藏,将状态栏和标题栏隐藏。
三、代码实现
代码主要分为两个部分:窗体模块和计时模块
1、窗体模块:
(1) 隐藏命令行显示:
# 隐藏命令行窗口
import ctypes
#调用kernel32.dll获取当前命令行的句柄
whnd = ctypes.windll.kernel32.GetConsoleWindow()
if whnd != 0:
#隐藏命令行窗口
ctypes.windll.user32.ShowWindow(whnd, 0)
#释放句柄资源(句柄类似于指针,这里只关闭了句柄,并不关闭线程)
ctypes.windll.kernel32.CloseHandle(whnd)
(2) 界面
界面使用tkinter组件
依赖的包:
import tkinter
from tkinter import *
获取焦点、获取鼠标动作等事件需要将窗体绑定windows事件。窗体的绑定事件代码格式为:
root.bind("<B1-Motion>", change)
设定窗体的各项参数:
#创建根窗体
root = tkinter.Tk()
#初始化窗体
def tkinterInit():
# 设定位置
root.geometry('260x120-10-40')
# UI窗口置于顶层
root.wm_attributes('-topmost', 1)
#标题栏标题
root.title("即将休眠")
# 背景色
root["background"] = "white"
# 设置透明度
root.attributes("-alpha", 0.8)
# 绑定win鼠标移动事件
# <B1-Motion> 拖动左键触发事件
# <B2-Motion> 拖动中键触发事件
# <B3-Motion> 拖动右键触发事件
root.bind("<B1-Motion>", change)
# 鼠标左键按下,2表示中键,3表示右键;
root.bind("<Button-1>", btnDown)
# 获取焦点
root.bind("<FocusIn>", focusIn)
# 失去焦点
root.bind("<FocusOut>", focusOut)
# 不可改变窗口大小
root.resizable(False, False)
# 设置窗口为工具样式:
# root.attributes("-toolwindow", True)
# # 设置全屏:
# root.attributes("-fullscreen", True)
# 设置成脱离工具栏样式(取消标题栏和状态栏图标)
root.overrideredirect(True)
创建窗口上的其他组件:
#创建窗口
def startTkinter():
global timer
tkinterInit()
# 提示标签
title = Label(root, text='距离休眠时间还有:', bg="white")
# 时间显示标签
timer = Label(root, textvariable=nowTime, fg="red", bg="white", font="黑体")
#1000ms后调用updateTime函数
timer.after(1000, updateTime)
# “重置”与“加时”按钮需要在一行,故把两个按钮放入一个框架内。
frame = tkinter.Frame(root)
#按钮方法需要使用函数名(不带括号)或lambda表达式调用函数
resetBtn = Button(frame, text='重置'
, command=resetTime
, width=12
, bg="white"
, relief='ridge'
)
addBtn = Button(frame, text='加时5分钟'
, command=addTime
, width=12
, bg="white"
, relief='ridge'
)
#放置重置按钮
resetBtn.pack(side=LEFT)
# 放置加时按钮
addBtn.pack(side=RIGHT)
quitBtn = Button(root, text='取消'
, command=lambda: root.quit()
, width=25
, bg="white"
, fg="Blue"
, relief='ridge'
)
#放置提示文本标签
title.pack()
# 时间显示框的位置
timer.place(x=150, y=20, anchor="center")
#放置时间显示标签
timer.pack()
#放置“重置”与“加时”框架
frame.pack()
#放置取消按钮
quitBtn.pack()
#开启窗口
root.mainloop()
鼠标拖动:
由于隐藏了标题栏,故无法直接拖动 。此时需要实现拖动界面来实现拖动动作。具体实现思路为:监听鼠标左键按下的动作,记录下当前鼠标在窗体中的位置,再监听鼠标滑动动作,计算鼠标相对上次的位移,获取窗体当前在win中的位置,最后鼠标在窗体上移动多少,窗体就在win上移动多少。以此实现鼠标直接拖动窗体的移动。
tkinterInit() 中已经绑定了鼠标事件:
# 绑定win鼠标移动事件
root.bind("<B1-Motion>", change)
# 鼠标左键按下,2表示中键,3表示右键;
root.bind("<Button-1>", btnDown)
以下为事件的具体实现:
oldX = 0
oldy = 0
# 鼠标移动窗体(具体原理就是鼠标在窗体上移动多少,窗体就在win上移动多少)
def change(event):
global oldX, oldy
# 获取窗体位置
winX = root.winfo_x()
winY = root.winfo_y()
# 获取鼠标相对旧位置移动的位置
x = event.x - oldX
y = event.y - oldy
root.geometry('260x120+' + str(winX + x) + '+' + str(winY + y))
# 监听鼠标按下事件
def btnDown(event):
global oldX, oldy
oldX = event.x
oldy = event.y
当程序在焦点时(活动窗口)透明度设置为80%,不在焦点时透明度设置为45%。
# 获取焦点,设置透明度为0.8
def focusIn(event):
root.attributes("-alpha", 0.8)
# 失去焦点设置透明度为0.45
def focusOut(event):
root.attributes("-alpha", 0.45)
2、定时模块
下面是两个按钮和一个倒计时标签对应的函数:
# 重置时间
def resetTime():
global timeNum, timer
timeNum = defTimeNum
nowTime.set(getNowTimeString(mode='str'))
#增加时间
def addTime():
global timeNum
timeNum = timeNum + 300
nowTime.set(getNowTimeString(mode='str'))
时间显示label的定时调用函数:
startTkinter() 中已经将时间label绑定了该函数
timer.after(1000, updateTime)
# labal定时执行的任务
def updateTime():
global timer
nowTime.set(getNowTimeString())
#循环调用达到计时的目的
timer.after(1000, updateTime)
时间递减与逻辑判断函数:
def getNowTimeString(mode='general'):
global timeNum
if (timeNum > 0):
if mode == 'general':
timeNum = timeNum - 1
minute = timeNum // 60
seconds = timeNum % 60
res = '{}分{}秒'.format((str(minute if minute >= 0 else 0)).zfill(2),
(str(seconds if seconds >= 0 else 0)).zfill(2))
return res
else:
command()
最后定义命令函数即可:
# 到时间执行任务
def command():
os.system('shutdown -h')
root.quit()
最后,完整的代码如下:
import tkinter
from tkinter import *
from tkinter import messagebox
# 隐藏命令行窗口
import ctypes
#获取当前命令行的句柄
whnd = ctypes.windll.kernel32.GetConsoleWindow()
if whnd != 0:
#隐藏命令行窗口
ctypes.windll.user32.ShowWindow(whnd, 0)
#释放句柄资源(句柄类似于指针,这里只关闭了句柄,并不关闭线程)
ctypes.windll.kernel32.CloseHandle(whnd)
#创建根窗体
root = tkinter.Tk()
defTimeNum = 300
timeNum = defTimeNum
def getNowTimeString(mode='general'):
global timeNum
if (timeNum > 0):
if mode == 'general':
timeNum = timeNum - 1
minute = timeNum // 60
seconds = timeNum % 60
res = '{}分{}秒'.format((str(minute if minute >= 0 else 0)).zfill(2),
(str(seconds if seconds >= 0 else 0)).zfill(2))
return res
else:
command()
nowTime = StringVar()
nowTime.set(getNowTimeString())
def tkinterInit():
# 设定位置
root.geometry('260x120-10-40')
# 置于顶层
root.wm_attributes('-topmost', 1)
root.title("即将休眠")
# 透明度
root["background"] = "white"
# 设置透明度
root.attributes("-alpha", 0.8)
# 绑定win鼠标移动事件
# <B1-Motion> 拖动左键触发事件
# <B2-Motion> 拖动中键触发事件
# <B3-Motion> 拖动右键触发事件
root.bind("<B1-Motion>", change)
# 鼠标左键按下,2表示中键,3表示右键;
root.bind("<Button-1>", btnDown)
# 获取焦点
root.bind("<FocusIn>", focusIn)
# 失去焦点
root.bind("<FocusOut>", focusOut)
# 不可改变窗口大小
root.resizable(False, False)
# 设置窗口为工具样式:
# root.attributes("-toolwindow", True)
# # 设置全屏:
# root.attributes("-fullscreen", True)
# 设置成脱离工具栏
root.overrideredirect(True)
#创建窗口
def startTkinter():
global timer
tkinterInit()
# 提示标签
title = Label(root, text='距离休眠时间还有:', bg="white")
# 时间显示标签
timer = Label(root, textvariable=nowTime, fg="red", bg="white", font="黑体")
#1000ms后调用updateTime函数
timer.after(1000, updateTime)
# “重置”与“加时”按钮需要在一行,故把两个按钮放入一个框架内。
frame = tkinter.Frame(root)
resetBtn = Button(frame, text='重置'
, command=resetTime
, width=12
, bg="white"
, relief='ridge'
)
addBtn = Button(frame, text='加时5分钟'
, command=addTime
, width=12
, bg="white"
, relief='ridge'
)
#放置重置按钮
resetBtn.pack(side=LEFT)
# 放置加时按钮
addBtn.pack(side=RIGHT)
quitBtn = Button(root, text='取消'
, command=lambda: root.quit()
, width=25
, bg="white"
, fg="Blue"
, relief='ridge'
)
#放置提示文本标签
title.pack()
# 时间显示框的位置
timer.place(x=150, y=20, anchor="center")
#放置时间显示标签
timer.pack()
#放置“重置”与“加时”框架
frame.pack()
#放置取消按钮
quitBtn.pack()
#开启窗口
root.mainloop()
# 到时间执行任务
def command():
os.system('shutdown -h')
root.quit()
# 重置时间
def resetTime():
global timeNum, timer
timeNum = defTimeNum
nowTime.set(getNowTimeString(mode='str'))
def addTime():
global timeNum
timeNum = timeNum + 300
nowTime.set(getNowTimeString(mode='str'))
def updateTime():
global timer
nowTime.set(getNowTimeString())
timer.after(1000, updateTime)
oldX = 0
oldy = 0
# 鼠标移动窗体(具体原理就是鼠标在窗体上移动多少,窗体就在win上移动多少)
def change(event):
global oldX, oldy
# 获取窗体位置
winX = root.winfo_x()
winY = root.winfo_y()
# 获取鼠标相对旧位置移动的位置
x = event.x - oldX
y = event.y - oldy
root.geometry('260x120+' + str(winX + x) + '+' + str(winY + y))
# 监听鼠标按下事件
def btnDown(event):
global oldX, oldy
oldX = event.x
oldy = event.y
# 获取焦点,设置透明度为0.8
def focusIn(event):
root.attributes("-alpha", 0.8)
# 失去焦点设置透明度为0.45
def focusOut(event):
root.attributes("-alpha", 0.45)
if __name__ == '__main__':
startTkinter()
四、部署
打开任务计划程序:
新建计划任务
名称自行设定、触发条件设定为每天20点、操作的设置如下:
“程序或脚本”为python的路径,如:D:\ProgramFiles\Anaconda3\python.exe
“添加参数”为上述代码文件的路径 如:D:\OA\自动休眠UI.py
“起始于”为python程序的上一级路径,如:D:\ProgramFiles\Anaconda3
如此,即可每晚8点打开该程序了。
五、成品
有焦点(透明度80%)
脱离焦点(透明度45%)
加时
取消(退出)
呐,已经退出了~
最终展示:
六、扩展:
这样的方法也可以制作成为桌面助手啦、网络悬浮窗啦等小功能组件。