Category Archives: Tkinter

Tkinter – Trò chơi rắn săn mồi

Trong phần này chúng ta viết lại một game rất nổi tiếng đó là game rắn săn mồi (Snake).

Snake

Game này được phát hành vào những năm 70 trên các hệ máy cầm tay, một thời gian sau được đưa lên PC. Trong game người chơi sẽ điều khiển một con rắn. Mục tiêu của người chơi là cố gắng cho con rắn ăn được càng nhiều mồi càng tốt. Mỗi lần con rắn ăn mồi, cơ thể nó sẽ dài ra. Game kết thúc khi người chơi cho rắn “ăn” phải tường hoặc cơ thể chính mình.

Mô tả game

Kích thước của mỗi đốt trên con rắn là 10px. Con rắn sẽ được điều khiển bằng các phím mũi tên. Khi game kết thúc, dòng chữ “Game Over” sẽ hiện lên giữa màn hình.

import sys
import random
from PIL import Image, ImageTk
from tkinter import Tk, Frame, Canvas, ALL, NW

WIDTH      = 300
HEIGHT     = 300
DELAY      = 100
DOT_SIZE   = 10
ALL_DOTS   = 900 #WIDTH * HEIGHT / (DOT_SIZE * DOT_SIZE)
RAND_POS   = 27

x = [0] * ALL_DOTS
y = [0] * ALL_DOTS

class Board(Canvas):
   def __init__(self, parent):
       Canvas.__init__(self, width=WIDTH, height=HEIGHT, 
                       background="black", highlightthickness=0)
       self.parent = parent
       self.initGame()
       self.pack()
 
   def initGame(self):
       self.right = True
       self.left = False
       self.up = False
       self.down = False
 
       self.inGame = True
       self.dots = 3
 
       self.apple_x = 100
       self.apple_y = 190
 
       for i in range(self.dots):
           x[i] = 50 - i * 10
           y[i] = 50 
 
       try: 
           self.dotImage = Image.open("dot.png")
           self.dotImage.thumbnail((10, 10), Image.ANTIALIAS)
           self.dot = ImageTk.PhotoImage(self.dotImage)
 
           self.headImage = Image.open("head.png")
           self.headImage.thumbnail((10, 10), Image.ANTIALIAS) 
           self.head = ImageTk.PhotoImage(self.headImage)
 
           self.appleImage = Image.open("apple.png")
           self.appleImage.thumbnail((10, 10), Image.ANTIALIAS) 
           self.apple = ImageTk.PhotoImage(self.appleImage)
 
       except IOError as e:
           print (e)
           sys.exit(1)
 
       self.createObjects()
       self.locateApple()
       self.bind_all("<Key>", self.onKeyPressed)
       self.after(DELAY, self.onTimer)
 
   def createObjects(self):
       self.create_image(self.apple_x, self.apple_y, image=self.apple,
       anchor=NW, tag="apple")
       self.create_image(x[0], y[0], image=self.head, anchor=NW, tag="head")
       self.create_image(x[1], y[1], image=self.dot, anchor=NW, tag="dot")
       self.create_image(x[2], y[2], image=self.dot, anchor=NW, tag="dot") 
 
   def locateApple(self):
       apple = self.find_withtag("apple")
       self.delete(apple[0])
 
       r = random.randint(0, RAND_POS)
       self.apple_x = r * DOT_SIZE
       r = random.randint(0, RAND_POS)
       self.apple_y = r * DOT_SIZE
 
       self.create_image(self.apple_x, self.apple_y, image=self.apple,
                         anchor=NW, tag="apple") 
 
   def doMove(self):
       dots = self.find_withtag("dot")
       head = self.find_withtag("head")
 
       items = dots + head
 
       z = 0
 
       while z < len(items) - 1:
           c1 = self.coords(items[z])
           c2 = self.coords(items[z + 1])
           self.move(items[z], c2[0]-c1[0], c2[1]-c1[1])
           z += 1
 
       if self.left:
           self.move(head, -DOT_SIZE, 0)
 
       if self.right:
           self.move(head, DOT_SIZE, 0)
 
       if self.up:
           self.move(head, 0, -DOT_SIZE)
 
       if self.down:
           self.move(head, 0, DOT_SIZE)
 
   def checkCollisions(self):
       dots = self.find_withtag("dot")
       head = self.find_withtag("head")
 
       x1, y1, x2, y2 = self.bbox(head)
       overlap = self.find_overlapping(x1, y1, x2, y2)
 
       for dot in dots:
           for over in overlap:
               if over == dot:
                   self.inGame = False
 
       if x1 < 0: 
           self.inGame = False 
       if x1 > WIDTH - DOT_SIZE:
           self.inGame = False
       if y1 < 0: 
           self.inGame = False 
       if y1 > HEIGHT - DOT_SIZE:
           self.inGame = False
 
   def checkApple(self):
       apple = self.find_withtag("apple")
       head = self.find_withtag("head")
 
       x1, y1, x2, y2 = self.bbox(head)
       overlap = self.find_overlapping(x1, y1, x2, y2)
 
       for ovr in overlap:
           if apple[0] == ovr:
               x, y = self.coords(apple)
               self.create_image(x, y, image=self.dot, anchor=NW, tag="dot")
               self.locateApple()
 
   def onTimer(self): 
       if self.inGame:
           self.checkCollisions()
           self.checkApple()
           self.doMove()
           self.after(DELAY, self.onTimer)
       else:
           self.gameOver()
 
   def onKeyPressed(self, e):
       key = e.keysym
 
       if key == "Left" and not self.right:
          self.left = True
          self.up = False
          self.down = False
 
       if key == "Right" and not self.left:
           self.right = True
           self.up = False
           self.down = False
 
       if key == "Up" and not self.down:
           self.up = True
           self.right = False
           self.left = False
 
       if key == "Down" and not self.up:
           self.down = True
           self.right = False
           self.left = False
 
      if key == "Escape":
          self.quit()
 
   def gameOver(self):
      self.delete(ALL)
      self.create_text(self.winfo_width() / 2, self.winfo_height() / 2, 
                       text="Game Over", fill="white")
 
class Snake(Frame):
   def __init__(self, parent):
       Frame.__init__(self, parent)
       parent.title("Snake")
       self.board = Board(parent)
       self.pack()
 
root = Tk()
snake = Snake(root)
root.mainloop()

