Python中基于Tkinter的学生信息管理系统GUI界面设计(第4节)


在日常生活和工作中,我们经常需要将大量数据保存到Excel电子表格中以进行分析和管理。在Python中,可以使用openpyxl库来操作Excel表格。通过使用Python,我们可以利用其简洁和功能强大的特性,结合Tkinter提供的GUI开发能力,快速构建一个学生信息管理系统GUI界面。

在本节教程中,我们将介绍如何使用Tkinter库来创建一个基于GUI界面的学生信息管理系统,简单实现添加学生信息到Excel表格中(包括:学号、姓名、性别、年龄、专业、班级、电话等)。系统支持从Excel表格中查询学生信息(支持按学号、姓名等任意条件查询),同时支持增加、删除和修改Excel表格中的学生信息,并将编辑后的数据内容保存到Excel表格中。运行该程序时,默认会在D盘创建一个StudentData.xlsx文件,用于存储学生信息。注意,运行该程序前,如果StudentData.xlsx文件已被双击打开,必须先关闭该Excel文件,否则会报错并自动退出程序。

学生信息管理系统具体流程图如下:

创建一个基于图形用户界面(GUI)的学生信息管理系统是一个涉及多个步骤的过程。下面简单介绍设计过程,使用了tkinter库来构建GUI和openpyxl库来处理数据存储。

第一步、安装openpyxl库

首先,要使用Python操作Excel表格,可以使用openpyxl库openpyxl库是一个用于处理Excel文件的第三方库。‌它专门处理xlsx格式的Excel文件,支持读写Excel文件、操作单元格、创建和修改工作表、设置样式、添加图表和公式等功能。

openpyxl不是Python的标准库,在使用openpyxl模块之前,首先需要将其安装到Python环境中。使用Python的包管理工具pip来安装openpyxl库是最简单的方法。打开Pyhint编辑器,点击Pyhint编辑器上面的“打开终端”按钮,在弹出的黑色cmd终端窗口中,输入以下命令安装openpyxl库:

pip install openpyxl

执行以上安装命令,如果运行结果出现“Successfully installed”,说明已经安装成功;如果运行结果出现“Requirement already satisfied”,说明该模块已经被安装过,不用再安装。不懂得安装第三方库,可以参考本教程第10章中第9节和第10节里面的内容。

第二步、使用openpyxl库操作Excel表格

首先,为了使学生信息的数据易于保存和读取,我们需要使用openpyxl库操作Excel表格来存储和读取数据。目前,Excel表格文件普遍被用于存储大量的数据,如名单、账目、库存列表等,同时我们也可以直接双击打开Excel表格手动输入数据。

这里,我们先指定Excel文件保存的路径为D盘的“StudentData.xlsx”。使用os.path.exists()方法检查文件是否存在,如果不存在则创建该Excel文件。当我们在使用openpyxl库操作Excel文件时,如果文件没有被关闭(已被人为双击打开),就会出现PermissionError: [Errno 13] Permission denied的错误。为了解决这个问题,这里我们使用try-except语句来捕获异常,并在发生异常时退出程序。代码如下:

动手练一练:

# 指定Excel文件路径
file_path = r"D:/StudentData.xlsx"
# 检查文件是否存在
if os.path.exists(file_path):
    pass
else:
    # 文件不存在,则创建一个新的Workbook对象
    workbook = openpyxl.Workbook()
    # 最后保存Workbook到指定路径
    # 将在D盘创建一个StudentData.xlsx文件
    workbook.save(file_path)
# 利用try语句打开Excel文件,捕获并处理可能出现的异常
try:
    # 获取指定的文件
    wb = openpyxl.load_workbook(file_path)
    # 选择默认的工作表
    ws = wb.active
    # 在Excel文件的第一行写入标题信息
    # 写入内容到对应单元格 A1
    ws['A1'] = "学生学号"
    # 写入内容到对应单元格 B1
    ws['B1'] = "学生姓名"
    # 写入内容到对应单元格 C1
    ws['C1'] = "学生性别"
    # 写入内容到对应单元格 D1
    ws['D1'] = "学生年龄"
    # 写入内容到对应单元格 E1
    ws['E1'] = "学生专业"
    # 写入内容到对应单元格 F1
    ws['F1'] = "班级"
    # 写入内容到对应单元格 G1
    ws['G1'] = "电话号码"
    # 保存文件到"D:/StudentData.xlsx"
    wb.save(file_path)
except PermissionError:
    # 如果Excel文件已经被打开,则捕获异常并弹出提示信息
    messagebox.showinfo("提示", "StudentData.xlsx文件已经被打开,请先关闭...")
    # 关闭主进程
    sys.exit()

上面的代码中,在存储和读取学生信息前,我们首先在Excel文件的默认工作表“Sheet”的第一行中写入标题信息,包括:学号、姓名、性别、年龄、专业、班级、电话,以便通过结构化的方式来组织数据,提高数据的可读性。

第三步、定义写入和读取Excel文件的方法

在写入和读取Excel文件前,我们先定义一个全局变量“Info= []”,用来存储学生信息,默认为空列表。函数ReadTxt()从Excel文件里面的第二行开始读取数据(注意:第一行为固定的标题信息),再将读取后的信息以“键值对”的字典形式返回一个混合列表,最后通过try语句将返回的列表信息存入全局变量Info中。代码如下:

动手练一练:

# 定义一个全局变量,用来存储学生信息,默认为空列表
Info = []

# 定义一个方法用于写入文件:传入已经变更好的信息列表,然后遍历写入Excel文件
# 点击"写入"按钮被触发的函数
def WriteTxt_w_Mode(Student_List):
    try:
        # 将第一行设置为当前行
        ws._current_row = 1
        # 获取第一行的最大列号
        max_column = ws.max_column
        # 遍历第一行之后的所有行并删除单元格内容
        # min_row=2表示从第二行开始迭代
        for row in ws.iter_rows(min_row=2):
            for cell in row[:max_column]:
                cell.value = None
        # 使用列表推导式提取所有字典的值
        multi_list = [list(d.values()) for d in Student_List]
        # 将多元列表中的数据追加到Excel工作表中
        for row in multi_list:
            # 使用append方法,将行数据按行追加写入
            ws.append(row)
        # 保存文件
        wb.save(file_path)
    except PermissionError:
        # 如果文件已经被打开,则捕获异常并弹出提示信息
        messagebox.showinfo("提示", "StudentData.xlsx文件已经被打开,请先关闭...")
        # 关闭主进程
        sys.exit()
