tkinter Basics (tkinter အခြေခံများ)

IA (Internal Assessment) အတွက် tool တစ်ခုပါ၊ စာမေးပွဲ ခေါင်းစဉ် မဟုတ်ပါဘူး: tkinter က စာမေးပွဲမှာ စစ်တဲ့ syllabus (IB သင်ရိုး) ထဲမှာ မပါလို့ သူ့ရဲ့ class (object တွေ ဖန်တီးဖို့ ပုံစံခွက်) တွေ၊ method (သီးခြား အလုပ်တစ်ခု လုပ်ပေးတဲ့ code အပိုင်း) တွေကို စာမေးပွဲမှာ မမေးပါဘူး။ tkinter က window တွေ၊ button တွေ၊ text box တွေ ပါတဲ့ program တွေကို တည်ဆောက်ဖို့ toolkit (အသင့်ရေးထားတဲ့ Python class အစု) တစ်ခုပါ။ Internal Assessment (IB သင်တန်းမှာ ကျောင်းသားတစ်ယောက်ချင်း ကိုယ်တိုင် တည်ဆောက်ရပြီး ကျောင်းက ဆရာက အမှတ်ပေးတဲ့ အပိုင်း) အတွက် product (ကိုယ်တိုင် တည်ဆောက်ရတဲ့ program) ကို Python နဲ့ တည်ဆောက်တဲ့အခါ tkinter ကို သုံးလို့ရပါတယ်။ Java နဲ့ ရေးနေသလား။ ဒီစာမျက်နှာရဲ့ Java version က JavaFX Basics ပါ။


ခြုံငုံ မိတ်ဆက်

Internal Assessment မှာ ကိုယ်တိုင် ရွေးထားတဲ့ ပြဿနာ တစ်ခုကို ဖြေရှင်းပေးတဲ့ product တစ်ခုကို code ရေးပြီး တည်ဆောက်ရပါတယ်။ ပြီးရင် အဲဒီ product အလုပ်လုပ်ပုံကို video တိုလေး တစ်ခုနဲ့ ပြရပါတယ်။ Graphical user interface (GUI၊ window၊ button၊ text box တွေကို မြင်ရပြီး နှိပ်သုံးရတဲ့ user interface) ရှိရင် product ကို သုံးတဲ့သူတွေက terminal (စာသားပဲ ပြတဲ့ window) ထဲမှာ command တွေ ရိုက်ထည့်စရာ မလိုဘဲ button တွေကို နှိပ်ပြီး box တွေထဲမှာ စာရိုက်ထည့်လို့ ရပါတယ်။

tkinter က Python နဲ့ အတူ ပါလာတဲ့ GUI toolkit ပါ။ Window တွေ၊ button တွေ၊ text box တွေနဲ့ layout (အဲဒီ အစိတ်အပိုင်းတွေကို screen ပေါ်မှာ စီစဉ်ထားပုံ) တွေအတွက် အသင့်ရေးထားတဲ့ class အစု တစ်ခုပါ။ ဒီစာမျက်နှာမှာ အလုပ်လုပ်တဲ့ interface တစ်ခု စတင် တည်ဆောက်ဖို့ လိုတဲ့ အခြေခံတွေကို လေ့လာပါမယ်: window တွေ၊ widget (button၊ text box စတဲ့ control တွေ) တွေနဲ့ event handling (user က နှိပ်တာ ဒါမှမဟုတ် စာရိုက်တာကို program က တုံ့ပြန်အောင် လုပ်ခြင်း) ပါ။ နောက်ပိုင်း စာမျက်နှာတွေမှာ MVC with tkinter (MVC: app ကို Model၊ View၊ Controller ဆိုပြီး အပိုင်းသုံးပိုင်း ခွဲရေးတဲ့ ပုံစံ) နဲ့ file persistence (program ပိတ်သွားပြီးနောက်မှာလည်း ဒေတာ ဆက်ရှိနေအောင် သိမ်းထားခြင်း) ကို လေ့လာပါမယ်။

ရည်မှန်းချက်: ဒီစာမျက်နှာ ပြီးတဲ့အခါ user ရဲ့ လုပ်ဆောင်ချက်တွေကို တုံ့ပြန်တဲ့ widget တွေ ပါတဲ့ window တစ်ခုကို ဖန်တီးနိုင်ရပါမယ်။

ကိုယ့် Python မှာ tkinter ပါမပါ စစ်ပါ။ Python ထဲမှာ import tkinter ကို run ကြည့်ပါ: error မပေါ်ရင် ပါပါတယ်။ Windows နဲ့ macOS အတွက် python.org ရဲ့ installer (software ကို ကွန်ပျူတာပေါ် ထည့်သွင်းပေးတဲ့ program) တွေမှာ tkinter ပါပြီးသားပါ။ အဲဒီ system တွေပေါ်မှာ Thonny editor (code ရေးတဲ့ software) ရဲ့ ကိုယ်ပိုင် installer မှာလည်း ပါပါတယ်။ တချို့ install လုပ်ပုံတွေမှာတော့ မပါပါဘူး: Mac ပေါ်မှာ Homebrew (Mac ပေါ်မှာ software install လုပ်တဲ့ tool) ကနေ ထည့်ထားတဲ့ Python အတွက် python-tk package (ထပ်ပြီး install လုပ်ရတဲ့ software အစု) ကို သီးခြား ထည့်ဖို့ လိုပါတယ် (brew install python-tk)။ Linux ပေါ်မှာလည်း tkinter က များသောအားဖြင့် သီးခြား package တစ်ခုပါ (ဥပမာ python3-tk)။ “No module named ‘_tkinter’” (‘_tkinter’ ဆိုတဲ့ module မရှိပါ) ဆိုတဲ့ error ကို မြင်ရရင် အဲဒီ package ကို install လုပ်ပါ၊ ဒါမှမဟုတ် Python ကို python.org ကနေ install လုပ်ပါ။


