Tkinter – Quản lý Layout

4.5/5 - (12 votes)

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à packgrid 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 ImageImageTk 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.

Capture

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, padxpady 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.

Capture

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ổ.

Capture

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.

Capture
4.3 3 votes
Article Rating
Subscribe
Thông báo cho tôi qua email khi
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

3 Comments
Inline Feedbacks
View all comments
tuấn
tuấn
6 năm trước

cho e hỏi với ví dụ với Button thì làm sao để nhấn close thì cửa sổ box đóng lại ạ

nguyên
nguyên
3 năm trước

ad em muốn dùng grid chung với pack thì như nào ạ em có làm như thông thường nhưng không được ạ

123
123
3 năm trước

Chào bạn, nếu mình muốn đọc ảnh bằng cv2 thì dùng gì thay cho imageTk.photoimage nhỉ 😀