# 定义一个方法用于读取Excel文件,“-> list”是注释,表示函数的返回值类型为“列表”
def ReadTxt() -> list:
    try:
        # 创建两个空的临时列表
        Temp_List1 = []
        Temp_List2 = []
        # 加载Excel文件
        workbook_read = openpyxl.load_workbook(filename=file_path)
        # 获取默认的工作表
        sheet = workbook_read.active
        # 从第二行开始遍历行,注意第一行为标题信息
        for row in sheet.iter_rows(min_row=2, values_only=True):
            # 将每一行的内容转换为字符串格式
            row_strings = [str(cell) for cell in row]
            # 将每一行的内容追加到临时列表“Temp_List1”中
            Temp_List1.append(list(row_strings))
        # 将读取的信息以“键值对”的字典形式并入临时列表“Temp_List2”
        while len(Temp_List2) < len(Temp_List1):
            for j in range(0,len(Temp_List1)):
                ID = Temp_List1[j][0]
                Name = Temp_List1[j][1]
                Sex = Temp_List1[j][2]
                Age = Temp_List1[j][3]
                Major = Temp_List1[j][4]
                Class = Temp_List1[j][5]
                Telephone = Temp_List1[j][6]
                Info_dict = {"ID":ID,
                            "Name":Name,
                            "Sex":Sex,
                            "Age":Age,
                            "Major":Major,
                            "Class":Class,
                            "Telephone":Telephone
                            }
                Temp_List2.append(Info_dict)
        # 关闭工作簿
        workbook_read.close()
        # 将含有学生信息的临时列表返回
        return Temp_List2
    except PermissionError:
        # 如果文件已经被打开,则捕获异常并弹出提示信息
        messagebox.showinfo("提示", "StudentData.xlsx文件已经被打开,请先关闭...")
        # 关闭主进程
        sys.exit()
# 用于将ReadTxt()方法返回的学生信息存入全局变量Info中
try:
    for i in ReadTxt():
        Info.append(i)
except:
    pass

第四步、设计学生信息管理系统GUI界面

学生信息管理系统主要包括以下几个GUI窗口界面:

添加学生信息窗口界面:通过输入学生的个人信息,即学号、姓名、性别、年龄、专业、班级、电话,来实现添加学生的操作。具体实现的过程是用户通过输入框输入学号、姓名、性别、年龄、专业、班级、电话,然后使用get()方法来获取各个输入框的内容,判断学号是否重复,并且限制学号为6位数,限制年龄范围在0~25内,限制电话号码为11位数字,限制班级为大于0的任何数字,如果所有的验证成功通过,则把信息写入Excel文件内。

删除学生信息窗口界面:通过输入学生的学号来实现删除学生信息的操作。具体实现的过程是用户通过输入框输入学号,然后通过get()方法来获取输入框的学号;再通过遍历全局变量Info来判断学号是否存在,如果存在则把用户输入学号的匹配数据所在行删除,最后将全局变量Info信息再写入Excel文件中。

修改学生信息窗口界面:通过输入学生的学号来实现对学生信息的修改。具体实现的过程是用户通过输入框输入学号,然后通过get()方法来获取输入框的学号;再通过遍历全局变量Info检查学号是否存在;如果存在则进入修改页面,输入信息到输入框,通过get()方法获取修改信息,最后将全局变量Info信息再写入Excel文件中。

查询学生信息窗口界面:通过输入学生的信息条件来实现对学生信息的查询。具体实现的过程是用户通过输入框输入学号、姓名、性别、年龄、专业、班级、电话其中的任何一个信息(可不全填),然后通过get()方法来获取输入框的信息;再通过遍历全局变量Info查询所有符合条件的学生信息,最后在主界面打印出符合条件的学生信息。

显示所有学生信息主界面:具体实现的过程是通过遍历全局变量Info的全部内容,通过获取result变量统计输出汇总学生人数,最后通过tkinter的Text文本控件把所有学生信息全部有序地在主界面显示出来。

退出窗口界面:点击“确认”退出程序,“确认”按钮控件绑定函数destroy()用于关闭主窗口,并释放所占用的资源。

部分窗口界面图片如下:

以下是学生信息管理系统的总代码:

动手练一练:

import os
import sys
# 使用tkinter模块实现GUI界面
import tkinter as tk
from tkinter import messagebox
# 导入库,openpyxl用于处理Excel文件
import openpyxl
from tkinter import ttk

# 定义一个全局变量,用来存储学生信息,默认为空列表
Info = []
# 指定Excel文件路径
file_path = r"D:/StudentData.xlsx"
# 检查文件是否存在
if os.path.exists(file_path):
    pass
else:
    # 文件不存在,则创建一个新的Workbook对象
    workbook = openpyxl.Workbook()
    # 最后保存Workbook到指定路径
    # 将在D盘创建一个StudentData.xlsx文件
    workbook.save(file_path)
# 利用try语句打开Excel文件,捕获并处理可能出现的异常
try:
    # 获取指定的文件
    wb = openpyxl.load_workbook(file_path)
    # 选择默认的工作表
    ws = wb.active
    # 在Excel文件的第一行写入标题信息
    # 写入内容到对应单元格 A1
    ws['A1'] = "学生学号"
    # 写入内容到对应单元格 B1
    ws['B1'] = "学生姓名"
    # 写入内容到对应单元格 C1
    ws['C1'] = "学生性别"
    # 写入内容到对应单元格 D1
    ws['D1'] = "学生年龄"
    # 写入内容到对应单元格 E1
    ws['E1'] = "学生专业"
    # 写入内容到对应单元格 F1
    ws['F1'] = "班级"
    # 写入内容到对应单元格 G1
    ws['G1'] = "电话号码"
    # 保存文件到"D:/StudentData.xlsx"
    wb.save(file_path)