Đầu tiên chúng ta định nghĩa một số hằng số dùng trong game:

  • WIDTHHEIGHT là kích thước cửa sổ chính.
  • DOT_SIZE là kích thước của mồi và mỗi đốt trên con rắn.
  • ALL_DOTS là số lượng đốt tối đa của rắn. Vì cửa sổ có kích thước 300 * 300, mỗi đốt rắn có kích thước 10 * 10 nến số lượng đốt tối đa là (300 * 300) / (10 * 10) = 900.
  • RAND_POS là hằng số để tính vị trí ngẫu nhiên của mồi.
  • DELAY là tốc độ của game.
x = [0] * ALL_DOTS
y = [0] * ALL_DOTS

Tiếp theo chúng ta tạo 2 mảng x và y, hai mảng này lưu trữ tọa độ của tất cả các đốt trên thân con rắn.

Kế tiếp là phương thức initGame(), nhiệm vụ của phương thức này là khởi tạo toàn bộ mọi thứ có trong game, từ khởi tạo biến, load ảnh đến khởi động timer…

try: 
    self.dotImage = Image.open("dot.png")
    self.dotImage.thumbnail((10, 10), Image.ANTIALIAS)
    self.dot = ImageTk.PhotoImage(self.dotImage)
 
    self.headImage = Image.open("head.png")
    self.headImage.thumbnail((10, 10), Image.ANTIALIAS) 
    self.head = ImageTk.PhotoImage(self.headImage)
 
    self.appleImage = Image.open("apple.png")
    self.appleImage.thumbnail((10, 10), Image.ANTIALIAS) 
    self.apple = ImageTk.PhotoImage(self.appleImage)
 
except (IOError, e):
    print (e)
    sys.exit(1)

Đoạn code trên thực hiện load ảnh dùng để hiển thị mồi và các đốt của con rắn. Do mỗi đốt của con rắn và mồi có kích thước 10×10 pixel nên chúng ta nên resize kích thước ảnh về 10×10 phòng trường hợp ảnh của chúng ta quá lớn bằng phương thức thumbnail().

self.createObjects()
self.locateApple()

Phương thức createObjects() có nhiệm vụ vẽ các đối tượng lên canvas. Phương thức locateApple() tạo tọa độ ngẫu nhiên của mồi và vẽ lên canvas.

self.bind_all("<Key>", self.onKeyPressed)

Tiếp theo chúng ta gọi phương thức bind_all() để gắn sự kiện bấm phím vào phương thức onKeyPressed().

def createObjects(self):

    self.create_image(self.apple_x, self.apple_y, image=self.apple,
        anchor=NW, tag="apple")
    self.create_image(50, 50, image=self.head, anchor=NW,  tag="head")
    self.create_image(30, 50, image=self.dot, anchor=NW, tag="dot")
    self.create_image(40, 50, image=self.dot, anchor=NW, tag="dot")

Bên trong phương thức createObjects() chúng ta vẽ các đối tượng lên canvas bao gồm mồi và 3 đốt đầu của con rắn. Ý nghĩa của các tham số đã được giải thích trong bài viết trước ngoại trừ tham số tag. Tham số tag đơn giản là giống như chúng ta đưa một ID cho ảnh vậy thôi, về sau chúng ta sẽ có các phương thức tìm các đối tượng này nhờ vào tag.

Bên trong phương thức checkApple() chúng ta kiểm tra xem con rắn có ăn trúng mồi hay không, nếu có thì thêm một đốt vào sau con rắn và gọi đến phương thức locateApple() để khởi tạo mồi mới.

apple = self.find_withtag("apple")
head = self.find_withtag("head")

Phương thức find_withtag() sẽ tìm đối tượng trên canvas dựa vào tag của đối tượng đó. Ở đây chúng ta cần tìm 2 đối tượng là mồi và đầu của con rắn – đốt đầu tiên.

x1, y1, x2, y2 = self.bbox(head)
overlap = self.find_overlapping(x1, y1, x2, y2)

Phương thức bbox() trả về một tuple là tọa độ điểm trái-trên và phải-dưới của một đối tượng. Phương thức find_overlapping() sẽ tìm các đối tượng nằm đè lên nhau. Tức là ở đây chúng ta kiểm tra xem đầu con rắn có chạm vào mồi hay không.

for ovr in overlap:
  
    if apple[0] == ovr:
        x, y = self.coords(apple)
        self.create_image(x, y, image=self.dot, anchor=NW, tag="dot")
        self.locateApple()

Nếu đầu con rắn chạm vào mồi thì chúng ta vẽ một đốt mới tại vị trí của mồi sau đó gọi đến phương thức locateApple() để khởi tạo mồi mới.

Bên trong phương thức doMove() chúng ta kiểm tra hướng đi hiện tại của con rắn và cập nhật tọa độ của tất cả các đốt trên con rắn.

z = 0
while z < len(items)-1:
    c1 = self.coords(items[z])
    c2 = self.coords(items[z+1])
    self.move(items[z], c2[0]-c1[0], c2[1]-c1[1])
    z += 1

Tọa độ của các đốt phía sau sẽ bằng tọa độ của đốt liền trước đó. Riêng tọa độ của đầu thì dựa trên hướng đi hiện tại.

Trong phương thức checkCollisions() chúng ta kiểm tra xem con rắn có cắn phải mình hay đụng tường hay không.

x1, y1, x2, y2 = self.bbox(head)
overlap = self.find_overlapping(x1, y1, x2, y2)

for dot in dots:
    for over in overlap:
        if over == dot:
          self.inGame = False

Nếu có thì chúng ta cho thuộc tính inGameFalse tức là kết thúc game.

if y1 > HEIGHT - DOT_SIZE:
    self.inGame = False

Phương thức locateApple() sẽ tạo ngẫu nhiên mồi mới trên màn hình.

apple = self.find_withtag("apple")
self.delete(apple[0])

Trước hết chúng ta xóa mồi cũ đi.

r = random.randint(0, RAND_POS)

Sau đó dùng phương thức randint() để lấy một số ngẫu nhiên từ 0 đến RAND_POS – 1.

self.apple_x = r * DOT_SIZE
...
self.apple_y = r * DOT_SIZE

Hai dòng trên set tọa độ của mồi.

Trong phương thức onKeyPressed() chúng ta kiểm tra sự kiện bấm phím.