အဓိက အယူအဆများ

Main Window (အဓိက window) နဲ့ သူ့ထဲက Widget များ

tkinter program တစ်ခုကို အပိုင်း သုံးပိုင်းနဲ့ တည်ဆောက်ပါတယ်:

အပိုင်း ဖန်တီးပုံ ဘာလဲ
Main window tk.Tk() Window ကိုယ်တိုင်ပါ။ Program တစ်ခုက တစ်ခုပဲ ဖန်တီးပါတယ်။
Widgets tk.Label, tk.Entry, tk.Button နဲ့ တခြား widget တွေ Window ထဲက control တွေပါ။ Widget တစ်ခုစီကို ဖန်တီးတဲ့အခါ သူ့ရဲ့ parent (widget ကို ထည့်ထားတဲ့ နေရာ၊ ဆိုလိုတာက window ဒါမှမဟုတ် window ထဲက frame (widget တွေကို စုထည့်တဲ့ box) တစ်ခု) ကို ပေးရပါတယ်။
Layout pack() ဒါမှမဟုတ် grid() Widget တစ်ခုစီကို သူ့ parent ထဲမှာ နေရာချပေးပါတယ်။ နေရာ ဘယ်တော့မှ မချခဲ့တဲ့ widget ကို မပြပါဘူး။

ပထမဆုံး Window

import tkinter as tk

window = tk.Tk()
window.title("My First App")
window.geometry("400x300")    # width x height, in pixels

greeting = tk.Label(window, text="Hello, tkinter!")
greeting.pack(pady=20)

window.mainloop()

ဒီ code ကို run တဲ့အခါ ဖြစ်တာတွေ:

  1. import tkinter as tk က tkinter ကို load လုပ်ပြီး code ထဲမှာ အတိုကောက် tk လို့ ခေါ်လို့ရအောင် လုပ်ပေးပါတယ်
  2. tk.Tk() က main window ကို ဖန်တီးပါတယ်။ title() နဲ့ geometry() က window ရဲ့ ခေါင်းစဉ်နဲ့ အရွယ်အစားကို သတ်မှတ်ပါတယ်
  3. tk.Label(window, ...) က window ကို parent အဖြစ် ယူထားတဲ့ label (စာသား ပြတဲ့ widget) တစ်ခုကို ဖန်တီးပါတယ်။ pack() က အဲဒီ label ကို နေရာချပါတယ် (pady=20 က label ရဲ့ အပေါ်နဲ့ အောက်မှာ pixel (ပုံထဲက အစက် တစ်စက်) 20 စီ နေရာလွတ် ချန်ပေးပါတယ်)
  4. window.mainloop() က window ကို ပြပြီး event loop ကို စတင်ပါတယ်: event loop ဆိုတာ click တွေ၊ key နှိပ်တာတွေကို စောင့်ပြီး ကိုက်ညီတဲ့ code ကို run ပေးတဲ့ loop (ထပ်ခါထပ်ခါ run တဲ့ code အပိုင်း) ပါ။ User က window ကို ပိတ်တဲ့အထိ ဆက် run နေပါတယ်

mainloop() နောက်မှာ ရေးထားတဲ့ code က window ပိတ်သွားမှပဲ run ပါတယ်။ Window တစ်ခုလုံးကို အရင် တည်ဆောက်ပြီးမှ နောက်ဆုံး line အဖြစ် mainloop() ကို ခေါ်ပါ။

Tutorial (အဆင့်ဆင့် သင်ပြတဲ့ လမ်းညွှန်) အဟောင်းတွေမှာ from tkinter import * ကို သုံးကြပါတယ်။ ဒါက tkinter ရဲ့ နာမည် အားလုံးကို ရှေ့မှာ tk. မပါဘဲ ကိုယ့် program ထဲ ယူလာပါတယ်။ import tkinter as tk ကို သုံးရင် ဘယ်နာမည်တွေက tkinter က လာတာလဲ၊ ဘယ်နာမည်တွေက ကိုယ်ပိုင် နာမည်တွေလဲ ဆိုတာ ရှင်းရှင်းလင်းလင်း သိနိုင်ပါတယ်။

Window ကို Class အဖြစ် ရေးခြင်း

IA product တစ်ခုမှာ widget တွေ မကြာခင် အများကြီး ဖြစ်လာတတ်လို့ window ကို class အဖြစ် ရေးရင် အထောက်အကူ ဖြစ်ပါတယ်။ Constructor (object ဖန်တီးချိန် attribute တွေကို စတင် သတ်မှတ်ပေးတဲ့ method) က widget တွေကို တည်ဆောက်ပြီး နောက်ပိုင်း လိုအပ်မယ့် widget တွေကို attribute (object ရဲ့ ဒေတာကို သိမ်းတဲ့ variable) တွေအဖြစ် သိမ်းထားပါတယ်။ Button တစ်ခုစီရဲ့ အလုပ်က method တစ်ခုစီ ဖြစ်လာပါတယ်။

import tkinter as tk


class HelloApp():
    def __init__(self, window):
        window.title("My First App")
        window.geometry("400x300")
        self._greeting = tk.Label(window, text="Hello, tkinter!")
        self._greeting.pack(pady=20)
        self._button = tk.Button(window, text="Say hi", command=self.say_hi)
        self._button.pack()

    def say_hi(self):
        self._greeting.config(text="Hi there!")


window = tk.Tk()
app = HelloApp(window)
window.mainloop()

Say hi ကို နှိပ်ရင် say_hi() run ပြီး config() နဲ့ label ရဲ့ စာသားကို ပြောင်းပါတယ်။ ဒီစာမျက်နှာရဲ့ ကျန်တဲ့ အပိုင်းတွေမှာ ဒီပုံစံကို သုံးပါမယ်။


အသုံးများတဲ့ Widget များ

