มาทำ python GUI ด้วย tkinter กันดีกว่า

Programming Jun 10, 2017

สวัสดีทุกท่าน เราเองอยากเขียน GUI program ด้วย python มานานแล้ว แต่ยังหาวิธีดีๆไม่ได้

ในวันนี้จะพาทุกท่านมาทำ GUI แบบง่ายๆด้วย python กัน ด้วย library ที่ชื่อว่า tkinter ซึ่งเราจะทำใน python 3.x และไม่ต้องไปหา library อะไรเพิ่มเติม มันมีอยู่แล้วแหละ มาเริ่มกันเลยดีกว่า ขอแนบคลิป reference นะคะ ดูอันนี้เสร็จจะมีอันอื่นๆต่อมาเรื่อยๆ หรือไปดูที่ document ของ python ก็ได้นะ

ในขึ้นแรก เรา Import tkinter ก่อน ดังนี้

from tkinter import *

จากนั้นมาสร้างหน้าต่างกัน

root = Tk()

เพิ่ม title และขนาดกัน ตามใจชอบ

app = Frame(root)app.grid()

และปิดด้วยสิ่งนี้ เพื่อให้แสดงผลออกมา

root.mainloop()

ผลคือ มีหน้าต่าง GUI แสดงออกมา ได้แล้วววว

ถ้าจะใส่ตัวหนังสือในหน้าต่าง หรือปุ่ม หรืออะไรก็ตาม ใส่ Frame ไปก่อนนะ โดยอ้างอิงจาก root เพื่อให้สามารถแสดงผลได้ตามต้องการ

app = Frame(root)
app.grid()

การเพิ่ม label ใส่ดังนี้

label = Label(app, text = "Test Labeller")
label.grid()

และให้ใส่ก่อน root.mainloop() นะ ผลเป็นดังนี้

การเพิ่มปุ่ม มีการใส่ตัวหนังสือในปุ่มสามวิธีด้วยกันนะ

แบบแรก ใส่แบบซื่อๆง่ายๆ

button1 = Button(app, text = "Button")
button1.grid()

แบบที่สอง เพิ่มแบบ config

button2 = Button(app)
button2.grid()
button2.configure(text = "show text")

แบบที่สาม เพิ่มแบบมี attribute

button3 = Button(app)
button3.grid()
button3['text'] = "test"

ผลเป็นดังนี้

การเขียน GUI ส่วนใหญ่ใช้หลักการของ object oriented programming หรือ OOP นั่นเอง เช่น ใน Mobile development เช่น Android, iOS และฝั่ง Windows ทั้ง desktop และ app

มาเริ่มทำของจริงดีกว่า

ตัวอย่างแรก มา!

เราให้มี Button และ Combobox โดยที่เรากดปุ่มและปริ๊นเลขออกมาผ่าน cmd แบบนี้ มาลงโค้ดดีกว่า

ก่อนอื่น import library กันก่อน

from tkinter import *
from tkinter.ttk import *

เพิ่มโครงร่าง OOP มีสองส่วน คือ class Application กับ main
ส่วน class Application มี function __init__ เป็นตัวเริ่มต้น
และ create_widgets เป็นตัวสร้างหน้าตา

class Application(Frame):
  def __init__(self, master):
    ...
  def create_widgets(self):
    ...

มาที่ function __init__ กันก่อน เป็นการสร้าง frame ขึ้นมา ซึ่งคือโครงร่างหน้าตาโปรแกรมเรานั่นเอง

def __init__(self, master):
  # initialize frame
  Frame.__init__(self, master)
  self.grid()
  self.create_widgets()

เพิ่ม label โดยให้ชื่อว่า Number อันนี้ใสๆปกติๆนะ เพิ่มใน create_widgets

สังเกตุว่า เราเพิ่มการวางตำแหน่งด้วย เนื่องจากการวางแบบ default นี่ วางจากบนไปล่างเรียงกันยาวๆไป

 #add label for folder
  self.label1 = Label(self)
  self.label1["text"] = "Number :"
  self.label1.grid(column=1, row=2)

เพิ่ม Combobox ให้ list ในนั้นเป็นตัวเลขเรียงกัน จากนั้น set default ให้แสดงผลค่าแรก นั่นคือแสดงค่า 1 จากนั้นเพิ่มการ binding เพื่อให้สามารถรับค่าจาก Combobox มาได้ จบลงด้วยที่การวางตำแหน่ง

# create combobox for select file name
  self.box = Combobox(self)
  self.box['values'] = ('1', '2', '3', '4', '5')
  self.box.current(0)
  self.box.bind("<<>ComboboxSelected>")
  self.box.grid(column=2, row=2)

สุดท้าย เพิ่มปุ่ม ใส่ตัวหนังสือ วางตำแหน่ง จุดพิเศษคือ การใส่คำสั่ง นั่นคือการทำงานของปุ่มนั่นเอง ในส่วนของ command

# create copy file button
  self.button = Button(self)
  self.button["text"] = "Print"
  self.button["command"] = self.print_number
  self.button.grid(column=2, row=3)

ดังนั้นเราสร้าง function การทำงานของปุ่มนี้ขึ้นมา ใน class Application เมื่อกดปุ่มนี้จะรับค่าจาก Combobox ที่เราเพิ่ง binding ไป และแสดงผลบน cmd

def copy_eimer(self):
    print("hi there, everyone! " + self.box.get())

สุดท้าย การทำงานก็ได้ดังรูปเนอะ แปะโค้ดเต็มๆมาให้อ่านกัน

ตัวอย่างต่อมา เพิ่ม EditText ไม่สิ เขาเรียกว่า Entry พร้อมทั้ง Radiobutton

หลักการธรรมดามากๆ ใส่ตัวหนังสือ เลือกใน Radiobutton แล้วปริ๊นออกมา

เริ่มเลย ข้ามในส่วนแรกๆ มาพูดในเรื่องการใส่วัตถุกันเลย เพิ่ม Entry

# add Entry
    self.entry = Entry(self, width=20)
    self.entry.grid(column=0, row=1)

เพิ่ม Radiobutton โดยสร้าง list ของ content โดยแต่ละอันมี key และ value ภายใน

food = [("chicken", "chicken"), ("pork", "pork"), ("meat", "meat")]
    cnt = 1
    for text, mode in food:
      self.radio = Radiobutton(self, text=text, variable=var, value=mode, width=5)
      self.radio.grid(column=cnt, row=1)
      cnt+=1

จากนั้นเพิ่มปุ่ม โดยคำสั่งจะไปเขียนเพิ่มใน print_food

# add button
    self.button = Button(self)
    self.button.grid()
    self.button["text"] = "Print"
    self.button["command"] = self.print_food
    self.button.grid(column=2, row=2)

คำสั่ง print_food รับค่าจาก Entry และ Radiobutton มาแสดงผล

# print input
  def print_food(self):
    print(self.entry.get(), var.get())

แปะโค้ดเต็มๆให้ดูเพื่อความกันงง

และสุดท้าย เราลองทำตัวโปรแกรม copy source code จาก folder นึงที่ใหญ่มาก ประมาณสามพันไฟล์ เลือกมาเฉพาะที่ใช้ จะได้ประหยัดเนื้อที่ในการทำงานเนาะ ที่เราแพลนมีดังนี้

  • input path มีต้นทาง กับ ปลายทาง
  • source folder เราเลือกที่ folder ไหน
  • ชื่อไฟล์หลักที่เราจะ copy ซึ่งมีข้อมูลไฟล์ที่ลากมาในไฟล์ text นะ

หน้าตาดังนี้ ขออนุญาติเซ็นเซอร์เนอะ

มาเริ่มกันเลยยยย

เริ่มแรก เรารับ input สองตัว จาก Entry นั่นคือ path เริ่มต้น และ path ปลายทาง ที่เราจะ copy file

จากนั้นเลือก option จาก Radiobutton

จากนั้นเลือกชื่อไฟล์จาก Combobox ภาษาชาวบ้านก็คือ drop-down list นั่นแล แล้วกดปุ่ม Copy

แต่สังเกตอะไรไหมเอ่ยยยยย ....
การจัดหน้าที่สวยงาม ด้วย LabelFrame ไง ซึ่งจะพิเศษกว่าอันอื่นๆนะ
ดังนั้นการเขียนโค้ด จะเริ่มที่ LabelFrame ก่อน และตัวอื่นๆที่อยู่ภายใน จะสืบทอดจาก LabelFrame แต่ละตัว และเราใช้ grid ในการจัดวางตำแหน่งตามใจชอบ

มาที่ส่วนแรก การรับค่า path มา ซึ่ง object ส่วนนี้ จะอยู่ใน LabelFrame ส่วนแรก

    # [1] add label frame for folder
    self.label1 = LabelFrame(self, text="Folder ")
    self.label1.grid(row=0, columnspan=7, sticky='WE', \
            padx=5, pady=5, ipadx=25, ipady=5)
    
    # [1.1] add label for eimer folder
    self.label_source = Label(self.label1, width=15)
    self.label_source.grid(column=0, row=2, sticky='E', padx=5, pady=5)
    self.label_source["text"] = "source path :"
    
    # [1.2] add Entry (EditText) to get eimer path
    self.entry_eimer = Entry(self.label1, width=50)
    self.entry_eimer.grid(column=1, row=2, sticky='E', padx=5, pady=5)
    
    # [1.3] add label for destination folder
    self.label_dest = Label(self.label1, width=15)
    self.label_dest.grid(column=0, row=3, sticky='E', padx=5, pady=5)
    self.label_dest["text"] = "destination path :"
    
    # [1.4] add Entry (EditText) to get source path
    self.entry_dest = Entry(self.label1, width=50)
    self.entry_dest.grid(column=1, row=3, sticky='E', padx=5, pady=5)