if key == "Left" and not self.right: 
    self.left = True
    self.up = False
    self.down = False

Trong trường hợp trên chúng ta kiểm tra nếu người dùng bấm phim mũi trên trái và hướng đi hiện tại của con rắn không phải hướng bên phải thì set các thuộc tính leftTrue, các thuộc tính còn lại là False. Tương tự với các hướng còn lại.

def onTimer(self):

    if self.inGame:
        self.checkCollisions()
        self.checkApple()
        self.doMove()
        self.after(DELAY, self.onTimer)
    else:
        self.gameOver() 

Bên trong phương thức onTimer(), chúng ta thực hiện một số công việc cứ sau 100 mili giây (lưu trong hằng số DELAY). Chúng ta kiểm tra sự va chạm của con rắn với mồi hay tường… cập nhật tọa độ con rắn rồi gọi phương thức after() một lần nữa để đồng hồ chạy tiếp 100ms và lại tiếp tục kiểm tra. Nếu vì một lý do gì đó mà thuộc tính inGame là False thì chúng ta gọi phuonwg thức gameOver().

def gameOver(self):

    self.delete(ALL)
    self.create_text(self.winfo_width()/2, self.winfo_height()/2, 
        text="Game Over", fill="white")     

Bên trong phương thức gameOver() chúng ta xóa các đối tượng ra khỏi bộ nhớ và in dòng chữ “Game Over” lên giữa màn hình.

Untitled

Tkinter – Đồ họa

Trong phần này chúng ta sẽ học cách vẽ các đối tượng hình học trong Tkinter bằng cách sử dụng widget Canvas.

Vẽ đoạn thẳng

Để vẽ đoạn thẳng thì chúng ta dùng phương thức create_line() của lớp CanvasBạn cứ hiểu Canvas giống như một tờ giấy để chúng ta vẽ mọi thứ trên đó, nếu bạn đã từng lập trình HTML5 thì chắc không còn xa lạ gì với canvas.

from tkinter import Tk, Canvas, Frame, BOTH

class Example(Frame):
   def __init__(self, parent):
       Frame.__init__(self, parent)
       self.parent = parent
       self.initUI()
 
   def initUI(self):
       self.parent.title("Lines")
       self.pack(fill=BOTH, expand=1)
 
       canvas = Canvas(self)
       canvas.create_line(15, 25, 200, 25)
       canvas.create_line(300, 25, 300, 200, dash=(4, 2))
       canvas.create_line(55, 85, 155, 85, 105, 180, 55, 85)
 
       canvas.pack(fill=BOTH, expand=1)
 
root = Tk()
ex = Example(root)
root.geometry("400x250+300+300")
root.mainloop()

Trong ví dụ trên chúng ta vẽ một vài đoạn thẳng lên canvas.

canvas = Canvas(self)

Đầu tiên chúng ta tạo đối tượng Canvas với tham số self để canvas bao phủ toàn bộ cửa sổ.

canvas.create_line(15, 25, 200, 25)

Phương thức create_line() nhận vào tham số là tọa độ x, y của điểm bắt đầu và điểm kết thúc của một đoạn thẳng.

canvas.create_line(300, 35, 300, 200, dash=(4, 2))

Tham số dash tùy chỉnh kiểu vẽ là các đường nét đứt, ở ví dụ trên, dash=(4, 2) nghĩa là vẽ các nốt dài 4 pixel cách nhau 2 pixel.

canvas.create_line(55, 85, 155, 85, 105, 180, 55, 85)

Bạn cũng có thể đưa vào nhiều điểm, dòng code trên đưa vào 3 điểm để vẽ một hình tam giác.

Capture

Vẽ màu

Màu trong máy tính là màu RGB, là tổ hợp của 3 giá trị đỏ (Red), xanh lá (Green) và xanh lam (Blue).

from tkinter import Tk, Canvas, Frame, BOTH

class Example(Frame):
   def __init__(self, parent):
       Frame.__init__(self, parent)
       self.parent = parent
       self.initUI()
 
   def initUI(self):
       self.parent.title("Colors")
       self.pack(fill=BOTH, expand=1)
 
       canvas = Canvas(self)
       canvas.create_rectangle(30, 10, 120, 80, outline="#fb0", fill="#fb0")
       canvas.create_rectangle(150, 10, 240, 80, outline="#f50", fill="#f50")
       canvas.create_rectangle(270, 10, 370, 80, outline="#05f", fill="#05f")
       canvas.pack(fill=BOTH, expand=1)
 
root = Tk()
ex = Example(root)
root.geometry("400x100+300+300")
root.mainloop()

Trong ví dụ trên chúng ta vẽ 3 hình chữ nhật với 3 màu khác nhau.

canvas.create_rectangle(30, 10, 120, 80, 
    outline="#fb0", fill="#fb0")

Phương thức create_rectangle() tạo một hình chữ nhật trên canvas với 4 tham số đầu tiên là tọa độ x, y của điểm trái-trên và điểm phải-dưới. Tham số outline là giá trị màu đường viền, tham số fill là giá trị màu của hình chữ nhật, tất cả đều ở dạng hexa.

Capture

Vẽ một số đối tượng hình học khác

from tkinter import Tk, Canvas, Frame, BOTH

class Example(Frame):
   def __init__(self, parent):
       Frame.__init__(self, parent)
       self.parent = parent
       self.initUI()
 
   def initUI(self):
       self.parent.title("Shapes")
       self.pack(fill=BOTH, expand=1)
 
       canvas = Canvas(self)
 
       canvas.create_oval(10, 10, 80, 80, outline="gray", fill="gray", width=2)
       canvas.create_oval(110, 10, 210, 80, outline="gray", fill="gray", width=2)
       canvas.create_rectangle(230, 10, 290, 60, outline="gray", fill="gray", width=2)
       canvas.create_arc(30, 200, 90, 100, start=0, extent=210, outline="gray", fill="gray", width=2)
 
       points = [150, 100, 200, 120, 240, 180, 210, 200, 150, 150, 100, 200]
       canvas.create_polygon(points, outline="gray", fill="gray", width=2)
 
       canvas.pack(fill=BOTH, expand=1)
 
root = Tk()
ex = Example(root)
root.geometry("330x220+300+300")
root.mainloop()

Trong ví dụ trên chúng ta vẽ 5 hình là hình tròn, hình elip, hình chữ nhật, hình quạt và một đa giác.

canvas.create_oval(10, 10, 80, 80, outline="red", 
        fill="green", width=2)

Phương thức create_oval() dùng để vẽ hình tròn. Bốn tham số đầu tiên là tọa độ x, y của điểm trái-trên và phải-dưới của hình tròn (bởi vì hình tròn trong Tkinter được nằm trong một hình chữ nhật).

canvas.create_arc(30, 200, 90, 100, start=0, 
    extent=210, outline="#f11", fill="#1f1", width=2)

Phương thức create_arc() vẽ hình quạt, 4 tham số đầu tiên cũng là tọa độ hình chữ nhật chứa hình quạt này. Tham số start là hướng mà quạt hướng tới, tham số extend là độ lớn của quạt, cả 2 tham số này đều có đơn vị độ (từ 0 đến 360 độ).

points = [150, 100, 200, 120, 240, 180, 210, 
    200, 150, 150, 100, 200]
canvas.create_polygon(points, outline='red', 
    fill='green', width=2)

Phương thức create_polygon() vẽ một đa giác, tham số là tọa độ x, y của các điểm, bạn có thể đưa các điểm này vào bằng tay như các phương thức khác hoặc lưu các điểm trong một list rồi đưa list này vào.

Capture

Hiển thị ảnh từ file

from tkinter import Tk, Canvas, Frame, BOTH, NW
from PIL import Image, ImageTk

class Example(Frame):
   def __init__(self, parent):
       Frame.__init__(self, parent)
        self.parent = parent
        self.initUI()
      
   def initUI(self):
       self.parent.title("Image")
       self.pack(fill=BOTH, expand=1)
 
       self.img = Image.open("C:\\tatras.jpeg")
       self.tatras = ImageTk.PhotoImage(self.img)
 
       canvas = Canvas(self, width=self.img.size[0]+20, height=self.img.size[1]+20)
       canvas.create_image(10, 10, anchor=NW, image=self.tatras)
       canvas.pack(fill=BOTH, expand=1)

root = Tk()
ex = Example(root)
root.mainloop()     

Ví dụ trên vẽ hình từ file lên canvas.

self.img = Image.open("tatras.jpg")
self.tatras = ImageTk.PhotoImage(self.img)

Ở đây chúng ta dùng lớp ImageImageTk từ module PIL. Module này không có sẵn trong Python mà bạn phải tự cài thêm vào.

canvas = Canvas(self, width=self.img.size[0]+20, height=self.img.size[1]+20)

Chúng ta tạo đối tượng canvas với chiều dài và chiều rộng của file cộng thêm 20 pixel.

canvas.create_image(10, 10, anchor=NW, image=self.tatras)

Để hiện hình từ file lên canvas chúng ta dùng phương thức create_image(). Hai tham số đầu tiên là tọa độ góc trái-trên của hình. Tham số anchor=NW có tác dụng cắt hình từ vị trí NW = North West = tây-bắc) cho đến hết góc phải-dưới của ảnh, tức là lấy nguyên toàn bộ ảnh, mặc định nếu không đặt tham số này thì chương trình chỉ hiển thị phần giữa (CENTER) của ảnh. Bạn có thể thử các giá trị khác như N, E, SE (nhớ import các hằng số vô trước)… dưới đây là sơ đồ của các hướng.

tkanchor

Tham số image là đối tượng ảnh cần hiển thị.

Capture

Ghi chữ lên canvas

from tkinter import Tk, Canvas, Frame, BOTH, W

class Example(Frame):
   def __init__(self, parent):
       Frame.__init__(self, parent)  
       self.parent = parent 
       self.initUI()
 
 
   def initUI(self):
 
       self.parent.title("Lyrics") 
       self.pack(fill=BOTH, expand=1)

       canvas = Canvas(self)
       canvas.create_text(20, 30, anchor=W, font="VNI-Dom 18", 
                          text="Most relationships seem so transitory")
       canvas.create_text(20, 60, anchor=W, font="VNI-Dom 18", 
                          text="They're good but not the permanent one")
       canvas.create_text(20, 130, anchor=W, font="VNI-Dom 18", 
                          text="Who doesn't long for someone to hold")
       canvas.create_text(20, 160, anchor=W, font="VNI-Dom 18", 
                          text="Who knows how to love without being told") 
       canvas.create_text(20, 190, anchor=W, font="VNI-Dom 18", 
                          text="Somebody tell me why I'm on my own") 
       canvas.create_text(20, 220, anchor=W, font="VNI-Dom 18", 
                          text="If there's a soulmate for everyone") 
       canvas.pack(fill=BOTH, expand=1)

 
root = Tk()
ex = Example(root)
root.geometry("420x250+300+300")
root.mainloop() 

Đoạn code trên thực hiện “vẽ” lời bài hát lên canvas.

canvas.create_text(20, 30, anchor=W, font="VNI-Dom 18",
                   text="Most relationships seem so transitory")

Chúng ta dùng phương thức  create_text() để hiển thị chữ lên canvas. Hai tham số đầu là tọa độ x, y của góc trái trên. Tham số font="VNI-Dom 18" tức là dùng font VNI-Dom với độ lớn là 18px. Tham số text là nội dung đoạn text được in lên canvas.

Capture

Tkinter – Hộp thoại

Trong bài này chúng ta sẽ học cách hiển thị các hộp thoại (Dialog) trong Tkinter.

Messagebox

Đây là kiểu hôp thoại dùng để hiển thị thông báo cho người dùng và đôi khi còn dùng để đưa ra yêu cầu chọn lựa cho người dùng.

from tkinter.ttk import Frame, Button
from tkinter import Tk, BOTH
import tkinter.messagebox as mbox

class Example(Frame):
    def __init__(self, parent):
        Frame.__init__(self, parent)
        self.parent = parent
        self.initUI()
 
    def initUI(self):
        self.parent.title("Message Boxes")
        self.pack()
 
        error = Button(self, text="Error", command=self.onError)
        error.grid(padx=5, pady=5)
        warning = Button(self, text="Warning", command=self.onWarn)
        warning.grid(row=1, column=0)
        question = Button(self, text="Question", command=self.onQuest)
        question.grid(row=0, column=1)
        inform = Button(self, text="Information", command=self.onInfo)
        inform.grid(row=1, column=1)
 
    def onError(self):
        mbox.showerror("Error", "Could not open file")
 
    def onWarn(self):
        mbox.showwarning("Warning", "Deprecated function call")
 
    def onQuest(self):
        mbox.askquestion("Question", "Are you sure to quit?")
      
    def onInfo(self):
        mbox.showinfo("Information", "Download completed")
 
