一.基本回顾与问题描述
在上篇代码的基础上,我用tkinter做了简易的可视化界面。目前为止共实现了对信息的基本的增删改查,以及叫号系统。问题描述如下:
(1)能够管理各参赛队的基本信息(包含参赛队编号,参赛作品名称,参赛学校,赛事类别,参赛者,指导老师),赛事类别共11项(参见大赛官网jsjds.blcu.edu.cn);包括增加、删除、修改参赛队伍的信息。
(2)从team.txt中读取参赛队伍的基本信息,实现基于二叉排序树的查找。根据提示输入参赛队编号,若查找成功,输出该赛事类别对应的基本信息(参赛作品名称、参赛学校、赛事类别、参赛者和指导老师信息),同时,输出查找成功时的平均查找长度ASL;否则,输出“查找失败!”。
(3)为省赛现场设计一个决赛叫号系统。所有参赛队按赛事组织文件中的赛事类别分到9个决赛室,决赛室按顺序叫号,被叫号参赛队进场,比赛结束后,下一参赛队才能进赛场。请模拟决赛叫号系统,演示省赛现场各决赛室的参赛队进场情况。(模拟时,要能直观展示叫号顺序与进场秩序一致)
二.设计思路
1.首先我创建了一个根窗口,利用ttk模块中的树视图,使得所有团队信息在交互窗口可视化,并在根窗口内增添6个entry输入框,以便用户输入需要增删改查参赛团队的团队编号并显示结果,以及四个增删改查的按钮,和一个附加的一键清空所有输入框内容的按键。当用户输入信息,按下按键后,就回调相应操作的函数。我创建了一个MyTable类,对树视图进行初始化,并将其作为MyTable类实例的一个属性。
以下是根窗口初始化的代码
root = Tk()
build_menu(root)
upframe = Frame(root)
downframe = Frame(root)
leftframe = Frame(downframe)
rightframe = Frame(downframe)
upframe.pack(side="top")
downframe.pack()
leftframe.pack(side="left")
rightframe.pack(side="right")
root.title("计设大赛赛事管理系统")
root.geometry('700x650+350+100')
mytable = MyTable(upframe)
for i, j in enumerate(tablecolumns):
Label(leftframe, text=j).grid(row=i)
entry1 = Entry(leftframe)
entry2 = Entry(leftframe)
entry3 = Entry(leftframe)
entry4 = Entry(leftframe)
entry5 = Entry(leftframe)
entry6 = Entry(leftframe)
entry1.grid(row=0, column=1, padx=20, pady=5)
entry2.grid(row=1, column=1, padx=20, pady=5)
entry3.grid(row=2, column=1, padx=20, pady=5)
entry4.grid(row=3, column=1, padx=20, pady=5)
entry5.grid(row=4, column=1, padx=20, pady=5)
entry6.grid(row=5, column=1, padx=20, pady=5)
button1 = Button(rightframe, text="由编号查询团队信息", command=search_team)
button2 = Button(rightframe, text="增添参赛团队信息", command=mytable.add_info)
button3 = Button(rightframe, text="由编号删除团队信息", command=mytable.delete_info)
button4 = Button(rightframe, text="一键清空输入框", command=clear_entry)
button5 = Button(rightframe, text="修改参赛团队信息", command=mytable.change_info)
button1.pack(anchor="w", padx=20, pady=5)
button2.pack(anchor="w", padx=20, pady=5)
button3.pack(anchor="w", padx=20, pady=5)
button4.pack(anchor="w", padx=20, pady=5)
button5.pack(anchor="w", padx=20, pady=5)
root.protocol('WM_DELETE_WINDOW', QueryWindow)
root.mainloop()
以下是对树视图初始化的代码
def __init__(self, master):
xscroll = Scrollbar(master, orient=HORIZONTAL) #增添滑动控件,方便用户查看
yscroll = Scrollbar(master, orient=VERTICAL)
xscroll.pack(side=BOTTOM, fill=X)
yscroll.pack(side=RIGHT, fill=Y)
self.table = ttk.Treeview(master=master,
columns=tablecolumns,
height=15,
show='headings',
xscrollcommand=xscroll.set,
yscrollcommand=yscroll.set)
self.table.pack()
xscroll.config(command=self.table.xview)
yscroll.config(command=self.table.yview)
for i in range(len(tablecolumns)):
self.table.heading(column=tablecolumns[i], text=tablecolumns[i], anchor=CENTER)
self.table.column(column=tablecolumns[i], anchor=CENTER, width=150, stretch=True)
self.table.column(column=tablecolumns[1], anchor=CENTER, width=380, stretch=True)
with open("team.txt", "r", encoding="utf-8") as file:
messages = file.readlines()
for message in messages[1:]:
ateam = message.replace("\t", "").strip().split("#")
self.table.insert('', END, values=ateam)
management[ateam[0]] = TeamInfo(ateam[1], ateam[2], ateam[3], ateam[4], ateam[5])
2.并为MyTable类增添为信息增删改的实例方法,直接对创建的树视图进行操作,并将结果展示出来。
def add_info(self):
tablevalues2 = list()
tablevalues2.append(entry1.get())
#将用户输入到输入框内的编号和其他信息作为新的键值对添加到Management字典中
tablevalues2.append(entry2.get())
tablevalues2.append(entry3.get())
tablevalues2.append(entry4.get())
tablevalues2.append(entry5.get())
tablevalues2.append(entry6.get())
self.table.insert('', END, values=tablevalues2)
management[entry1.get()] = TeamInfo(entry2.get(),
entry3.get(),
entry4.get(),
entry5.get(),
entry6.get())
self.table.selection_set(self.table.get_children()[-1])
messagebox.showinfo(title="提示", message="已添加!")
def delete_info(self):
if entry1.get() in management.keys():
index = list(management.keys()).index(entry1.get())
self.table.delete(self.table.get_children()[index])
del management[entry1.get()]
messagebox.showinfo(title="提示", message="已删除!")
else:
messagebox.showerror("提示", "该编号不存在!")
def change_info(self):
tablevalues1 = list()
tablevalues1.append(entry1.get())
tablevalues1.append(entry2.get())
tablevalues1.append(entry3.get())
tablevalues1.append(entry4.get())
tablevalues1.append(entry5.get())
tablevalues1.append(entry6.get())
dex = list(management.keys()).index(entry1.get())
self.table.item(self.table.get_children()[dex], values=tablevalues1)
management[entry1.get()] = TeamInfo(entry2.get(),
entry3.get(),
entry4.get(),
entry5.get(),
entry6.get())
messagebox.showinfo("提示", "修改成功!")
def clear_entry():
entry1.delete(0, "end")
entry2.delete(0, "end")
entry3.delete(0, "end")
entry4.delete(0, "end")
entry5.delete(0, "end")
entry6.delete(0, "end")
利用二叉排序树,由团队编号查询团队信息的代码不涉及树视图的操作,所以不作为类的实例方法,当查找编号不存在时即弹出提示窗口。
def search_team():
numbers = list(management.keys())
a = BiSortTree(len(numbers))
a.create_bst(numbers)
num = entry1.get()
result, asl = a.search(a.root, int(num))
if result == "查找成功":
entry2.insert(0, management[num].work)
entry3.insert(0, management[num].school)
entry4.insert(0, management[num].category)
entry5.insert(0, management[num].competitor)
entry6.insert(0, management[num].teacher)
messagebox.showinfo(title="查询结果",message="查找成功,ASL="+str(asl))
else:
messagebox.showerror(title="错误",message="查找失败")
为了用户下次进入该系统时,仍然保留上次操作的结果,我们使用WM_DELETE_WINDOW协议,当用户需要退出界面时,不直接销毁窗口,而是弹出询问框询问用户是否需要保存当前修改,如果选择是,则将修改的结果一次性全部覆盖之前的内容,重新读入txt文档。
def QueryWindow():
if messagebox.askyesno("提示", "是否保存所有对信息的修改?"):
with open("team.txt", "w", encoding="utf-8") as file:
file.write("参赛队编号\t#\t参赛作品名称\t#\t参赛学校\t#\t赛事类别\t#\t参赛者\t#\t指导老师\n")
for i in management.keys():
file.write(i + "\t")
file.write("#\t" + management[i].work + "\t")
file.write("#\t" + management[i].school + "\t")
file.write("#\t" + management[i].category + "\t")
file.write("#\t" + management[i].competitor + "\t")
file.write("#\t" + management[i].teacher + "\n")
root.destroy()
这是界面展示:
接着是叫号系统,为了使界面美观,增添菜单,把叫号系统单独做一个界面,使其可以选择功能。叫号系统分两个部分:一个是由编号查询团队所在的决赛室、叫号,另一个是让用户选择模拟一个决赛室的叫号。
以下是创建顶级菜单和菜单项的代码,展示用户可以选择的功能,当点击不同的功能时会新创建一个顶级窗口toplevel,并调用相应的函数实现其功能。
def build_menu(menu_bar):
main_menu=Menu(menu_bar)
main_menu.add_command(label="管理")
submenu=Menu(main_menu,activebackground="pink",tearoff=False)
submenu.add_command(label="查询叫号",command=lambda:build_toplevel1(menu_bar))
submenu.add_command(label="模拟叫号",command=build_toplevel2)
main_menu.add_cascade(label="叫号",menu=submenu)
menu_bar.config(menu=main_menu)
先将全部团队平均、随机地分配到九个决赛室,同时创建9个列表存入团队编号,并将每个决赛室对应地列表存入到全局变量rooms总列表中,以便其他函数的使用。
def allocation():
on_list = set(management.keys())
for i in range(8):
extraction = set(random.sample(list(on_list), 44))
rooms.append(extraction)
on_list = on_list - extraction
这是创建查询决赛室和叫号的窗口代码,并增添了一个按钮控件和输入控件。当用户输入团队编号并按下按键时,将会调用查询决赛室和叫号的函数,并弹出提示框。
def build_toplevel1(root):
toplevel = Toplevel(root)
toplevel.transient(root)
toplevel.geometry("200x100+600+400")
toplevel.title("查询叫号")
lb = Label(toplevel, text="需要查询的团队编号:")
lb.pack()
inp = Entry(toplevel)
inp.pack()
bn = Button(toplevel, text="查询",command=lambda: check_call(inp))
bn.pack()
def check_call(inp):
allocation()
needed_enquire = inp.get()
for room_number, teams in enumerate(rooms, 1):
if needed_enquire in teams:
messagebox.showinfo(title="结果",
message="决赛室"+str(room_number)+",叫号为"+str(list(teams).index(needed_enquire)))
break
else:
messagebox.showerror(title="结果", message="该团队编号不存在,输入有误")
而为了实现更好的交互功能,在实现模拟叫号功能时需弹出两个窗口,一个是用户选择模拟的决赛室号,以及选择之后弹出的模拟叫号的窗口。我们在用户选择模拟决赛窗口的窗口中增添九个单选按钮和一个确定按钮,当用户按下确定按钮后,便会调用创建模拟窗口的函数。为了动态展示叫号顺序与进场顺序的一致,我在左边展示了全部的叫号顺序在右边展示动态的进场过程,并增添开始和停止两个按钮,当用户按下停止的按钮,模拟界面会被销毁(应该做成停止模拟的状态的,直接销毁不太好其实,后续改进)。并设置1s钟出现一个团队编号,那么考虑到每次调用叫号模拟函数后会从头开始遍历该决赛室的列表,使用迭代器,保证每次调用该函数时都是接着从上次迭代的位置开始迭代。
def build_toplevel3(event,toplevel,room_number):
allocation()
call_number = iter(rooms[room_number])
subwindow = Toplevel(toplevel)
subwindow.transient()
subwindow.geometry("500x500+600+200")
subwindow.title("正在模拟叫号")
lframe = Frame(subwindow)
lframe.pack(side="left")
rframe = Frame(subwindow)
rframe.pack(side="right")
yscroll = Scrollbar(subwindow, orient=VERTICAL)
yscroll.activate("slider")
yscroll.pack(side=RIGHT, fill=Y)
Label(lframe,text="叫号顺序",font=("微软雅黑",20,"bold")).grid(row=0)
Label(rframe, text="正在模拟决赛室" + str(room_number + 1), font=("微软雅黑", 20, "bold")).pack(side="top")
Button(rframe, text="开始", command=lambda: call_stimulation(call_number, text)).pack()
Button(rframe, text="停止", command=lambda: subwindow.destroy()).pack()
text = Text(rframe, font=("微软雅黑", 20, "bold"), fg="crimson",yscrollcommand=yscroll.set)
text.pack(fill=Y)
yscroll.config(command=text.yview)
for j,i in enumerate(rooms[room_number],1):
Label(lframe,text=str(i),font=("微软雅黑", 20, "bold")).grid(row=j+1)
def call_stimulation(call_number,text):
try:
text.insert(END,str(next(call_number))+"\n")
text.after(1000,lambda:call_stimulation(call_number,text))
except:
messagebox.showinfo("提示","叫号完毕")
效果展示如下:
感觉这个界面有点小小拉跨,考虑用ttkbootstrap美化一下界面,但是安装不了,总是报错。
下次更新