အောက်က snippet (code အပိုင်းအစ) တိုလေး တစ်ခုစီက line အနည်းငယ်ပဲ ပါတဲ့ သီးခြား ဥပမာ တစ်ခုစီပါ။ Window class ရဲ့ __init__ ထဲမှာ ရေးထားသလို ရေးထားလို့ window က parent ပါ။ အားလုံးကို ပေါင်းစပ်ခြင်း အောက်က program အပြည့်အစုံက ဒီအပိုင်းတွေ အလုပ်လုပ်တဲ့ app တစ်ခုထဲမှာ ဘယ်လို ပေါင်းစပ်သွားလဲ ပြပါတယ်။

Label (စာသား ပြတဲ့ widget)

User ပြင်လို့ မရတဲ့ စာသားကို ပြပါတယ်။

result = tk.Label(window, text="Welcome")
result.pack()

result.config(text="Student added")    # change the text later

Entry (စာ တစ်ကြောင်း ရိုက်ထည့်တဲ့ box)

User စာရိုက်ထည့်တဲ့ line တစ်ကြောင်းတည်း ရှိတဲ့ box ပါ။

name_entry = tk.Entry(window, width=30)
name_entry.pack()

name = name_entry.get().strip()    # what the user typed, without spaces at the ends
name_entry.delete(0, tk.END)       # clear the box

get() က string (စာသား) ကိုပဲ အမြဲ ပြန်ပေးပါတယ်။ အဲဒီ စာသားကို ဂဏန်းအဖြစ် သုံးချင်ရင် int() ဒါမှမဟုတ် float() နဲ့ ပြောင်းပါ (Input Validation ကို ကြည့်ပါ)။

Button (နှိပ်လို့ရတဲ့ ခလုတ်)

add_button = tk.Button(window, text="Add", command=self.handle_add)
add_button.pack()

command=self.handle_add က နှိပ်တိုင်း run ဖို့ class ရဲ့ handle_add method ကို button ကို ပေးလိုက်တာပါ။ နာမည်ကို ကွင်း မပါဘဲ ရေးပါ: command=self.handle_add() လို့ ရေးရင် window ကို တည်ဆောက်နေတုန်းမှာ method က တစ်ကြိမ် run သွားပြီး button ကို method အစား method ရဲ့ ရလဒ်ကို ပေးလိုက်မိပါလိမ့်မယ်။

Text (စာ line အများကြီး ရိုက်ထည့်တဲ့ box)

စာ အရှည်တွေ ရေးဖို့ line အများကြီး ပါတဲ့ box ပါ။ Text box ထဲက နေရာတွေကို "line.character" ပုံစံနဲ့ ရေးပါတယ်: အစက်ရဲ့ ရှေ့က line နံပါတ်၊ နောက်က အဲဒီ line ထဲက character (စာလုံး ဒါမှမဟုတ် သင်္ကေတ) နံပါတ်ပါ။ ဒါကြောင့် "1.0" က ပထမ line ရဲ့ အစပါ။

notes = tk.Text(window, width=40, height=5)
notes.pack()

notes.insert(tk.END, "First line\n")    # add text at the end
text = notes.get("1.0", tk.END)          # all the text
notes.delete("1.0", tk.END)              # clear it

Item (အချက်) တွေရဲ့ စာရင်းကို ပြချင်ရင် tk.Listbox က များသောအားဖြင့် ပိုရိုးရှင်းပါတယ်: insert(tk.END, item) က line တစ်ကြောင်း ထည့်ပြီး delete(0, tk.END) က line အားလုံးကို ဖျက်ပါတယ်။ အောက်က program အပြည့်အစုံမှာ Listbox တစ်ခု သုံးထားပါတယ်။

Combobox (ရွေးချယ်စရာ စာရင်း)

Drop-down list (နှိပ်လိုက်ရင် အောက်ကို ကျလာတဲ့ ရွေးချယ်စရာ စာရင်း) ပါ။ tkinter.ttk ထဲမှာ ပါပါတယ်။ tkinter.ttk က ပိုခေတ်မီတဲ့ ပုံစံ widget တွေ ပါတဲ့ tkinter ရဲ့ အစိတ်အပိုင်း တစ်ခုပါ။

from tkinter import ttk

grade_box = ttk.Combobox(window, values=["11", "12"], state="readonly")
grade_box.pack()

grade_box.set("11")          # choose an item from the code
grade = grade_box.get()      # the chosen item, as a string

state="readonly" က user ကို စာရင်းထဲကနေ ရွေးခွင့် ပေးပေမယ့် ကိုယ်ပိုင် တန်ဖိုး ရိုက်ထည့်ခွင့် မပေးပါဘူး။

Checkbutton (အမှန်ခြစ် အကွက်)

Tick box (အမှန်ခြစ် ခြစ်လို့ရတဲ့ အကွက်) ပါ။ သူ့တန်ဖိုးကို tkinter variable object (တန်ဖိုး သိမ်းဖို့ tkinter ရဲ့ class တစ်ခုကနေ ဖန်တီးထားတဲ့ object) တစ်ခုထဲမှာ သိမ်းပါတယ်။ ဒီဥပမာမှာ tk.BooleanVar ပါ။ tk.BooleanVar က True ဒါမှမဟုတ် False ကို သိမ်းထားပြီး box ကို ခြစ်လိုက်တဲ့အခါ တန်ဖိုး ပြောင်းသွားပါတယ်။

self._hl_choice = tk.BooleanVar()    # an attribute, so it lasts after __init__
hl_check = tk.Checkbutton(window, text="HL", variable=self._hl_choice)
hl_check.pack()

# later, in a handler such as handle_register:
if self._hl_choice.get() == True:
    print("HL is ticked")

tkinter variable ကို attribute အဖြစ် သိမ်းထားပါ။ BooleanVar ဒါမှမဟုတ် StringVar ကို local variable (method ထဲမှာပဲ ရှိတဲ့ variable) ထဲမှာပဲ သိမ်းထားရင် __init__ ပြီးသွားတာနဲ့ ပျက်သွားပြီး tick box က သူ့တန်ဖိုးကို ဆက် မှတ်မထားနိုင်တော့ပါဘူး။


Layout (widget တွေကို နေရာချခြင်း)

Layout manager (widget တွေကို နေရာချပေးတဲ့ စနစ်) က widget တစ်ခုစီကို သူ့ parent ထဲမှာ ဘယ်နေရာ ထားမလဲ ဆုံးဖြတ်ပေးပါတယ်။ tkinter မှာ အသုံးအများဆုံး layout manager နှစ်ခု ရှိပါတယ်။

pack: တစ်ခုပြီး တစ်ခု

pack() က widget တွေကို pack လုပ်တဲ့ အစဉ်အတိုင်း အပေါ်ကနေ အောက်ကို တစ်ခုချင်း စီချပါတယ်။ padx နဲ့ pady က widget ပတ်လည်မှာ နေရာလွတ် ထည့်ပေးပါတယ်။

tk.Label(window, text="Name").pack(pady=4)
tk.Entry(window).pack(pady=4)
tk.Button(window, text="Save").pack(pady=4)

grid: Row (အလျားလိုက် အတန်း) နဲ့ Column (ဒေါင်လိုက် ကော်လံ)

grid() က widget တစ်ခုစီကို row တစ်ခုနဲ့ column တစ်ခုမှာ နေရာချပါတယ်။ Row နဲ့ column ကို 0 ကနေ စ ရေတွက်ပါတယ်။ columnspan က widget တစ်ခုကို column တစ်ခုထက် ပိုပြီး နေရာယူစေပါတယ်။ sticky="w" က widget ကို သူ့ cell (ဇယားကွက်ထဲက အကွက်) ရဲ့ ဘယ်ဘက် (west၊ အနောက်ဘက်) အစွန်းမှာ ကပ်ပြီး တန်းစီပေးပါတယ်။

tk.Label(window, text="Name").grid(row=0, column=0, sticky="w")
tk.Entry(window).grid(row=0, column=1)
tk.Label(window, text="Grade").grid(row=1, column=0, sticky="w")
tk.Entry(window).grid(row=1, column=1)
tk.Button(window, text="Save").grid(row=2, column=0, columnspan=2)

Frame: Widget တွေကို အုပ်စုဖွဲ့ခြင်း

tk.Frame က တခြား widget တွေကို ထည့်ထားတဲ့ box အလွတ် တစ်ခုပါ။ Window ကို အပိုင်းလိုက် တည်ဆောက်ဖို့ frame တွေကို သုံးပါ: frame တွေကို window ထဲ pack လုပ်ပြီးမှ frame တစ်ခုစီထဲမှာ widget တွေကို စီစဉ်ပါ။

form = tk.Frame(window)
form.pack(padx=10, pady=10)

tk.Label(form, text="Name").grid(row=0, column=0)
tk.Entry(form).grid(row=0, column=1)

Parent တစ်ခုစီမှာ layout manager တစ်ခုတည်းကိုပဲ သုံးပါ။ Window တစ်ခု ဒါမှမဟုတ် frame တစ်ခုထဲမှာ widget အားလုံးကို pack() နဲ့ပဲ နေရာချပါ၊ ဒါမှမဟုတ် widget အားလုံးကို grid() နဲ့ပဲ နေရာချပါ။ Layout setting တွေကို default (သတ်မှတ်မထားရင် သုံးတဲ့ တန်ဖိုး) အတိုင်း ထားရင် parent တစ်ခုတည်းမှာ နှစ်ခုကို ရောသုံးတဲ့အခါ လက်ရှိ tkinter version တွေက cannot use geometry manager (geometry manager ဆိုတဲ့ layout manager ကို သုံးလို့ မရပါ) နဲ့ စတဲ့ error နဲ့ program ကို ရပ်လိုက်ပါတယ်။ အပေါ်က ဥပမာမှာလို frame တစ်ခုက သူ့ကို ထည့်ထားတဲ့ window နဲ့ မတူတဲ့ layout manager ကို သုံးလို့ ရပါတယ်။


Event Handling (user ရဲ့ လုပ်ဆောင်ချက်ကို တုံ့ပြန်ခြင်း)

Event (ဖြစ်ရပ်) ဆိုတာ program က တုံ့ပြန်နိုင်တဲ့ အရာ တစ်ခုပါ။ များသောအားဖြင့် button ကို နှိပ်တာ၊ Enter ကို နှိပ်တာ ဒါမှမဟုတ် list ထဲက item တစ်ခုကို ရွေးတာလို user ရဲ့ လုပ်ဆောင်ချက် တစ်ခုပါ။ Event တစ်ခုကို တုံ့ပြန်တဲ့ code ကို အဲဒီ event ရဲ့ event handler လို့ ခေါ်ပါတယ်။ GUI program တစ်ခုက event-driven (event တွေ ဖြစ်မှ code run တဲ့) program ပါ: window ကို တည်ဆောက်ပြီးလို့ mainloop() run နေတာနဲ့ program က စောင့်နေပြီး event တစ်ခု ဖြစ်တိုင်း သူ့ event handler က run ပါတယ်။ User နှိပ်တဲ့ အစဉ်က ကိုယ့် code run မယ့် အစဉ်ကို ဆုံးဖြတ်ပါတယ်။

Button များ: command

Button ရဲ့ handler က အပေါ်မှာ ပြခဲ့သလို command ကို ပေးထားတဲ့ method ပါ။ အဲဒီ method ကို argument (method ကို ခေါ်တဲ့အခါ တကယ် ပေးလိုက်တဲ့ တန်ဖိုး) မပါဘဲ ခေါ်ပါတယ်။

Key တွေနဲ့ တခြား Event များ: bind

တခြား event တွေကို bind() နဲ့ ချိတ်ပါတယ်။ Handler ကို argument တစ်ခု ပေးပါတယ်: ဘာဖြစ်ခဲ့လဲ ဖော်ပြတဲ့ event object တစ်ခုပါ။ ဒါကြောင့် handler method က အဲဒီ object ကို မသုံးရင်တောင် သူ့အတွက် parameter (method ကို ပေးလိုက်တဲ့ တန်ဖိုးကို လက်ခံတဲ့ variable) တစ်ခု ပါရပါတယ်။

def handle_enter(self, event):    # a key binding passes an event object
    self.handle_add()

# in __init__:
self._name_entry.bind("<Return>", self.handle_enter)    # Enter key in the box

Combobox က ရွေးချယ်မှု အသစ်ကို "<<ComboboxSelected>>" event နဲ့ အသိပေးပါတယ်:

def handle_grade(self, event):
    self._result.config(text="Selected: " + self._grade_box.get())

# in __init__:
self._grade_box.bind("<<ComboboxSelected>>", self.handle_grade)

Handler ကို တန်ဖိုး တစ်ခု ပေးချင်ရင်။ command က သူ့ method ကို argument မပါဘဲ ခေါ်ပါတယ်။ Button အများက method တစ်ခုတည်းကို မတူတဲ့ တန်ဖိုးတွေနဲ့ မျှသုံးတဲ့အခါ tutorial တွေမှာ command=lambda: self.choose("S") လို့ ရေးလေ့ ရှိပါတယ်။ lambda က နာမည် မပါတဲ့ function (သီးခြား အလုပ်တစ်ခု လုပ်ပေးတဲ့ code အပိုင်း) အတိုလေး တစ်ခုပါ။ ဒီမှာတော့ button ကို နှိပ်တဲ့အခါ choose("S") ကို ခေါ်ပေးပါတယ်။ Lambda တွေက syllabus မှာ စစ်တဲ့ Python ထဲမှာ မပါလို့ IA အတွက် tool တစ်ခုအဖြစ်ပဲ သဘောထားပါ၊ ဒါမှမဟုတ် button တစ်ခုစီအတွက် method အသေးလေး တစ်ခုစီ ရေးပါ။

Event handler တွေကို တိုတို ထားပါ။ Handler တစ်ခုက line အနည်းငယ်ထက် ရှည်လာရင် အဲဒီ အလုပ်ကို သီးခြား method တစ်ခုထဲ ရွှေ့ပြီး ခေါ်သုံးပါ။ အောက်က program အပြည့်အစုံမှာ refresh_display() နဲ့ ဒီလို လုပ်ထားပါတယ်။ တိုတဲ့ handler တွေက ဖတ်ရတာ၊ ပြင်ရတာ ပိုလွယ်ပါတယ်။


Message Box များ

Message box ဆိုတာ message တစ်ခုကို ပြတာ၊ user ကို တစ်ခုခု အတည်ပြုခိုင်းတာ ဒါမှမဟုတ် error ကို အသိပေးတာ လုပ်တဲ့ pop-up (ရုတ်တရက် ပေါ်လာတဲ့) window အသေးလေး တစ်ခုပါ။ User က ပိတ်လိုက်တဲ့အထိ ကိုယ့် code ကို ခဏ ရပ်ထားပါတယ်။

from tkinter import messagebox

messagebox.showinfo("Success", "Student added successfully.")
messagebox.showerror("Error", "Name cannot be empty.")

if messagebox.askyesno("Confirm Delete", "Delete this student? This cannot be undone.") == True:
    # the user clicked Yes, so delete the student here
    print("Deleted")

User က Yes ကို နှိပ်ရင် askyesno() က True ကို ပြန်ပေးပြီး No ကို နှိပ်ရင် False ကို ပြန်ပေးပါတယ်။

Message box တွေကို လိုမှသာ သုံးပါ။ User ထည့်လိုက်တဲ့ input (program ထဲ ထည့်ပေးတဲ့ ဒေတာ) ကို ပြင်ဖို့ လိုတဲ့အခါ (ဥပမာ “name is required”၊ နာမည် ထည့်ဖို့ လိုပါတယ်) pop-up ထက် label ထဲက message က များသောအားဖြင့် ပိုကောင်းပါတယ်။ Message box တွေကို အတည်ပြုချက် တောင်းတာတွေနဲ့ user မလွတ်သွားရမယ့် အရေးကြီးတဲ့ error တွေအတွက်ပဲ ထားပါ။


အားလုံးကို ပေါင်းစပ်ခြင်း

ဒါက user ကို list ထဲ နာမည်တွေ ထည့်ပြီး screen ပေါ်မှာ list ကို ကြည့်ခွင့်ပေးတဲ့ application အသေးလေး အပြည့်အစုံ တစ်ခုပါ။

import tkinter as tk
from tkinter import messagebox


class NameListApp():
    def __init__(self, window):
        self._names = []

        window.title("Name List Manager")
        tk.Label(window, text="Name List Manager").pack(pady=(10, 0))

        self._display = tk.Listbox(window, width=40, height=10)
        self._display.pack(padx=10, pady=10)

        form = tk.Frame(window)
        form.pack(padx=10, pady=(0, 10))
        self._name_entry = tk.Entry(form, width=30)
        self._name_entry.grid(row=0, column=0, columnspan=2, pady=4)
        tk.Button(form, text="Add", command=self.handle_add).grid(row=1, column=0, pady=4)
        tk.Button(form, text="Clear All", command=self.handle_clear).grid(row=1, column=1, pady=4)
        self._status = tk.Label(form, text="")
        self._status.grid(row=2, column=0, columnspan=2)

        self._name_entry.bind("<Return>", self.handle_enter)

    def handle_add(self):
        name = self._name_entry.get().strip()
        if name == "":
            self._status.config(text="Please enter a name.")
        else:
            self._names.append(name)
            self._name_entry.delete(0, tk.END)
            self._status.config(text="Added: " + name)
            self.refresh_display()

    def handle_enter(self, event):    # a key binding passes an event object
        self.handle_add()

    def handle_clear(self):
        if messagebox.askyesno("Confirm", "Clear all names?") == True:
            self._names = []
            self._status.config(text="List cleared.")
            self.refresh_display()

    def refresh_display(self):
        self._display.delete(0, tk.END)
        for i in range(len(self._names)):
            self._display.insert(tk.END, str(i + 1) + ". " + self._names[i])