root = Tk()
ex = Example(root)
root.geometry("300x150+300+300")
root.mainloop()

Trong ví dụ trên chúng ta hiển thị 4 button, mỗi button sẽ hiển thị một kiểu message box khác nhau.

import tkinter.messagebox as mbox

Để sử dụng được message box thì bạn import module tkinger.messagebox (hoặc tkMessageBox nếu bạn dùng Python phiên bản 2.x).

error = Button(self, text="Error", command=self.onError)

Ở dòng trên chúng tạo button Error, khi bấm vào button này phương thức onError() sẽ được gọi để hiển thị một message box thông báo lỗi.

def onError(self):
    box.showerror("Error", "Could not open file")

Để hiển thị hộp thoại thông báo lỗi thì chúng ta gọi phương thức showerror(). Ngoài thông báo lỗi thì Tkinter cung cấp cho chúng ta các kiểu messagebox khác như sau:

  • askokcancel(title, message, option)
  • askquestion(title, message, option)
  • askretrycancel(title, message, option)
  • askyesno(title, message, option)
  • showinfo(title, message, option)
  • showwarning(title, message, option)

Trong đó tham số option là tham số tùy chỉnh hộp thoại dùng để tùy chỉnh một số tính chất như chọn Icon cho hộp thoại, chọn cửa sổ cha… bạn có thể không cần quan tâm đến tham số này.

Các hộp thoại askokcancel, askretrycancel, và askyesno trả về giá trị True khi người dùng bấm vào nút “OK” hoặc “Yes“, False khi bấm vào “No” hoặc “Cancel“. Hộp thoại askquestion trả về giá trị “yes” khi bấm nút “Yes” và “no” khi bấm nút “No“.

Capture

Hộp thoại chọn màu (Color chooser)

Các hệ điều hành hay có sẵn hộp thoại chọn màu cho chúng ta sử dụng. Để hiển thị hộp thoại này chúng ta dùng module colorchooser (hoặc tkColorChooser nếu bạn dùng Python phiên bản 2.x).

from tkinter import Tk, Frame, Button, BOTH, SUNKEN
from tkinter.colorchooser import askcolor

class Example(Frame):
  def __init__(self, parent):
     Frame.__init__(self, parent)
 
     self.parent = parent
     self.initUI()
 
  def initUI(self):
     self.parent.title("Color chooser")
     self.pack(fill=BOTH, expand=1)
 
     self.btn = Button(self, text="Choose Color", command=self.onChoose)
     self.btn.place(x=30, y=30)
 
     self.frame = Frame(self, border=1, relief=SUNKEN, width=100, height=100)
     self.frame.place(x=160, y=30)
 
  def onChoose(self):
     (rgb, hx) = askcolor()
     self.frame.config(bg=hx)
 
root = Tk()
ex = Example(root)
root.geometry("300x150+300+300")
root.mainloop()

Trong ví dụ trên chúng ta hiển thị một button và một frame lên cửa sổ chính. Button sẽ mở một hộp thoại chọn màu, chọn màu nào thì màu đó sẽ hiện lên trên frame của chúng ta.

(rgb, hx) = askcolor()
self.frame.config(bg=hx)

Để hiển thị hộp thoại chọn màu thì chúng ta gọi đến hàm askcolor(). Hàm này sẽ trả về một tuple, bên trong tuple này có 2 phần tử là một tuple bao gồm 3 giá trị R-G-B và một giá trị màu ở dạng hexa, bạn có thể dùng hàm print(hx) bên trong phương thức onChoose() để biết giá trị hexa này. Chúng ta dùng giá tị hexa để đặt làm màu nền cho Frame.

Capture

Hộp thoại chọn file (File Dialog)

Trong ví dụ dưới đây chúng ta sẽ dùng hàm Open của module tkinter.filedialog để mở một File Dialog.
from tkinter import Frame, Tk, BOTH, Text, Menu, END
from tkinter.filedialog import Open

class Example(Frame):
    def __init__(self, parent):
        Frame.__init__(self, parent)
        self.parent = parent
        self.initUI()
 
    def initUI(self):
        self.parent.title("File dialog")
        self.pack(fill=BOTH, expand=1)
 
        menubar = Menu(self.parent)
        self.parent.config(menu=menubar)
 
        fileMenu = Menu(menubar)
        fileMenu.add_command(label="Open", command=self.onOpen)
        menubar.add_cascade(label="File", menu=fileMenu)
 
        self.txt = Text(self)
        self.txt.pack(fill=BOTH, expand=1)
 
    def onOpen(self):
         ftypes = [('Python files', '*.py'), ('All files', '*')]
         dlg = Open(self, filetypes = ftypes)
         fl = dlg.show()
 
     if fl != '':
         text = self.readFile(fl)
         self.txt.insert(END, text)
     
    def readFile(self, filename):
       f = open(filename, "r")
       text = f.read()
       return text
 
root = Tk()
ex = Example(root)
root.geometry("300x250+300+300")
root.mainloop()

Chúng ta sẽ tạo một menu cho phép mở file dialog và đọc nội dung của file được chọn vào lớp Text.

ftypes = [('Python files', '*.py'), ('All files', '*')]

Dòng trên là List lưu các định dạng file khác nhau để lọc file.

dlg = Open(self, filetypes = ftypes)
fl = dlg.show()

Để hiển thị file dialog thì chúng ta gọi hàm Open(). Hàm này trả về một string là đường dẫn đến file được chọn.

Capture

Tkinter – Menu

Trong phần này chúng ta sẽ học cách tạo menu trong Tkinter.

Ví dụ

from tkinter import Frame, Tk, Menu

class Example(Frame):
  def __init__(self, parent):
    Frame.__init__(self, parent)
 
    self.parent = parent
    self.initUI()
  
  def initUI(self):
    self.parent.title("Simple Menu")
 
    menuBar = Menu(self.parent)
    self.parent.config(menu=menuBar)
 
    fileMenu = Menu(menuBar)
    fileMenu.add_command(label="Exit", command=self.onExit)
    menuBar.add_cascade(label="File", menu=fileMenu)
 
  def onExit(self):
    self.quit()
 
