Ở bài trước, mình đã chia sẻ với anh em cách hiển thị widget Label
bằng pack()
. Nó chỉ đơn giản là ném widget vào cửa sổ. Nhưng để xây dựng một ứng dụng thực sự, ta cần kiểm soát vị trí của từng widget một cách chính xác.
Trong Tkinter, nhóm các công cụ làm việc này được gọi là Trình quản lý Bố cục (Geometry Managers) bao gồm 3 thành phần chính:
pack()
: Xếp chồng các widget lên nhau (theo chiều dọc hoặc ngang).grid()
: Sắp xếp các widget vào một hệ thống lưới (hàng và cột).place()
: Đặt các widget tại một tọa độ (x, y) cụ thể.
pack()
Khi "pack" một widget, nó sẽ chiếm một khoảng không gian và widget tiếp theo sẽ được xếp vào không gian còn lại.
import tkinter as tk root = tk.Tk()
root.title("Tìm hiểu Pack")
root.geometry("300x200") # Mặc định, pack() sẽ xếp các widget từ trên xuống dưới
label1 = tk.Label(root, text="Label 1", bg="red", fg="white")
label1.pack() label2 = tk.Label(root, text="Label 2", bg="green", fg="white")
label2.pack() label3 = tk.Label(root, text="Label 3", bg="blue", fg="white")
label3.pack() root.mainloop()
Lưu ý: bg
là background color, fg
là foreground color (màu chữ).
Trên dây là một ví dụ đơn giản về pack()
. Ngoài ra, pack()
cũng cung cấp một số tham số giúp anh em cấu hình linh hoạt hơn:
side
: Xác định hướng xếp. Giá trị có thể làtk.TOP
(mặc định),tk.BOTTOM
,tk.LEFT
,tk.RIGHT
.fill
: Widget có lấp đầy không gian được cấp hay không. Giá trị có thể làtk.X
(lấp đầy theo chiều ngang),tk.Y
(lấp đầy theo chiều dọc),tk.BOTH
(lấp đầy cả hai).expand
: Khi cửa sổ được thay đổi kích thước, widget có "giành" thêm không gian hay không. Giá trị làTrue
hoặcFalse
.padx
,pady
: Thêm khoảng đệm (padding) bên ngoài widget theo chiều ngang (x) và chiều dọc (y).
Ví dụ 2
import tkinter as tk root = tk.Tk()
root.title("Pack Nâng Cao")
root.geometry("500x300") # Label này sẽ ở trên cùng và lấp đầy theo chiều ngang
label_top = tk.Label(root, text="TOP", bg="red", fg="white")
label_top.pack(side=tk.TOP, fill=tk.X) # Label này sẽ ở dưới cùng và lấp đầy theo chiều ngang
label_bottom = tk.Label(root, text="BOTTOM", bg="blue", fg="white")
label_bottom.pack(side=tk.BOTTOM, fill=tk.X) # Label này sẽ ở bên trái
label_left = tk.Label(root, text="LEFT", bg="green", fg="white")
label_left.pack(side=tk.LEFT, fill=tk.Y) # Label này sẽ ở bên phải và được "giãn ra" khi cửa sổ thay đổi kích thước
label_right = tk.Label(root, text="RIGHT (expand=True)", bg="purple", fg="white")
# expand=True và fill=tk.BOTH làm cho nó chiếm hết không gian còn lại
label_right.pack(side=tk.RIGHT, expand=True, fill=tk.BOTH, padx=10, pady=10) root.mainloop()
Thông thường mình sẽ dùng pack()
khi chỉ cần xây dựng bố cục đơn giản, ví dụ như xếp một vài widget chồng lên nhau, hoặc tạo một thanh công cụ ở trên cùng, một thanh trạng thái ở dưới cùng.
grid()
grid()
là công cụ được khuyến khích sử dụng nhiều nhất. Nó chia cửa sổ thành một lưới các hàng (rows) và cột (columns), giống như một bảng tính Excel. Khi thêm widget, anh em cần phải chỉ cần chỉ định widget sẽ nằm ở hàng nào, cột nào.
Ví dụ tạo một form đăng nhập đơn giản
import tkinter as tk root = tk.Tk()
root.title("Tìm hiểu Grid")
root.geometry("300x150") # Tạo các widget
label_user = tk.Label(root, text="Tên đăng nhập:")
label_pass = tk.Label(root, text="Mật khẩu:")
entry_user = tk.Entry(root)
entry_pass = tk.Entry(root, show="*") # show="*" để che mật khẩu
button_login = tk.Button(root, text="Đăng nhập") # Sắp xếp chúng trên lưới
# Hàng 0
label_user.grid(row=0, column=0, padx=5, pady=5)
entry_user.grid(row=0, column=1, padx=5, pady=5) # Hàng 1
label_pass.grid(row=1, column=0, padx=5, pady=5)
entry_pass.grid(row=1, column=1, padx=5, pady=5) # Hàng 2
button_login.grid(row=2, column=1, padx=5, pady=5) root.mainloop()
Các tham số của grid()
:
columnspan
: Widget sẽ chiếm bao nhiêu cột. (Giống như "Merge Cells" trong Excel).rowspan
: Widget sẽ chiếm bao nhiêu hàng.sticky
: Quan trọng! Nó quyết định widget sẽ "dính" vào cạnh nào của ô lưới. Giá trị là các hướng la bàn:N
(bắc),S
(nam),E
(đông),W
(tây). Anh em có thể kết hợp chúng, ví dụ:NW
(tây bắc),SE
(đông nam).sticky='we'
: Dính vào cạnh Tây và Đông -> widget sẽ giãn ra theo chiều ngang để lấp đầy ô.sticky='ns'
: Dính vào cạnh Bắc và Nam -> widget sẽ giãn ra theo chiều dọc.sticky='nsew'
: Lấp đầy toàn bộ ô lưới. Đây là giá trị sẽ dùng rất thường xuyên.
Ví dụ nâng cao với sticky
và columnspan
:
import tkinter as tk root = tk.Tk()
root.title("Grid Nâng Cao") # Cấu hình để cột 1 có thể co giãn khi cửa sổ thay đổi kích thước
root.columnconfigure(1, weight=1) label_user = tk.Label(root, text="Tên đăng nhập:")
entry_user = tk.Entry(root)
label_pass = tk.Label(root, text="Mật khẩu:")
entry_pass = tk.Entry(root, show="*")
button_login = tk.Button(root, text="Đăng nhập")
button_cancel = tk.Button(root, text="Hủy bỏ") # Dùng sticky='w' (West) để căn lề trái các Label
label_user.grid(row=0, column=0, padx=5, pady=5, sticky='w')
# Dùng sticky='we' (West-East) để ô nhập liệu giãn theo chiều ngang
entry_user.grid(row=0, column=1, columnspan=2, padx=5, pady=5, sticky='we') label_pass.grid(row=1, column=0, padx=5, pady=5, sticky='w')
entry_pass.grid(row=1, column=1, columnspan=2, padx=5, pady=5, sticky='we') # Đặt 2 button trên cùng một hàng
button_login.grid(row=2, column=1, padx=5, pady=5, sticky='e')
button_cancel.grid(row=2, column=2, padx=5, pady=5, sticky='w') root.mainloop()
place()
place()
cho phép anh em đặt widget tại một tọa độ cụ thể.
x
,y
: Tọa độ tuyệt đối tính bằng pixel so với góc trên bên trái của cửa sổ cha.relx
,rely
: Tọa độ tương đối (giá trị từ 0.0 đến 1.0). Ví dụrelx=0.5
có nghĩa là đặt widget ở vị trí giữa theo chiều ngang.
Tuy nhiên rất hiếm khi sử dụng place()
. Việc dùng tọa độ tuyệt đối làm cho giao diện:
- Không co giãn tốt khi thay đổi kích thước cửa sổ.
- Trông khác nhau trên các hệ điều hành/độ phân giải màn hình khác nhau.
- Khó bảo trì khi cần thêm/bớt widget.
Nó chỉ hữu ích trong một số trường hợp đặc biệt, ví dụ như đặt một nút bấm lên trên một tấm ảnh.
QUY TẮC VÀNG
KHÔNG BAO GIỜ TRỘN LẪN
pack()
VÀgrid()
TRONG CÙNG MỘT CỬA SỔ CHA (master window/frame).
Hai trình quản lý này sử dụng hai thuật toán khác nhau để tính toán không gian. Việc trộn lẫn chúng sẽ làm Tkinter bị bối rối và ứng dụng của anh em có thể sẽ bị treo. Anh em có thể dùng pack()
trong một Frame
và grid()
trong một Frame
khác, nhưng không được dùng cả hai trực tiếp trong root
. (Khái niệm Frame
mình sẽ trình bày chi tiét sau, tạm thời anh em có thể hiểu đơn giản đó là các cửa sổ con của root
)
Trong bài tiếp theo, mình sẽ cùng anh em tìm hiểu các Widget cơ bản trong Tkinter. Có thắc mắc / góp ý gì anh em cứ để lại comment, mình sẽ phản hồi và tiếp thu nhé .