window = tk.Tk()
app = NameListApp(window)
window.mainloop()

ဒီ program က ပြသတဲ့ အချက်များ:

  • Class အဖြစ် ရေးထားတဲ့ window တစ်ခု၊ နောက်ပိုင်း လိုမယ့် widget တွေကို attribute အဖြစ် သိမ်းထားပါတယ်
  • Window ထဲမှာ pack() နဲ့ frame ထဲမှာ grid() (parent တစ်ခုစီမှာ manager တစ်ခုတည်း)
  • Input အတွက် Entry တစ်ခု၊ Enter key ကို Add button နဲ့ အလုပ်တူ လုပ်အောင် ချိတ် (bind) ထားပါတယ်
  • ဖတ်ရုံသာ ရတဲ့ display (ဒေတာ ပြတဲ့ နေရာ) အဖြစ် သုံးထားတဲ့ Listbox
  • နာမည် အားလုံးကို မဖျက်ခင် user ကို အတည်ပြုခိုင်းတဲ့ message box
  • ဒေတာကို သိမ်းဖို့ list တစ်ခု (window ပိတ်သွားရင် နာမည်တွေ ပျောက်သွားပါတယ်။ နာမည်တွေကို file ထဲ သိမ်းပြီး ပြန် load လုပ်ပုံကို File I/O in Apps မှာ ပြထားပါတယ်)
  • Display ကို update လုပ်ပေးပြီး handler နှစ်ခုလုံးက ခေါ်သုံးတဲ့ သီးခြား method refresh_display()

IA Project တွေမှာ အသုံးများတဲ့ ပုံစံများ

Input Validation (ထည့်ပေးတဲ့ ဒေတာ မှန်မမှန် စစ်ခြင်း)

Input validation ဆိုတာ user ထည့်လိုက်တဲ့ input ကို မသုံးခင် စစ်တာပါ။ Program အပြည့်အစုံထဲက handle_add ရဲ့ ဒီ version မှာ အရှည်ကို စစ်တဲ့ အဆင့် တစ်ခု ထပ်ထည့်ထားပါတယ်:

def handle_add(self):
    name = self._name_entry.get().strip()
    if name == "":
        self._status.config(text="Error: Name cannot be empty.")
    elif len(name) > 50:
        self._status.config(text="Error: Name too long (max 50 characters).")
    else:
        # the input is valid, so add the name
        self._names.append(name)
        self._name_entry.delete(0, tk.END)
        self._status.config(text="Added: " + name)
        self.refresh_display()

ရိုက်ထည့်တဲ့ စာသားက ဂဏန်း ဟုတ်မဟုတ် စစ်ဖို့ try block ထဲမှာ int() ဒါမှမဟုတ် float() နဲ့ ပြောင်းပါ။ စာသားက ဂဏန်း မဟုတ်ရင် ပြောင်းတဲ့အခါ ValueError ကို raise (exception ဖြစ်ကြောင်း အချက်ပြ) လုပ်ပါတယ်။ Exception Handling စာမျက်နှာမှာလို ကိုယ့် except block က message တစ်ခု ပြပြီး အဲဒီ exception ကို ကိုင်တွယ်နိုင်ပါတယ်။

Widget တွေကို Disable လုပ်ခြင်း

Button ရဲ့ လုပ်ဆောင်ချက်က အဓိပ္ပာယ် မရှိတဲ့ အချိန်မှာ အဲဒီ button ကို disable (မီးခိုးရောင် ဖြစ်သွားပြီး နှိပ်လို့ မရအောင်) လုပ်ထားနိုင်ပါတယ်။ ဥပမာ name box အလွတ် ဖြစ်နေချိန်မှာ Add button ပါ။ Box နဲ့ ချိတ်ထားတဲ့ tk.StringVar က စာသား ပြောင်းတိုင်း method တစ်ခုကို ခေါ်ပေးနိုင်ပါတယ်။ ဘယ်လိုပဲ ပြောင်းပြောင်း (စာရိုက်တာ၊ paste လုပ်တာ ဒါမှမဟုတ် program က box ကို ရှင်းလိုက်တာ) ခေါ်ပေးပါတယ်:

def update_add_button(self, name, index, mode):    # tkinter passes three values; this method does not need them
    if self._name_text.get().strip() == "":
        self._add_button.config(state="disabled")
    else:
        self._add_button.config(state="normal")

# in __init__:
self._name_text = tk.StringVar()
self._name_entry = tk.Entry(window, textvariable=self._name_text)
self._add_button = tk.Button(window, text="Add", command=self.handle_add, state="disabled")
self._name_text.trace_add("write", self.update_add_button)    # run it whenever the text changes

handle_add() ထဲက စစ်ဆေးချက်ကိုလည်း ဆက်ထားပါ: disable လုပ်ထားတဲ့ button က user အတွက် အကူအညီ တစ်ခုပဲ ဖြစ်ပြီး input ကို သုံးလို့ ရမရ ဆိုတာကို handler ကပဲ ဆက်ပြီး သေချာအောင် စစ်ပေးပါတယ်။

Status Label ကို Update လုပ်ခြင်း

Window ရဲ့ အောက်ခြေက status label (အခြေအနေ ပြတဲ့ label) က pop-up မသုံးဘဲ ဘာဖြစ်သွားလဲ ဆိုတာကို user ကို ပြောပြပါတယ်:

self._status.config(text="Ready")                  # default state
self._status.config(text="Added: " + name)         # after a successful action
self._status.config(text="Error: invalid input")   # on a validation failure