except PermissionError:
    # 如果Excel文件已经被打开,则捕获异常并弹出提示信息
    messagebox.showinfo("提示", "StudentData.xlsx文件已经被打开,请先关闭...")
    # 关闭主进程
    sys.exit()

# 定义一个方法用于写入文件:传入已经变更好的信息列表,然后遍历写入Excel文件
# 点击"写入"按钮被触发的函数
def WriteTxt_w_Mode(Student_List):
    try:
        # 将第一行设置为当前行
        ws._current_row = 1
        # 获取第一行的最大列号
        max_column = ws.max_column
        # 遍历第一行之后的所有行并删除单元格内容
        # min_row=2表示从第二行开始迭代
        for row in ws.iter_rows(min_row=2):
            for cell in row[:max_column]:
                cell.value = None
        # 使用列表推导式提取所有字典的值
        multi_list = [list(d.values()) for d in Student_List]
        # 将多元列表中的数据追加到Excel工作表中
        for row in multi_list:
            # 使用append方法,将行数据按行追加写入
            ws.append(row)
        # 保存文件
        wb.save(file_path)
    except PermissionError:
        # 如果文件已经被打开,则捕获异常并弹出提示信息
        messagebox.showinfo("提示", "StudentData.xlsx文件已经被打开,请先关闭...")
        # 关闭主进程
        sys.exit()

# 定义一个方法用于读取Excel文件,“-> list”是注释,表示函数的返回值类型为“列表”
def ReadTxt() -> list:
    try:
        # 创建两个空的临时列表
        Temp_List1 = []
        Temp_List2 = []
        # 加载Excel文件
        workbook_read = openpyxl.load_workbook(filename=file_path)
        # 获取默认的工作表
        sheet = workbook_read.active
        # 从第二行开始遍历行,注意第一行为标题信息
        for row in sheet.iter_rows(min_row=2, values_only=True):
            # 将每一行的内容转换为字符串格式
            row_strings = [str(cell) for cell in row]
            # 将每一行的内容追加到临时列表“Temp_List1”中
            Temp_List1.append(list(row_strings))
        # 将读取的信息以“键值对”的字典形式并入临时列表“Temp_List2”
        while len(Temp_List2) < len(Temp_List1):
            for j in range(0,len(Temp_List1)):
                ID = Temp_List1[j][0]
                Name = Temp_List1[j][1]
                Sex = Temp_List1[j][2]
                Age = Temp_List1[j][3]
                Major = Temp_List1[j][4]
                Class = Temp_List1[j][5]
                Telephone = Temp_List1[j][6]
                Info_dict = {"ID":ID,
                            "Name":Name,
                            "Sex":Sex,
                            "Age":Age,
                            "Major":Major,
                            "Class":Class,
                            "Telephone":Telephone
                            }
                Temp_List2.append(Info_dict)
        # 关闭工作簿
        workbook_read.close()
        # 将含有学生信息的临时列表返回
        return Temp_List2
    except PermissionError:
        # 如果文件已经被打开,则捕获异常并弹出提示信息
        messagebox.showinfo("提示", "StudentData.xlsx文件已经被打开,请先关闭...")
        # 关闭主进程
        sys.exit()

# 用于将ReadTxt()方法返回的学生信息存入全局变量Info中
try:
    for i in ReadTxt():
        Info.append(i)
except:
    pass
# 定于一个方法,用于检查学号是否规范,返回6位数字
def is_ID(ID):
    return len(ID) == 6 and ID.isdigit()
# 定于一个方法,用于检查年龄是否规范,返回0~25的数字
def is_Age(Age):
    return Age.isdigit() and 0 <= int(Age) and int(Age) <= 25
# 定于一个方法,用于检查班级是否规范,返回任何大于0的数字
def is_Class(Class):
    return Class.isdigit() and int(Class) > 0
# 定于一个方法,用于检查电话是否规范,返回11位数字的电话号码
def is_Telephone(Telephone):
    return len(Telephone) == 11 and Telephone.isdigit()
# 定义一个方法,用于判断是否为中文字符
def is_Chinese(ch):
    if ch >= '\u4e00' and ch <='\u9fa5':
        return True
    else:
        return False
# 定义一个方法,用于计算中西文混合字符串的字符串长度
def Len_Str(string):
    count = 0
    for line in string:
        if is_Chinese(line):
            count = count + 2
        else:
            count = count + 1
    return count
# 提示信息
def Tip_Add():
    messagebox.showinfo("提示信息","添加成功")
def Tip_Search():
    messagebox.showinfo("提示信息","查询成功")
def Tip_Del():
    messagebox.showinfo("提示信息","删除成功")
def Tip_Mod():
    messagebox.showinfo("提示信息","修改成功")
def Tip_Add_ID_Repeat():
    messagebox.showinfo("提示信息","此学号已经存在,请勿重复添加!")
def Tip_Del_ID_None():
    messagebox.showinfo("提示信息","此学号不存在,请重新输入!")
def Tip_Search_None():
    messagebox.showinfo("提示信息","未查询到有关学生信息!")
def Tip_Add_ID():
    messagebox.showinfo("提示信息","学号格式有误,请重新输入!")
def Tip_Add_Age():
    messagebox.showinfo("提示信息","年龄格式有误,请重新输入!")
def Tip_Add_Class():
    messagebox.showinfo("提示信息","班级格式有误,请重新输入!")
def Tip_Add_Telephone():
    messagebox.showinfo("提示信息","电话格式有误,请重新输入!")
# 1.添加学生信息的主方法
def Add_Student_Info(ID, Name, Sex, Age, Major, Class, Telephone):
    # 导入信息
    global Info
    # 检查输入信息是否规范
    if not is_ID(ID):
        Tip_Add_ID()
        return
    # 判断学号是否重复
    for i in Info:
        if ID == i['ID']:
            Tip_Add_ID_Repeat()
            return
    if not is_Age(Age):
        Tip_Add_Age()
        return
    if not is_Class(Class):
        Tip_Add_Class()
        return
    if not is_Telephone(Telephone):
        Tip_Add_Telephone()
        return
    # 用字典整合学生信息
    Info_dict = {'ID':ID, 'Name':Name, 'Sex':Sex, 'Age':Age, 'Major':Major, 'Class':Class, 'Telephone':Telephone}
    # 将字典存入总列表
    Info.append(Info_dict)
    # 添加成功
    Tip_Add()
    # 将信息写入文件
    WriteTxt_w_Mode(Info)

