├── .gitignore ├── icon.ico ├── README.md ├── config.json ├── LICENSE └── class_form.py /.gitignore: -------------------------------------------------------------------------------- 1 | /__pycache__ 2 | class_form_demo.py 3 | class_form.exe -------------------------------------------------------------------------------- /icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DuguSand/class_form/HEAD/icon.ico -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 灵动课表 2 | ## 功能介绍 3 | - 显示课表 4 | - 根据当日星期几显示配置文件中对应的课表 5 | - 支持分隔符来规划课表内容 6 | - 根据时间高亮当前课程 7 | - 在电脑最上方且大小合适,前后排的人都能看清 8 | - 支持单个课程多字描述 9 | - 窗口美观,不破坏电脑整体观感 10 | - 上/下课提醒 11 | - 在设置的时间准时执行动画 12 | - 动画平滑,令人舒适 13 | - 下课时置顶提醒下课 14 | - 上课时解除置顶并且上隐避免遮挡 15 | - config里可以设置上隐开关,true为开 16 | - 换课 17 | - 双击对应课程即可换课,方便应对繁复的临时换课 18 | - 也可双击星期位置更换整体课表,应对调休 19 | - 双击对应选项即可完成更换 20 | - 双击原本所选课程收起换课 21 | - 双击其他课程更换目标 22 | 23 | ## 使用方法 24 | - 源码使用 25 | - 1.配置python环境 26 | - 2.运行class_form.py 27 | - 3.若要免去黑色运行窗口请用白色的pythonw/python作为打开方式 28 | - 打包使用 29 | - 双击class_form.exe 30 | - 如果要关闭请打开任务管理器,在后台程序中找到class_form.exe(有两个)全部结束任务 31 | - 注意事项 32 | - 每天课表长度保持一致(不计分隔符) 33 | - 更换选项目前只支持单字 34 | - 开始/结束提醒目前只支持一个 35 | - 开始/结束时间请与课表长度保持一致 36 | - 开始/结束时间的格式为(小时,分钟)24小时制。如:2点13分=(2,13),15点5分=(15,5) 37 | - 开始/结束时间请按时间顺序依次排序,以免出现无法预料的错误 38 | 39 | 更多功能仍在开发中…… 40 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "日程表":{ 3 | "1":["早","|","语","物","化","英","|","数","体","心","数","卓","|","晚","晚"], 4 | "2":["早","|","英","英","数","政","|","史","化","信","语","社","|","晚","晚"], 5 | "3":["早","|","语","语","数","英","|","艺","体","物","自","晨","|","晚","晚"], 6 | "4":["早","|","英","语","史","体","|","地","生","数","信","政","|","晚","晚"], 7 | "5":["早","|","数","语","英","体","|","选","数","物","无","无","|","无","无"], 8 | "6":["早","|","语","物","化","英","|","数","体","心","数","卓","|","晚","晚"], 9 | "7":["早","|","语","物","化","英","|","数","体","心","数","卓","|","晚","晚"] 10 | }, 11 | 12 | "更换选项":["语","数","英","物","化","生","政","史","自"], 13 | 14 | "分隔符":["|"], 15 | 16 | "开始时间":[ 17 | [7,28],[8,28],[9,18],[10,18],[11,8],[12,48],[13,38],[14,38],[15,28],[16,18],[17,58],[19,43] 18 | ], 19 | 20 | "结束时间":[ 21 | [8,0],[9,10],[10,0],[11,0],[11,50],[13,30],[14,20],[15,20],[16,10],[17,0],[19,30],[21,15] 22 | ], 23 | 24 | "开始提示":["准备上课"], 25 | 26 | "结束提示":["下课时间"], 27 | 28 | "上隐开关": true 29 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 DuguSand 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /class_form.py: -------------------------------------------------------------------------------- 1 | import json 2 | from datetime import datetime,date 3 | from tkinter import * 4 | from time import sleep 5 | 6 | class Calender(): 7 | def __init__(self): 8 | self.xroot=0 #窗口x 9 | self.yroot=10 #窗口y 10 | self.x=10 #课表左右留空 11 | self.y=10 #课表上下留空 12 | self.width=0 #窗口宽度 13 | self.height=0 #窗口高度 14 | self.classes=[] #课程 15 | self.labels=[] #课程label 16 | self.left=[] #分隔符 17 | self.leftlabs=[] #分隔符label 18 | self.selects=[] #换课选项 19 | self.selectlabs=None #换课label 20 | self.times=[] #课程时间 21 | self.selected=False #选中状态,False表示未选中,数字表示选中的位置 22 | self.window=None #窗口主体 23 | self.nowclass=0 #当前课程 24 | self.onw="" #上课提示 25 | self.offw="" #下课提示 26 | self.status=False #上/下课 27 | self.offtk=[] #下课窗口 28 | self.movon=True #上隐开关 29 | 30 | 31 | """平滑动画函数""" 32 | def move(self,wins,hopel,hopew,hopex,hopey,times=1.2): 33 | length=[] 34 | width=[] 35 | x=[] 36 | y=[] 37 | win=[] 38 | for b in range(len(wins)): 39 | length.append(wins[b].winfo_width()) 40 | width.append(wins[b].winfo_height()) 41 | x.append(wins[b].winfo_x()) 42 | y.append(wins[b].winfo_y()) 43 | a=-5 44 | while a<5: 45 | length_now=-(length[b]-hopel[b])/(1+3**(-a))+length[b] 46 | width_now=-(width[b]-hopew[b])/(1+3**(-a))+width[b] 47 | x_now=-(x[b]-hopex[b])/(1+3**(-a))+x[b] 48 | y_now=-(y[b]-hopey[b])/(1+3**(-a))+y[b] 49 | win.append(str(int(length_now))+"x"+str(int(width_now))+"+"+str(int(x_now))+"+"+str(int(y_now))) 50 | a+=10/int(times/0.005) 51 | 52 | b=1 53 | while b',self.select) 95 | 96 | #x坐标依次显示label 97 | position=self.x 98 | for x in self.classes: 99 | class_lab=Label(self.window,text=x,font=('幼圆',40),fg='white',bg='black',wraplength=40) 100 | class_lab.place(x=position,y=self.y) 101 | if x in self.left: 102 | self.leftlabs.append(class_lab) 103 | else: 104 | class_lab.x=position #为label添加x 105 | class_lab.num=len(self.leftlabs+self.labels) #添加label所处classes位置 106 | self.labels.append(class_lab) 107 | position+=class_lab.winfo_reqwidth() 108 | 109 | #设置窗口高度,并且考虑换行情况 110 | height=class_lab.winfo_reqheight()+self.y*2 111 | if height>self.height: 112 | self.height=height 113 | 114 | self.width=position+self.x #设置窗口宽度 115 | self.xroot=int((self.window.winfo_screenwidth()-self.width)/2) 116 | self.window.geometry('1x'+str(self.height)+'+'+str(int(self.window.winfo_screenwidth()/2))+'+'+str(self.yroot)) 117 | self.window.update() 118 | self.move([self.window],[self.width],[self.height],[self.xroot],[self.yroot]) #创建窗口 119 | 120 | 121 | """换课功能""" 122 | def select(self,event): 123 | self.window.unbind('') 124 | if event.y_root-self.yroot=l.x: 128 | a+=1 129 | 130 | if self.selected==False: 131 | if a<2: #选中星期 132 | self.labels[0]['bg']='grey' 133 | self.labels[1]['bg']='grey' 134 | self.selectlabs=Label(self.window,text='一 二 三 四 五 六 日',font=('幼圆',40),fg='white',bg='black') 135 | else: 136 | self.labels[a]['bg']='grey' 137 | self.selectlabs=Label(self.window,text=' '.join(self.selects),font=('幼圆',40),fg='white',bg='black') 138 | self.selectlabs.place(x=self.x,y=self.height) 139 | if self.width6: 180 | a=6 181 | """重新设置课表""" 182 | self.load_class(a) 183 | for x in self.leftlabs+self.labels: 184 | x.destroy() 185 | self.leftlabs=[] 186 | self.labels=[] 187 | #x坐标依次显示label 188 | self.height=0 189 | position=self.x 190 | for x in self.classes: 191 | class_lab=Label(self.window,text=x,font=('幼圆',40),fg='white',bg='black',wraplength=40) 192 | class_lab.place(x=position,y=self.y) 193 | if x in self.left: 194 | self.leftlabs.append(class_lab) 195 | else: 196 | class_lab.x=position #为label添加x 197 | class_lab.num=len(self.leftlabs+self.labels) #添加label所处classes位置 198 | self.labels.append(class_lab) 199 | position+=class_lab.winfo_reqwidth() 200 | 201 | #设置窗口高度,并且考虑换行情况 202 | height=class_lab.winfo_reqheight()+self.y*2 203 | if height>self.height: 204 | self.height=height 205 | self.width=position+self.x #设置窗口宽度 206 | self.xroot=int((self.window.winfo_screenwidth()-self.width)/2) 207 | self.nowclass=0 208 | self.status=False 209 | else: 210 | if a>len(self.selects)-1: 211 | a=len(self.selects)-1 212 | self.labels[self.selected]['bg']='black' 213 | self.labels[self.selected]['text']=self.selects[a] 214 | self.classes[self.labels[self.selected].num]=self.selects[a] 215 | 216 | self.move([self.window],[self.width],[self.height],[self.xroot],[self.yroot],0.1) 217 | self.selectlabs.destroy() 218 | self.selected=False 219 | 220 | self.window.bind('',self.select) 221 | 222 | 223 | """上课动画""" 224 | def onclass(self): 225 | if self.times[0]==[datetime.now().hour,datetime.now().minute]: 226 | self.window.unbind('') 227 | sleep(1) 228 | xe=self.labels[self.nowclass+2].x 229 | oncl=Label(self.window,text=self.labels[self.nowclass+2]['text'],font=('幼圆',40),fg='yellow',bg='black',wraplength=40) #先行创建上方label 230 | if self.offtk!=[]: 231 | self.move([self.window,self.offtk[0]],[self.width,1],[self.height,self.y*2+self.offtk[1].winfo_reqheight()],[self.xroot,int((self.window.winfo_screenwidth()+self.width)/2)-self.x+20],[self.yroot,self.yroot]) 232 | 233 | #删除原有 234 | for l in self.labels+self.leftlabs: 235 | l.destroy() 236 | try: 237 | self.selectlabs.destroy() 238 | except AttributeError: 239 | pass 240 | if self.offtk!=[]: 241 | self.offtk[1].destroy() 242 | self.offtk[0].destroy() 243 | 244 | #缩至一格 245 | self.move([self.window],[self.x*2+oncl.winfo_reqwidth()],[self.y*2+oncl.winfo_reqheight()],[self.xroot+xe],[self.yroot]) 246 | #居中放置 247 | oncl.place(y=self.y,relx=0.5,anchor='n') 248 | self.window.update() 249 | sleep(1) 250 | #第二动画 251 | onlab=Label(self.window,text=self.onw,font=('幼圆',40),fg='yellow',bg='black') 252 | onlab.place(relx=0.5,anchor='n',y=self.window.winfo_height()) 253 | self.move([self.window],[self.x*2+onlab.winfo_reqwidth()],[self.y*3+oncl.winfo_reqheight()+onlab.winfo_reqheight()],[self.xroot+xe-(onlab.winfo_reqwidth()/2)],[self.yroot]) 254 | sleep(5) 255 | #恢复动画 256 | self.move([self.window],[self.x*2+oncl.winfo_reqwidth()],[self.y*2+oncl.winfo_reqheight()],[self.xroot+xe],[self.yroot]) 257 | oncl.destroy() 258 | onlab.destroy() 259 | self.window.update() 260 | self.move([self.window],[self.width],[self.height],[self.xroot],[self.yroot]) 261 | 262 | #x坐标依次显示label 263 | self.leftlabs=[] 264 | self.labels=[] 265 | self.xlist=[] 266 | position=self.x 267 | for x in self.classes: 268 | class_lab=Label(self.window,text=x,font=('幼圆',40),fg='white',bg='black',wraplength=40) 269 | class_lab.place(x=position,y=self.y) 270 | if x in self.left: 271 | self.leftlabs.append(class_lab) 272 | else: 273 | class_lab.x=position #为label添加x 274 | class_lab.num=len(self.leftlabs+self.labels) #添加label所处classes位置 275 | self.labels.append(class_lab) 276 | position+=class_lab.winfo_reqwidth() 277 | self.window.bind('',self.select) 278 | 279 | if self.movon: 280 | self.move([self.window],[self.width],[self.height],[self.xroot],[-60],0.1) 281 | self.window.unbind('') 282 | 283 | self.labels[self.nowclass+1]['fg']='white' 284 | self.labels[self.nowclass+2]['fg']='yellow' 285 | del(self.times[0]) 286 | self.window.attributes('-topmost',False) 287 | 288 | 289 | """下课动画""" 290 | def offclass(self): 291 | if self.times[0]==[datetime.now().hour,datetime.now().minute]: 292 | self.window.unbind('') 293 | sleep(0.5) 294 | if self.selected<2: 295 | self.labels[0]['bg']='black' 296 | self.labels[1]['bg']='black' 297 | else: 298 | self.labels[self.selected]['bg']='black' 299 | if self.movon: 300 | self.move([self.window],[self.width],[self.height],[self.xroot],[self.yroot]) 301 | self.offtk=[Tk()] 302 | 303 | #判断放学(未完成) 304 | if len(self.times)==1: 305 | self.offtk.append(Label(self.offtk[0],text="放学了",font=('幼圆',40),fg='yellow',bg='black')) 306 | else: 307 | self.offtk.append(Label(self.offtk[0],text=self.offw,font=('幼圆',40),fg='yellow',bg='black')) 308 | 309 | self.offtk[0].overrideredirect(True) 310 | self.offtk[0].attributes('-topmost',True) 311 | self.offtk[0].attributes('-alpha',0.7) 312 | self.offtk[0].config(bg='black') 313 | self.offtk[0].geometry('1x'+str(self.y*2+self.offtk[1].winfo_reqheight())+'+'+str(int((self.window.winfo_screenwidth()+self.width)/2)-self.x+20)+'+'+str(self.yroot)) 314 | self.offtk[1].place(x=self.x,y=self.y) 315 | self.offtk[0].update() 316 | self.move([self.window,self.offtk[0]],[self.width,self.x*2+self.offtk[1].winfo_reqwidth()],[self.height,self.y*2+self.offtk[1].winfo_reqheight()],[int((self.window.winfo_screenwidth()-self.width-self.x*2-self.offtk[1].winfo_reqwidth()-40)/2),int((self.window.winfo_screenwidth()+self.width-self.offtk[1].winfo_reqwidth())/2)+20-self.x],[self.yroot,self.yroot]) 317 | try: 318 | self.selectlabs.destroy() 319 | except AttributeError: 320 | pass 321 | self.window.bind('',self.select) 322 | 323 | self.window.attributes('-topmost',True) 324 | if len(self.times)==1: 325 | self.window.mainloop() 326 | del(self.times[0]) 327 | self.nowclass+=1 328 | self.labels[self.nowclass+1]['fg']='white' 329 | self.labels[self.nowclass+2]['fg']='yellow' 330 | 331 | 332 | def find(self): 333 | if self.times==[]: 334 | self.window.mainloop() 335 | self.window.unbind('') 336 | else: 337 | if self.times[0][0]