Bài 5: Widget Nâng Cao
Giai đoạn 2 – Widget Nâng Cao · Mục tiêu: Thành thạo Listbox, Combobox, Checkbutton, Radiobutton, Scale và Spinbox để xây dựng form phong phú.
1. Checkbutton — Ô chọn nhiều
1.1. Cơ bản
import tkinter as tk
root = tk.Tk()
root.geometry("300x250")
# Biến lưu trạng thái
var_python = tk.BooleanVar()
var_java = tk.BooleanVar(value=True) # Mặc định tick
var_js = tk.BooleanVar()
tk.Label(root, text="Ngôn ngữ bạn biết:", font=("Arial", 11, "bold")).pack(pady=10)
tk.Checkbutton(root, text="Python ", variable=var_python).pack(anchor="w", padx=30)
tk.Checkbutton(root, text="Java ", variable=var_java).pack(anchor="w", padx=30)
tk.Checkbutton(root, text="JavaScript ", variable=var_js).pack(anchor="w", padx=30)
def show_result():
langs = []
if var_python.get(): langs.append("Python")
if var_java.get(): langs.append("Java")
if var_js.get(): langs.append("JavaScript")
print("Bạn biết:", ", ".join(langs) if langs else "Không có")
tk.Button(root, text="Xác nhận", command=show_result).pack(pady=15)
root.mainloop()
1.2. Checkbutton với trace (tự động phản hồi)
import tkinter as tk
root = tk.Tk()
root.geometry("350x200")
var = tk.BooleanVar()
label = tk.Label(root, text="", font=("Arial", 13))
label.pack(pady=20)
def on_change(*args):
if var.get():
label.config(text=" Đã đồng ý điều khoản", fg="#27AE60")
else:
label.config(text=" Chưa đồng ý", fg="#E74C3C")
var.trace_add("write", on_change)
tk.Checkbutton(root, text="Tôi đồng ý với điều khoản sử dụng",
variable=var, font=("Arial", 11)).pack()
root.mainloop()
2. Radiobutton — Chọn một trong nhiều
2.1. Cơ bản
import tkinter as tk
root = tk.Tk()
root.geometry("300x250")
# Một IntVar (hoặc StringVar) dùng chung cho tất cả Radiobutton trong nhóm
gender = tk.StringVar(value="nam")
tk.Label(root, text="Giới tính:", font=("Arial", 11, "bold")).pack(pady=10)
tk.Radiobutton(root, text="Nam ", variable=gender, value="nam").pack(anchor="w", padx=30)
tk.Radiobutton(root, text="Nữ ", variable=gender, value="nu").pack(anchor="w", padx=30)
tk.Radiobutton(root, text="Khác ", variable=gender, value="khac").pack(anchor="w", padx=30)
def show():
print("Giới tính:", gender.get())
tk.Button(root, text="Xác nhận", command=show).pack(pady=15)
root.mainloop()
2.2. Nhiều nhóm Radiobutton
import tkinter as tk
root = tk.Tk()
root.geometry("350x300")
# Mỗi nhóm dùng một biến riêng
level = tk.StringVar(value="trung_binh")
lang = tk.StringVar(value="python")
# Nhóm 1: Trình độ
frame1 = tk.LabelFrame(root, text="Trình độ", padx=10, pady=5)
frame1.pack(fill="x", padx=15, pady=8)
for text, val in [("Cơ bản", "co_ban"), ("Trung bình", "trung_binh"), ("Nâng cao", "nang_cao")]:
tk.Radiobutton(frame1, text=text, variable=level, value=val).pack(anchor="w")
# Nhóm 2: Ngôn ngữ
frame2 = tk.LabelFrame(root, text="Ngôn ngữ", padx=10, pady=5)
frame2.pack(fill="x", padx=15, pady=8)
for text, val in [("Python ", "python"), ("JavaScript ", "js"), ("Java ", "java")]:
tk.Radiobutton(frame2, text=text, variable=lang, value=val).pack(anchor="w")
def show():
print(f"Trình độ: {level.get()} | Ngôn ngữ: {lang.get()}")
tk.Button(root, text="Xác nhận", command=show).pack(pady=10)
root.mainloop()
3. Listbox — Danh sách lựa chọn
3.1. Cơ bản
import tkinter as tk
root = tk.Tk()
root.geometry("350x300")
tk.Label(root, text="Chọn môn học:", font=("Arial", 11, "bold")).pack(pady=8)
# Tạo Listbox
listbox = tk.Listbox(
root,
height=6,
font=("Arial", 11),
selectmode="single", # single / multiple / browse / extended
activestyle="dotbox"
)
listbox.pack(padx=15, fill="x")
# Thêm item
for mon in ["Toán", "Lý", "Hóa", "Sinh", "Văn", "Sử", "Địa", "Tin học"]:
listbox.insert(tk.END, mon)
def on_select(event):
indices = listbox.curselection() # Tuple các index được chọn
if indices:
selected = [listbox.get(i) for i in indices]
print("Đã chọn:", selected)
listbox.bind("<<ListboxSelect>>", on_select)
root.mainloop()
3.2. Listbox với Scrollbar & thao tác CRUD
import tkinter as tk
from tkinter import simpledialog, messagebox
root = tk.Tk()
root.title("Danh sách sinh viên")
root.geometry("400x400")
# Frame listbox
frame = tk.Frame(root)
frame.pack(fill="both", expand=True, padx=10, pady=10)
scrollbar = tk.Scrollbar(frame)
scrollbar.pack(side="right", fill="y")
listbox = tk.Listbox(frame, yscrollcommand=scrollbar.set, font=("Arial", 11),
selectmode="single")
listbox.pack(side="left", fill="both", expand=True)
scrollbar.config(command=listbox.yview)
for sv in ["Nguyễn Văn An", "Trần Thị Bích", "Lê Văn Cường", "Phạm Thị Dung"]:
listbox.insert(tk.END, sv)
# Buttons
btn_frame = tk.Frame(root)
btn_frame.pack(pady=5)
def add():
name = simpledialog.askstring("Thêm", "Nhập tên sinh viên:")
if name:
listbox.insert(tk.END, name)
def delete():
sel = listbox.curselection()
if sel:
listbox.delete(sel[0])
else:
messagebox.showwarning("Cảnh báo", "Chọn một mục trước!")
def edit():
sel = listbox.curselection()
if sel:
old = listbox.get(sel[0])
new = simpledialog.askstring("Sửa", "Tên mới:", initialvalue=old)
if new:
listbox.delete(sel[0])
listbox.insert(sel[0], new)
tk.Button(btn_frame, text=" Thêm", command=add, bg="#27AE60", fg="white", padx=10).pack(side="left", padx=3)
tk.Button(btn_frame, text=" Sửa", command=edit, bg="#F39C12", fg="white", padx=10).pack(side="left", padx=3)
tk.Button(btn_frame, text=" Xóa", command=delete, bg="#E74C3C", fg="white", padx=10).pack(side="left", padx=3)
root.mainloop()
4. Combobox — Danh sách thả xuống
Combobox nằm trong module tkinter.ttk:
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
root.geometry("350x250")
tk.Label(root, text="Chọn tỉnh/thành:", font=("Arial", 11)).pack(pady=10)
# Danh sách lựa chọn
cities = ["Hà Nội", "TP Hồ Chí Minh", "Đà Nẵng", "Cần Thơ", "Hải Phòng"]
combo = ttk.Combobox(root, values=cities, width=25, font=("Arial", 11))
combo.set("Chọn tỉnh/thành") # Placeholder
combo.config(state="readonly") # Chỉ chọn, không gõ
combo.pack(pady=5)
result = tk.Label(root, text="", font=("Arial", 13), fg="#2980B9")
result.pack(pady=15)
def on_select(event):
result.config(text=f"Bạn chọn: {combo.get()}")
combo.bind("<<ComboboxSelected>>", on_select)
root.mainloop()
5. Scale — Thanh trượt
import tkinter as tk
root = tk.Tk()
root.geometry("350x300")
tk.Label(root, text="Điều chỉnh độ sáng:", font=("Arial", 11, "bold")).pack(pady=10)
brightness_var = tk.IntVar(value=50)
scale = tk.Scale(
root,
variable=brightness_var,
from_=0, # Giá trị nhỏ nhất
to=100, # Giá trị lớn nhất
orient="horizontal",# horizontal / vertical
length=300,
tickinterval=25, # Khoảng cách giữa các vạch
resolution=5, # Bước nhảy
label="Độ sáng (%)"
)
scale.pack(pady=10)
label = tk.Label(root, text="50%", font=("Arial", 20, "bold"))
label.pack(pady=10)
def on_change(val):
label.config(text=f"{val}%")
# Thay đổi độ sáng label demo
hex_val = int(255 * int(val) / 100)
color = f"#{hex_val:02x}{hex_val:02x}{hex_val:02x}"
label.config(fg=color if int(val) < 80 else "#2C3E50")
scale.config(command=on_change)
root.mainloop()
6. Spinbox — Ô nhập số có mũi tên
import tkinter as tk
root = tk.Tk()
root.geometry("350x250")
tk.Label(root, text="Nhập số lượng:", font=("Arial", 11, "bold")).pack(pady=10)
qty_var = tk.IntVar(value=1)
spinbox = tk.Spinbox(
root,
from_=1,
to=99,
increment=1, # Bước nhảy
textvariable=qty_var,
width=8,
font=("Arial", 14),
justify="center"
)
spinbox.pack(pady=5)
# Spinbox với danh sách giá trị
tk.Label(root, text="Chọn cỡ áo:", font=("Arial", 11, "bold")).pack(pady=10)
size_spin = tk.Spinbox(root, values=("XS", "S", "M", "L", "XL", "XXL"),
width=8, font=("Arial", 14), justify="center", state="readonly")
size_spin.pack(pady=5)
def show():
print(f"Số lượng: {qty_var.get()} | Cỡ: {size_spin.get()}")
tk.Button(root, text="Thêm vào giỏ ", command=show).pack(pady=15)
root.mainloop()
7. Ứng dụng tổng hợp: Form đặt hàng
import tkinter as tk
from tkinter import ttk, messagebox
root = tk.Tk()
root.title("Form đặt hàng")
root.geometry("450x500")
root.resizable(False, False)
# ── Thông tin khách hàng ──────────────────────────────────
frame_info = tk.LabelFrame(root, text="Thông tin khách hàng", padx=10, pady=8)
frame_info.pack(fill="x", padx=15, pady=8)
tk.Label(frame_info, text="Họ tên:").grid(row=0, column=0, sticky="e", pady=4)
entry_name = tk.Entry(frame_info, width=28); entry_name.grid(row=0, column=1, padx=8, pady=4, sticky="ew")
tk.Label(frame_info, text="Tỉnh/Thành:").grid(row=1, column=0, sticky="e", pady=4)
cities = ["Hà Nội", "TP Hồ Chí Minh", "Đà Nẵng", "Cần Thơ"]
combo_city = ttk.Combobox(frame_info, values=cities, state="readonly", width=26)
combo_city.grid(row=1, column=1, padx=8, pady=4, sticky="ew")
# ── Sản phẩm ─────────────────────────────────────────────
frame_prod = tk.LabelFrame(root, text="Sản phẩm", padx=10, pady=8)
frame_prod.pack(fill="x", padx=15, pady=5)
products = ["Áo thun - 150,000đ", "Quần jeans - 350,000đ", "Giày sneaker - 500,000đ"]
listbox = tk.Listbox(frame_prod, height=3, selectmode="multiple", font=("Arial", 10))
listbox.pack(fill="x")
for p in products: listbox.insert(tk.END, p)
# ── Tùy chọn ─────────────────────────────────────────────
frame_opt = tk.LabelFrame(root, text="Tùy chọn", padx=10, pady=8)
frame_opt.pack(fill="x", padx=15, pady=5)
# Cỡ
tk.Label(frame_opt, text="Cỡ:").grid(row=0, column=0, sticky="e")
spin_size = tk.Spinbox(frame_opt, values=("S", "M", "L", "XL"), width=5, state="readonly")
spin_size.grid(row=0, column=1, padx=8, sticky="w")
# Số lượng
tk.Label(frame_opt, text="Số lượng:").grid(row=0, column=2, sticky="e")
spin_qty = tk.Spinbox(frame_opt, from_=1, to=10, width=5)
spin_qty.grid(row=0, column=3, padx=8, sticky="w")
# Giao nhanh
var_fast = tk.BooleanVar()
tk.Checkbutton(frame_opt, text="Giao nhanh (+20,000đ)", variable=var_fast).grid(
row=1, column=0, columnspan=4, sticky="w", pady=5)
# Thanh toán
frame_pay = tk.LabelFrame(root, text="Thanh toán", padx=10, pady=8)
frame_pay.pack(fill="x", padx=15, pady=5)
pay_var = tk.StringVar(value="cod")
for text, val in [("Tiền mặt (COD)", "cod"), ("Chuyển khoản", "bank"), ("Ví điện tử", "ewallet")]:
tk.Radiobutton(frame_pay, text=text, variable=pay_var, value=val).pack(side="left", padx=10)
# ── Nút đặt hàng ─────────────────────────────────────────
def order():
name = entry_name.get()
city = combo_city.get()
sels = [listbox.get(i) for i in listbox.curselection()]
if not name or not city or not sels:
messagebox.showwarning("Thiếu thông tin", "Vui lòng điền đầy đủ thông tin!")
return
msg = (f"Đặt hàng thành công! \n\n"
f"Khách: {name} — {city}\n"
f"Sản phẩm: {', '.join(sels)}\n"
f"Cỡ: {spin_size.get()} | SL: {spin_qty.get()}\n"
f"Giao nhanh: {'Có' if var_fast.get() else 'Không'}\n"
f"Thanh toán: {pay_var.get().upper()}")
messagebox.showinfo("Đặt hàng", msg)
tk.Button(root, text=" Đặt hàng ngay", command=order,
font=("Arial", 12, "bold"), bg="#E74C3C", fg="white",
padx=20, pady=8).pack(pady=12)
root.mainloop()
Bài tập thực hành
Trắc nghiệm
-
Widget nào dùng để chọn một trong nhiều tùy chọn?
- A. Checkbutton
- B. Radiobutton
- C. Listbox
- D. Spinbox
-
Combobox nằm trong module nào?
- A.
tkinter - B.
tkinter.ttk - C.
tkinter.widgets - D.
tk.combobox
- A.
-
Sự kiện nào kích hoạt khi chọn item trong Listbox?
- A.
<Select> - B.
<ListboxChange> - C.
<<ListboxSelect>> - D.
<ItemSelect>
- A.
Thực hành
- Tạo form "Khảo sát kỹ năng": Tên (Entry), Kỹ năng (Checkbutton: Python, JS, SQL, Excel), Kinh nghiệm (Spinbox 0-20 năm), nút Gửi.
- Tạo app "Bộ lọc màu sắc" với 3 Scale (R, G, B từ 0-255) — cập nhật màu nền cửa sổ theo thời gian thực.
- Tạo Listbox danh sách công việc với chức năng Thêm, Đánh dấu hoàn thành (gạch qua), Xóa.
- (Nâng cao) Tạo app "Quiz trắc nghiệm": 5 câu hỏi, mỗi câu dùng Radiobutton 4 đáp án, nút Nộp bài tính điểm.
Bài trước: Bài 4 – Xử Lý Sự Kiện · Bài tiếp: Bài 6 – Menu & Hộp Thoại