# 2.删除学生信息的主方法
def Del_Student_Info(ID):
    # 检查输入信息是否规范
    if not is_ID(ID):
        Tip_Add_ID()
        return
    # 用于指示是否删除的状态指标
    Flag = True
    # 导入信息
    global Info
    # 遍历,删除学生信息
    for i in Info:
        if ID == i["ID"]:
            Info.remove(i)
            Flag = False
            break
    if Flag:
        Tip_Del_ID_None()
        return
    # 删除成功
    Tip_Del()
    # 将删除后的信息写入文件
    WriteTxt_w_Mode(Info)

# 3.修改学生信息,ID符合条件则打开修改学生信息的窗口
def Mod_Student_Info(ID):
    if not is_ID(ID):
        Tip_Add_ID()
        return
    # 用于指示是否打开窗口的指标
    Flag = True
    global Info
    for i in Info:
        if ID == i["ID"]:
            Window_Mod_Input(ID)
            Flag = False
            break
    if Flag:
        Tip_Del_ID_None()
        return

# 3.修改学生信息的主方法
def Mod_Student_Info_1(ID, Name, Sex, Age, Major, Class, Telephone):
    # 检查输入信息是否规范
    if not is_ID(ID):
        Tip_Add_ID()
        return
    if not is_Age(Age):
        Tip_Add_Age()
        return
    if not is_Class(Class):
        Tip_Add_Class()
        return
    if not is_Telephone(Telephone):
        Tip_Add_Telephone()
        return
    # 导入信息
    global Info
    # 遍历,修改学生信息
    for i in Info:
        if i["ID"] == ID:
            i["Name"] = Name
            i["Sex"] = Sex
            i["Age"] = Age
            i["Major"] = Major
            i["Class"] = Class
            i["Telephone"] = Telephone
    # 修改成功
    Tip_Mod()
    # 将修改后的信息写入文件
    WriteTxt_w_Mode(Info)

# 4.查找学生信息的主方法
def Search_Student_Info(ID, Name, Major, Age, Class, Telephone):
    # 检查输入是否规范,规范的和空字符串通过
    if len(ID) != 0 and not is_ID(ID):
        Tip_Add_ID()
        return
    if len(Age) != 0 and not is_Age(Age):
        Tip_Add_Age()
        return
    if len(Class) != 0 and not is_Class(Class):
        Tip_Add_Class()
        return
    if len(Telephone) != 0 and not is_Telephone(Telephone):
        Tip_Add_Telephone()
        return
    # 导入信息
    global Info
    # 临时列表
    List = []
    # 用来指示是否查找到学生的信息指标
    Flag = False
    # 遍历,根据输入的部分信息找到符合条件的学生
    for i in Info:
        # and后面加“\”,代表行续符,用于告诉Python解释器,当前行代码未完,下一行也是这个代码的一部分
        if (i["ID"]==ID or len(ID)==0)and \
            (i["Name"]==Name or len(Name)==0)and \
            (i["Major"]==Major or len(Major)==0)and \
            (i["Age"]==Age or len(Age)==0)and \
            (i["Class"]==Class or len(Class)==0)and \
            (i["Telephone"]==Telephone or len(Telephone)==0)and \
            (len(ID)!=0 or len(Name)!=0 or len(Major)!=0 or len(Age)!=0 or len(Class)!=0 or len(Telephone)!=0 ):
            List.append(i)
    if len(List) != 0:
        Flag = True
    # 在主界面打印符合条件学生信息
    Print_Student_Info(List)
    # 是否查找成功
    if Flag:
        Tip_Search()
    else:
        Tip_Search_None()

# 5.显示所有学生的主方法
def Print_Student_Info(Student_Info):
    # 定义一个字符串用于存储想要输出显示的内容
    str_out = ""
    # 学生信息为空将返回
    if Student_Info is None:
        result.set("学生信息为空")
        return
    if len(Student_Info) == 0:
        # 清空输出框,'1.0' 是开始位置,表示第一行的第0个字符
        # tk.END是一个特殊的常量,表示Text控件中的最后一个字符的位置。
        Show_result.delete(1.0, tk.END)
        # 在主界面显示学生信息
        result.set("无学生信息")
        Show_result.insert('end', result.get())       
        return
    # 学生信息不为空
    if len(Student_Info) != 0:
        # 显示信息标题
        str_out += ("{:^5}".format("学生学号") +
                    "{:^7}".format("学生姓名") +
                    "{:^7}".format("学生性别") +
                    "{:^7}".format("学生年龄") +
                    "{:^7}".format("学生专业") +
                    "{:^5}".format("班级") +
                    "{:^9}".format("电话号码") +
                    "\n")
        for i in range(0, len(Student_Info)):
            # 格式化字符串
            str_out += ("{:^}".format(Student_Info[i].get("ID")) + ' '*(11-Len_Str(Student_Info[i].get("ID"))) +
                        "{:^}".format(Student_Info[i].get("Name")) +' '*(11-Len_Str(Student_Info[i].get("Name")))+
                        "{:^}".format(Student_Info[i].get("Sex")) +' '*(11-Len_Str(Student_Info[i].get("Sex")))+
                        "{:^}".format(Student_Info[i].get("Age")) +' '*(10-Len_Str(Student_Info[i].get("Age")))+
                        "{:^}".format(Student_Info[i].get("Major")) +' '*(13-Len_Str(Student_Info[i].get("Major")))+
                        "{:^}".format(Student_Info[i].get("Class")) +' '*(5-Len_Str(Student_Info[i].get("Class")))+
                        "{:^}".format(Student_Info[i].get("Telephone")) +' '*(11-Len_Str(Student_Info[i].get("Telephone")))+
                        "\n")
        # 清空输出框,'1.0' 是开始位置,表示第一行的第0个字符
        # tk.END是一个特殊的常量,表示Text控件中的最后一个字符的位置。
        Show_result.delete(1.0, tk.END)
        # 在主界面显示学生信息
        result.set(str_out)
        Show_result.insert('end', result.get())
        # 清空“汇总”统计输出框
        total_text.delete(1.0, tk.END)
        # 在主界面显示学生信息的数量
        lists_str = result.get()
        lists = [lst for lst in lists_str.split('\n') if lst]
        total_text.insert(tk.END, f'{len(lists)-1}')

# 添加学生信息的窗口
def Window_Add():
    # 实例化对象,创建app的子窗口window
    window = tk.Toplevel(app)
    # 窗口名字
    window.title("添加学生信息")
    # 设置窗口大小:宽x高(500x500),并指定窗口在屏幕上的位置为(500, 200)
    window.geometry('500x500+500+200')
    # 关于学号的 label 和 entry
    Txt_ID = tk.StringVar()
    Txt_ID.set("")
    Label_Line1 = tk.Label(window, text = "学 号 (6 位):", font = ('黑体', 12), width = 15)
    Label_Line1.place(x = 75, y = 50, anchor = 'nw')
    Entry_Line1 = tk.Entry(window, show = None, font = ('黑体', 15), textvariable = Txt_ID, width = 20)
    Entry_Line1.place(x = 200, y = 50, anchor = 'nw')
    # 关于姓名的 label 和 entry
    Txt_Name = tk.StringVar()
    Txt_Name.set("")
    Label_Line2 = tk.Label(window, text = "姓 名:", font = ('黑体', 12), width = 15)
    Label_Line2.place(x = 75, y = 100, anchor = 'nw')
    Entry_Line2 = tk.Entry(window, show = None, font = ('黑体', 15), textvariable = Txt_Name, width = 20)
    Entry_Line2.place(x = 200, y = 100, anchor = 'nw')
    # 关于性别的 label 和 entry
    sex = tk.StringVar()
    sex.set("男")
    Label_Line7 = tk.Label(window, text = "性 别:", font = ('黑体', 12), width = 15)
    Label_Line7.place(x = 75, y = 150, anchor = 'nw')
    sex_input1 = tk.Radiobutton(window, text="男", variable=sex, value="男")
    sex_input2 = tk.Radiobutton(window, text="女", variable=sex, value="女")
    sex_input1.place(x = 200, y = 150, anchor = 'nw')
    sex_input2.place(x = 260, y = 150, anchor = 'nw')
    # 关于年龄的 label 和 entry
    Txt_Age = tk.StringVar()
    Txt_Age.set("")
    Label_Line4 = tk.Label(window, text = "年 龄 (0~25):", font = ('黑体', 12), width = 15)
    Label_Line4.place(x = 75, y = 200, anchor = 'nw')
    Entry_Line4 = tk.Entry(window, show = None, font = ('黑体', 15), textvariable = Txt_Age, width = 20)
    Entry_Line4.place(x = 200, y = 200, anchor = 'nw')
    # 关于专业的 label 和 entry
    Txt_Major = tk.StringVar()
    Txt_Major.set("")
    Label_Line3 = tk.Label(window, text = "专 业:", font = ('黑体', 12), width = 15)
    Label_Line3.place(x = 75, y = 250, anchor = 'nw')
    Entry_Line3 = tk.Entry(window, show = None, font = ('黑体', 15), textvariable = Txt_Major, width = 20)
    Entry_Line3.place(x = 200, y = 250, anchor = 'nw')
    # 关于班级的 label 和 entry
    Txt_Class = tk.StringVar()
    Txt_Class.set("")
    Label_Line5 = tk.Label(window, text = "班 级 (数字):", font = ('黑体', 12), width = 15)
    Label_Line5.place(x = 75, y = 300, anchor = 'nw')
    Entry_Line5 = tk.Entry(window, show = None, font = ('黑体', 15), textvariable = Txt_Class, width = 20)
    Entry_Line5.place(x = 200, y = 300, anchor = 'nw')
    # 关于电话的 label 和 entry
    Txt_Telephone = tk.StringVar()
    Txt_Telephone.set("")
    Label_Line6 = tk.Label(window, text = "电 话 (11位):", font = ('黑体', 12), width = 15)
    Label_Line6.place(x = 75, y = 350, anchor = 'nw')
    Entry_Line6 = tk.Entry(window, show = None, font = ('黑体', 15), textvariable = Txt_Telephone, width = 20)
    Entry_Line6.place(x = 200, y = 350, anchor = 'nw')
    # 关于"确认"组件,此处绑定函数Add_Student_Info用于添加学生信息
    Button1_Yes = tk.Button(window, text = '确认', bg = 'silver', font = ('黑体', 12), command = lambda:Add_Student_Info(Txt_ID.get(), Txt_Name.get(), sex.get(), Txt_Age.get(), Txt_Major.get(), Txt_Class.get(), Txt_Telephone.get()), width = 10)
    Button1_Yes.place(x = 75, y = 400, anchor = 'nw')
    # 关于"取消"组件,此处绑定函数destroy()用于关闭窗口
    Button2_No = tk.Button(window, text = '取消', bg = 'silver', font = ('黑体', 12), command = lambda:window.destroy(), width = 10)
    Button2_No.place(x = 325, y = 400, anchor = 'nw')
    # 窗口显示
    window.mainloop() 

# 删除学生信息的窗口
def Window_Del():
    # 创建app的子窗口
    window = tk.Toplevel(app)
    window.title("删除学生信息")
    # 设置窗口大小:宽x高(500x300),并指定窗口在屏幕上的位置为(500, 200)
    window.geometry('500x300+500+200')
    # 关于学号的 label 和 entry
    Txt_ID = tk.StringVar()
    Txt_ID.set("")
    Label_Line1 = tk.Label(window, text = "学 号 (6 位):", font = ('黑体', 12), width = 15)
    Label_Line1.place(x = 75, y = 100, anchor = 'nw')
    Entry_Line1 = tk.Entry(window, show = None, font = ('黑体', 15), textvariable = Txt_ID, width = 20)
    Entry_Line1.place(x = 200, y = 100, anchor = 'nw')
    # 关于"确认"组件,此处绑定函数Del_Student_Info用于删除学生信息
    Button1_Yes = tk.Button(window, text = '确认', bg = 'silver', font = ('黑体', 12), command = lambda:Del_Student_Info(Txt_ID.get()), width = 10)
    Button1_Yes.place(x = 75, y = 200, anchor = 'nw')
    # 关于"取消"组件,此处绑定函数destroy()用于关闭窗口
    Button2_No = tk.Button(window, text = '取消', bg = 'silver', font = ('黑体', 12), command = lambda:window.destroy(), width = 10)
    Button2_No.place(x = 325, y = 200, anchor = 'nw')
    # tk.StringVar()用于接收用户输入
    result = tk.StringVar()
    result.set(">>>请输入待删除学生的学号<<<")
    # 在界面中显示文本框,打印result的信息
    Show_result = tk.Label(window, bg = "white", fg = "black", font = ("黑体", 12), bd = '0', anchor = 'center', textvariable = result)
    Show_result.place(x = "50", y = "30", width = "400", height = "50")
    # 显示窗口
    window.mainloop()

