如今,Python编程智能技术广泛应用于教育行业,一个高效、可靠的可视化学生成绩管理系统对于提升教育管理效率至关重要。本节教程将介绍如何设计一个基于Python的可视化学生成绩管理系统,该系统采用Tkinter作为GUI图形化界面,SQLite3作为数据库。本系统通过多种可视化图表展示成绩分布,旨在帮助教师和学生轻松管理和查看成绩,具有添加、修改、删除和查询学生成绩的功能。
1、可视化学生成绩管理系统介绍
我们的目标是利用Python开发一个基于Tkinter库的可视化学生信息管理系统,旨在提供简便、直观、图形化的用户界面,用于管理学生的基本信息。本系统通过使用SQLite3存储数据,实现了登录、添加、修改、删除、查询和导出数据到Excel文件的功能,SQLite3作为轻量级的数据库系统是Python标准库的一部分,不需要单独安装,经常用于小型应用程序设计。
首次启动该系统时,会在当前脚本工作目录下创建一个名为“student.db”的数据库文件,并自动在数据库文件中创建名为“users”和“students”两个数据库表,用于保存账号和学生信息,系统默认会向users表中插入一个教师账号和5个学生账号信息,学生账号默认为学生的学号。系统同样默认会向students表中插入5条完整的学生成绩信息,以供参考。
功能说明
多角色登录系统:区分教师和学生权限,教师账号为“admin”,密码为“admin123”。学生账号默认为学生的学号,密码为“123456”。通过教师账号登录该系统,可以查看所有学生信息和添加学生成绩。通过学生账号登录该系统,只能查看学生自己的基本信息和成绩。
成绩管理:教师账号有权限添加学生、查看所有学生、修改学生成绩和删除学生成绩。全程通过SQLite3保存数据,即使退出管理系统,下次登录时,仍然能查看之前添加或修改的数据,实现持久化保存。
数据可视化分析:提供了丰富的成绩分析功能,使用Python的matplotlib库生成多种可视化图表,展示成绩分布信息,包括个人成绩折线图、雷达图、学科平均分对比柱状图、成绩分布饼图、学生成绩对比柱状图。
学生信息搜索:支持按学号搜索和按姓名模糊搜索学生信息。
导出学生信息:通过Python的Pandas库,支持将学生成绩列表中的数据导出到excel文件中。
支持排序:学生成绩列表支持排序操作,可以按总成绩从小到大排序,方便教师查看成绩排名。
支持鼠标快捷操作:在学生成绩列表中,通过右击鼠标可以删除单个学生信息,也可以点击界面中的“删除学生”按钮,删除指定学生信息。在学生成绩列表中左键双击鼠标,可以直接打开修改学生信息的页面,也可以点击界面中的“修改学生”按钮,修改指定学生信息。
自动创建学生账号:每次添加新学生信息时,数据库会自动创建新的学生账号,新学生账号默认为新学生的学号,密码默认为“123456”。每次添加新的学生信息后,即可通过新学号登录系统,查看新学生个人的成绩和分析图表。
2、可视化学生成绩管理系统界面演示
教师登录账号:admin,密码:admin123
学生登录账号:默认为学生的学号,比如:student1,密码:123456
3、代码实现
(1)安装numpy库、matplotlib库和pandas库
首先,确保你已经安装了numpy库、matplotlib库和pandas库。如果还没有安装,可以通过pip安装:
pip install numpy
pip install matplotlib
pip install pandas
(2)导入必要的库
import os, numpy, pandas, sqlite3, webbrowser
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
这里导入了Tkinter库用于创建GUI应用图形界面。numpy库可以实现高效数值计算和科学计算,numpy库广泛应用于数据分析和机器学习领域。matplotlib库是Python中专门用于绘制图表的第三方库,matplotlib库广泛应用于数据可视化分析,提供了静态、动态和交互式图表的绘制功能。pandas库是基于numpy和matplotlib开发的开源数据分析工具,这里我们利用pandas库将数据写入Excel文件。
(3)可视化学生成绩管理系统,完整代码如下所示:
动手练一练:
import os, numpy, pandas, sqlite3, webbrowser
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
# 获取当前脚本文件所在的目录
default_directory = os.path.dirname(os.path.abspath(__file__))
# 将当前脚本文件所在的目录设置为工作目录
os.chdir(default_directory)
database_file = "student.db" # 定义数据库文件为“student.db”
teacher_name = "admin" # 定义教师登录账号为“admin”
teacher_password = "admin123" # 定义教师登录密码为“admin123”
student_password = "123456" # 定义学生登录密码为“123456”
# 连接到数据库(如果数据库不存在,则会自动创建)
try:
if not os.path.exists(database_file):
# 创建数据库连接,插入测试数据
def create_db():
# 使用with语句自动管理数据库连接的生命周期
with sqlite3.connect(database_file) as conn:
connection = conn.cursor()
# 创建账号表,用于存储账号和密码
connection.execute('''
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password TEXT NOT NULL,
category TEXT NOT NULL CHECK(category IN ('student', 'teacher'))
)''')
# 创建学生信息表
connection.execute('''
CREATE TABLE IF NOT EXISTS students (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
chinese REAL NOT NULL,
math REAL NOT NULL,
english REAL NOT NULL,
physics REAL NOT NULL,
chemistry REAL NOT NULL,
total REAL NOT NULL,
average REAL NOT NULL)
''')
# 添加测试账号和密码,包括一个教师账号和5个学生账号
users_data = [
(teacher_name, teacher_password, 'teacher'),
('student1', student_password, 'student'),
('student2', student_password, 'student'),
('student3', student_password, 'student'),
('student4', student_password, 'student'),
('student5', student_password, 'student')
]
# 使用元组列表将默认的账号和密码插入users表中
connection.executemany('INSERT INTO users (username, password, category) VALUES (?, ?, ?)', users_data)
# 添加测试学生数据
students_data = [
('student1', '张三', 92, 85, 82, 76, 84, 419, 83.8),
('student2', '李四', 85, 76, 83, 95, 79, 418, 83.6),
('student3', '王五', 78, 84, 79, 88, 81, 410, 82.0),
('student4', '赵六', 92, 77, 96, 74, 87, 426, 85.2),
('student5', '钱七', 88, 94, 87, 85, 82, 436, 87.2)
]
# 使用元组列表将默认的学生信息插入students表中
connection.executemany('INSERT INTO students VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)', students_data)
conn.commit() # 提交事务以保存数据库的更改
conn.close() # 关闭连接
# 执行插入数据
create_db()
except:
pass
# 验证用户账号登录操作
def check_user(username, password):
# 使用with语句自动管理数据库连接的生命周期
with sqlite3.connect(database_file) as conn:
cursor = conn.cursor()
cursor.execute(
"SELECT username, category FROM users WHERE username=? AND password=?",
(username, password)
)
result = cursor.fetchone()
conn.close() # 关闭连接
if result:
return {'success': True, 'username': result[0], 'category': result[1]}
return {'success': False, 'message': '用户名或密码错误'}
# 添加新学生信息时,自动创建新学生账号
def add_user(username, password, category):
# 使用with语句自动管理数据库连接的生命周期
with sqlite3.connect(database_file) as conn:
cursor = conn.cursor()
cursor.execute(
"INSERT INTO users (username, password, category) VALUES (?, ?, ?)",
(username, password, category)
)
conn.commit() # 提交事务以保存数据库的更改
return True
conn.close() # 关闭连接
# 从数据库中获取所有的学生信息
def search_all_students():
# 使用with语句自动管理数据库连接的生命周期
with sqlite3.connect(database_file) as conn:
cursor = conn.cursor()
cursor.execute("SELECT * FROM students")
columns = [column[0] for column in cursor.description]
students = [dict(zip(columns, row)) for row in cursor.fetchall()]
conn.commit() # 提交事务以保存数据库的更改
return students
conn.close() # 关闭连接
# 根据学号获取学生信息
def search_students_id(student_id):
# 使用with语句自动管理数据库连接的生命周期
with sqlite3.connect(database_file) as conn:
cursor = conn.cursor()
cursor.execute("SELECT * FROM students WHERE id=?", (student_id,))
columns = [column[0] for column in cursor.description]
student = cursor.fetchone()
conn.commit() # 提交事务以保存数据库的更改
return dict(zip(columns, student)) if student else None
conn.close() # 关闭连接
# 根据学号或者学生姓名搜索学生信息
def search_students(keyword):
# 使用with语句自动管理数据库连接的生命周期
with sqlite3.connect(database_file) as conn:
cursor = conn.cursor()
cursor.execute(
"SELECT * FROM students WHERE id LIKE ? OR name LIKE ?",
(f'%{keyword}%', f'%{keyword}%')
)
columns = [column[0] for column in cursor.description]
students = [dict(zip(columns, row)) for row in cursor.fetchall()]
return students
conn.close() # 关闭连接
# 添加学生信息
def add_student(student_data):
# 计算总分
total = sum([
student_data['chinese'],
student_data['math'],
student_data['english'],
student_data['physics'],
student_data['chemistry']
])
# 计算平均分
average = round(total / 5, 2)
# 使用with语句自动管理数据库连接的生命周期
with sqlite3.connect(database_file) as conn:
cursor = conn.cursor()
cursor.execute(
"INSERT INTO students VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
(
student_data['id'],
student_data['name'],
student_data['chinese'],
student_data['math'],
student_data['english'],
student_data['physics'],
student_data['chemistry'],
total,
average
)
)
conn.commit() # 提交事务以保存数据库的更改
return True
conn.close() # 关闭连接
# 更新学生信息
def update_student(student_id, new_data):
# 计算学生总分
total = sum([
new_data.get('chinese', 0),
new_data.get('math', 0),
new_data.get('english', 0),
new_data.get('physics', 0),
new_data.get('chemistry', 0)
])
# 计算平均分
average = round(total / 5, 2)
# 使用with语句自动管理数据库连接的生命周期
with sqlite3.connect(database_file) as conn:
cursor = conn.cursor()
# 创建更新语句
set_clause = []
params = []
for field, value in new_data.items():
set_clause.append(f"{field}=?")
params.append(value)
set_clause.append("total=?")
params.append(total)
set_clause.append("average=?")
params.append(average)
params.append(student_id)
cursor.execute(
f"UPDATE students SET {', '.join(set_clause)} WHERE id=?",
params
)
conn.commit() # 提交事务以保存数据库的更改
return cursor.rowcount > 0
conn.close() # 关闭连接
# 删除学生信息,同时删除保存的登录账号
def delete_student(student_id):
# 使用with语句自动管理数据库连接的生命周期
with sqlite3.connect(database_file) as conn:
cursor = conn.cursor()
cursor.execute("DELETE FROM students WHERE id=?", (student_id,))
cursor.execute("DELETE FROM users WHERE username=?", (student_id,))
conn.commit() # 提交事务以保存数据库的更改
return cursor.rowcount > 0
conn.close() # 关闭连接
# 获取成绩统计数据
def data_count():
# 使用with语句自动管理数据库连接的生命周期
with sqlite3.connect(database_file) as conn:
cursor = conn.cursor()
# 计算各科平均分
cursor.execute("""
SELECT
AVG(chinese) as chinese_avg,
AVG(math) as math_avg,
AVG(english) as english_avg,
AVG(physics) as physics_avg,
AVG(chemistry) as chemistry_avg
FROM students
""")
averages = cursor.fetchone()
# 成绩分布
def get_grade_dist(subject):
cursor.execute(f"""
SELECT
SUM(CASE WHEN {subject} >= 90 THEN 1 ELSE 0 END) as excellent,
SUM(CASE WHEN {subject} >= 80 AND {subject} < 90 THEN 1 ELSE 0 END) as good,
SUM(CASE WHEN {subject} >= 70 AND {subject} < 80 THEN 1 ELSE 0 END) as medium,
SUM(CASE WHEN {subject} >= 60 AND {subject} < 70 THEN 1 ELSE 0 END) as pass,
SUM(CASE WHEN {subject} < 60 THEN 1 ELSE 0 END) as fail
FROM students
""")
counts = cursor.fetchone()
return {
'优秀(>=90)': counts[0],
'良好(80-89)': counts[1],
'中等(70-79)': counts[2],
'及格(60-69)': counts[3],
'不及格(<60)': counts[4]
}
distributions = {
'语文': get_grade_dist('chinese'),
'数学': get_grade_dist('math'),
'英语': get_grade_dist('english'),
'物理': get_grade_dist('physics'),
'化学': get_grade_dist('chemistry')
}
conn.commit() # 提交事务以保存数据库的更改
return {
'averages': {
'chinese': averages[0],
'math': averages[1],
'english': averages[2],
'physics': averages[3],
'chemistry': averages[4]
},
'distributions': distributions
}
conn.close() # 关闭连接
# 定义学生管理类,处理学生信息的增、删、改、查
class ControlStudent:
# 添加学生信息
def add_student(self, student_data):
# 添加新学生信息时,自动创建新学生账号
add_user(student_data['id'], student_password, 'student')
return add_student(student_data)
# 获取所有学生信息
def search_all_students(self):
return search_all_students()
# 根据学号获取学生信息
def search_students_id(self, student_id):
return search_students_id(student_id)
# 根据学号或姓名搜索学生
def search_students(self, keyword):
return search_students(keyword)
# 更新学生信息
def update_student(self, student_id, new_data):
return update_student(student_id, new_data)
# 删除学生信息
def delete_student(self, student_id):
return delete_student(student_id)
# 获取成绩统计数据
def data_count(self):
return data_count()
# 定义用户账号管理类,处理用户登录和权限
class Users:
# 验证用户登录
def authenticate(self, username, password):
return check_user(username, password)
# 添加新用户
def add_user(self, username, password, category):
return add_user(username, password, category)
# 定义登录窗口页面
class UserLogin:
def __init__(self, success_login):
self.success_login = success_login
self.user_manager = Users()
self.app = tk.Tk()
self.app.title("学生成绩管理系统 - 登录")
# 设置窗口居中
window_width = 330
window_height = 330
screen_width = self.app.winfo_screenwidth()
screen_height = self.app.winfo_screenheight()
x = (screen_width - window_width) / 2
y = (screen_height - window_height) / 2
self.app.geometry('%dx%d+%d+%d' % (window_width, window_height, x, y))
self.app.resizable(width=False, height=False)
# 主界面创
self.main_widgets()
# 创建登录界面控件
def main_widgets(self):
# 鼠标点击时,清除Entry控件中的提示信息
def on_entry_click(event):
if event.widget.get() == '请输入用户名' or event.widget.get() == '请输入密码':
event.widget.delete(0, tk.END) # 删除默认文本
event.widget.config(fg='black') # 更改文本颜色为黑色
# 鼠标离开时,在Entry控件中显示提示信息
def on_focus_out(event):
if event.widget.get() == '':
if event.widget == self.username_entry:
event.widget.insert(0, '请输入用户名')
elif event.widget == self.password_entry:
event.widget.insert(0, '请输入密码')
event.widget.config(fg='gray') # 更改文本颜色为灰色
# 在上面放置标题
label1 = tk.Label(self.app, text='欢迎使用', font=('宋体', 20, 'bold'), fg="#3d87b4", justify=tk.CENTER)
label1.grid(row=1, column=0, padx=50, pady=15)
label2 = tk.Label(self.app, text='学生成绩管理系统', font=('宋体', 20, 'bold'), fg="#3d87b4", justify=tk.CENTER)
label2.grid(row=2, column=0, padx=50, pady=15)
# 在下面设计登录窗口
self.username_entry = tk.Entry(self.app, highlightthickness=1, font=('宋体', 15), bg = "white", width=20)
self.username_entry.grid(row=3, column=0, padx=50, pady=15)
self.username_entry.insert(0, '请输入用户名') # 插入默认文本
self.username_entry.config(fg='gray') # 设置文本颜色为灰色
self.username_entry.bind('<FocusIn>', on_entry_click)
self.username_entry.bind('<FocusOut>', on_focus_out)
self.password_entry = tk.Entry(self.app, highlightthickness=1,font=('宋体', 15), bg = "white", width=20)
self.password_entry.grid(row=4, column=0, padx=50, pady=15)
self.password_entry.insert(0, '请输入密码') # 插入默认文本
self.password_entry.config(fg='gray') # 设置文本颜色为灰色
self.password_entry.bind('<FocusIn>', on_entry_click)
self.password_entry.bind('<FocusOut>', on_focus_out)
button_login = tk.Button(self.app, text="登录", font=('宋体', 15), bg="#a3cdfd", width=20, command=self.login)
button_login.grid(row=5, column=0, columnspan=2, padx=50, pady=20)
# 检查输入的账号和密码是否正确
def login(self):
username = self.username_entry.get().strip()
password = self.password_entry.get().strip()
# 判断输入的用户名和密码不能为空
if not username or not password:
messagebox.showerror("错误", "用户名和密码不能为空!")
return
result = self.user_manager.authenticate(username, password)
if result['success']:
messagebox.showinfo(title="登录成功", message=f"欢迎登录学生成绩管理系统,{username}!")
self.app.destroy()
self.success_login(result['username'], result['category'])
else:
messagebox.showerror("错误", result['message'])
# 显示登录窗口
def run(self):
self.app.mainloop()
# 定义可视化工具类,用于生成各种成绩分析图表
class ViewShow:
def __init__(self):
# 设置字体为SimHei(黑体)
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 绘制各科平均分柱状图
def plot_subject_average(self, averages):
# 先关闭已打开的所有图表
plt.close('all')
subjects = ["语文", "数学", "英语", "物理", "化学"]
values = list(averages.values())
plt.figure(figsize=(10, 6))
bars = plt.bar(subjects, values, color=['#5DA5DA', '#FAA43A', '#60BD68', '#F17CB0', '#B2912F'], zorder=3)
plt.title("各科平均分对比", fontsize=16)
plt.xlabel("科目", fontsize=12)
plt.ylabel("平均分", fontsize=12)
plt.grid(axis='y', zorder=0)
# 在柱子上显示数值
for bar in bars:
height = bar.get_height()
plt.text(bar.get_x() + bar.get_width() / 2., height,
f'{height:.1f}',
ha='center', va='bottom')
plt.tight_layout()
plt.show()
# 绘制学生成绩雷达图
def plot_student_radar(self, student_data):
# 先关闭已打开的所有图表
plt.close('all')
subjects = ["语文", "数学", "英语", "物理", "化学"]
scores = [
student_data['chinese'],
student_data['math'],
student_data['english'],
student_data['physics'],
student_data['chemistry']
]
angles = numpy.linspace(0, 2 * numpy.pi, len(subjects), endpoint=False).tolist()
scores += scores[:1] # 闭合图形
angles += angles[:1]
fig = plt.figure(figsize=(7, 7))
ax = fig.add_subplot(111, polar=True)
ax.plot(angles, scores, 'o-', linewidth=2)
ax.fill(angles, scores, alpha=0.25)
ax.set_thetagrids(numpy.degrees(angles[:-1]), subjects)
ax.set_ylim(0, 150)
ax.set_title(f"{student_data['name']}成绩雷达图", fontsize=16, pad=20)
# 在每个数据点添加数值标签
for angle, score in zip(angles[:-1], scores[:-1]):
ax.text(angle, score + 5, str(score), ha='center', va='center')
plt.tight_layout()
plt.show()
# 绘制成绩分布饼图
def plot_grade_distribution(self, distributions):
# 先关闭已打开的所有图表
plt.close('all')
subjects = list(distributions.keys())
# 创建子图
fig, axes = plt.subplots(2, 3, figsize=(12, 6))
fig.suptitle("各科成绩分布", fontsize=16)
# 调整子图布局
plt.subplots_adjust(wspace=0.4, hspace=0.4)
# 为每个科目绘制饼图
for i, subject in enumerate(subjects):
ax = axes[i // 3, i % 3]
data = distributions[subject]
labels = list(data.keys())
sizes = list(data.values())
# 过滤掉人数为0的等级
filtered_labels = []
filtered_sizes = []
for label, size in zip(labels, sizes):
if size > 0:
filtered_labels.append(label)
filtered_sizes.append(size)
if not filtered_sizes:
ax.text(0.5, 0.5, "无数据", ha='center', va='center')
ax.set_title(subject)
continue
# 绘制饼图
wedges, texts, autotexts = ax.pie(
filtered_sizes,
labels=filtered_labels,
autopct='%1.1f%%',
startangle=90,
wedgeprops={'linewidth': 1, 'edgecolor': 'white'},
textprops={'fontsize': 8}
)
# 调整标签位置
for text in texts:
text.set_fontsize(8)
ax.set_title(subject)
ax.axis('equal')
# 如果科目不足6个,隐藏多余的子图
for j in range(i + 1, 6):
axes[j // 3, j % 3].axis('off')
plt.tight_layout()
plt.show()
# 绘制两名学生成绩对比柱状图
def plot_student_comparison(self, student1, student2):
# 先关闭已打开的所有图表
plt.close('all')
subjects = ["语文", "数学", "英语", "物理", "化学"]
scores1 = [
student1['chinese'],
student1['math'],
student1['english'],
student1['physics'],
student1['chemistry']
]
scores2 = [
student2['chinese'],
student2['math'],
student2['english'],
student2['physics'],
student2['chemistry']
]
x = numpy.arange(len(subjects))
width = 0.35
fig, ax = plt.subplots(figsize=(10, 6))
rects1 = ax.bar(x - width / 2, scores1, width, label=student1['name'])
rects2 = ax.bar(x + width / 2, scores2, width, label=student2['name'])
ax.set_title(f"{student1['name']}与{student2['name']}成绩对比", fontsize=16)
ax.set_xticks(x)
ax.set_xticklabels(subjects)
ax.legend()
# 在柱子上显示数值
def autolabel(rects):
for rect in rects:
height = rect.get_height()
ax.annotate(f'{height}',
xy=(rect.get_x() + rect.get_width() / 2, height),
xytext=(0, 3),
textcoords="offset points",
ha='center', va='bottom')
autolabel(rects1)
autolabel(rects2)
plt.tight_layout()
plt.show()
# 学生登录界面,每个学生只能查看自己的成绩和分析图表
class StudentWindow:
def __init__(self, username):
self.username = username
self.student_manager = ControlStudent()
self.visualizer = ViewShow()
self.app = tk.Tk()
self.app.title(f"学生成绩管理系统-{username}学生视图")
# 设置窗口居中
window_width = 1000
window_height = 600
screen_width = self.app.winfo_screenwidth()
screen_height = self.app.winfo_screenheight()
x = (screen_width - window_width) / 2
y = (screen_height - window_height) / 2
self.app.geometry('%dx%d+%d+%d' % (window_width, window_height, x, y))
self.app.resizable(width=False, height=False)
# 窗口样式配置
self.style = ttk.Style()
self.set_styles()
# 简单映射,实际应用中应该有更复杂的映射关系
self.student_id = username
self.main_widgets()
# 定义样式函数
def set_styles(self):
# 设置主题为clam
self.style.theme_use('clam')
# 组件基础样式配置
self.style.configure('TFrame', background='#F3F3F3')
self.style.configure('TLabel', background='#F3F3F3',
foreground='#605E5C')
self.style.configure('TEntry', fieldbackground='#FFFFFF',
bordercolor='#CCCCCC', relief='solid')
self.style.configure('TButton', padding=3, relief='flat',
background='#0078D4',
font=('宋体', 12),
foreground='white')
self.style.map('TButton',
background=[('active', '#004da3'), ('disabled', '#E0E0E0')],
foreground=[('disabled', '#A0A0A0')])
self.style.configure('TLabelframe', bordercolor='#D0D0D0',
relief='groove', padding=10, background='#F3F3F3')
self.style.configure('TLabelframe.Label', padding=10, foreground='#0078D4', font=('宋体', 14, 'bold'), background='#F3F3F3')
# 创建学生界面控件
def main_widgets(self):
# 主框架
main_frame = tk.Frame(self.app)
main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 左侧信息面板
information_frame = ttk.LabelFrame(main_frame, text=f"{self.username}-学生个人信息")
information_frame.pack(side=tk.LEFT, fill=tk.Y, padx=5, pady=5)
# 获取学生信息
student_data = self.student_manager.search_students_id(self.student_id)
if not student_data:
messagebox.showerror("错误", "找不到对应的学生信息!")
self.app.destroy()
return
# 显示学生信息
tk.Label(information_frame, text=f"学号: {student_data['id']}").pack(anchor=tk.W, pady=5)
tk.Label(information_frame, text=f"姓名: {student_data['name']}").pack(anchor=tk.W, pady=5)
tk.Label(information_frame, text=f"语文: {student_data['chinese']}").pack(anchor=tk.W, pady=5)
tk.Label(information_frame, text=f"数学: {student_data['math']}").pack(anchor=tk.W, pady=5)
tk.Label(information_frame, text=f"英语: {student_data['english']}").pack(anchor=tk.W, pady=5)
tk.Label(information_frame, text=f"物理: {student_data['physics']}").pack(anchor=tk.W, pady=5)
tk.Label(information_frame, text=f"化学: {student_data['chemistry']}").pack(anchor=tk.W, pady=5)
tk.Label(information_frame, text=f"总分: {student_data['total']}").pack(anchor=tk.W, pady=5)
tk.Label(information_frame, text=f"平均分: {student_data['average']}").pack(anchor=tk.W, pady=5)
# 右侧功能面板
view_frame = tk.Frame(main_frame)
view_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=5, pady=5)
# 折线图区域
line_chart_frame = ttk.LabelFrame(view_frame)
line_chart_frame.pack(fill=tk.X, pady=10)
# 创建一个画布大小为5×4英寸,分辨率100DPI,dpi参数用于设置图像的分辨率
line_fig = plt.figure(figsize=(5, 4), dpi=100)
# 创建一个subplot图形
line_subplot = line_fig.add_subplot()
# 创建一些数据
x = ['语文', '数学', '英语', '物理', '化学']
y = [
student_data['chinese'],
student_data['math'],
student_data['english'],
student_data['physics'],
student_data['chemistry']
]
# 绘制折线图
line_subplot.plot(x, y, marker='o')
line_subplot.set_ylim(0, 150)
line_subplot.grid(True) # 显示网格线,便于阅读数据点位置
line_subplot.set_title("个人成绩折线图", fontsize=16, y=1.03)
# 为每个点添加标签显示X值
for i, tx in enumerate(x):
line_subplot.annotate(f'{y[i]}', (x[i], y[i]), textcoords="offset points", xytext=(0,10), ha='center')
# 创建FigureCanvasTkAgg对象,用于在Tkinter窗口中显示图形
canvas = FigureCanvasTkAgg(line_fig, master=line_chart_frame)
canvas.draw()
# 将图形放置到Tkinter窗口中
canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH)
# 功能按钮
button_frame = ttk.Frame(view_frame)
button_frame.pack(fill=tk.X, pady=10)
ttk.Button(button_frame, text="查看成绩雷达图", command=self.open_radar_chart).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="查看班级平均分", command=self.open_class_average).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="查看成绩分布", command=self.open_grade_distribution).pack(side=tk.LEFT, padx=5)
# 图表显示区域
self.chart_frame = tk.Frame(view_frame)
self.chart_frame.pack(fill=tk.BOTH, expand=True)
# 系统退出按钮
ttk.Button(main_frame, text="退出系统", command=self.app.destroy).pack(side=tk.BOTTOM, pady=10)
# 显示学生成绩雷达图
def open_radar_chart(self):
student_data = self.student_manager.search_students_id(self.student_id)
if student_data:
# 清除之前的图表
for widget in self.chart_frame.winfo_children():
widget.destroy()
# 创建新图表
self.visualizer.plot_student_radar(student_data)
else:
messagebox.showerror("错误", "无法获取学生数据!")
# 显示班级平均分图表
def open_class_average(self):
stats = self.student_manager.data_count()
if stats:
# 清除之前的图表
for widget in self.chart_frame.winfo_children():
widget.destroy()
# 创建新图表
self.visualizer.plot_subject_average(stats['averages'])
else:
messagebox.showerror("错误", "无法获取统计数据!")
# 显示成绩分布图表
def open_grade_distribution(self):
stats = self.student_manager.data_count()
if stats:
# 清除之前的图表
for widget in self.chart_frame.winfo_children():
widget.destroy()
# 创建新图表
self.visualizer.plot_grade_distribution(stats['distributions'])
else:
messagebox.showerror("错误", "无法获取统计数据!")
# 显示学生窗口
def run(self):
self.app.mainloop()
# 创建教师界面,教师账号可以管理所有学生成绩
class TeacherWindow:
def __init__(self, username):
self.username = username
self.student_manager = ControlStudent()
self.visualizer = ViewShow()
self.app = tk.Tk()
self.app.title(f"学生成绩管理系统-{username}教师视图")
# 设置窗口居中
window_width = 1000
window_height = 770
screen_width = self.app.winfo_screenwidth()
screen_height = self.app.winfo_screenheight()
x = (screen_width - window_width) / 2
y = (screen_height - window_height) / 2
self.app.geometry('%dx%d+%d+%d' % (window_width, window_height, x, y))
self.app.resizable(width=False, height=False)
# 窗口样式配置
self.style = ttk.Style()
self.set_styles()
# 主界面创建
self.main_widgets()
# 刷新学生列表
self.refresh_data()
# 将窗口关闭事件与confirm_close函数关联
self.app.protocol("WM_DELETE_WINDOW", self.confirm_close)
# 点击关闭窗口触发函数
def confirm_close(self):
if messagebox.askokcancel("退出", "你确定要退出吗?"): # 弹出确认对话框
self.app.destroy() # 点击确定后关闭窗口
# 定义样式函数
def set_styles(self):
# 设置主题为clam
self.style.theme_use('clam')
# 组件基础样式配置
self.style.configure('TFrame', background='#F3F3F3')
self.style.configure('TLabel', background='#F3F3F3',
foreground='#605E5C')
self.style.configure('TEntry', fieldbackground='#FFFFFF',
bordercolor='#CCCCCC', relief='solid')
self.style.configure('TButton', padding=3, relief='flat',
background='#0078D4',
font=('宋体', 12),
foreground='white')
self.style.map('TButton',
background=[('active', '#004da3'), ('disabled', '#E0E0E0')],
foreground=[('disabled', '#A0A0A0')])
self.style.configure('TLabelframe', bordercolor='#D0D0D0',
relief='groove', padding=10, background='#F3F3F3')
self.style.configure('TLabelframe.Label', padding=10, foreground='#0078D4', font=('宋体', 14, 'bold'), background='#F3F3F3')
# 创建教师界面控件
def main_widgets(self):
# 主框架
main_frame = tk.Frame(self.app)
main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 顶部标题框架
top_frame = tk.Frame(main_frame, padx=10, pady=5)
top_frame.pack(fill=tk.X, padx=5, pady=5)
top_Label = tk.Label(top_frame, text="学生成绩管理系统", font=("宋体", 20), width=30)
top_Label.pack(fill=tk.X, expand=True)
# 学生成绩列表面板
list_frame = ttk.LabelFrame(main_frame, text="所有学生成绩列表")
list_frame.pack(fill=tk.X, padx=5, pady=5)
# 搜索框架
search_frame = ttk.Frame(list_frame)
search_frame.pack(fill=tk.X, pady=10)
self.search_entry = ttk.Entry(search_frame)
self.search_entry.pack(side=tk.LEFT)
ttk.Button(search_frame, text="搜索", command=self.search_students).pack(side=tk.LEFT, padx=5)
ttk.Button(search_frame, text="按总分排序", command=lambda: self.sort_treeview(self.student_tree, "7", False)).pack(side=tk.RIGHT, padx=15)
# 创建“汇总”信息,显示学生信息的数量
self.total_text = tk.Text(search_frame, relief=tk.FLAT, bg='#f0f0f0', font=10, width=2, height=1)
self.total_text.pack(side=tk.RIGHT)
total_label = tk.Label(search_frame, text='汇总:', font=10)
total_label.pack(side=tk.RIGHT)
# 利用Treeview控件显示学生列表
self.student_tree = ttk.Treeview(list_frame, columns=('id', 'name', 'chinese', 'math', 'english', 'physics', 'chemistry', 'total', 'average'), show='headings')
self.student_tree.heading('id', text='学号')
self.student_tree.heading('name', text='姓名')
self.student_tree.heading('chinese', text='语文')
self.student_tree.heading('math', text='数学')
self.student_tree.heading('english', text='英语')
self.student_tree.heading('physics', text='物理')
self.student_tree.heading('chemistry', text='化学')
self.student_tree.heading('total', text='总分')
self.student_tree.heading('average', text='平均分')
self.student_tree.column('id', width=100, anchor="center")
self.student_tree.column('name', width=80, anchor="center")
self.student_tree.column('chinese', width=80, anchor="center")
self.student_tree.column('math', width=80, anchor="center")
self.student_tree.column('english', width=80, anchor="center")
self.student_tree.column('physics', width=80, anchor="center")
self.student_tree.column('chemistry', width=80, anchor="center")
self.student_tree.column('total', width=60, anchor="center")
self.student_tree.column('average', width=60, anchor="center")
self.student_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
# 刷新学生列表
self.refresh_data()
# 创建滚动条控件
scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.student_tree.configure(yscrollcommand=scrollbar.set)
scrollbar.config(command=self.student_tree.yview)
# 当鼠标或键盘操作选中学生列表中的某一行时,会触发该事件对应的处理函数
self.student_tree.bind('<<TreeviewSelect>>', self.on_student_selected)
# 鼠标双击学生信息列表任意一行,可以触发修改学生信息页面
def on_double_click(event):
# 获取被双击的项的IID
iid = self.student_tree.focus()
if iid == '':
return # 如果没有选中任何一行,则直接返回
# 选中指定的行
self.student_tree.selection_set((iid,))
self.edit_student()
# 绑定鼠标双击事件
self.student_tree.bind("<Double-1>", on_double_click)
# 鼠标右键点击学生信息列表任意一行,就会显示“删除该学生”按钮
def on_right_click(event):
# 获取当前焦点项的iid
iid = self.student_tree.focus()
# 选中指定的行
self.student_tree.selection_set((iid,))
if iid:
# 弹出菜单选项
menu = tk.Menu(self.student_tree, tearoff=0)
menu.add_command(label="删除该学生", command=self.delete_student)
menu.tk_popup(event.x_root, event.y_root)
# 绑定鼠标右键点击事件
self.student_tree.bind("<Button-3>", on_right_click)
# 学生操作按钮
button_frame = ttk.Frame(main_frame)
button_frame.pack(fill=tk.X, pady=5)
ttk.Button(button_frame, text="显示所有学生成绩", command=self.refresh_data).pack(side=tk.LEFT, padx=6)
ttk.Button(button_frame, text="添加学生", command=self.add_student).pack(side=tk.LEFT, padx=6)
ttk.Button(button_frame, text="修改学生", command=self.edit_student).pack(side=tk.LEFT, padx=6)
ttk.Button(button_frame, text="删除学生", command=self.delete_student).pack(side=tk.LEFT, padx=6)
ttk.Button(button_frame, text="导出以上学生成绩", command=self.export_students).pack(side=tk.LEFT, padx=6)
ttk.Button(button_frame, text="退出", command=self.confirm_close).pack(side=tk.LEFT, padx=6)
# 左侧信息面板
information_frame = ttk.Frame(main_frame)
information_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=10, pady=5)
# 学生详细信息
detail_frame = ttk.LabelFrame(information_frame, text="学生详细信息")
detail_frame.pack(fill=tk.X, pady=5)
self.detail_text = tk.Text(detail_frame, width=20, height=10)
self.detail_text.pack(fill=tk.BOTH, expand=True)
# 右侧折线图区域
self.line_chart_frame = tk.LabelFrame(main_frame)
self.line_chart_frame.pack(side=tk.RIGHT, fill=tk.X, padx=10, pady=10)
# 创建一个matplotlib的figure
self.line_fig = plt.figure(figsize=(5, 4), dpi=100)
# 添加一个subplot
self.line_subplot = self.line_fig.add_subplot()
# 初始化折线图
self.x = ['语文', '数学', '英语', '物理', '化学']
self.y = [0, 0, 0, 0, 0]
# 绘制折线图
self.line_subplot.plot(self.x, self.y, marker='o')
self.line_subplot.set_ylim(0, 150)
self.line_subplot.grid(True) # 显示网格线,便于阅读数据点位置
self.line_subplot.set_title("个人成绩折线图", fontsize=16, y=1.03)
# 为每个点添加标签显示X值
for i, tx in enumerate(self.x):
self.line_subplot.annotate(f'{self.y[i]}', (self.x[i], self.y[i]), textcoords="offset points", xytext=(0,10), ha='center')
# 创建FigureCanvasTkAgg对象,用于在Tkinter窗口中显示图形
self.canvas = FigureCanvasTkAgg(self.line_fig, master=self.line_chart_frame)
self.canvas.draw()
# 将图形放置到Tkinter窗口中
self.canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH)
# 图表功能按钮
view_button_frame = ttk.Frame(information_frame)
view_button_frame.pack(fill=tk.X, pady=5)
ttk.Button(view_button_frame, text="查看成绩雷达图", command=self.open_radar_chart).pack(side=tk.LEFT, padx=5)
ttk.Button(view_button_frame, text="查看班级平均分", command=self.open_class_average).pack(side=tk.LEFT, padx=5)
ttk.Button(view_button_frame, text="查看成绩分布", command=self.open_grade_distribution).pack(side=tk.LEFT, padx=5)
ttk.Button(view_button_frame, text="学生成绩对比", command=self.contrast).pack(side=tk.LEFT, padx=5)
# 关于程序讲解
help_frame = ttk.Frame(information_frame)
help_frame.pack(side=tk.BOTTOM, fill=tk.X, pady=5)
ttk.Button(help_frame, text="关于程序", command=self.program_about).pack(side=tk.LEFT, padx=6)
ttk.Button(help_frame, text="程序讲解", command=self.help_window).pack(side=tk.LEFT, padx=6)
# 图表显示区域
self.chart_frame = tk.Frame(information_frame)
self.chart_frame.pack(fill=tk.BOTH, expand=True)
# 刷新学生列表
def refresh_data(self):
# 清空当前列表
for item in self.student_tree.get_children():
self.student_tree.delete(item)
# 添加学生数据
students = self.student_manager.search_all_students()
for student in students:
self.student_tree.insert('', 'end', values=(
student['id'],
student['name'],
student['chinese'],
student['math'],
student['english'],
student['physics'],
student['chemistry'],
student['total'],
student['average'],
))
# “汇总”统计输出
self.total_text.delete(1.0, tk.END)
self.total_text.insert(tk.END, len(self.student_tree.get_children()))
# 搜索学生
def search_students(self):
keyword = self.search_entry.get().strip()
if not keyword:
# 刷新学生列表
self.refresh_data()
return
# 清空当前列表
for item in self.student_tree.get_children():
self.student_tree.delete(item)
# 添加搜索结果
students = self.student_manager.search_students(keyword)
for student in students:
self.student_tree.insert('', 'end', values=(
student['id'],
student['name'],
student['chinese'],
student['math'],
student['english'],
student['physics'],
student['chemistry'],
student['total'],
student['average'],
))
# “汇总”统计输出
self.total_text.delete(1.0, tk.END)
self.total_text.insert(tk.END, len(self.student_tree.get_children()))
# 使用自定义函数手动排序
def sort_treeview(self, tv, col, reverse):
l = [(tv.set(k, col), k) for k in tv.get_children('')]
l.sort(reverse=reverse)
# rearrange items in sorted positions
for index, (val, k) in enumerate(l):
tv.move(k, '', index)
tv.heading(col, command=lambda: self.sort_treeview(tv, col, not reverse))
# 鼠标选择学生时显示该学生成绩折线图
def on_student_selected(self, event):
selected_item = self.student_tree.selection()
if not selected_item:
return
student_id = self.student_tree.item(selected_item)['values'][0]
student = self.student_manager.search_students_id(student_id)
if student:
information = f"学号: {student['id']}\n"
information += f"姓名: {student['name']}\n"
information += f"语文: {student['chinese']}\n"
information += f"数学: {student['math']}\n"
information += f"英语: {student['english']}\n"
information += f"物理: {student['physics']}\n"
information += f"化学: {student['chemistry']}\n"
information += f"总分: {student['total']}\n"
information += f"平均分: {student['average']}"
self.detail_text.delete(1.0, tk.END)
self.detail_text.insert(tk.END, information)
self.line_subplot.cla() # 清除旧轴上的所有内容
self.x = ['语文', '数学', '英语', '物理', '化学'] # 重新计算x轴数据
# 更新y轴数据
self.y = [
student['chinese'],
student['math'],
student['english'],
student['physics'],
student['chemistry']
]
self.line_subplot.plot(self.x, self.y, marker='o')
self.line_subplot.set_ylim(0, 150)
self.line_subplot.grid(True) # 显示网格线,便于阅读数据点位置
self.line_subplot.set_title("个人成绩折线图", fontsize=16, y=1.03)
# 为每个点添加标签显示X值
for i, tx in enumerate(self.x):
self.line_subplot.annotate(f'{self.y[i]}', (self.x[i], self.y[i]), textcoords="offset points", xytext=(0,10), ha='center')
self.canvas.draw_idle() # 重新绘制图表
# 重新修改add_student方法
def add_student(self):
add_window = tk.Toplevel(self.app)
add_window.title("添加学生")
# 设置窗口居中
window_width = 500
window_height = 520
screen_width = add_window.winfo_screenwidth()
screen_height = add_window.winfo_screenheight()
x = (screen_width - window_width) / 2
y = (screen_height - window_height) / 2
add_window.geometry('%dx%d+%d+%d' % (window_width, window_height, x, y))
add_window.resizable(width=False, height=False)
# 创建标题标签控件
Show_result = tk.Label(add_window, text=">>>添加学生信息<<<", bg = "white", fg = "black", font = ("宋体", 18), bd = '0', anchor = 'center')
Show_result.place(x = 50, y = 30, width = 400, height = 50)
# 学号
tk.Label(add_window, text="学号(不可重复):", font = ('宋体', 12), width = 15).place(x = 75, y = 100, anchor = 'nw')
id_entry = tk.Entry(add_window, font = ('宋体', 15), width = 20)
id_entry.place(x = 200, y = 100, anchor = 'nw')
# 姓名
tk.Label(add_window, text="姓名:", font = ('宋体', 12), width = 15).place(x = 75, y = 150, anchor = 'nw')
name_entry = tk.Entry(add_window, font = ('宋体', 15), width = 20)
name_entry.place(x = 200, y = 150, anchor = 'nw')
# 语文成绩
tk.Label(add_window, text="语文成绩:", font = ('宋体', 12), width = 15).place(x = 75, y = 200, anchor = 'nw')
chinese_entry = tk.Entry(add_window, font = ('宋体', 15), width = 20)
chinese_entry.place(x = 200, y = 200, anchor = 'nw')
# 数学成绩
tk.Label(add_window, text="数学成绩:", font = ('宋体', 12), width = 15).place(x = 75, y = 250, anchor = 'nw')
math_entry = tk.Entry(add_window, font = ('宋体', 15), width = 20)
math_entry.place(x = 200, y = 250, anchor = 'nw')
# 英语成绩
tk.Label(add_window, text="英语成绩:", font = ('宋体', 12), width = 15).place(x = 75, y = 300, anchor = 'nw')
english_entry = tk.Entry(add_window, font = ('宋体', 15), width = 20)
english_entry.place(x = 200, y = 300, anchor = 'nw')
# 物理成绩
tk.Label(add_window, text="物理成绩:", font = ('宋体', 12), width = 15).place(x = 75, y = 350, anchor = 'nw')
physics_entry = tk.Entry(add_window, font = ('宋体', 15), width = 20)
physics_entry.place(x = 200, y = 350, anchor = 'nw')
# 化学成绩
tk.Label(add_window, text="化学成绩:", font = ('宋体', 12), width = 15).place(x = 75, y = 400, anchor = 'nw')
chemistry_entry = tk.Entry(add_window, font = ('宋体', 15), width = 20)
chemistry_entry.place(x = 200, y = 400, anchor = 'nw')
# 保存学生信息
def save_student():
try:
student_data = {
'id': id_entry.get().strip(),
'name': name_entry.get().strip(),
'chinese': int(chinese_entry.get()),
'math': int(math_entry.get()),
'english': int(english_entry.get()),
'physics': int(physics_entry.get()),
'chemistry': int(chemistry_entry.get())
}
if any(score < 0 or score > 150 for score in [
student_data['chinese'],
student_data['math'],
student_data['english'],
student_data['physics'],
student_data['chemistry']
]):
messagebox.showerror("错误", "成绩必须在0-150之间!")
return
if self.student_manager.add_student(student_data):
messagebox.showinfo("成功", "学生添加成功!")
add_window.destroy()
# 刷新学生列表
self.refresh_data()
else:
messagebox.showerror("错误", "学号已存在!")
except ValueError:
messagebox.showerror("错误", "请输入有效的成绩数字!")
# 定义“保存”按钮
save_button = ttk.Button(add_window, text="保存", command=save_student, width=15)
save_button.place(x = 105, y = 450, anchor = 'nw')
# 定义“取消”按钮
cancel_button = ttk.Button(add_window, text="取消", command=add_window.destroy, width=15)
cancel_button.place(x = 280, y = 450, anchor = 'nw')
# 修改学生信息
def edit_student(self):
selected_item = self.student_tree.selection()
if not selected_item:
messagebox.showerror("错误", "请先选择一个学生!")
return
student_id = self.student_tree.item(selected_item)['values'][0]
student = self.student_manager.search_students_id(student_id)
if not student:
messagebox.showerror("错误", "找不到该学生!")
return
modify_window = tk.Toplevel(self.app)
modify_window.title("修改学生信息")
# 设置窗口居中
window_width = 500
window_height = 520
screen_width = modify_window.winfo_screenwidth()
screen_height = modify_window.winfo_screenheight()
x = (screen_width - window_width) / 2
y = (screen_height - window_height) / 2
modify_window.geometry('%dx%d+%d+%d' % (window_width, window_height, x, y))
modify_window.resizable(width=False, height=False)
# 创建标题标签控件
Show_result = tk.Label(modify_window, text=">>>修改学生信息<<<", bg = "white", fg = "black", font = ("宋体", 18), bd = '0', anchor = 'center')
Show_result.place(x = 50, y = 30, width = 400, height = 50)
# 学号(不可编辑)
tk.Label(modify_window, text="学号(不可修改):", font = ('宋体', 12), width = 15).place(x = 75, y = 100, anchor = 'nw')
Student_ID = tk.StringVar()
Student_ID.set(student['id'])
number_entry = tk.Entry(modify_window, show = None, font = ('宋体', 15), textvariable = Student_ID, width = 20)
number_entry.place(x = 200, y = 100, anchor = 'nw')
# 设置默认值为相应的学号,学号不可改变
def on_key_release(event):
if event.widget.get():
event.widget.delete(0, tk.END)
event.widget.insert(0, student['id'])
# 使用bind绑定函数,当用户释放任意按键时,绑定的回调函数会被触发。
number_entry.bind('<KeyRelease>', on_key_release)
# 姓名
tk.Label(modify_window, text="姓名:", font = ('宋体', 12), width = 15).place(x = 75, y = 150, anchor = 'nw')
name_entry = tk.Entry(modify_window, font = ('宋体', 15), width = 20)
name_entry.insert(0, student['name'])
name_entry.place(x = 200, y = 150, anchor = 'nw')
# 语文成绩
tk.Label(modify_window, text="语文成绩:", font = ('宋体', 12), width = 15).place(x = 75, y = 200, anchor = 'nw')
chinese_entry = tk.Entry(modify_window, font = ('宋体', 15), width = 20)
chinese_entry.insert(0, student['chinese'])
chinese_entry.place(x = 200, y = 200, anchor = 'nw')
# 数学成绩
tk.Label(modify_window, text="数学成绩:", font = ('宋体', 12), width = 15).place(x = 75, y = 250, anchor = 'nw')
math_entry = tk.Entry(modify_window, font = ('宋体', 15), width = 20)
math_entry.insert(0, student['math'])
math_entry.place(x = 200, y = 250, anchor = 'nw')
# 英语成绩
tk.Label(modify_window, text="英语成绩:", font = ('宋体', 12), width = 15).place(x = 75, y = 300, anchor = 'nw')
english_entry = tk.Entry(modify_window, font = ('宋体', 15), width = 20)
english_entry.insert(0, student['english'])
english_entry.place(x = 200, y = 300, anchor = 'nw')
# 物理成绩
tk.Label(modify_window, text="物理成绩:", font = ('宋体', 12), width = 15).place(x = 75, y = 350, anchor = 'nw')
physics_entry = tk.Entry(modify_window, font = ('宋体', 15), width = 20)
physics_entry.insert(0, student['physics'])
physics_entry.place(x = 200, y = 350, anchor = 'nw')
# 化学成绩
tk.Label(modify_window, text="化学成绩:", font = ('宋体', 12), width = 15).place(x = 75, y = 400, anchor = 'nw')
chemistry_entry = tk.Entry(modify_window, font = ('宋体', 15), width = 20)
chemistry_entry.insert(0, student['chemistry'])
chemistry_entry.place(x = 200, y = 400, anchor = 'nw')
# 更新学生信息
def update_student():
try:
new_data = {
'name': name_entry.get().strip(),
'chinese': float(chinese_entry.get()),
'math': float(math_entry.get()),
'english': float(english_entry.get()),
'physics': float(physics_entry.get()),
'chemistry': float(chemistry_entry.get())
}
if any(score < 0 or score > 150 for score in [
new_data['chinese'],
new_data['math'],
new_data['english'],
new_data['physics'],
new_data['chemistry']
]):
messagebox.showerror("错误", "成绩必须在0-150之间!")
return
if self.student_manager.update_student(student['id'], new_data):
messagebox.showinfo("成功", "学生信息更新成功!")
modify_window.destroy()
# 刷新学生列表
self.refresh_data()
self.on_student_selected(None)
else:
messagebox.showerror("错误", "更新失败!")
except ValueError as e:
messagebox.showerror("错误", "请输入有效的成绩数字!")
# 保存按钮
save_button = tk.Button(modify_window, text="保存", command=update_student, width=15)
save_button.place(x = 105, y = 450, anchor = 'nw')
# 取消按钮
cancel_button = tk.Button(modify_window, text="取消", command=modify_window.destroy, width=15)
cancel_button.place(x = 280, y = 450, anchor = 'nw')
# 删除学生
def delete_student(self):
selected_item = self.student_tree.selection()
if not selected_item:
messagebox.showerror("错误", "请先选择一个学生!")
return
student_id = self.student_tree.item(selected_item)['values'][0]
student_name = self.student_tree.item(selected_item)['values'][1]
if messagebox.askyesno("确认", f"确定要删除学生“{student_name}”吗?"):
if self.student_manager.delete_student(student_id):
messagebox.showinfo("成功", "学生删除成功!")
# 刷新学生列表
self.refresh_data()
self.detail_text.delete(1.0, tk.END)
else:
messagebox.showerror("错误", "删除失败!")
# 获取当前项的所有列值组成的元组
def get_all_rows(self):
rows = []
for item in self.student_tree.get_children():
row = self.student_tree.item(item, 'values')
rows.append(row)
return rows
# 导出学生信息到指定Excel表格
def export_students(self):
export_result = self.get_all_rows()
if not any(len(employee_list[0]) > 0 for employee_list in export_result):
messagebox.showwarning("警告", "没有数据可导出")
return
try:
# 自定义列名
columns = ['学号', '姓名', '语文', '数学', '英语', '物理', '化学', '总分', '平均分']
# 将数据转换为DataFrame
df = pandas.DataFrame(export_result, columns=columns)
save_path = filedialog.asksaveasfilename(
defaultextension=".xlsx",
filetypes=[("Excel文件", "*.xlsx")])
if save_path:
df.to_excel(save_path, index=False)
messagebox.showinfo("成功", "导出成功!")
except PermissionError:
messagebox.showerror("错误", "文件被占用,请关闭文件后重试")
except Exception as e:
messagebox.showerror("错误", f"导出失败:{str(e)}")
# 显示学生成绩雷达图
def open_radar_chart(self):
selected_item = self.student_tree.selection()
if not selected_item:
messagebox.showerror("错误", "请先选择一个学生!")
return
student_id = self.student_tree.item(selected_item)['values'][0]
student = self.student_manager.search_students_id(student_id)
if student:
# 清除之前的图表
for widget in self.chart_frame.winfo_children():
widget.destroy()
# 创建新图表
self.visualizer.plot_student_radar(student)
else:
messagebox.showerror("错误", "无法获取学生数据!")
# 显示班级平均分图表
def open_class_average(self):
stats = self.student_manager.data_count()
if stats:
# 清除之前的图表
for widget in self.chart_frame.winfo_children():
widget.destroy()
# 创建新图表
self.visualizer.plot_subject_average(stats['averages'])
else:
messagebox.showerror("错误", "无法获取统计数据!")
# 显示成绩分布图表
def open_grade_distribution(self):
stats = self.student_manager.data_count()
if stats:
# 清除之前的图表
for widget in self.chart_frame.winfo_children():
widget.destroy()
# 创建新图表
self.visualizer.plot_grade_distribution(stats['distributions'])
else:
messagebox.showerror("错误", "无法获取统计数据!")
# 比较两名学生的成绩
def contrast(self):
students = self.student_manager.search_all_students()
if len(students) < 2:
messagebox.showerror("错误", "至少需要两名学生才能进行比较!")
return
# 创建选择对话框
contrast_window = tk.Toplevel(self.app)
contrast_window.title("比较两名学生的成绩")
# 设置窗口居中
window_width = 400
window_height = 290
screen_width = contrast_window.winfo_screenwidth()
screen_height = contrast_window.winfo_screenheight()
x = (screen_width - window_width) / 2
y = (screen_height - window_height) / 2
contrast_window.geometry('%dx%d+%d+%d' % (window_width, window_height, x, y))
contrast_window.resizable(width=False, height=False)
# 创建标题标签控件
Show_result = tk.Label(contrast_window, text=">>>学生成绩对比<<<", bg = "white", fg = "black", font = ("宋体", 18), bd = '0', anchor = 'center')
Show_result.place(x = 60, y = 30, width = 280, height = 50)
# 选择第一个学生
tk.Label(contrast_window, text="请选择第一个学生:").place(x = 60, y = 130, anchor = 'nw')
student1_var = tk.StringVar()
student1_combo = ttk.Combobox(contrast_window, textvariable=student1_var, width = 20)
student1_combo['values'] = [f"{s['id']} - {s['name']}" for s in students]
student1_combo.place(x = 185, y = 130, anchor = 'nw')
# 选择第二个学生
tk.Label(contrast_window, text="请选择第二个学生:").place(x = 60, y = 180, anchor = 'nw')
student2_var = tk.StringVar()
student2_combo = ttk.Combobox(contrast_window, textvariable=student2_var, width = 20)
student2_combo['values'] = [f"{s['id']} - {s['name']}" for s in students]
student2_combo.place(x = 185, y = 180, anchor = 'nw')
# 开始比较
def start_contrast():
student1_id = student1_var.get().split(' - ')[0]
student2_id = student2_var.get().split(' - ')[0]
student1 = self.student_manager.search_students_id(student1_id)
student2 = self.student_manager.search_students_id(student2_id)
if not student1 or not student2:
messagebox.showerror("错误", "请选择两名不同的学生!")
return
if student1['id'] == student2['id']:
messagebox.showerror("错误", "不能选择同一名学生进行比较!")
return
# 清除之前的图表
for widget in self.chart_frame.winfo_children():
widget.destroy()
# 创建比较图表
self.visualizer.plot_student_comparison(student1, student2)
contrast_window.destroy()
tk.Button(contrast_window, text="开始比较", command=start_contrast).place(x = 170, y = 230)
# 关于程序页面
def program_about(self):
about = tk.Tk()
about.title('关于程序')
# 设置窗口居中
window_width = 490
window_height = 520
screen_width = about.winfo_screenwidth()
screen_height = about.winfo_screenheight()
x = (screen_width - window_width) / 2
y = (screen_height - window_height) / 2
about.geometry('%dx%d+%d+%d' % (window_width, window_height, x, y))
about.resizable(width=False, height=False)
about_frame = tk.Frame(about, width=490, height=520)
about_frame.pack()
tk.Label(about_frame, text='学生成绩管理系统', font=("宋体", 16)).place(x=150, y=20)
tk.Label(about_frame, text='使用编程语言:Python', font=("宋体", 14)).place(x=50, y=70)
tk.Label(about_frame, text='使用数据库:SQLite3数据库', font=("宋体", 14)).place(x=50, y=120)
tk.Label(about_frame, text='数据库文件:student.db', font=("宋体", 14)).place(x=50, y=170)
tk.Label(about_frame, text='修改学生信息:在学生信息列表直接双击鼠标', font=("宋体", 14)).place(x=50, y=220)
tk.Label(about_frame, text='删除学生信息:在学生信息列表右击鼠标', font=("宋体", 14)).place(x=50, y=270)
tk.Label(about_frame, text='导出学生信息方式:导出到Excel文件内', font=("宋体", 14)).place(x=50, y=320)
tk.Label(about_frame, text='教师登录方式:请用教师账号登录', font=("宋体", 14)).place(x=50, y=370)
tk.Label(about_frame, text='学生登录方式:请用学生账号登录', font=("宋体", 14)).place(x=50, y=420)
tk.Label(about_frame, text='创作者:www.pyhint.com', font=("宋体", 14)).place(x=50, y=470)
about.mainloop()
# 程序讲解页面
def help_window(self):
webbrowser.open("https://www.pyhint.com/article/156.html")
# 显示教师窗口
def run(self):
self.app.mainloop()
# 登录成功后,判断账号如果是学生账号则进入学生界面,如果是教师账号则进入教师界面
def success_login(username, category):
if category == 'student':
student_enter = StudentWindow(username)
student_enter.run()
elif category == 'teacher':
teacher_enter = TeacherWindow(username)
teacher_enter.run()
# 当前模块直接被执行
if __name__ == "__main__":
# 创建登录窗口
login_window = UserLogin(success_login)
login_window.run()