TKINTER NÂNG CAO (PHẦN 2)¶
1. Ôn tập Tkinter cơ bản¶
1.1. Những gì đã học¶
- Window: Cửa sổ chính với
tk.Tk()
- Label: Hiển thị văn bản
tk.Label()
- Button: Nút bấm với
command
tk.Button()
- Entry: Nhập liệu một dòng
tk.Entry()
- Layout:
pack()
,grid()
để sắp xếp widget
1.2. Cấu trúc ứng dụng Tkinter¶
import tkinter as tk
class UngDungCuaToi:
def __init__(self):
self.window = tk.Tk()
self.window.title("Tiêu đề")
self.window.geometry("400x300")
self.tao_giao_dien()
def tao_giao_dien(self):
# Tạo các widget ở đây
pass
def chay(self):
self.window.mainloop()
app = UngDungCuaToi()
app.chay()
2. Widget Text - Nhập liệu nhiều dòng¶
2.1. Text widget cơ bản¶
import tkinter as tk
window = tk.Tk()
window.title("Text Widget Demo")
window.geometry("500x400")
# Label hướng dẫn
label = tk.Label(window, text="Nhập nội dung:", font=("Arial", 12))
label.pack(pady=10)
# Text widget
text_area = tk.Text(window, font=("Arial", 11), width=50, height=15)
text_area.pack(pady=10, padx=20)
# Nút để lấy nội dung
def lay_noi_dung():
noi_dung = text_area.get(1.0, tk.END) # Lấy từ dòng 1, ký tự 0 đến cuối
print("Nội dung:", noi_dung)
btn_lay = tk.Button(window, text="Lấy nội dung", command=lay_noi_dung)
btn_lay.pack(pady=10)
window.mainloop()
2.2. Text với Scrollbar¶
import tkinter as tk
window = tk.Tk()
window.title("Text với Scrollbar")
window.geometry("600x450")
# Frame chứa text và scrollbar
text_frame = tk.Frame(window)
text_frame.pack(pady=20, padx=20, fill="both", expand=True)
# Text widget
text_area = tk.Text(text_frame, font=("Arial", 11), wrap="word")
text_area.pack(side="left", fill="both", expand=True)
# Scrollbar
scrollbar = tk.Scrollbar(text_frame, command=text_area.yview)
scrollbar.pack(side="right", fill="y")
# Liên kết scrollbar với text
text_area.config(yscrollcommand=scrollbar.set)
# Thêm nội dung mẫu
noi_dung_mau = """Đây là nội dung mẫu rất dài.
Bạn có thể cuộn lên xuống để xem toàn bộ.
Text widget rất hữu ích cho việc nhập văn bản nhiều dòng.
Một số tính năng của Text widget:
- Hỗ trợ nhiều dòng
- Có thể cuộn nội dung
- Hỗ trợ định dạng text
- Có thể chèn hình ảnh
- Hỗ trợ tag để định dạng"""
text_area.insert(1.0, noi_dung_mau)
window.mainloop()
3. Widget Listbox - Danh sách lựa chọn¶
3.1. Listbox cơ bản¶
import tkinter as tk
window = tk.Tk()
window.title("Listbox Demo")
window.geometry("400x350")
# Label
label = tk.Label(window, text="Danh sách môn học:", font=("Arial", 12))
label.pack(pady=10)
# Listbox
listbox = tk.Listbox(window, font=("Arial", 11), height=8)
listbox.pack(pady=10, padx=20)
# Thêm items vào listbox
mon_hoc = ["Toán", "Vật Lý", "Hóa Học", "Sinh Học", "Văn", "Anh", "Sử", "Địa"]
for mon in mon_hoc:
listbox.insert(tk.END, mon)
# Hàm xử lý khi chọn
def xu_ly_chon():
try:
index = listbox.curselection()[0] # Lấy index của item được chọn
mon_chon = listbox.get(index) # Lấy nội dung
print(f"Bạn chọn: {mon_chon}")
except IndexError:
print("Chưa chọn môn học nào!")
# Button
btn_chon = tk.Button(window, text="Xem môn đã chọn", command=xu_ly_chon)
btn_chon.pack(pady=10)
window.mainloop()
3.2. Listbox với chức năng thêm/xóa¶
import tkinter as tk
class QuanLyMonHoc:
def __init__(self):
self.window = tk.Tk()
self.window.title("Quản lý môn học")
self.window.geometry("500x400")
self.tao_giao_dien()
def tao_giao_dien(self):
# Title
title = tk.Label(self.window, text="QUẢN LÝ MÔN HỌC",
font=("Arial", 16, "bold"), fg="blue")
title.pack(pady=15)
# Frame nhập môn mới
input_frame = tk.Frame(self.window)
input_frame.pack(pady=10)
tk.Label(input_frame, text="Tên môn học:", font=("Arial", 11)).pack(side="left")
self.entry_mon = tk.Entry(input_frame, font=("Arial", 11), width=20)
self.entry_mon.pack(side="left", padx=10)
btn_them = tk.Button(input_frame, text="Thêm", command=self.them_mon,
bg="green", fg="white", font=("Arial", 10))
btn_them.pack(side="left", padx=5)
# Listbox với scrollbar
list_frame = tk.Frame(self.window)
list_frame.pack(pady=20, padx=20, fill="both", expand=True)
self.listbox = tk.Listbox(list_frame, font=("Arial", 11), height=12)
self.listbox.pack(side="left", fill="both", expand=True)
scrollbar = tk.Scrollbar(list_frame, command=self.listbox.yview)
scrollbar.pack(side="right", fill="y")
self.listbox.config(yscrollcommand=scrollbar.set)
# Thêm dữ liệu mẫu
mon_hoc_mac_dinh = ["Toán", "Vật Lý", "Hóa Học", "Sinh Học", "Văn", "Anh"]
for mon in mon_hoc_mac_dinh:
self.listbox.insert(tk.END, mon)
# Buttons
btn_frame = tk.Frame(self.window)
btn_frame.pack(pady=10)
btn_xoa = tk.Button(btn_frame, text="Xóa môn", command=self.xoa_mon,
bg="red", fg="white", font=("Arial", 11))
btn_xoa.pack(side="left", padx=10)
btn_sua = tk.Button(btn_frame, text="Sửa môn", command=self.sua_mon,
bg="orange", fg="white", font=("Arial", 11))
btn_sua.pack(side="left", padx=10)
btn_xoa_tat_ca = tk.Button(btn_frame, text="Xóa tất cả", command=self.xoa_tat_ca,
bg="purple", fg="white", font=("Arial", 11))
btn_xoa_tat_ca.pack(side="left", padx=10)
def them_mon(self):
ten_mon = self.entry_mon.get().strip()
if ten_mon:
# Kiểm tra trùng
danh_sach_hien_tai = self.listbox.get(0, tk.END)
if ten_mon not in danh_sach_hien_tai:
self.listbox.insert(tk.END, ten_mon)
self.entry_mon.delete(0, tk.END)
print(f"Đã thêm môn: {ten_mon}")
else:
print("Môn học đã tồn tại!")
else:
print("Vui lòng nhập tên môn học!")
def xoa_mon(self):
try:
index = self.listbox.curselection()[0]
ten_mon = self.listbox.get(index)
self.listbox.delete(index)
print(f"Đã xóa môn: {ten_mon}")
except IndexError:
print("Vui lòng chọn môn cần xóa!")
def sua_mon(self):
try:
index = self.listbox.curselection()[0]
ten_cu = self.listbox.get(index)
# Hiển thị tên cũ trong entry
self.entry_mon.delete(0, tk.END)
self.entry_mon.insert(0, ten_cu)
# Xóa item cũ
self.listbox.delete(index)
print(f"Đang sửa môn: {ten_cu}")
except IndexError:
print("Vui lòng chọn môn cần sửa!")
def xoa_tat_ca(self):
self.listbox.delete(0, tk.END)
print("Đã xóa tất cả môn học!")
def chay(self):
self.window.mainloop()
# Chạy ứng dụng
app = QuanLyMonHoc()
app.chay()
4. Widget Frame - Nhóm các widget¶
4.1. Frame để nhóm widgets¶
import tkinter as tk
window = tk.Tk()
window.title("Frame Demo")
window.geometry("600x400")
# Frame header
header_frame = tk.Frame(window, bg="blue", height=60)
header_frame.pack(fill="x", pady=(0, 10))
header_frame.pack_propagate(False) # Không cho frame co giãn
title = tk.Label(header_frame, text="ỨNG DỤNG DEMO FRAME",
font=("Arial", 16, "bold"), fg="white", bg="blue")
title.pack(pady=15)
# Frame chính
main_frame = tk.Frame(window)
main_frame.pack(fill="both", expand=True, padx=20)
# Frame trái
left_frame = tk.Frame(main_frame, bg="lightblue", width=250)
left_frame.pack(side="left", fill="y", padx=(0, 10))
left_frame.pack_propagate(False)
tk.Label(left_frame, text="PANEL TRÁI", font=("Arial", 12, "bold"),
bg="lightblue").pack(pady=10)
tk.Button(left_frame, text="Button 1", width=15).pack(pady=5)
tk.Button(left_frame, text="Button 2", width=15).pack(pady=5)
tk.Button(left_frame, text="Button 3", width=15).pack(pady=5)
# Frame phải
right_frame = tk.Frame(main_frame, bg="lightgreen")
right_frame.pack(side="right", fill="both", expand=True)
tk.Label(right_frame, text="PANEL PHẢI", font=("Arial", 12, "bold"),
bg="lightgreen").pack(pady=10)
tk.Label(right_frame, text="Nội dung chính ở đây", bg="lightgreen").pack(pady=20)
# Frame footer
footer_frame = tk.Frame(window, bg="gray", height=40)
footer_frame.pack(side="bottom", fill="x")
footer_frame.pack_propagate(False)
tk.Label(footer_frame, text="Footer - Thông tin bản quyền",
fg="white", bg="gray").pack(pady=10)
window.mainloop()
5. Widget Menu - Tạo menu bar¶
5.1. Menu cơ bản¶
import tkinter as tk
from tkinter import messagebox
class UngDungVoiMenu:
def __init__(self):
self.window = tk.Tk()
self.window.title("Ứng dụng với Menu")
self.window.geometry("500x400")
self.tao_menu()
self.tao_giao_dien()
def tao_menu(self):
# Tạo menu bar
menubar = tk.Menu(self.window)
self.window.config(menu=menubar)
# Menu File
file_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="File", menu=file_menu)
file_menu.add_command(label="Mới", command=self.file_moi, accelerator="Ctrl+N")
file_menu.add_command(label="Mở", command=self.file_mo, accelerator="Ctrl+O")
file_menu.add_separator() # Dòng phân cách
file_menu.add_command(label="Lưu", command=self.file_luu, accelerator="Ctrl+S")
file_menu.add_command(label="Lưu dạng...", command=self.file_luu_dang)
file_menu.add_separator()
file_menu.add_command(label="Thoát", command=self.thoat, accelerator="Ctrl+Q")
# Menu Edit
edit_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="Edit", menu=edit_menu)
edit_menu.add_command(label="Hoàn tác", command=self.edit_hoan_tac, accelerator="Ctrl+Z")
edit_menu.add_command(label="Làm lại", command=self.edit_lam_lai, accelerator="Ctrl+Y")
edit_menu.add_separator()
edit_menu.add_command(label="Sao chép", command=self.edit_sao_chep, accelerator="Ctrl+C")
edit_menu.add_command(label="Dán", command=self.edit_dan, accelerator="Ctrl+V")
# Menu View
view_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="View", menu=view_menu)
view_menu.add_command(label="Phóng to", command=self.view_phong_to)
view_menu.add_command(label="Thu nhỏ", command=self.view_thu_nho)
view_menu.add_command(label="Kích thước gốc", command=self.view_kich_thuoc_goc)
# Menu Help
help_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="Help", menu=help_menu)
help_menu.add_command(label="Hướng dẫn", command=self.help_huong_dan)
help_menu.add_command(label="Về chúng tôi", command=self.help_ve_chung_toi)
def tao_giao_dien(self):
# Nội dung chính
self.text_area = tk.Text(self.window, font=("Arial", 12))
self.text_area.pack(fill="both", expand=True, padx=10, pady=10)
# Thêm nội dung mẫu
self.text_area.insert(1.0, "Đây là ứng dụng demo với menu.\n\n"
"Bạn có thể sử dụng các menu ở trên để thực hiện các chức năng.")
# Các hàm xử lý menu File
def file_moi(self):
self.text_area.delete(1.0, tk.END)
messagebox.showinfo("File", "Đã tạo file mới!")
def file_mo(self):
messagebox.showinfo("File", "Chức năng mở file!")
def file_luu(self):
messagebox.showinfo("File", "Đã lưu file!")
def file_luu_dang(self):
messagebox.showinfo("File", "Chức năng lưu dạng...")
def thoat(self):
if messagebox.askokcancel("Thoát", "Bạn có chắc muốn thoát?"):
self.window.quit()
# Các hàm xử lý menu Edit
def edit_hoan_tac(self):
messagebox.showinfo("Edit", "Chức năng hoàn tác!")
def edit_lam_lai(self):
messagebox.showinfo("Edit", "Chức năng làm lại!")
def edit_sao_chep(self):
messagebox.showinfo("Edit", "Đã sao chép!")
def edit_dan(self):
messagebox.showinfo("Edit", "Đã dán!")
# Các hàm xử lý menu View
def view_phong_to(self):
current_font = self.text_area.cget("font")
self.text_area.config(font=("Arial", 14))
messagebox.showinfo("View", "Đã phóng to!")
def view_thu_nho(self):
self.text_area.config(font=("Arial", 10))
messagebox.showinfo("View", "Đã thu nhỏ!")
def view_kich_thuoc_goc(self):
self.text_area.config(font=("Arial", 12))
messagebox.showinfo("View", "Về kích thước gốc!")
# Các hàm xử lý menu Help
def help_huong_dan(self):
messagebox.showinfo("Hướng dẫn",
"Cách sử dụng:\n"
"1. Dùng menu File để tạo, mở, lưu file\n"
"2. Dùng menu Edit để chỉnh sửa\n"
"3. Dùng menu View để thay đổi hiển thị")
def help_ve_chung_toi(self):
messagebox.showinfo("Về chúng tôi",
"Ứng dụng Demo Menu\n"
"Phiên bản 1.0\n"
"Tác giả: Học sinh Python")
def chay(self):
self.window.mainloop()
# Chạy ứng dụng
app = UngDungVoiMenu()
app.chay()
6. Messagebox - Hộp thoại thông báo¶
6.1. Các loại messagebox¶
import tkinter as tk
from tkinter import messagebox
window = tk.Tk()
window.title("Demo Messagebox")
window.geometry("400x500")
# Title
title = tk.Label(window, text="DEMO CÁC LOẠI MESSAGEBOX",
font=("Arial", 14, "bold"), fg="blue")
title.pack(pady=20)
# Các hàm demo messagebox
def demo_showinfo():
messagebox.showinfo("Thông báo", "Đây là thông báo thông thường!")
def demo_showwarning():
messagebox.showwarning("Cảnh báo", "Đây là cảnh báo!")
def demo_showerror():
messagebox.showerror("Lỗi", "Đây là thông báo lỗi!")
def demo_askquestion():
result = messagebox.askquestion("Câu hỏi", "Bạn có thích học Python không?")
print(f"Kết quả: {result}") # yes hoặc no
def demo_askokcancel():
result = messagebox.askokcancel("Xác nhận", "Bạn có muốn tiếp tục?")
print(f"Kết quả: {result}") # True hoặc False
def demo_askyesno():
result = messagebox.askyesno("Lựa chọn", "Bạn có muốn lưu file?")
print(f"Kết quả: {result}") # True hoặc False
def demo_askretrycancel():
result = messagebox.askretrycancel("Thử lại", "Kết nối thất bại. Thử lại?")
print(f"Kết quả: {result}") # True hoặc False
# Các nút demo
buttons = [
("showinfo - Thông báo", demo_showinfo, "lightblue"),
("showwarning - Cảnh báo", demo_showwarning, "yellow"),
("showerror - Lỗi", demo_showerror, "lightcoral"),
("askquestion - Câu hỏi", demo_askquestion, "lightgreen"),
("askokcancel - OK/Cancel", demo_askokcancel, "orange"),
("askyesno - Yes/No", demo_askyesno, "lightpink"),
("askretrycancel - Retry/Cancel", demo_askretrycancel, "lightgray")
]
for text, command, color in buttons:
btn = tk.Button(window, text=text, command=command,
bg=color, font=("Arial", 11), width=30, height=2)
btn.pack(pady=5)
window.mainloop()
7. Ví dụ tổng hợp - Ứng dụng tính điểm trung bình¶
7.1. Ứng dụng hoàn chỉnh¶
import tkinter as tk
from tkinter import messagebox, ttk
import json
import os
class UngDungTinhDiem:
def __init__(self):
self.window = tk.Tk()
self.window.title("Ứng dụng tính điểm trung bình")
self.window.geometry("800x600")
self.window.configure(bg="lightgray")
# Dữ liệu
self.danh_sach_hs = []
self.file_data = "diem_hoc_sinh.json"
self.tao_menu()
self.tao_giao_dien()
self.tai_du_lieu()
def tao_menu(self):
menubar = tk.Menu(self.window)
self.window.config(menu=menubar)
# Menu File
file_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="File", menu=file_menu)
file_menu.add_command(label="Mới", command=self.file_moi)
file_menu.add_command(label="Mở", command=self.tai_du_lieu)
file_menu.add_command(label="Lưu", command=self.luu_du_lieu)
file_menu.add_separator()
file_menu.add_command(label="Thoát", command=self.thoat)
# Menu View
view_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="View", menu=view_menu)
view_menu.add_command(label="Thống kê", command=self.hien_thi_thong_ke)
view_menu.add_command(label="Làm mới", command=self.cap_nhat_bang)
# Menu Help
help_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="Help", menu=help_menu)
help_menu.add_command(label="Hướng dẫn", command=self.hien_thi_huong_dan)
help_menu.add_command(label="Về ứng dụng", command=self.ve_ung_dung)
def tao_giao_dien(self):
# Header
header = tk.Frame(self.window, bg="darkblue", height=60)
header.pack(fill="x")
header.pack_propagate(False)
title = tk.Label(header, text="🎓 ỨNG DỤNG TÍNH ĐIỂM TRUNG BÌNH",
font=("Arial", 16, "bold"), fg="white", bg="darkblue")
title.pack(pady=15)
# Main container
main_frame = tk.Frame(self.window, bg="lightgray")
main_frame.pack(fill="both", expand=True, padx=10, pady=10)
# Left panel - Form nhập
left_panel = tk.LabelFrame(main_frame, text="Nhập thông tin học sinh",
font=("Arial", 12, "bold"), bg="white", width=300)
left_panel.pack(side="left", fill="y", padx=(0, 10))
left_panel.pack_propagate(False)
# Form fields
tk.Label(left_panel, text="Họ tên:", bg="white").grid(row=0, column=0, sticky="w", padx=10, pady=5)
self.entry_ten = tk.Entry(left_panel, width=25)
self.entry_ten.grid(row=0, column=1, padx=10, pady=5)
tk.Label(left_panel, text="Lớp:", bg="white").grid(row=1, column=0, sticky="w", padx=10, pady=5)
self.entry_lop = tk.Entry(left_panel, width=25)
self.entry_lop.grid(row=1, column=1, padx=10, pady=5)
tk.Label(left_panel, text="Toán:", bg="white").grid(row=2, column=0, sticky="w", padx=10, pady=5)
self.entry_toan = tk.Entry(left_panel, width=25)
self.entry_toan.grid(row=2, column=1, padx=10, pady=5)
tk.Label(left_panel, text="Văn:", bg="white").grid(row=3, column=0, sticky="w", padx=10, pady=5)
self.entry_van = tk.Entry(left_panel, width=25)
self.entry_van.grid(row=3, column=1, padx=10, pady=5)
tk.Label(left_panel, text="Anh:", bg="white").grid(row=4, column=0, sticky="w", padx=10, pady=5)
self.entry_anh = tk.Entry(left_panel, width=25)
self.entry_anh.grid(row=4, column=1, padx=10, pady=5)
# Buttons
button_frame = tk.Frame(left_panel, bg="white")
button_frame.grid(row=5, column=0, columnspan=2, pady=20)
tk.Button(button_frame, text="Thêm", command=self.them_hoc_sinh,
bg="green", fg="white", width=10).pack(side="left", padx=5)
tk.Button(button_frame, text="Xóa form", command=self.xoa_form,
bg="orange", fg="white", width=10).pack(side="left", padx=5)
# Right panel - Bảng kết quả
right_panel = tk.Frame(main_frame, bg="white")
right_panel.pack(side="right", fill="both", expand=True)
# Treeview
self.tree = ttk.Treeview(right_panel, columns=("Ten", "Lop", "Toan", "Van", "Anh", "TB", "XL"),
show="headings", height=20)
# Headers
self.tree.heading("Ten", text="Họ tên")
self.tree.heading("Lop", text="Lớp")
self.tree.heading("Toan", text="Toán")
self.tree.heading("Van", text="Văn")
self.tree.heading("Anh", text="Anh")
self.tree.heading("TB", text="Điểm TB")
self.tree.heading("XL", text="Xếp loại")
# Column widths
self.tree.column("Ten", width=120)
self.tree.column("Lop", width=60)
self.tree.column("Toan", width=50)
self.tree.column("Van", width=50)
self.tree.column("Anh", width=50)
self.tree.column("TB", width=60)
self.tree.column("XL", width=80)
self.tree.pack(fill="both", expand=True, padx=10, pady=10)
# Bottom buttons
bottom_frame = tk.Frame(right_panel, bg="white")
bottom_frame.pack(fill="x", padx=10, pady=5)
tk.Button(bottom_frame, text="Xóa học sinh", command=self.xoa_hoc_sinh,
bg="red", fg="white").pack(side="left", padx=5)
tk.Button(bottom_frame, text="Thống kê", command=self.hien_thi_thong_ke,
bg="blue", fg="white").pack(side="left", padx=5)
tk.Button(bottom_frame, text="Lưu dữ liệu", command=self.luu_du_lieu,
bg="purple", fg="white").pack(side="right", padx=5)
def them_hoc_sinh(self):
try:
ten = self.entry_ten.get().strip()
lop = self.entry_lop.get().strip()
toan = float(self.entry_toan.get())
van = float(self.entry_van.get())
anh = float(self.entry_anh.get())
if not ten or not lop:
messagebox.showwarning("Cảnh báo", "Vui lòng nhập đầy đủ thông tin!")
return
if not (0 <= toan <= 10 and 0 <= van <= 10 and 0 <= anh <= 10):
messagebox.showerror("Lỗi", "Điểm phải từ 0 đến 10!")
return
diem_tb = (toan + van + anh) / 3
xep_loai = self.tinh_xep_loai(diem_tb)
hoc_sinh = {
"ten": ten,
"lop": lop,
"toan": toan,
"van": van,
"anh": anh,
"diem_tb": diem_tb,
"xep_loai": xep_loai
}
self.danh_sach_hs.append(hoc_sinh)
self.cap_nhat_bang()
self.xoa_form()
messagebox.showinfo("Thành công", f"Đã thêm học sinh {ten}!")
except ValueError:
messagebox.showerror("Lỗi", "Vui lòng nhập điểm hợp lệ!")
def tinh_xep_loai(self, diem_tb):
if diem_tb >= 8.5:
return "Giỏi"
elif diem_tb >= 6.5:
return "Khá"
elif diem_tb >= 5.0:
return "Trung bình"
else:
return "Yếu"
def cap_nhat_bang(self):
# Xóa dữ liệu cũ
for item in self.tree.get_children():
self.tree.delete(item)
# Thêm dữ liệu mới
for hs in self.danh_sach_hs:
self.tree.insert("", "end", values=(
hs["ten"], hs["lop"], hs["toan"], hs["van"],
hs["anh"], f"{hs['diem_tb']:.1f}", hs["xep_loai"]
))
def xoa_form(self):
self.entry_ten.delete(0, tk.END)
self.entry_lop.delete(0, tk.END)
self.entry_toan.delete(0, tk.END)
self.entry_van.delete(0, tk.END)
self.entry_anh.delete(0, tk.END)
self.entry_ten.focus()
def xoa_hoc_sinh(self):
selected = self.tree.selection()
if not selected:
messagebox.showwarning("Cảnh báo", "Vui lòng chọn học sinh cần xóa!")
return
index = self.tree.index(selected[0])
ten_hs = self.danh_sach_hs[index]["ten"]
if messagebox.askyesno("Xác nhận", f"Xóa học sinh {ten_hs}?"):
del self.danh_sach_hs[index]
self.cap_nhat_bang()
def hien_thi_thong_ke(self):
if not self.danh_sach_hs:
messagebox.showinfo("Thống kê", "Chưa có dữ liệu!")
return
tong_hs = len(self.danh_sach_hs)
diem_tb_chung = sum(hs["diem_tb"] for hs in self.danh_sach_hs) / tong_hs
# Đếm xếp loại
gioi = sum(1 for hs in self.danh_sach_hs if hs["xep_loai"] == "Giỏi")
kha = sum(1 for hs in self.danh_sach_hs if hs["xep_loai"] == "Khá")
tb = sum(1 for hs in self.danh_sach_hs if hs["xep_loai"] == "Trung bình")
yeu = sum(1 for hs in self.danh_sach_hs if hs["xep_loai"] == "Yếu")
thong_ke = f"""THỐNG KÊ TỔNG QUAN
Tổng số học sinh: {tong_hs}
Điểm TB chung: {diem_tb_chung:.2f}
Xếp loại:
• Giỏi: {gioi} HS ({gioi/tong_hs*100:.1f}%)
• Khá: {kha} HS ({kha/tong_hs*100:.1f}%)
• Trung bình: {tb} HS ({tb/tong_hs*100:.1f}%)
• Yếu: {yeu} HS ({yeu/tong_hs*100:.1f}%)"""
messagebox.showinfo("Thống kê", thong_ke)
def luu_du_lieu(self):
try:
with open(self.file_data, 'w', encoding='utf-8') as f:
json.dump(self.danh_sach_hs, f, ensure_ascii=False, indent=4)
messagebox.showinfo("Lưu dữ liệu", "Đã lưu thành công!")
except Exception as e:
messagebox.showerror("Lỗi", f"Không thể lưu: {e}")
def tai_du_lieu(self):
try:
if os.path.exists(self.file_data):
with open(self.file_data, 'r', encoding='utf-8') as f:
self.danh_sach_hs = json.load(f)
self.cap_nhat_bang()
except Exception as e:
messagebox.showerror("Lỗi", f"Không thể tải: {e}")
def file_moi(self):
if messagebox.askyesno("File mới", "Xóa tất cả dữ liệu hiện tại?"):
self.danh_sach_hs = []
self.cap_nhat_bang()
self.xoa_form()
def hien_thi_huong_dan(self):
huong_dan = """HƯỚNG DẪN SỬ DỤNG
1. Nhập thông tin học sinh vào form bên trái
2. Nhấn 'Thêm' để thêm vào danh sách
3. Chọn học sinh trong bảng và nhấn 'Xóa' để xóa
4. Dùng menu File để lưu/tải dữ liệu
5. Xem thống kê qua menu View hoặc nút Thống kê
Chú ý: Điểm phải từ 0 đến 10"""
messagebox.showinfo("Hướng dẫn", huong_dan)
def ve_ung_dung(self):
messagebox.showinfo("Về ứng dụng",
"Ứng dụng tính điểm TB\nPhiên bản 2.0\nTkinter Advanced Demo")
def thoat(self):
if messagebox.askokcancel("Thoát", "Lưu dữ liệu trước khi thoát?"):
self.luu_du_lieu()
self.window.quit()
def chay(self):
self.window.mainloop()
# Chạy ứng dụng
app = UngDungTinhDiem()
app.chay()
8. Bài tập thực hành tại lớp¶
Bài 1: Text Editor đơn giản¶
Tạo ứng dụng soạn thảo văn bản với Text widget, có menu File (Mới, Mở, Lưu, Thoát).
Bài 2: Quản lý sách thư viện¶
Tạo form nhập thông tin sách (Tên, Tác giả, Năm XB) và hiển thị trong Listbox.
Bài 3: Máy tính với nhiều chức năng¶
Tạo máy tính có giao diện đẹp với Frame để nhóm các nút, có menu để chọn chế độ tính.
Bài 4: Ứng dụng ghi chú¶
Tạo app ghi chú với Text widget, có thể lưu/tải ghi chú từ file.
9. Bài tập về nhà¶
Bài 1: Ứng dụng quản lý chi tiêu cá nhân¶
Tạo app theo dõi thu chi với form nhập, bảng hiển thị và thống kê.
Bài 2: Game tic-tac-toe (X-O)¶
Tạo game cờ caro 3x3 với giao diện Button.
Bài 3: Ứng dụng từ điển Anh-Việt mini¶
Tạo từ điển tra cứu với Entry nhập từ và Text hiển thị nghĩa.
Bài 4: Máy tính BMI nâng cao¶
Tạo app tính BMI với lưu lịch sử, biểu đồ và lời khuyên chi tiết.
10. Ghi chú quan trọng¶
10.1. Widget nâng cao đã học¶
- Text: Nhập liệu nhiều dòng, có scrollbar
- Listbox: Danh sách lựa chọn, thêm/xóa items
- Frame: Nhóm widgets, tạo layout phức tạp
- Menu: Tạo menu bar, submenu
- Treeview: Bảng dữ liệu dạng cây
- Messagebox: Các loại hộp thoại
10.2. Layout và thiết kế¶
- Sử dụng Frame để tạo layout phức tạp
- Kết hợp pack(), grid() trong các Frame khác nhau
LabelFrame
để tạo khung có tiêu đềpack_propagate(False)
để cố định kích thước Frame
10.3. Xử lý sự kiện nâng cao¶
- Menu với accelerator (phím tắt)
- Bind sự kiện cho widget (như double-click)
- Xử lý nhiều loại messagebox khác nhau
- Validation dữ liệu đầu vào
10.4. Quản lý dữ liệu¶
- Lưu/tải dữ liệu JSON
- Cập nhật giao diện khi dữ liệu thay đổi
- Xử lý lỗi với try-except
- Backup dữ liệu định kỳ
10.5. Best practices¶
- Tách biệt logic và giao diện
- Sử dụng class để tổ chức code
- Validation đầu vào người dùng
- Feedback rõ ràng qua messagebox
- Code có thể tái sử dụng
10.6. Mở rộng thêm¶
- Tìm hiểu thêm về ttk (themed widgets)
- Sử dụng Canvas để vẽ đồ họa
- Tích hợp với database
- Tạo executable file (.exe)
- Responsive design cho nhiều kích thước màn hình