# 修改学生信息的窗口
def Window_Mod():
    # 创建app的子窗口
    window = tk.Toplevel(app)
    window.title("修改学生信息")
    # 设置窗口大小:宽x高(500x300),并指定窗口在屏幕上的位置为(500, 200)
    window.geometry('500x300+500+200')
    # 关于学号的 label 和 entry
    Txt_ID = tk.StringVar()
    Txt_ID.set("")
    Label_Line1 = tk.Label(window, text = "学 号 (6 位):", font = ('黑体', 12), width = 15)
    Label_Line1.place(x = 75, y = 100, anchor = 'nw')
    Entry_Line1 = tk.Entry(window, show = None, font = ('黑体', 15), textvariable = Txt_ID, width = 20)
    Entry_Line1.place(x = 200, y = 100, anchor = 'nw')
    # 关于"确认"组件,此处绑定函数Mod_Student_Info用于修改学生信息
    Button1_Yes = tk.Button(window, text = '确认', bg = 'silver', font = ('黑体', 12), command = lambda:Mod_Student_Info(Txt_ID.get()), width = 10)
    Button1_Yes.place(x = 75, y = 200, anchor = 'nw')
    # 关于"取消"组件,此处绑定函数destroy()用于关闭窗口
    Button2_No = tk.Button(window, text = '取消', bg = 'silver', font = ('黑体', 12), command = lambda:window.destroy(), width = 10)
    Button2_No.place(x = 325, y = 200, anchor = 'nw')
    # 在界面中显示文本框,打印result的信息
    result = tk.StringVar()
    result.set(">>>请输入待修改学生的学号<<<")
    Show_result = tk.Label(window, bg = "white", fg = "black", font = ("黑体", 12), bd = '0', anchor = 'center', textvariable = result)
    Show_result.place(x = "50", y = "30", width = "400", height = "50")
    #显示窗口
    window.mainloop()

# 输入修改学生信息的窗口
def Window_Mod_Input(ID):
    # 创建app的子窗口
    window = tk.Toplevel(app)
    window.title("修改学生信息")
    # 设置窗口大小:宽x高(500x500),并指定窗口在屏幕上的位置为(500, 200)
    window.geometry('500x500+500+200')
    for i in Info:
        if i["ID"] == ID:
            Name = i["Name"]
            Sex = i["Sex"]
            Age = i["Age"]
            Major = i["Major"]
            Class = i["Class"]
            Telephone = i["Telephone"]
     # 关于姓名的 label 和 entry
    Txt_Name = tk.StringVar()
    Txt_Name.set(Name)
    Label_Line2 = tk.Label(window, text = "姓 名:", font = ('黑体', 12), width = 15)
    Label_Line2.place(x = 75, y = 100, anchor = 'nw')
    Entry_Line2 = tk.Entry(window, show = None, font = ('黑体', 15), textvariable = Txt_Name, width = 20)
    Entry_Line2.place(x = 200, y = 100, anchor = 'nw')
    # 关于性别的 label 和 entry
    sex = tk.StringVar()
    sex.set(Sex)
    Label_Line7 = tk.Label(window, text = "性 别:", font = ('黑体', 12), width = 15)
    Label_Line7.place(x = 75, y = 150, anchor = 'nw')
    sex_input1 = tk.Radiobutton(window, text="男", variable=sex, value="男")
    sex_input2 = tk.Radiobutton(window, text="女", variable=sex, value="女")
    sex_input1.place(x = 200, y = 150, anchor = 'nw')
    sex_input2.place(x = 260, y = 150, anchor = 'nw')
    # 关于年龄的 label 和 entry
    Txt_Age = tk.StringVar()
    Txt_Age.set(Age)
    Label_Line4 = tk.Label(window, text = "年 龄 (0~25):", font = ('黑体', 12), width = 15)
    Label_Line4.place(x = 75, y = 200, anchor = 'nw')
    Entry_Line4 = tk.Entry(window, show = None, font = ('黑体', 15), textvariable = Txt_Age, width = 20)
    Entry_Line4.place(x = 200, y = 200, anchor = 'nw')
    # 关于专业的 label 和 entry
    Txt_Major = tk.StringVar()
    Txt_Major.set(Major)
    Label_Line3 = tk.Label(window, text = "专 业:", font = ('黑体', 12), width = 15)
    Label_Line3.place(x = 75, y = 250, anchor = 'nw')
    Entry_Line3 = tk.Entry(window, show = None, font = ('黑体', 15), textvariable = Txt_Major, width = 20)
    Entry_Line3.place(x = 200, y = 250, anchor = 'nw')
    # 关于班级的 label 和 entry
    Txt_Class = tk.StringVar()
    Txt_Class.set(Class)
    Label_Line5 = tk.Label(window, text = "班 级 (数字):", font = ('黑体', 12), width = 15)
    Label_Line5.place(x = 75, y = 300, anchor = 'nw')
    Entry_Line5 = tk.Entry(window, show = None, font = ('黑体', 15), textvariable = Txt_Class, width = 20)
    Entry_Line5.place(x = 200, y = 300, anchor = 'nw')
    # 关于电话的 label 和 entry
    Txt_Telephone = tk.StringVar()
    Txt_Telephone.set(Telephone)
    Label_Line6 = tk.Label(window, text = "电 话 (11位):", font = ('黑体', 12), width = 15)
    Label_Line6.place(x = 75, y = 350, anchor = 'nw')
    Entry_Line6 = tk.Entry(window, show = None, font = ('黑体', 15), textvariable = Txt_Telephone, width = 20)
    Entry_Line6.place(x = 200, y = 350, anchor = 'nw')
    # 关于"确认"组件,此处绑定函数Mod_Student_Info_1用于修改学生信息
    Button1_Yes = tk.Button(window, text = '确认', bg = 'silver', font = ('黑体', 12), command = lambda:Mod_Student_Info_1(ID, Txt_Name.get(), sex.get(), Txt_Age.get(), Txt_Major.get(), Txt_Class.get(), Txt_Telephone.get()), width = 10)
    Button1_Yes.place(x = 75, y = 400, anchor = 'nw')
    # 关于"取消"组件,此处绑定函数destroy()用于关闭窗口
    Button2_No = tk.Button(window, text = '取消', bg = 'silver', font = ('黑体', 12), command = lambda:window.destroy(), width = 10)
    Button2_No.place(x = 325, y = 400, anchor = 'nw')
    # 在界面中显示文本框,打印result的信息
    result = tk.StringVar()
    result.set(">>>请输入修改后的信息<<<")
    Show_result = tk.Label(window, bg = "white", fg = "black", font = ("黑体", 12), bd = '0', anchor = 'center', textvariable = result)
    Show_result.place(x = "50", y = "30", width = "400", height = "50")
    # 显示窗口
    window.mainloop()

# 查找学生信息的窗口
def Window_Ser():
    # 创建app的子窗口
    window = tk.Toplevel(app)
    window.title("查询学生信息")
    # 设置窗口大小:宽x高(500x500),并指定窗口在屏幕上的位置为(500, 200)
    window.geometry('500x500+500+200')
    # 关于学号的 label 和 entry
    Txt_ID = tk.StringVar()
    Txt_ID.set("")
    Label_Line1 = tk.Label(window, text = "学 号 (6 位):", font = ('黑体', 12), width = 15)
    Label_Line1.place(x = 75, y = 100, anchor = 'nw')
    Entry_Line1 = tk.Entry(window, show = None, font = ('黑体', 15), textvariable = Txt_ID, width = 20)
    Entry_Line1.place(x = 200, y = 100, anchor = 'nw')
    # 关于姓名的 label 和 entry
    Txt_Name = tk.StringVar()
    Txt_Name.set("")
    Label_Line2 = tk.Label(window, text = "姓 名:", font = ('黑体', 12), width = 15)
    Label_Line2.place(x = 75, y = 150, anchor = 'nw')
    Entry_Line2 = tk.Entry(window, show = None, font = ('黑体', 15), textvariable = Txt_Name, width = 20)
    Entry_Line2.place(x = 200, y = 150, anchor = 'nw')
    # 关于年龄的 label 和 entry
    Txt_Age = tk.StringVar()
    Txt_Age.set("")
    Label_Line4 = tk.Label(window, text = "年 龄 (0~25):", font = ('黑体', 12), width = 15)
    Label_Line4.place(x = 75, y = 200, anchor = 'nw')
    Entry_Line4 = tk.Entry(window, show = None, font = ('黑体', 15), textvariable = Txt_Age, width = 20)
    Entry_Line4.place(x = 200, y = 200, anchor = 'nw')
    # 关于专业的 label 和 entry
    Txt_Major = tk.StringVar()
    Txt_Major.set("")
    Label_Line3 = tk.Label(window, text = "专 业:", font = ('黑体', 12), width = 15)
    Label_Line3.place(x = 75, y = 250, anchor = 'nw')
    Entry_Line3 = tk.Entry(window, show = None, font = ('黑体', 15), textvariable = Txt_Major, width = 20)
    Entry_Line3.place(x = 200, y = 250, anchor = 'nw')
    # 关于班级的 label 和 entry
    Txt_Class = tk.StringVar()
    Txt_Class.set("")
    Label_Line5 = tk.Label(window, text = "班 级 (数字):", font = ('黑体', 12), width = 15)
    Label_Line5.place(x = 75, y = 300, anchor = 'nw')
    Entry_Line5 = tk.Entry(window, show = None, font = ('黑体', 15), textvariable = Txt_Class, width = 20)
    Entry_Line5.place(x = 200, y = 300, anchor = 'nw')
    # 关于电话的 label 和 entry
    Txt_Telephone = tk.StringVar()
    Txt_Telephone.set("")
    Label_Line6 = tk.Label(window, text = "电 话 (11位):", font = ('黑体', 12), width = 15)
    Label_Line6.place(x = 75, y = 350, anchor = 'nw')
    Entry_Line6 = tk.Entry(window, show = None, font = ('黑体', 15), textvariable = Txt_Telephone, width = 20)
    Entry_Line6.place(x = 200, y = 350, anchor = 'nw')
    # 关于"确认"组件,此处绑定函数Search_Student_Info用于修改学生信息
    Button1_Yes = tk.Button(window, text = '确认', bg = 'silver', font = ('黑体', 12), command = lambda:Search_Student_Info(Txt_ID.get(), Txt_Name.get(), Txt_Major.get(),Txt_Age.get(),Txt_Class.get(),Txt_Telephone.get()), width = 10)
    Button1_Yes.place(x = 75, y = 400, anchor = 'nw')
    # 关于"取消"组件,此处绑定函数destroy()用于关闭窗口
    Button2_No = tk.Button(window, text = '取消', bg = 'silver', font = ('黑体', 12), command = lambda:window.destroy(), width = 10)
    Button2_No.place(x = 325, y = 400, anchor = 'nw')
    # 在界面中显示文本框,打印result的信息
    result = tk.StringVar()
    result.set(">>>请输入待查找学生的部分信息(可不全填)<<<")
    Show_result = tk.Label(window, bg = "white", fg = "black", font = ("黑体", 12), bd = '0', anchor = 'center', textvariable = result)
    Show_result.place(x = "50", y = "30", width = "400", height = "50")
    # 显示窗口
    window.mainloop()