root = Tk()
root.geometry("250x150+300+300")
app = Example(root)
root.mainloop()

Đoạn code trên tạo một cửa sổ có một menu, trong menu này có nút Exit, bấm vào nút này thì thoát chương trình.

menubar = Menu(self.parent)
self.parent.config(menu=menubar)

Để hiển thị các menu thì đầu tiên chúng ta phải tạo một menu bar trước. Chúng ta tạo menu bar từ lớp Menu.

fileMenu = Menu(menubar)

Tiếp theo chúng ta tạo menu File cũng từ lớp Menu.

fileMenu.add_command(label="Exit", command=self.onExit)

Để thêm các item vào một menu thì chúng ta dùng phương thức add_command(), ở trên chúng ta tạo item Exit và cho item này gọi đến phương thức onExit().

menubar.add_cascade(label="File", menu=fileMenu)

Sau khi tạo xong thì chúng ta gắn menu File vào menu bar bằng phương thức add_cascade().

Untitled

Tạo menu con

Ví dụ dưới đây sẽ tạo một menu con từ một menu cha.

Chúng ta tạo menu File, trong menu này có 1 item, 1 separator (đường kẻ ngang phân cách) và 1 menu con. Trong menu con có 3 item khác.

submenu = Menu(fileMenu)
submenu.add_command(label="New feed")
submenu.add_command(label="Bookmarks")
submenu.add_command(label="Mail")

Chúng ta tạo menu con cũng giống như tạo một menu bình thường từ lớp Menu.

fileMenu.add_cascade(label='Import', menu=submenu)

Chúng ta thêm menu con bằng cách gọi phương thức add_cascade từ menu cha và truyền tham chiếu đến đối tượng menu con vào tham số menu. Như vậy các menu từ menu bar cho đến submenu đều được tạo ra từ lớp Menu. Chỉ khác là menu bar chính là menu gốc, được gắn vào cửa sổ chính.

fileMenu.add_separator()

Để hiển thị một separator (đường kẻ ngang) thì chúng ta gọi phương thức add_separator().

Untitled

Popup menu

Popup menu hay còn được gọi là menu ngữ cảnh là menu được hiện ra khi click chuột lên cửa sổ.

from Tkinter import Tk, Frame, Menu


class Example(Frame):
  
    def __init__(self, parent):
        Frame.__init__(self, parent)   
         
        self.parent = parent
        
        self.initUI()
        
        
    def initUI(self):
      
        self.parent.title("Popup menu")
        self.menu = Menu(self.parent, tearoff=0)
        self.menu.add_command(label="Beep", command=self.bell())
        self.menu.add_command(label="Exit", command=self.onExit)

        self.parent.bind("<Button-3>", self.showMenu)
        self.pack()
        
        
    def showMenu(self, e):
        self.menu.post(e.x_root, e.y_root)
       

    def onExit(self):
        self.quit()


root = Tk()
root.geometry("250x150+300+300")
app = Example(root)
root.mainloop()  

Trong ví dụ trên chúng tạo popup menu có 2 item khi click chuột phải lên cửa sổ.

self.menu = Menu(self.parent, tearoff=0)

Ban đầu chúng ta cũng tạo một đối tượng Menu như menu bình thường. Tham số tearoff=0 sẽ tắt chế độ hiển thị menu trên một cửa sổ riêng.

self.parent.bind("<Button-3>", self.showMenu)

Dòng code trên gắn sự kiện click chuột phải (<Button-3>) vào phương thức showMenu().

def showMenu(self, e):
    self.menu.post(e.x_root, e.y_root)

Trong phương thức showMenu(), chúng ta gọi phương thức self.menu.post(x, y) để hiển thị popup menu tại vị trí được chỉ định trên cửa sổ. Tham số là đối tượng của sự kiện click chuột.

Untitled

Tkinter – Widget

Trong phần này chúng ta sẽ tìm hiểu về một số widget cơ bản trong Tkinter là Checkbutton, Label, Scale, và Listbox.

Widget là các thành phần cấu tạo nên một ứng dụng GUI. Widget rất đa dạng, có một số widget quan trọng cần phải có của bất kì nền tảng nào kể cả Tkinter ví dụ như button (nút bấm), check box hay scroll bar (thanh cuộn). Ngoài những widget cơ bản lập trình viên còn có thể tùy chỉnh widget của riêng mình.

Checkbutton

Checkbutton là widget hiển thị hộp đánh dấu.

from tkinter import Tk, Frame, Checkbutton
from tkinter import BooleanVar, BOTH

class Example(Frame):
  def __init__(self, parent):
    Frame.__init__(self, parent)
 
    self.parent = parent
    self.initUI()
 
  def initUI(self):
    self.parent.title("Checkbutton")
    self.pack(fill=BOTH, expand=True)
    self.var = BooleanVar()
    
    cb = Checkbutton(self, text="Show Title", variable=self.var, command=self.onClick)
    cb.select()
    cb.place(x=50, y=50)
 
  def onClick(self):
    if self.var.get() == True:
      self.master.title("Checkbutton")
    else:
      self.master.title("")
 
root = Tk()
root.geometry("250x150+300+300")
app = Example(root)
root.mainloop()

Trong ví dụ trên chúng ta hiển thị một check button lên cửa sổ có chức năng hiện/ẩn tiêu đề cửa sổ khi check.

self.var = BooleanVar()

Ở trên chúng ta tạo ra một đối tượng BooleanVar, đối tượng này sẽ được kết nối với checkbutton, mỗi khi trạng thái check thay đổi thì giá trị của self.var cũng sẽ thay đổi.

cb = Checkbutton(self, text="Show title", variable=self.var, command=self.onClick)

Ở dòng trên chúng ta tạo một đối tượng Checkbutton. Chúng ta kết nối biến self.var thông qua tham số variable. Tham số command chỉ định phương thức nào sẽ được gọi khi check, ở đây là phương thức onClick().

cb.select()

Chúng ta sử dụng phương thức select() để thiết lập trạng thái cho check cho button.

if self.var.get() == True:
    self.master.title("Checkbutton")
else:
    self.master.title("")

Tại phương thức onClick() chúng ta cho hiện/ẩn tiêu đề cửa sổ thông qua phương thức master.title(), chúng ta kiểm tra check button có được check hay không thông qua thuộc tính self.var.

Capture

Label

Label dùng để hiển thị text hoặc hình ảnh. Trong ví dụ dưới đây, chúng ta sẽ dùng Label để hiển thị ảnh lên màn hình.

from PIL import Image, ImageTk
from tkinter import Tk, Frame, Label

class Example(Frame):
  def __init__(self, parent):
    Frame.__init__(self, parent)
 
    self.parent = parent
 
    self.initUI()
 
  def initUI(self):
    self.parent.title("Label")
 
    self.img = Image.open("C:\\tatras.jpg")
    tatras = ImageTk.PhotoImage(self.img)
    label = Label(self, image=tatras)
 
    label.image = tatras
 
    label.pack()
    self.pack()
 
  def setGeometry(self):
    w, h = self.img.size
    self.parent.geometry(("%dx%d+300+300") % (w, h))
 
root = Tk()
ex = Example(root)
ex.setGeometry()
root.mainloop()

Để chạy được ví dụ trên thì bạn phải có thư viện Pillow trong Python.

from PIL import Image, ImageTk

Mặc định thì Label trong Tkinter cũng có các module có thể hiển thị ảnh nhưng rất hạn chế.

label = Label(self, image=tatras)

Chúng ta dùng tham số image khi khởi tạo để gán ảnh vào label.

label.image = tatras

Tiếp theo bạn phải tạo một biến để giữ lại tham chiếu đến ảnh nếu không bộ thu dọn tài nguyên của Python sẽ xóa mất ảnh của chúng ta.

w, h = self.img.size
self.parent.geometry(("%dx%d+300+300") % (w, h))

Hai dòng code trên chúng ta cho kích thước màn hình bằng đúng với kích thước ảnh.

Capture

Scale

Scale là widget hiển thị một thanh cuộn gắn với một khoảng giá trị nào đó.

from tkinter import Tk, BOTH, IntVar, LEFT
from tkinter.ttk import Frame, Label, Scale, Style

class Example(Frame):
  def __init__(self, parent):
    Frame.__init__(self, parent)
    self.parent = parent
    self.initUI()
 
  def initUI(self):
    self.parent.title("Scale")
    self.style = Style()
    self.style.theme_use("default")
 
    self.pack(fill=BOTH, expand=1)
 
    scale = Scale(self, from_=0, to=100, command=self.onScale)
    scale.pack(side=LEFT, padx=15)
 
    self.var = IntVar()
    self.label = Label(self, text=0, textvariable=self.var)
    self.label.pack(side=LEFT)
 
  def onScale(self, val):
    v = int(float(val))
    self.var.set(v)
 
root = Tk()
ex = Example(root)
root.geometry("250x100+300+300")
root.mainloop()

Trong ví dụ trên chúng ta cho hiển thị một scale và một label, text trên label sẽ thay đổi khi kéo thanh cuộn trên scale.

scale = Scale(self, from_=0, to=100, command=self.onScale)

Tạo một đối tượng Scale. Hai tham số from_ và to là phạm vi giá trị của scale. Tham số command chỉ định phương thức sẽ được gọi khi di chuyển thanh cuộn.

self.var = IntVar()
self.label = Label(self, text=0, textvariable=self.var)    

Cũng giống như Checkbutton, ở đây chúng ta tạo label, một đối tượng IntVar và kết nối đối tượng đó với label bằng tham số textvariable. Giá trị của biến này sẽ được hiển thị trên label.

def onScale(self, val):
  
    v = int(float(val))
    self.var.set(v)

Khi di chuyển thanh cuộn, phương thức onScale() sẽ được gọi kèm theo giá trị hiện tại của Scale. Chúng ta chuyển đổi kiểu dữ liệu của tham số này từ sang float rồi sang int sau đó đặt làm giá trị của đối tượng IntVar và đoạn text hiển thị trên label sẽ thay đổi theo

Capture

Listbox

Listbox cho phép hiển thị một danh sách các item. Người dùng có thể chọn một hoặc nhiều item.

from tkinter.ttk import Frame, Label
from tkinter import Tk, BOTH, Listbox, StringVar, END

class Example(Frame):
  def __init__(self, parent):
    Frame.__init__(self, parent)
 
    self.parent = parent
    self.initUI()
 
  def initUI(self):
    self.parent.title("Listbox")
    self.pack(fill=BOTH, expand=1)
 
    acts = ["Scarlet Johansson", "Rachel Weiss", "Natalie Portman", "Jessica Alba"]
 
    lb = Listbox(self)
 
    for i in acts:
       lb.insert(END, i)
 
    lb.bind("<<ListboxSelect>>", self.onSelect)
 
    lb.pack(pady=15)
 
    self.var = StringVar()
    self.label = Label(self, text=0, textvariable=self.var)
    self.label.pack()
 
  def onSelect(self, val):
    sender = val.widget
    idx = sender.curselection()
    value = sender.get(idx)
 
    self.var.set(value)
 
root = Tk()
ex = Example(root)
root.geometry("300x250+300+300")
root.mainloop()

Trong ví dụ trên, chúng ta hiển thị một danh sách tên các nữ diễn viên trong Listbox. Tên được chọn sẽ được hiển thị trên một label.

lb = Listbox(self)
for i in acts:
    lb.insert(END, i)

Chúng ta tạo một đối tượng Listbox và thêm các item vào bằng phương thức insert().

lb.bind("<<ListboxSelect>>", self.onSelect)    

Khi chọn một item thì sự kiện <<ListboxSelect>> sẽ được thực thi. Chúng ta gán sự kiện này vào phương thức onSelect().

sender = val.widget

Trong phương thức onSelect() chúng ta lấy tham chiếu đến Listbox thông qua thuộc tính val.widget và gán vào biến sender.

idx = sender.curselection()

Phương thức curselection() lấy về chỉ số của item đang được chọn.

value = sender.get(idx)  
self.var.set(value)

Từ chỉ số chúng ta lấy giá trị text của item bằng phương thức get(), sau đó gán vào label.

Capture

Tkinter – Quản lý Layout

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

Tkinter – Giới thiệu về Tkinter

 