สังเกตุตัวหนา ปกติเราจะใส่ self เพื่อให้ขึ้นมาตามปกตินะ แต่ในที่นี้ใช้ LabelFrame ดังนั้นมันจะอยู่ใน LabelFrame นั่นแหละ

ส่วนต่อมา ใส่ Radiobutton กับ Combobox สร้าง LabelFrame อีกอัน ขออนุญาติตัดส่วนโค้ดให้สั้นๆแล้วกัน

    # [2] add label frame for filename
    self.label2 = LabelFrame(self, text="File ")
    self.label2.grid(row=4, columnspan=7, sticky='WE', \
             padx=5, pady=5, ipadx=25, ipady=5)

การใส่ Radiobutton เราสร้าง list ขึ้นมาตัวนึงเนาะ เพื่อสร้าง key และ value จากนั้นจะวนลูปสร้างทีละอันจนหมด

    # [2.1] create Radiobutton to choose mc0 or mc2
    self.label_dest = Label(self.label2, width=15)
    self.label_dest.grid(column=0, row=5, sticky='E', padx=5, pady=5)
    self.label_dest["text"] = "source ab :"
    
    eimer_mc = [("ab0", "ab0"), ("ab2", "ab2")]
    cnt = 1
    for text, mode in eimer_mc:
      self.radio = Radiobutton(self.label2, text=text, variable=var, value=mode, width=5)
      self.radio.grid(column=cnt, row=5)
      cnt+=1

สร้าง Combobox โดยใส่ value เป็นชื่อไฟล์ต่างๆ มีการ bind ไปที่ ComboboxSelected คือ รับค่าที่เราเลือกมานั่นแหละ

    # [2.3] create combobox for select file name
    self.box = Combobox(self.label2)
    self.box['values'] = ("1.c", "2.c", "3.c", "4.c", "5.c", "6.c")
    self.box.bind("<<>ComboboxSelected>")
    self.box.grid(column=1, row=6)

สุดท้ายสร้างปุ่ม ขอข้ามแล้วกัน แปะแค่นี้ คือเมื่อกดปุ่ม จะทำงานด้วยคำสั่งใน copy_source

self.button["command"] = self.copy_source

จากนั้นสร้าง function ใหม่ใน class ของเรา

การสร้าง message box เป็นดังนี้ เราสร้างหน้าต่างใหม่มาหนึ่งอัน ใส่หัว กำหนดขนาด ใส่ตัวหนังสือ และใส่ปุ่ม เมื่อกด ตัว message box จะปิด หลักการทำง่ายๆเนอะ

      msgbox = Tk()
      msgbox.title("Warning")
      msgbox.geometry("130x40")
      label = Label(msgbox, text="Please put eimer path")
      label.grid()
      btn = Button(msgbox, text="OK", command=msgbox.destroy)
      btn.grid()

และส่วนสำคัญ การ copy file สร้างตัวแปรสองตัวเพื่อนับจำนวนไฟล์ทั้งหมด และจำนวนไฟล์ที่ก็อปได้จากนั้นเริ่ม copy ทีละไฟล์ตามข้อมูลที่ได้รับมา

      cnt_all = 0
      cnt_copy = 0
      for i in f_list:
        srcfile = source_path + "\\source_" + source + "\\" + i.decode("utf-8")
        dstdir = dest_path + "\\" + i.decode("utf-8")
        #if not found some source file in source
        if (os.path.isfile(srcfile) == False):
          print("Cannot copy" + i.decode("utf-8"))
          not_found += i.decode("utf-8") + "\r\n"
        else:
          print(i.decode("utf-8"))
          shutil.copy(srcfile, dstdir)
          cnt_copy += 1
        cnt_all += 1

ไฟล์ตัวอย่างทั้งหมด มีใน github ที่นี่เนอะ ยกเว้นอันสุดท้าย ไม่รู้เขาให้เราเผยแพร่หรือเปล่า เอาเป็นว่าขอหลังไมค์แล้วกันนะ

mikkipastel/TestPythonGUI
try to create GUI in python 3.x by tkinter. Contribute to mikkipastel/TestPythonGUI development by creating an account on GitHub.

Reference :

Tags

Minseo Chayabanjonglerd

I am a full-time Android Developer and part-time contributor with developer community and web3 world, who believe people have hard skills and soft skills to up-skill to da moon.