文章目录
- 抽象
- 显示效果
- 管理课程
- 实现课程表
- 导入必要包
- 导入数据
- 时间初始化
- 绘制课程表的核心
- 创建主窗体
- 绘制课程小格子
- 其他功能
- 上下周
- 跳转周
- 管理课程
- 设置周
- 转换数据
抽象
首先,我们需要明确自己的设计需求。
课程表应该能读取课程,显示课程,管理课程。
显示效果
首先,我们显示的应该是本周的课程,课程信息应该包括 课程名、节、地点、时间等必要信息,不同课程最好有不同颜色区分,同种课程的颜色最好相同。如下:
星期一 | 星期二 | …… | |
第一节 | 课程一 | 课程二 | …… |
第二节 | 课程三 | 课程四 | …… |
…… | …… | …… | …… |
另外,我们有时候需要查看上下周的课程,所以应该有一个切换周次的按钮。
管理课程
我们需要根据当前是第几周来显示课程,所以需要保存第一周的时间;同时,也要显示当前是第几周的课程数据,所以需要保存每一节课的时间。
我们需要添加课程、删除课程,必要时能够修改课程。
另外,有时不想手动输入课程数据,我们应该能够直接从教务系统导入课程,需要一系列的键值对。
因此,我们的需要一个启动文件data.json:
{"start": [2020, 2, 20],
"local": ["kbList"],
"book": {
"name": "kcmc",
"teacher": "xm",
"week": "zcd",
"part": "jcor",
"weekday": "xqjmc",
"address": "cdmc"},
"time": {"1": "8:50",
"2": "9:40",
"3": "10:40",
"4": "11:30",
"5": "14:00",
"6": "14:50",
"7": "15:45",
"8": "16:35",
"9": "19:00",
"10": "19:55",
"11": "20:50",
"12": "21:45"}
}
接下来是保存课程的数据:
{"课程名": {"color": "#665A79", "subject": [{"teacher": "教师", "week": "1-16周(双)", "weekday": "星期几", "address": "地点", "part": "3-4"}]}}
json不支持中文,打开后会有很多转义符。
实现课程表
导入必要包
根据上面需求,我们能够显示时间的datetime包,读取数据的json包,绘制界面的tkinter包,判断文件是否存在的os包,还要一个随机数来随机生成颜色。
import datetime
import json
import tkinter
import os.path
from random import randint
导入数据
首先我们要判断启动文件是否存在,如果不存在就生成一个启动文件。
if os.path.isfile("data.json"):
# 用外部文件来保存键值对
with open("data.json", "r") as f:
init = json.loads(f.read())
else:
with open("data.json", "w") as f:
init = {"start": [2020, 2, 20],
"time":
{"1": "8:50",
"2": "9:40",
"3": "10:40",
"4": "11:30",
"5": "14:00",
"6": "14:50",
"7": "15:45",
"8": "16:35",
"9": "19:00",
"10": "19:55",
"11": "20:50",
"12": "21:45"}}
json.dump(init, f)
启动文件存在后,用就开始读取课程数据。如果课程数据文件不存在,则创建。
# 刷新
def flesh():
global js, weeks
if os.path.isfile("my_class.json"):
# 保存课程数据
with open("my_class.json", "rb") as f:
class_js = f.read()
js = json.loads(class_js) # 转化为json
else:
with open("my_class.json", "w") as f:
f.write("{}")
js = {}
read_class(weeks)
时间初始化
我们要判断当前是第几周。
首先,我们要知道第一周在什么时候。我们把第一周的时间保存在启动文件的start上面,第一周的开始应该是星期天,如果不是星期天,就更改到星期天。
然后,我们要算出今天离第一周差了几天,根据天数计算出当前是第几周,保存到week上。
另外,还需要判断今天是星期几,保存在now_week上
def time_init():
global weeks, now_week
# 确认开始的日期 年/月/日
init_year = init["start"][0]
init_mouth = init["start"][1]
init_day = init["start"][2]
# 如果开始的一天不是周日,则将开始的日期变为周日
if not datetime.datetime(init_year, init_mouth, init_day).strftime("%w") == 0:
init_day -= eval(datetime.datetime(init_year, init_mouth, init_day).strftime("%w"))
# 初始化的时间
init_date = datetime.datetime(init_year, init_mouth, init_day)
# 现在的时间
now_date = datetime.datetime.today()
# 间隔的天数
days = (now_date - init_date).days
# 间隔的周数,第一周为1
weeks = int(days / 7) + 1
# 框出今天星期几
now_week = eval(now_date.strftime("%w"))
time_init()
weekday = ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"]
绘制课程表的核心
创建主窗体
创建一个窗体,窗体由几部分构成,分别是课程表的框架还有一些功能的按钮。
top = tkinter.Tk() # 创建一个窗体
top.geometry("1100x650+200+50") # 改变窗体的大小
top.title('课程表')
top.resizable(0, 0)
# 框架
box = tkinter.LabelFrame(top, text="课程表", background="#F8F8FF", height=600, width=1100)
box.pack()
set = tkinter.LabelFrame(top, text="管理", height=100, width=1100)
set.pack()
label_now_week = tkinter.Label(set, text="现在是第{}周,当前的周数为".format(weeks))
label_now_week.pack(side=tkinter.LEFT)
week = tkinter.Variable()
week.set(weeks)
entry = tkinter.Entry(set, textvariable=week, width=10)
entry.pack(side=tkinter.LEFT)
last = tkinter.Button(set, text="跳转", command=jump)
last.pack(side=tkinter.LEFT)
# 上下周按钮
last = tkinter.Button(set, text="上一周", command=last_week)
next = tkinter.Button(set, text="下一周", command=next_week)
last.pack(side=tkinter.LEFT)
next.pack(side=tkinter.LEFT)
# 数据控制按钮
chang_button = tkinter.Button(set, text="导入数据", command=data_change)
chang_button.pack(side=tkinter.LEFT)
# 数据控制按钮
command_button = tkinter.Button(set, text="管理课程", command=command)
command_button.pack(side=tkinter.LEFT)
# 刷新
time_button = tkinter.Button(set, text="管理起始周-上课时间", command=set_time)
time_button.pack(side=tkinter.LEFT)
# 刷新
flesh_button = tkinter.Button(set, text="刷新", command=flesh)
flesh_button.pack(side=tkinter.LEFT)
# 关于
about_button = tkinter.Button(set, text="关于", command=about)
about_button.pack(side=tkinter.LEFT)
实现的效果如下:
绘制课程小格子
接下来让我们显示,这个功能是根据当前是第几周来显示课程,如果是现在周就显示现在的课程,如果是点了上一周下一周就显示该周课程。
这个函数主要用于判断课程是否是当前周的课程。
在点击上一周下一周后,该函数会被用到,所以要先删去课程表上之前周的课程。
如果课程是单周课程(数据包含单字),需要判断现在是否是单周,双周同理。
def read_class(_week):
for widget in box.winfo_children():
widget.destroy()
draw_week()
for c in js:
name = c
for i in js[c]['subject']:
_week = i["week"]
# 判断课程是否是单双周的课程
if "单" in _week:
_week = _week.replace("周(单)", "")
_week = _week.split("-")
# 开始周/结束周
start_week, end_week = eval(_week[0]), eval(_week[-1])
if weeks % 2 == 1: # 判断是否是单周
if start_week <= weeks <= end_week: # 判断该课程是否是当前周的课程
if start_week <= weeks <= end_week: # 判断该课程是否是当前周的课程
# 根据节来优化显示效果
draw_box(name, i)
elif "双" in _week:
_week = _week.replace("周(双)", "")
_week = _week.split("-")
# 开始周/结束周
start_week, end_week = eval(_week[0]), eval(_week[-1])
if weeks % 2 == 0: # 判断是否是双周
if start_week <= weeks <= end_week: # 判断该课程是否是当前周的课程
if start_week <= weeks <= end_week: # 判断该课程是否是当前周的课程
draw_box(name, i)
else:
_week = _week.replace("周", "")
_week = _week.split("-")
# 开始周/结束周
start_week, end_week = eval(_week[0]), eval(_week[-1])
if start_week <= weeks <= end_week: # 判断该课程是否是当前周的课程
# 根据节来优化显示效果
draw_box(name, i)
如果确实是单周课程,就进行绘制。
def draw_box(courses, course):
scr = "{}\n讲师 {}\n周 {}\n地点 {}".format(
courses, course["teacher"], course["week"], course["address"]) # 要显示的课程信息
part = course["part"]
part = part.split("-")
start_part, end_part = eval(part[0]), eval(part[-1])
# 确认文本的位置
x = weekday.index(course["weekday"])
# 创建一个文本控件
text = tkinter.Label(box, text=scr, width=20, fg="#FFFFFF", bg=js[courses]['color'],
height=2 * (end_part - start_part + 1))
text.place(x=x * 150 + 40, y=start_part * 40 + 20) # 在屏幕上放置文本控件
绘制效果如下:
其他功能
上下周
两个函数用于显示上下周,与两个按钮绑定。
上一周时,如果周数为1,就禁止上一周的按钮操作。
def next_week():
global weeks
weeks += 1
read_class(weeks)
week.set(weeks)
def last_week():
global weeks
if weeks > 1:
weeks -= 1
read_class(weeks)
week.set(weeks)
跳转周
上下周按了太多了?直接输入数字快速跳转周吧。
需要判断输入的是否是数字,如果不是数字就判断为无效输入。
# 跳转
def jump():
global weeks
if entry.get().isnumeric():
weeks = eval(entry.get())
read_class(weeks)
week.set(weeks)
else:
week.set(weeks)
## 得到一个彩色
tkinter用的是两位十六进制保存红、绿、蓝颜色的数据。不同数值混合得到的颜色不同。结果前面需要加个“#”。我们只取中间色,在5到B之间取。最后返回一个类似“#AAAAAA”的数据。
def get_a_color():
# 多彩效果
text = "56789AB"
color = "#"
for i in range(6):
index = randint(0, len(text) - 1)
color = color + text[index]
return color
管理课程
我们对课程数据的管理有获取、增加、删除、保存的操作。
用列表保存课程名,如果获取之后可以修改。
增加课程时,随机获取一种颜色,保存到数据中。
上面两个,都需要用户按下保存时才进行保存。
删除时,直接删除课程。
# 管理课程
def command():
def get_info():
data = js[list.get(tkinter.ACTIVE)]
subjects = data["subject"]
data_information.delete(0.0, tkinter.END)
data_information.insert(0.0, "{} {}\n".format(list.get(tkinter.ACTIVE), data["color"]))
for subject in subjects:
if len(subject["teacher"]) > 7:
teacher = subject["teacher"][0:7] + "等"
else:
teacher = subject["teacher"]
scr = "{} {} {} {} {}\n". \
format(teacher, subject["week"], subject["weekday"], subject["address"], subject["part"])
data_information.insert(tkinter.INSERT, scr)
def new():
data_information.delete(0.0, tkinter.END)
data_information.insert(0.0, "课程名 {}\n教师 1-20周(单) 星期一 地点 1-12".format(get_a_color()))
def save():
scr = data_information.get(0.0, tkinter.END)
scr = scr.split("\n")
name = scr[0]
subject = []
for i in scr[1:-1]:
if i == "":
pass
else:
i = i.split(" ")
subject.append({"teacher": i[0], "week": i[1], "weekday": i[2], "address": i[3], "part": i[4]})
class_key = scr[0].split(" ")
js[class_key[0]] = {"color": class_key[1], "subject": subject}
with open("my_class.json", "w") as f:
json.dump(js, f)
myself_flesh()
def delete():
js.pop(list.get(tkinter.ACTIVE))
with open("my_class.json", "w") as f:
json.dump(js, f)
myself_flesh()
def myself_flesh():
list.delete(0, tkinter.END)
n = 0
for i in js:
list.insert(n, i)
n += 1
list.pack(side=tkinter.LEFT)
command_win = tkinter.Tk() # 创建一个窗体
command_win.geometry("500x200+200+50") # 改变窗体的大小
command_win.title('管理数据')
command_win.resizable(0, 0)
list = tkinter.Listbox(command_win)
n = 0
for i in js:
list.insert(n, i)
n += 1
list.pack(side=tkinter.LEFT)
data_frame = tkinter.LabelFrame(command_win, text="数据详情")
data_frame.pack(side=tkinter.LEFT)
button_frame = tkinter.Frame(data_frame)
button_get = tkinter.Button(button_frame, text="获取", command=get_info)
button_get.pack(side=tkinter.LEFT)
button_new = tkinter.Button(button_frame, text="新增", command=new)
button_new.pack(side=tkinter.LEFT)
button_save = tkinter.Button(button_frame, text="保存", command=save)
button_save.pack(side=tkinter.LEFT)
button_del = tkinter.Button(button_frame, text="删除", command=delete)
button_del.pack(side=tkinter.LEFT)
button_frame.pack()
data_information = tkinter.Text(data_frame)
data_information.pack()
实现的效果如下:
设置周
用户可以直接输入起始周,也可以更改时间。
def set_time():
def save():
# 判断是否有效日期
try:
datetime.datetime.strptime(start_time.get(), "%Y-%m-%d")
split_time = start_time.get().split("-")
split_time = [eval(split_time[0]), eval(split_time[1]), eval(split_time[2])]
init["start"] = split_time
except Exception:
start_time.delete(0, tkinter.END)
start_time.insert(0, "{}-{}-{}".format(init_year, init_mouth, init_day))
# 修改课程时间
part = text.get(0.0, tkinter.END).split("\n")
dic = {}
n = 0
for little_part in part:
if little_part == "":
pass
else:
n += 1
dic[str(n)] = little_part
init["time"] = dic
# 保存数据
with open("data.json", "w") as f:
json.dump(init, f)
set_time_win = tkinter.Tk() # 创建一个窗体
set_time_win.geometry("250x200+200+50") # 改变窗体的大小
set_time_win.title('关于软件')
set_time_win.resizable(0, 0)
init_year = init["start"][0]
init_mouth = init["start"][1]
init_day = init["start"][2]
frame = tkinter.Frame(set_time_win)
frame.pack()
label = tkinter.Label(frame, text="起始周的时间")
label.pack(side=tkinter.LEFT)
start_time = tkinter.Entry(frame)
start_time.insert(0, "{}-{}-{}".format(init_year, init_mouth, init_day))
start_time.pack()
times = tkinter.LabelFrame(set_time_win, text="每节课的时间")
times.pack(side=tkinter.LEFT)
text = tkinter.Text(times, width=25, height=10)
dic = init["time"]
for i in dic:
text.insert(tkinter.END, dic[i] + "\n")
text.pack()
print(dic)
button_save = tkinter.Button(set_time_win, text="保存", command=save)
button_save.pack(side=tkinter.RIGHT)
实现的效果如下:
转换数据
参照我的上一篇博客,从教务处导入数据,并把转换的结果显示在窗口的右边。
# 改变
def data_change():
change_win = tkinter.Tk() # 创建一个窗体
change_win.geometry("500x265+200+50") # 改变窗体的大小
change_win.title('导入数据')
change_win.resizable(0, 0)
data_change.origin = tkinter.Text(change_win, height=20, width=30)
data_change.origin.pack(side=tkinter.LEFT)
frame_button = tkinter.Frame(change_win, height=300, width=100)
frame_button.pack(side=tkinter.LEFT)
data_change.translate = tkinter.Text(change_win, height=20, width=30)
data_change.translate.pack(side=tkinter.LEFT)
button_translate = tkinter.Button(frame_button, text="转换并保存", command=write)
button_translate.pack(side=tkinter.TOP)
change_win.mainloop()
然后要写入数据,
def write():
key = init["book"]
text = json.loads(data_change.origin.get(0.0, tkinter.END)) # 转化为json
data = {}
# 抽象化读取字典
for i in init["local"]:
text = text[i]
for course in text:
class_data = {}
print(course[key["name"]])
if course[key["name"]] in data:
subject = data[course[key["name"]]]["subject"]
print(course[key["name"]])
else:
subject = []
subject.append({"teacher": course[key["teacher"]], "week": course[key["week"]],
"weekday": course[key["weekday"]],
"address": course[key["address"]], "part": course[key["part"]]})
class_data["color"] = get_a_color()
class_data["subject"] = subject
data[course[key["name"]]] = class_data
with open("my_class.json", "w") as f:
json.dump(data, f)
data_change.translate.insert(0.0, data)
实现的效果如下: