สร้าง GUI สำหรับ Python โดยใช้ Tkinter
ในบทความนี้ จะเป็นตัวอย่างการสร้าง GUI ง่ายๆ สำหรับ Python โดยโปรแกรมที่ผมจะหยิบยกมาเป็นตัวอย่างนั้นก็คือ โปรแกรมนาฬิกา นั่นเองครับ
เราจะสร้าง GUI ได้อย่างไร
ไม่ว่าเราจะเขียนโปรแกรมด้วยภาษาไหนก็ตาม ถ้าหากจะเขียนให้คนอื่นใช้ สิ่งที่เราจำเป็นต้องทำแบบขาดไม่ได้เลยนั่นก็คือ Graphic User Interface หรือ GUI นั่นเองครับ เพื่อทำให้โปรแกรมของเราดูน่าใช้งาน และใช้งานได้ง่าย
สำหรับ Python เครื่องมือในการสร้าง GUI นั้นมีอยู่เยอะมากครับ ซึ่งต่างชาติเขารีวิว 6 เครื่องมือที่ดีที่สุดเอาไว้แล้ว ดังนี้ครับ
ส่วนในบทความนี้ผมจะใช้ Tkinter ซึ่งเป็นเครื่องมือสร้าง GUI มาตรฐานของ Python สามารถใช้งานได้ฟรี และยังเป็น 1 ใน 6 เครื่องมือสร้าง GUI ที่ดีที่สุดอีกด้วยครับ
ขั้นตอนที่ 1 ออกแบบหน้าตาโปรแกรม
ก่อนเราจะเริ่มสร้าง GUI เราต้องออกแบบหน้าตาโปรแกรมของเราก่อน เพื่อให้รู้ว่าเราจะต้องใช้อะไรบ้าง และจัดวางตำแหน่งไว้ตรงไหน ซึ่งในตัวอย่างนี้ผมออกแบบไว้ดังนี้ครับ
ถ้าใครเคยใช้ Visual Studio มาก่อน ก็คงจะคุ้นเคยกับการออกแบบหน้าโปรแกรมง่ายๆ อยากได้อะไรก็สามารถจับลากวางได้เลย แต่สำหรับ Tkinter เนื่องจากมันเป็นของฟรี จึงไม่มีเครื่องมืออำนวยความสะดวกให้เราแบบนั้นครับ หากเราอยากได้อะไร จำเป็นต้องเขียนเป็นโค้ดทั้งหมด
ในเมื่อเราไม่สามารถลากวางได้ การกำหนดตำแหน่งให้ object แต่ละตัวก็จะเป็นเรื่องยาก ดังนั้น Tkinter จึงมีวิธีกำหนดตำแหน่งในรูปแบบที่แตกต่างออกไปจากโปรแกรมอื่นๆ นั่นก็คือ กำหนดในรูปแบบกริด หรือ ตาราง ดังนั้นสิ่งที่เราต้องทำจึงเหลือแค่กำหนดว่า object ตัวนี้จะอยู่แถวไหน คอลัมภ์ไหน เท่านั้น ซึ่งทำให้ชีวิตง่ายขึ้นเยอะ (ถึงแม้จะไม่ง่ายเท่าลากวางก็เถอะ)
เมื่อเราออกแบบหน้าตาโปรแกรมเสร็จ เราก็จะรู้ว่าต้องใช้เครื่องมืออะไรบ้าง ซึ่งในตัวอย่างนี้ มีเครื่องมือที่ต้องใช้ดังนี้
- Label 1 ใส่ข้อความว่า “Current Time:” วางไว้ในตำแหน่ง แถวที่ 0 คอลัมภ์ที่ 0
- Label 2 ใส่ข้อความว่า “00:00:00” วางไว้ในตำแหน่ง แถวที่ 0 คอลัมภ์ที่ 1
- Button 1 ใส่ข้อความว่า “RUN” วางไว้ในตำแหน่ง แถวที่ 1 คอลัมภ์ที่ 0
- Button 2 ใส่ข้อความว่า “STOP” วางไว้ในตำแหน่ง แถวที่ 1 คอลัมภ์ที่ 1
แต่ถ้าหากเราใส่ object ทั้งหมดนี้ลงไปในหน้าโปรแกรมเลย object ทั้งหมดมันจะอยู่ชิดกัน ทำให้ดูไม่สวย ดังนั้นเราจึงกำหนด padding ให้ object แต่ละตัวด้วยครับ
ทำความรู้จักกับ Padding
Padding ก็คือระยะห่างระหว่าง object กับขอบของกริด (หรือขอบตาราง) ตามรูปด้านบน ซึ่งแบ่งออกเป็น 2 ส่วนคือ padx และ pady
padx ประกอบด้วยค่า 2 ค่าคือ (left, right)
pady ประกอบด้วยค่า 2 ค่าคือ (top, bottom)
ซึ่งค่า padx และ pady นี้เราจะต้องกำหนดในตอนที่สร้าง object นั้นขึ้นมา (หากไม่กำหนด object ก็จะไม่มี padding ทำให้ object อยู่ติดกัน)
โดยในโปรแกรมนี้ ผมกำหนด padding ของ object ทั้งหมดดังนี้
ขั้นตอนที่ 2 เริ่มเขียนโปรแกรม
import โมดูลต่างที่จำเป็นต้องใช้งาน
import tkinter as tk
from datetime import datetime
import time
import threading
สร้างหน้าโปรแกรม
window = tk.Tk()
window.title("Python Tkinter Simple Clock")
window.resizable(0,0) #don't allow resize in x or y direction
ในส่วนนี้ถ้าเราไม่กำหนด window.resizeable(0,0) ผู้ใช้งานก็จะสามารถปรับขนาดหน้าต่างโปรแกรมของเราได้
สร้าง Label 1
lb1 = tk.Label(master=window, text="Current Time: ")
lb1.grid(row=0, column=0, padx=(20,5), pady=(20,5))
lb1.config(font=("Tahoma", 28))
บรรทัดแรกคือการสร้าง Label โดยกำหนดให้ master=window ก็คือให้แสดงบนหน้าโปรแกรมที่เราสร้างขึ้นมา (เราสร้างหน้าโปรแกรมที่ชื่อว่า window ขึ้นมาก่อนหน้านี้แล้ว) และกำหนดให้ text= “Current Time: ” ก็คือกำหนดให้ Label แสดงข้อความนี้
บรรทัดที่สอง คือกำหนดตำแหน่งของ Label และขนาก padding ส่วนบรรทัดที่สาม คือการกำหนดประเภทตัวอักษรและขนาด
สร้าง Label 2
lb2 = tk.Label(master=window, text="00:00:00")
lb2.grid(row=0, column=1, padx=(10,5), pady=(20,5))
lb2.config(font=("Tahoma", 28), width=10)
ในบรรทัดที่สามของ Label 2 เรากำหนดความกว้างให้ Label เท่ากับ 10 ด้วย เพื่อให้ Label 2 มีความยาวเพิ่มขึ้น ทำให้โปรแกรมดูสวยงามขึ้น
สร้าง Button 1
btRun = tk.Button(master=window, text="RUN", command=runThread)
btRun.grid(row=1, column=0, padx=(20,5), pady=(5,20))
btRun.config(font=("Tahoma", 36), width=10)
คำสั่ง command=runThread ในบรรทัดที่ 1 คือการกำหนดว่าเมื่อคลิกปุ่มนี้แล้ว ให้โปรแกรมไปทำงานที่ฟังก์ชันอะไร ซึ่งฟังก์ชัน runThread เป็นฟังก์ชันที่ผมเขียนขึ้นมา (เดี๋ยวจะอธิบายต่อไป)
สร้าง Button 2
btStop = tk.Button(master=window, text="STOP", command=stopProgram)
btStop.grid(row=1, column=1, padx=(5,20), pady=(5,20))
btStop.config(font=("Tahoma", 36), width=10)
สร้างฟังก์ชันแสดงเวลา
ฟังก์ชันนี้จะทำหน้าที่อ่านเวลาจากเครื่องมาแสดงผลในหน้าโปรแกรม ซึ่งการแสดงผลนั้นจะต้องอัพเดทตลอดเวลา ดังนั้นเราจึงต้องใช้ “วนลูป” เพื่ออ่านค่าเวลามาแสดงเรื่อยๆ
def startProgram():
btRun["state"] = "disabled"
while btRun["state"]=="disabled":
curr_datetime = datetime.now()
curr_sec = curr_datetime.second
curr_min = curr_datetime.minute
curr_hour = curr_datetime.hour
lb2["text"] = "{0:02d}:{1:02d}:{2:02d}".format(curr_hour,curr_min,curr_sec)
time.sleep(0.1)
โดยเมื่อผู้ใช้งานกดปุ่ม RUN แล้ว ปุ่มนี้จะไม่สามารถกดได้อีก จนกว่าผู้ใช้งานจะกดปุ่ม STOP ปุ่ม RUN จึงจะกลับมาใช้งานได้อีกครั้ง
สร้าง Thread ฟังก์ชัน
เนื่องจากฟัง์ชัน startProgram() จะวนลูปเพื่ออ่านเวลา และแสดงค่าตลอดเวลา ดังนั้นถ้าหากเรารันฟังก์ชันนี้โดยตรง โปรแกรมจะมีปัญหาเพราะ….
หลักการพื้นฐานของโปรแกรมเลยก็คือ “มันจะทำงานทีละบรรทัด” หรือในอีกความหมายก็คือ โค้ดบรรทัดถัดไปจะไม่ทำงาน ถ้าโค้ดบรรทัดก่อนหน้ายังทำงานไม่เสร็จ
ดังนั้นหากเราสั่งให้โปรแกรมวนลูปตลอดเวลา โปรแกรมเราจะค้าง เพราะว่าคำสั่ง
วนลูปมันยังทำงานไม่เสร็จ ทำให้ส่วนอื่นๆ ของโปรแกรมใช้งานไม่ได้ เราจึงจำเป็นต้องใช้ Threading เพื่อแก้ปัญหานี้
Threading คือการแยกส่วนการทำงานของโปรแกรม ทำให้โปรแกรมสามารถทำงานต่อไปได้ โดยไม่ต้องรอให้งานก่อนหน้าเสร็จสิ้น ยกตัวอย่างเช่น
โค้ดชุดที่ 1 วนลูป 10000 รอบ
โค้ดชุดที่ 2 แสดงรูปภาพ
ถ้าหากเราไม่ใช้ Threading โปรแกรมจะต้องวนลูปให้ครบ 10000 รอบก่อน โปรแกรมจึงจะแสดงรูปภาพขึ้นมา แต่ถ้าหากเราใช้ Threading เมื่อสั่งโค้ดชุดที่ 1 วนลูปแล้ว โค้ดชุดที่ 2 จะเริ่มทำงานทันที ทำรูปภาพแสดงขึ้นมาทันทีโดยไม่ต้องรอให้โค้ดชุดที่ 1 วนลูปจนครบ 10000 รอบ
ดังนั้นเราจึงจะเปลี่ยนขั้นตอนการทำงานของโปรแกรม จากปกติแทนที่จะกดปุ่ม RUN แล้วไปเรียกใช้ฟังก์ชัน startProgram() โดยตรง เปลี่ยนมาเป็น เมื่อกดปุ่ม RUN ให้ไปเรียกใช้งานฟังก์ชัน runThread() แล้วให้ฟังก์ชันนี้ไปเรียกใช้ startProgram() อีกทอดหนึ่ง
def runThread():
x = threading.Thread(target=startProgram)
x.start()
สร้างฟังก์ชัน stopProgram()
ฟังก์ชันนี้จะทำงานเมื่อผู้ใช้กดปุ่ม STOP ทำหน้าทีรีเซ็ตปุ่ม RUN ให้กลับมาใช้งานได้
def stopProgram():
btRun["state"] = "normal"
สั่งให้โปรแกรมเริ่มทำงาน
window.mainloop()