အရောင်များ

Widget အများစုက bg (background၊ နောက်ခံ အရောင်) နဲ့ fg (စာလုံး အရောင်) ကို လက်ခံပါတယ်။ အရောင် နာမည် ဒါမှမဟုတ် hex code (အခြေ 16 ဂဏန်းစနစ်နဲ့ ရေးတဲ့ အရောင် code) နဲ့ ပေးလို့ရပါတယ်:

tk.Label(window, text="Saved", bg="#E6F4EA", fg="#1E4620").pack()

Mac ပေါ်မှာ tk.Button က များသောအားဖြင့် system ရဲ့ ကိုယ်ပိုင် button ပုံစံကို ဆက်သုံးပြီး bg ကို လျစ်လျူရှုနိုင်ပါတယ်။ Label တွေနဲ့ frame တွေကတော့ အရောင်ကို လက်ခံပါတယ်။


Quick Check (အမြန် စစ်ဆေးမေးခွန်း)

Q1. tkinter program တစ်ခုမှာ ဘယ် line က window ကို ပြပြီး click တွေ၊ key နှိပ်တာတွေကို စောင့်ဖို့ စတင်သလဲ။

Q2. ကျောင်းသား တစ်ယောက်က window ကို parent အဖြစ် ပေးပြီး label တစ်ခု ဖန်တီးပေမယ့် label က ဘယ်တော့မှ မပေါ်လာပါဘူး။ Program က error မရှိဘဲ run ပါတယ်။ ဖြစ်နိုင်ခြေ အများဆုံး အကြောင်းရင်းက ဘာလဲ။

Q3. Window တစ်ခုက ခေါင်းစဉ်ကို pack() နဲ့၊ form (ဒေတာ ဖြည့်ရတဲ့ အကွက်တွေ ပါတဲ့ အပိုင်း) ကို grid() နဲ့ window ထဲမှာပဲ တိုက်ရိုက် နေရာချထားလို့ program က error နဲ့ ရပ်သွားပါတယ်။ အကောင်းဆုံး ပြင်နည်းက ဘာလဲ။

Q4. User တစ်ယောက်က Entry box ထဲမှာ 42 လို့ ရိုက်ထည့်ပါတယ်။ Box ရဲ့ get() method က ဘာကို ပြန်ပေးသလဲ။

Q5. ကျောင်းသား တစ်ယောက်က window.mainloop() ရဲ့ နောက်မှာ ရေးထားတဲ့ line တစ်ကြောင်းနဲ့ button အသစ် တစ်ခု ထည့်ပါတယ်။ အဲဒီ line က ဘယ်အချိန်မှာ run သလဲ။


Practice Exercises (လေ့ကျင့်ခန်းများ)

(4 marks) စတာတွေက မေးခွန်း တစ်ခုစီအတွက် ရနိုင်တဲ့ အမှတ်ပါ။ IB စာမေးပွဲ မေးခွန်းတွေမှာ အမှတ်ကို ဒီလို ပြလေ့ ရှိပါတယ်။

Core (အခြေခံ အဆင့်)

  1. နာမည်ကတ် App (4 marks): Entry တစ်ခု၊ “Make Tag” လို့ စာတန်း ပါတဲ့ Button တစ်ခုနဲ့ Label တစ်ခု ပါတဲ့ tkinter application တစ်ခုကို Construct (ကိုယ်တိုင် တည်ဆောက်ပါ)။ Button ကို နှိပ်တဲ့အခါ box ထဲမှာ ရိုက်ထည့်ထားတဲ့ နာမည်ကို ရှေ့နဲ့ နောက်မှာ space မပါဘဲ စာလုံးကြီးနဲ့ label မှာ ပြရပါမယ် (ဥပမာ “ mia “ က “MIA” ဖြစ်သွားပါတယ်)။

  2. ရေတွက်တဲ့ App (4 marks): ဂဏန်း တစ်ခု (0 ကနေ စ) ကို ပြတဲ့ Label တစ်ခုနဲ့ “+” နဲ့ “-“ ဆိုတဲ့ button နှစ်ခု ပါတဲ့ application တစ်ခုကို Construct လုပ်ပါ။ “+” ကို နှိပ်ရင် ဂဏန်းကို 1 တိုးပြီး “-“ ကို နှိပ်ရင် 1 လျှော့ပါ၊ ဒါကြောင့် 0 မှာ “-“ ကို တစ်ချက် နှိပ်ရင် -1 ကို ပြပါတယ်။ နှိပ်ပြီးတိုင်း label ကို update လုပ်ရပါမယ်။

Extension (တိုးချဲ့ အဆင့်)

  1. စာဖတ်ချိန် ခန့်မှန်းတဲ့ App (6 marks): စာအုပ် တစ်အုပ်ရဲ့ စာမျက်နှာ အရေအတွက်အတွက် Entry တစ်ခု၊ စာဖတ်နှုန်း ရွေးဖို့ Combobox တစ်ခု (“Slow: 20 pages an hour”၊ “Medium: 30 pages an hour” ဒါမှမဟုတ် “Fast: 45 pages an hour”၊ ဆိုလိုတာက နှေး: တစ်နာရီ 20 မျက်နှာ၊ အလယ်အလတ်: တစ်နာရီ 30 မျက်နှာ၊ မြန်: တစ်နာရီ 45 မျက်နှာ)၊ “Estimate” button တစ်ခုနဲ့ စာဖတ်ချိန်ကို နာရီနဲ့ မိနစ်နဲ့ ပြတဲ့ Label တစ်ခု ပါတဲ့ app တစ်ခုကို Construct လုပ်ပါ (ဥပမာ မြန်တဲ့ နှုန်းနဲ့ဆိုရင် 250 မျက်နှာက 5 နာရီ 33 မိနစ်လောက် ကြာပါတယ်)။ မခန့်မှန်းခင် input က 1 ကနေ 5000 အထိ ကိန်းပြည့် ဟုတ်မဟုတ် စစ်ပြီး မဟုတ်ရင် label ထဲမှာ message တစ်ခု ပြပါ။

  2. ခရီးစဉ် စာရင်းပေးသွင်းတဲ့ List (6 marks): နေရာ 20 ရှိတဲ့ အတန်း ခရီးစဉ် တစ်ခုအတွက် app တစ်ခုကို Construct လုပ်ပါ။ User က နာမည် တစ်ခု ရိုက်ထည့်ပြီး “Sign Up” ကို နှိပ်ရင် အဲဒီ နာမည်ကို list ထဲ ထည့်ပါတယ်။ List ကို Listbox ထဲမှာ နံပါတ်တပ်ထားတဲ့ စာရင်းအဖြစ် ပြပါ။ နာမည် အလွတ်၊ list ထဲမှာ ရှိပြီးသား နာမည် (စာလုံး အကြီး အသေး မတူရင်တောင်) နဲ့ နေရာ 20 လုံး ပြည့်သွားပြီးနောက် စာရင်းပေးသွင်းတာ မှန်သမျှကို လက်မခံပါနဲ့။ တစ်ခါစီမှာ status label ထဲ message တစ်ခု ပြပါ။ နောက်ဆုံး ထည့်ခဲ့တဲ့ နာမည်ကို မဖယ်ခင် အတည်ပြုချက် တောင်းတဲ့ “Remove Last” button တစ်ခုလည်း ထည့်ပါ။

Challenge (စိန်ခေါ်မှု အဆင့်)

  1. ကျောင်းသား စာရင်းသွင်းခြင်း (8 marks): နာမည်၊ အတန်း (11 နဲ့ 12 ပါတဲ့ Combobox) နဲ့ HL ဒါမှမဟုတ် SL (Checkbutton) အတွက် box တွေ ပါတဲ့ form တစ်ခုကို Construct လုပ်ပါ (HL (Higher Level) နဲ့ SL (Standard Level) ဆိုတာ IB သင်တန်းရဲ့ အဆင့်နှစ်ဆင့်ပါ)။ နာမည် ရိုက်ထည့်ထားပြီး အတန်း ရွေးထားပြီလား စစ်ပြီး ကျောင်းသားရဲ့ အချက်အလက်ကို အရင် စာရင်းသွင်းထားတဲ့ ကျောင်းသားတွေရဲ့ အောက်မှာ Listbox ထဲက line အသစ် တစ်ကြောင်းအဖြစ် ထည့်ပေးတဲ့ “Register” button တစ်ခု ထည့်ပါ။ Input အားလုံးကို default တန်ဖိုးတွေဆီ ပြန်ထားပေးတဲ့ “Clear Form” button တစ်ခုလည်း ထည့်ပါ။

  2. Event တွေ ဖြစ်တဲ့ အစဉ်ကို ဖော်ပြပါ (6 marks): အားလုံးကို ပေါင်းစပ်ခြင်း ထဲက Name List Manager မှာ user က app ကို စဖွင့်ပြီး “Mia” လို့ ရိုက်ထည့်ကာ Enter ကို နှိပ်ပါတယ်။ NameListApp(window) ကို ခေါ်လိုက်တဲ့ အချိန်ကနေ list ထဲမှာ “1. Mia” ပေါ်လာတဲ့အထိ program က ဘာတွေ လုပ်လဲဆိုတာကို အစဉ်လိုက် Describe (ဖော်ပြရှင်းပြပါ)။ User စာရိုက်နေတုန်း mainloop() က ဘာလုပ်နေလဲ၊ Enter ကို နှိပ်တဲ့အခါ ဘယ် method run လဲ၊ ဘာကြောင့်လဲ၊ handle_add() က list ကို ကိုယ်တိုင် update မလုပ်ဘဲ ဘာကြောင့် refresh_display() ကို ခေါ်သလဲ ဆိုတာတွေကို ထည့်ရေးပါ။ Code ရေးဖို့ မလိုပါဘူး။


ဆက်စပ် ခေါင်းစဉ်များ

  • အရင် လေ့လာထားရမယ့် ခေါင်းစဉ်: Classes and Objects (class နဲ့ object များ): window ကို constructor နဲ့ method တွေ ပါတဲ့ class အဖြစ် ရေးထားပါတယ်
  • အရင် လေ့လာထားရမယ့် ခေါင်းစဉ်: Exception Handling (exception တွေကို ကိုင်တွယ်ခြင်း): ရိုက်ထည့်တဲ့ စာသားကို ဂဏန်းအဖြစ် ပြောင်းဖို့ try နဲ့ except
  • Java version: JavaFX Basics: အလားတူ အယူအဆတွေကို JavaFX (Java နဲ့ desktop app တည်ဆောက်တဲ့ library) နဲ့ ပြထားပါတယ်
  • နောက်ထပ် လေ့လာမယ့် ခေါင်းစဉ်: MVC with tkinter: ဒေတာ၊ window နဲ့ user ကို တုံ့ပြန်တဲ့ code တွေကို ခွဲခြားခြင်း
  • နောက်ထပ် လေ့လာမယ့် ခေါင်းစဉ်: File I/O in Apps: ဒေတာကို သိမ်းခြင်းနဲ့ ပြန် load လုပ်ခြင်း

© EduCS.me. ကွန်ပျူတာသိပ္ပံ ပညာရေးအတွက် အရင်းအမြစ်တွေ စုထားတဲ့ နေရာပါ။ EduCS is an independent educational resource. It is not affiliated with, or endorsed by, the International Baccalaureate Organization. (EduCS ဟာ သီးခြား ပညာရေး အရင်းအမြစ်တစ်ခုပါ။ International Baccalaureate Organization နဲ့ ဆက်စပ်မှု မရှိသလို သူတို့ရဲ့ ထောက်ခံချက်လည်း မရှိပါဘူး။) ဆက်သွယ်ရန်