มาทำ python GUI ด้วย tkinter กันดีกว่า
สวัสดีทุกท่าน เราเองอยากเขียน 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 ที่นี่เนอะ ยกเว้นอันสุดท้าย ไม่รู้เขาให้เราเผยแพร่หรือเปล่า เอาเป็นว่าขอหลังไมค์แล้วกันนะ
Reference :