Trong phần này chúng ta sẽ tìm hiểu cách sử dụng layout trong Tkinter.
Trong Tkinter có hai loại widget, loại thứ nhất là các widget thường như nút bấm, textbox… loại thứ hai là containter, đây là những widget chứa các widget khác, chúng còn có một cái tên nữa là layout.
Trong Tkinter có 3 loại layout là pack
, grid
và place.
Trong đó place
là kiểu layout tự do, thuật ngữ gọi là Absolute Positioning, tức là bạn sẽ phải tự quy định vị trí cũng như kích thước của các widget. Layout
pack
sắp xếp các widget của bạn theo chiều ngang và chiều dọc. Còn layout grid
sắp xếp widget theo dạng bảng.
Thiết kế tự do
Thường thì khi thiết kế giao diện chúng ta dùng layout nhiều hơn là thiết kế theo kiểu tự do. Khi thiết kế theo kiểu tự do này chúng ta sẽ phải tự quy định vị trí và kích thước cho các widget trên màn hình, nếu kích thước cửa sổ thay đổi thì kích thước và vị trí của các widget không thay đổi. Thiết kế theo kiểu tự do cũng rất bất tiện khi bạn muốn thay đổi, thêm hay bớt các widget thì hầu như bạn sẽ phải sắp xếp lại toàn bộ widget.
from PIL import Image, ImageTk from tkinter import Tk, Label, BOTH from tkinter.ttk import Frame, Style class Example(Frame): def __init__(self, parent): Frame.__init__(self, parent) self.parent = parent self.initUI() def initUI(self): self.parent.title("Absolute positioning") self.pack(fill=BOTH, expand=1) Style().configure("TFrame", background="#333") bard = Image.open("C:\\bardejov.jpg") bardejov = ImageTk.PhotoImage(bard) label1 = Label(self, image=bardejov) label1.image = bardejov label1.place(x=20, y=20) rot = Image.open("C:\\rotunda.jpg") rotunda = ImageTk.PhotoImage(rot) label2 = Label(self, image=rotunda) label2.image = rotunda label2.place(x=40, y=160) minc = Image.open("C:\\mincol.jpg") mincol = ImageTk.PhotoImage(minc) label3 = Label(self, image=mincol) label3.image = mincol label3.place(x=170, y=50) root = Tk() root.geometry("300x280+300+300") app = Example(root) root.mainloop()
Trong ví dụ trên, chúng ta hiển thị 3 ảnh lên cửa sổ.
from PIL import Image, ImageTk
Để hiển thị ảnh thì chúng ta có thể dùng module PhotoImage
trong gói Tkinter
nhưng module này rất hạn chế nên ở đây mình dùng module Image
và ImageTk
trong thư viện Pillow, thư viện này không có sẵn trong Python 3 mà bạn phải tự down về và cài vào.
Style().configure("TFrame", background="#333")
Dòng trên sẽ tô màu nền cửa sổ là màu xám đen.
bard = Image.open("C:\\bardejov.jpg") bardejov = ImageTk.PhotoImage(bard)
Hai dòng trên sẽ tạo các đối tượng ảnh và tải chúng vào chương trình.
label1 = Label(self, image=bardejov)
Chúng ta tạo đối tượng Label
, đây là một lớp dùng để hiển thị chữ và ảnh.
label1.image = bardejov
Chúng ta phải lưu lại tham chiếu đến ảnh nếu không sẽ bị xóa.
label1.place(x=20, y=20)
Chúng ta đặt ảnh tại vị trí x = 20 và y = 20.
Button
Trong ví dụ dưới đây, chúng ta sẽ hiển thị hai button (nút bấm) ở góc trái màn hình sử dụng layout pack
.
from tkinter import Tk, RIGHT, BOTH, RAISED from tkinter.ttk import Frame, Button, Style class Example(Frame): def __init__(self, parent): Frame.__init__(self, parent) self.parent = parent self.initUI() def initUI(self): self.parent.title("Button") self.style = Style() self.style.theme_use("default") frame = Frame(self, relief=RAISED, borderwidth=1) frame.pack(fill=BOTH, expand=True) self.pack(fill=BOTH, expand=True) closeButton = Button(self, text="Close") closeButton.pack(side=RIGHT, padx=5, pady=5) okButton = Button(self, text="OK") okButton.pack(side=RIGHT) root = Tk() root.geometry("300x200+300+300") app = Example(root) root.mainloop()
Chúng ta tạo ra 2 frame, frame đầu tiên là layout chính dùng để chứa toàn bộ widget. Frame thứ hai chúng ta đặt như lên frame đầu tiên và cho kích thước dãn ra theo cả chiều ngang và chiều dọc. Hai button được đặt theo chiều ngang ở góc bên phải của cửa sổ.
frame = Frame(self, relief=RAISED, borderwidth=1) frame.pack(fill=BOTH, expand=True)
Hai dòng code trên tạo ra Frame
thứ hai, frame này sẽ chiếm phần lớn diện tích Frame
chính. Tham số relief
mô phỏng hiệu ứng hiển thị 3D của Frame,
ngoài giá trị RAISED bạn có thể thử một số giá trị khác như FLAT, SUNKEN, GROOVE, và RIDGE. Tham số borderwidth
hiển thị đường viền của frame, mặc định tham số này là 0 nên bạn ko thể thấy đường viền.
closeButton = Button(self, text="Close") closeButton.pack(side=RIGHT, padx=5, pady=5)
Ở hai dòng trên chúng ta tạo nút close và tham chiếu đến đối tượng closeButton
. Phương thức pack
()
quy định cách hiển thị của button trên cửa sổ, tham số side=RIGHT
sẽ đưa button vào vị trí bên phải, padx
và pady
duy định khoảng cách giữa button với các widget khác và với đường viền cửa sổ chính, ở đây khoảng cách là 5 pixel.
okButton.pack(side=RIGHT)
Nút OK cũng được tạo ra tương tự và cũng được đưa sang phía bên phải màn hình, sát bên cạnh nút Close với khoảng cách 5 pixel.
Tạo Form
Trong ví dụ dưới đây, chúng ta tiếp tục tìm hiểu về layout pack.
from tkinter import Tk, Text, TOP, BOTH, X, N, LEFT from tkinter.ttk import Frame, Label, Entry class Example(Frame): def __init__(self, parent): Frame.__init__(self, parent) self.parent = parent self.initUI() def initUI(self): self.parent.title("Review") self.pack(fill=BOTH, expand=True) frame1 = Frame(self) frame1.pack(fill=X) lbl1 = Label(frame1, text="Title", width=6) lbl1.pack(side=LEFT, padx=5, pady=5) entry1 = Entry(frame1) entry1.pack(fill=X, padx=5, expand=True) frame2 = Frame(self) frame2.pack(fill=X) lbl2 = Label(frame2, text="Author", width=6) lbl2.pack(side=LEFT, padx=5, pady=5) entry2 = Entry(frame2) entry2.pack(fill=X, padx=5, expand=True) frame3 = Frame(self) frame3.pack(fill=BOTH, expand=True) lbl3 = Label(frame3, text="Review", width=6) lbl3.pack(side=LEFT, anchor=N, padx=5, pady=5) txt = Text(frame3) txt.pack(fill=BOTH, pady=5, padx=5, expand=True) root = Tk() root.geometry("300x300+300+300") app = Example(root) root.mainloop()
Chúng ta thiết kế một cửa sổ hiển thị form đánh giá.
self.pack(fill=BOTH, expand=True)
Đầu tiên chúng ta tạo frame chính để chứa các widget còn lại kể cả các frame khác.
frame1 = Frame(self) frame1.pack(fill=X) lbl1 = Label(frame1, text="Title", width=6) lbl1.pack(side=LEFT, padx=5, pady=5) entry1 = Entry(frame1) entry1.pack(fill=X, padx=5, expand=True)
Chúng ta tạo một frame và đặt 2 widget vào đó, đó là một label và một entry. Label
là widget dùng để hiển thị text, entry
là widget tạo nhập text. Label có độ dài cố định còn entry thì có độ dài tự động thay đổi theo kích thước cửa sổ nhờ vào 2 tham số fill
và expand
. Frame thứ hai cũng tương tự.
frame3 = Frame(self) frame3.pack(fill=BOTH, expand=True) lbl3 = Label(frame3, text="Review", width=6) lbl3.pack(side=LEFT, anchor=N, padx=5, pady=5) txt = Text(frame3) txt.pack(fill=BOTH, pady=5, padx=5, expand=True)
Trong frame thứ ba chúng ta đặt một Label
và một Text
. Label được đặt về phía trên của frame, còn text tự động co dãn theo kích thước cửa sổ.
Grid layout
Tiếp theo chúng ta sẽ tìm hiểu về layout grid
.
from tkinter import Tk, W, E from tkinter.ttk import Frame, Button, Style from tkinter.ttk import Entry class Example(Frame): def __init__(self, parent): Frame.__init__(self, parent) self.parent = parent self.initUI() def initUI(self): self.parent.title("Calculator") Style().configure("TButton", padding=(0, 5, 0, 5), font='serif 10') self.columnconfigure(0, pad=3) self.columnconfigure(1, pad=3) self.columnconfigure(2, pad=3) self.columnconfigure(3, pad=3) self.rowconfigure(0, pad=3) self.rowconfigure(1, pad=3) self.rowconfigure(2, pad=3) self.rowconfigure(3, pad=3) self.rowconfigure(4, pad=3) entry = Entry(self) entry.grid(row=0, columnspan=4, sticky=W+E) cls = Button(self, text="Cls") cls.grid(row=1, column=0) bck = Button(self, text="Back") bck.grid(row=1, column=1) lbl = Button(self) lbl.grid(row=1, column=2) clo = Button(self, text="Close") clo.grid(row=1, column=3) sev = Button(self, text="7") sev.grid(row=2, column=0) eig = Button(self, text="8") eig.grid(row=2, column=1) nin = Button(self, text="9") nin.grid(row=2, column=2) div = Button(self, text="/") div.grid(row=2, column=3) fou = Button(self, text="4") fou.grid(row=3, column=0) fiv = Button(self, text="5") fiv.grid(row=3, column=1) six = Button(self, text="6") six.grid(row=3, column=2) mul = Button(self, text="*") mul.grid(row=3, column=3) one = Button(self, text="1") one.grid(row=4, column=0) two = Button(self, text="2") two.grid(row=4, column=1) thr = Button(self, text="3") thr.grid(row=4, column=2) mns = Button(self, text="-") mns.grid(row=4, column=3) zer = Button(self, text="0") zer.grid(row=5, column=0) dot = Button(self, text=".") dot.grid(row=5, column=1) equ = Button(self, text="=") equ.grid(row=5, column=2) pls = Button(self, text="+") pls.grid(row=5, column=3) self.pack() root = Tk() app = Example(root) root.mainloop()
Đoạn code trên thiết kế giao diện của một chiếc máy tính bỏ túi.
Style().configure("TButton", padding=(0, 5, 0, 5), font='serif 10')
Chúng ta quy định kiểu phông chữ thông qua tham số font
và khoảng cách của frame với cửa sổ thông qua tham số padding.
self.columnconfigure(0, pad=3) ... self.rowconfigure(0, pad=3)
Hai phương thức columnconfigure()
và rowconfigure()
quy định khoảng cách giữa các widget.
entry = Entry(self) entry.grid(row=0, columnspan=4, sticky=W+E)
Chúng ta tạo một Entry để hiển thị số cũng như kết quả tính toán. Tham số sticky
quy định cách thức co dãn theo kích thước cửa sổ, W+E tức là co dãn theo chiều từ trái sang phải.
cls = Button(self, text="Cls") cls.grid(row=1, column=0)
Nút cls
được đặt tại vị trí hàng 1 cột 0, chỉ số hàng và cột bắt đầu từ 0.
self.pack()
Phương thức pack()
hiển thị các widget lên màn hình và khởi tạo kích thước cho chúng. Nếu chúng ta chỉ gọi pack()
mà không đưa các tham số vào như trên thì kích thước của các widget luôn luôn là kích thước vừa đủ để bao bọc lấy đoạn text bên trong, ngoài ra không có tham số truyền vào thì phương thức này sẽ đặt layout ở vị trí trên cùng của frame. Bạn có thể kéo dãn cửa sổ để thấy cách nó hoạt động.