Tkinter là một gói trong Python có chứa module Tk hỗ trợ cho việc lập trình GUI. Tk ban đầu được viết cho ngôn ngữ Tcl. Sau đó Tkinter được viết ra để sử dụng Tk bằng trình thông dịch Tcl trên nền Python. Ngoài Tkinter ra còn có một số công cụ khác giúp tạo một ứng dụng GUI viết bằng Python như wxPython, PyQt, và PyGTK.

Về ngôn ngữ Python

python logo

Python là một ngôn ngữ lập trình hướng đối tượng, python hoàn toàn tạo kiểu động, cấp phát bộ nhớ động. Mục đích ra đời của Python là cung cấp một ngôn ngữ lập trình có cấu trúc rõ ràng, sáng sủa, thuận tiện cho người mới học lập trình. Python được phát triển bởi Guido và Rossum. Phiên bản đầu tiên được phát hành vào năm 1991. Python được lấy cảm hứng từ ABC, Haskell, Java, Lisp, Icon và Perl. Python là một ngôn ngữ thông dịch, đa nền tảng. Một trong những đặc điểm độc nhất của Python là ngôn ngữ này không dùng đến dấu chấm phẩy, dấu mở-đóng ngoặc {} để kết thúc câu lệnh hay khối lệnh, mà cách duy nhất để nó nhận biết một lệnh là dấu thụt đầu dòng.

Hiện tại Python có hai dòng phiên bản là dòng 2.x và 3.x. Phiên bản Python mà series này sử dụng là phiên bản 3.5.1. Ngày nay Python được phát triển bởi một cộng đồng tình nguyện trên khắp thế giới, và lẽ dĩ nhiên nó là một phần mềm mã nguồn mở.

Update: Python phiên bản 2.7.18 được phát hành vào ngày 20/04/2020 là phiên bản cuối cùng của dòng 2.x, nghĩa là từ nay Python sẽ chỉ cập nhật phiên bản 3.x trở lên.

Trang chủ của Python nằm tại địa chỉ python.org.

Ví dụ

Đoạn code dưới đây ví dụ về cách tạo một cửa sổ bằng Tkinter.

from tkinter import Tk, Frame, BOTH

class Example(Frame):
 def __init__(self, parent):
 Frame.__init__(self, parent, background="white")
 self.parent = parent
 self.initUI()
 
 def initUI(self):
 self.parent.title("Simple")
 self.pack(fill=BOTH, expand=1)
 
root = Tk()
root.geometry("250x150+300+300")
app = Example(root)
root.mainloop()

Đoạn code trên sẽ tạo ra một cửa sổ nhỏ trên màn hình.

from tkinter import Tk, Frame

Ở dòng trên chúng ta import hai lớp Tk Frame từ gói Tkinter. Lớp Tk được dùng để tạo cửa sổ, lớp Frame dùng để quản lý các widget. Chúng ta sẽ tìm hiểu về widget ở các bài sau.

class Example(Frame):
  
    def __init__(self, parent):
        Frame.__init__(self, parent, background="white")   

Chúng ta định nghĩa lớp Excample kế thừa từ lớp Frame. Lớp Frame là một widget dùng để quản lý các widet khác – các lớp kiểu này được gọi chung là containter hoặc layout. Trong phương thức khởi tạo __init__() chúng ta gọi lại phương thức khởi tạo từ lớp Frame và đưa vào tham số màu nền.

self.parent = parent

Chúng ta dùng thuộc tính parent để lưu lại đối tượng root.

self.initUI()

Chúng ta định nghĩa phương thức initUI() dùng để tạo các widget trên cửa sổ.

self.parent.title("Simple")

Phương thức title() sẽ thay đổi tiêu đề cửa sổ.

self.pack(fill=BOTH, expand=1)

Phương thức pack() có tác dụng sắp xếp vị trí các widget trước khi gắn chúng lên cửa sổ chính. Tham số fill=BOTH sẽ dãn widget ra theo chiều ngang và chiều rộng, tức là widget đó sẽ chiếm toàn bộ không gian cửa sổ.

root = Tk()

Ở dòng trên chúng ta tạo một cửa sổ và gán vào biến root để quản lý. Cửa sổ luôn phải được tạo trước khi tạo các widget.

root.geometry("250x150+300+300")

Phương thức geometry() quy định kích thước cửa sổ và vị trí hiển thị trên màn hình. Hai tham số đầu tiên là kích thước cửa sổ, hai phương thức sau là vị trí của cửa sổ trên màn hình.

app = Example(root)

Dòng code trên chúng ta tạo một Frame và gắn nó vào cửa sổ chính.

root.mainloop()  

Cuối cùng là phương thức mainloop(). Bạn cứ hiểu đơn giản là phải gọi phương thức này thì cửa sổ mới hiện lên màn hình và bắt đầu nhận các sự kiện để xử lý.
Capture

Hiển thị Button

Trong ví dụ dưới đây, chúng ta sẽ tạo nút Quit, khi bấm vào nút đó thì thoát chương trình.

from tkinter import Tk, BOTH
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("Quit button")
 self.style = Style()
 self.style.theme_use("default")
 
 self.pack(fill=BOTH, expand=1)
 
 quitButton = Button(self, text="Quit", command=self.quit)
 quitButton.place(x=50, y=50)
 
root = Tk()
root.geometry("250x150+300+300")
app = Example(root)
root.mainloop()

Chúng ta sẽ đặt một nút bấm trên cửa sổ, bấm vào nút đó thì thoát chương trình.

from tkinter.ttk import Frame, Button, Style

Một số widget trong Tkinter được hỗ trợ hiển thị màu sắc hoa lá khác nhau, thuật ngữ gọi là theme. Để sử dụng theme thì chúng ta import module ttk vào.

self.style = Style()
self.style.theme_use("default")

Để quy định kiểu theme thì chúng ta khởi tạo đối tượng Style() và gọi tới phương thức theme_use(). Một số kiểu theme mà mình biết là clam, default, alt, và classic.

quitButton = Button(self, text="Quit",
    command=self.quit)

Ở dòng trên chúng ta tạo một đối tượng widget là Button. Tham số đầu tiên là đối tượng container của nó. Tham số thứ hai là đoạn text sẽ hiển thị trên nút. Tham số thứ ba là lệnh được thực thi khi nút được bấm, ở đây là phương thức quit() tức là thoát chương trình.

quitButton.place(x=50, y=50)

Phương thức place() để quy định vị trí của nút bấm trên cửa sổ.

Capture