# 退出管理系统的窗口
def Window_Exit():
    # 创建app的子窗口
    window = tk.Toplevel()
    window.title("退出管理系统")
    # 设置窗口大小:宽x高(400x300),并指定窗口在屏幕上的位置为(500, 200)
    window.geometry('400x300+500+200')
    # 关于"确认"组件,此处绑定函数destroy()用于关闭主窗口
    Button1_Yes = tk.Button(window, text = '确认', bg = 'silver', font = ('黑体', 12), command = lambda:app.destroy(), width = 10)
    Button1_Yes.place(x = 50, y = 200, anchor = 'nw')
    # 关于"取消"组件,此处绑定函数destroy()用于关闭窗口
    Button2_No = tk.Button(window, text = '取消', bg = 'silver', font = ('黑体', 12), command = lambda:window.destroy(), width = 10)
    Button2_No.place(x = 250, y = 200, anchor = 'nw')
    # 在界面中显示文本框,打印result的信息
    result = tk.StringVar()
    result.set(">>>您确认离开系统吗?<<<")
    Show_result = tk.Label(window, bg = "white", fg = "black", font = ("黑体", 15), bd = '0', anchor = 'center', textvariable = result)
    Show_result.place(x = "50", y = "75", width = "300", height = "50")
    # 显示窗口
    window.mainloop()

# 在Text控件上方点击"查找"按钮被触发的函数
def search():
    # 删除文本标签名“found”内容,但不删除文本定义
    Show_result.tag_remove('found', '1.0', tk.END)
    # 设置起始值为1.0,也就是第一排第0个字符
    start = 1.0
    # 获取输入框的值
    key = entry.get()
    # strip()方法用于移除字符串两端的空白字符
    if len(key.strip()) == 0:
        # 如果没有输入时,就当什么都没有发生
        return -1
    # 死循环,因为我们查找的可能不止一个值,可能文本框中有多个
    while True:
        # 执行查找,key是我们需要查询的,start是起始值,END是从头查到尾,函数自动返回查询到的索引位置
        pos = Show_result.search(key, start, tk.END)
        # 查不到就结束,break是退出死循环
        if pos == '':
            break
        # 进行到这一步说明查到了数据
        # 添加标签名“found”,pos是查询到的索引位置
        Show_result.tag_add('found', pos, '%s+%dc' % (pos, len(key)))
        # 这里是更新查找起始位置
        start = '%s+%dc' % (pos, len(key))
        # 找到字符串后,滚动到该位置
        Show_result.see(pos)
    # 定义找到的标签
    Show_result.tag_configure('found', background='yellow')

# 如果该文件是程序运行的主文件,将会运行以下代码
if __name__ == '__main__':
    # 创建主窗口
    app = tk.Tk()
    app.title("学生信息管理系统")
    # 设置窗口大小:宽x高(900x500),并指定窗口在屏幕上的位置为(200, 200)
    app.geometry('930x550+200+200')
    # 关于"添加学生信息"控件,此处绑定函数Search_Student_Info用于修改学生信息
    Button1_Add = tk.Button(app, text = '添 加 学 生 信 息', bg = 'silver', font = ('黑体', 15), command = Window_Add, width = 20)
    Button1_Add.place(x = 40, y = 30, anchor = 'nw')
    Button2_Del = tk.Button(app, text = '删 除 学 生 信 息', bg = 'silver', font = ('黑体', 15), command = Window_Del, width = 20)
    Button2_Del.place(x = 40, y = 110, anchor = 'nw')
    Button3_Mod = tk.Button(app, text = '修 改 学 生 信 息', bg = 'silver', font = ('黑体', 15), command = Window_Mod, width = 20)
    Button3_Mod.place(x = 40, y = 190, anchor = 'nw')
    Button4_Ser = tk.Button(app, text = '查 询 学 生 信 息', bg = 'silver', font = ('黑体', 15), command = Window_Ser, width = 20)
    Button4_Ser.place(x = 40, y = 270, anchor = 'nw')
    Button5_Show = tk.Button(app, text = '显 示 所 有 信 息', bg = 'silver', font = ('黑体', 15), command = lambda:Print_Student_Info(Info), width = 20)
    Button5_Show.place(x = 40, y = 350, anchor = 'nw')
    Button6_Exit = tk.Button(app, text = '退 出 管 理 系 统', bg = 'silver', font = ('黑体', 15), command = Window_Exit, width = 20)
    Button6_Exit.place(x = 40, y = 430, anchor = 'nw')
    # 输入查找的文本框
    entry = tk.Entry()
    entry.place(x = "280", y = "30", width = "200", height = "25")
    # 查找按钮
    btu = tk.Button(app, text='查找', command=search)
    btu.place(x = "490", y = "30", width = "40", height = "25")
    # 创建一个Text控件,用于输出学生信息
    Show_result = tk.Text(app, spacing1=2, spacing2=2, spacing3=2, wrap='none', font=10, width=40, height=10)
    Show_result.place(x = "280", y = "70", width = "600", height = "400")
    result = tk.StringVar()
    result.set("                     >>>欢迎使用学生信息管理系统<<<\n\n 使用说明:运行该程序时,默认会在D盘创建一个StudentData.xlsx文件,用于\n 存储学生信息。注意,运行该程序前,如果StudentData.xlsx文件已被双击打开,\n 必须先关闭该Excel文件,否则会报错并自动退出程序。")
    # 创建一个垂直滚动条并将其与Text控件关联
    v_scrollbar = ttk.Scrollbar(app, orient='vertical', command=Show_result.yview)
    v_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
    Show_result.configure(yscrollcommand=v_scrollbar.set)
    # 创建一个水平滚动条并将其与Text控件关联
    h_scrollbar = ttk.Scrollbar(app, orient='horizontal', command=Show_result.xview)
    h_scrollbar.pack(side=tk.BOTTOM, fill=tk.X)
    Show_result.configure(xscrollcommand=h_scrollbar.set)
    # 绑定变量到Text控件
    Show_result.insert('end', result.get())
    # 在主界面创建“汇总”信息,显示学生信息的数量
    total_label = tk.Label(app, text='汇总:', font=10)
    total_label.place(x = "740", y = "38", width = "50", height = "25")
    total_text = tk.Text(app, relief=tk.FLAT, bg='#f0f0f0', font=10)
    total_text.place(x = "790", y = "40", width = "50", height = "25")
    # 开启主循环,让窗口处于显示状态
    app.mainloop()