Dasar Pemrograman GUI dengan Python dan Tkinter
Pada awal kemunculan Python, kombinasi antara bahasa pemrograman ini dan GUI terdengar seperti kombinasi yang mustahil. “Memangnya bisa, script punya tampilan jendela seperti program terkompilasi?” adalah pertanyaan yang kerap dilontarkan oleh para pemula yang menganggap bahasa pemrograman terinterpretasi seperti Python dan Perl sama seperti batch file DOS/Win32 atau shell script Unix. Kenyataannya adalah, sebagaimana perbedaan definisi antara bahasa pemrograman terinterpretasi dan bahasa terkompilasi telah semakin kabur (semakin banyak bahasa dengan interpreter yang mengkompilasi script menjadi bytecode yang lebih ringkas dan dieksekusi lebih cepat), begitu pula yang terjadi dengan fasilitas yang ditawarkan keduanya. Bahasa-bahasa seperti Perl, Tcl dan PHP yang lebih populer sebagai bahasa scripting pendukung Internet menyediakan pula fasilitas-fasilitas pemrograman sistem. Modul antarmuka pengguna adalah salah satu fasilitas tambahan yang mulai dikembangkan dalam beberapa bahasa terinterpretasi.
Python, sebagai pendatang yang relatif baru dibandingkan bahasa scripting lainnya tidak memiliki modul GUI yang dikembangkan secara native; namun kemudahan ekstensibilitasnya dengan menulis modul dalam bahasa C membuatnya fleksibel dalam menggunakan fasilitas yang sama yang telah dikembangkan dalam bahasa lain. Sebagai contoh adalah wxPython yang menghubungkan Python dengan wxWindows, PyQt dengan Qt (pustaka grafis yang dipakai oleh – antara lain – Gnome), modul MFC (dalam PyWin32) dengan pustaka MFC dalam platform Win32, dan Tkinter dengan pustaka Tk dalam bahasa Tcl. Di antara semua modul GUI yang tersedia untuk Python, kemungkinan besar Tkinter-lah yang pertama menarik perhatian para pengguna bahasa ini: Tkinter adalah bagian dari pustaka standar Python, IDLE (editor-shell-debugger terintegrasi bawaan Python) menggunakan modul ini, dan – terima kasih pada Tcl/Tk yang lebih dahulu populer – merupakan modul GUI yang paling stabil dalam berbagai platform. Menguasai kombinasi Python-Tkinter sama seperti menguasai kombinasi C++-wxWindows: aplikasi GUI apapun yang Anda bangun di atasnya yang stabil di satu platform kemungkinan besar akan berjalan mulus pula di platform lain, lengkap dengan tampilan antarmuka yang relatif konsisten.
Start from The Root
Bahasan kita akan dimulai dari komponen paling mendasar dalam
modul ini, yaitu Tk. Kelas ini adalah representasi
jendela utama sebuah aplikasi dan interpreter Tcl untuk aplikasi
tersebut, yang berarti dalam satu instans program: (1) hanya ada satu
objek dari kelas ini yang berjalan, dan (2) objek tersebut hanya
dapat menjalankan loop utama satu kali. Sesuai kebiasaan yang berakar
dari Tcl/Tk, objek induk ini akan kita sebut sebagai root.
Sebagai permulaan, buka shell interpreter Python dan masukkan
rangkaian perintah berikut:
>>> from Tkinter import *
>>> root = Tk()
>>> root.mainloop()
Tiga baris pernyataan tersebut memang bukan rangkaian instruksi yang bermanfaat, namun telah menggambarkan secara garis besar siklus yang dijalani aplikasi kita:
Baris pertama mengimpor semua modul dalam
Tkinter, sehingga kita dapat memanggilTkalih-alihTkinter.Tk(Semua komponen dasar Tkinter berada dalam namespace1Tkinter, sehingga impor namespace seperti ini akan banyak meringkas kode).Setelah itu, program membuat dan menginisialisasi satu objek induk GUI, dan langsung menjalankan loop utamanya. Dalam loop tersebut, objek induk ini terus-menerus mendengar dan menunggui semua saluran event yang disediakan oleh sistem operasi: interrupt dari CPU, dari perangkat I/O seperti mouse atau printer, atau komponen lain yang terhubung dengan sistem. Lazimnya, kita melakukan inisialisasi aplikasi di antara perintah inisialisasi dan perintah loop objek induk (karena kode yang diletakkan dibawah perintah
mainlooptersebut hanya dijalankan saat jendela utama ditutup = saat aplikasi selesai), sehingga meskipun kita dapat meringkas dua baris terakhir menjadiTk().mainloop(), kita tidak akan melakukannya.
Agar lebih familiar dengan komponen-komponen Tkinter seperti Tk,
lihat dokumentasinya dengan menjalankan help(Tk) dalam
shell. Agak panjang memang, tapi untuk sementara Anda hanya
perlu memusatkan perhatian pada atribut dan metode spesifik komponen
yang selalu diterakan di bagian teratas dokumentasi.
Inheritance dan Callback-Handler
Jendela yang dihasilkan rangkaian kode di atas menggunakan setting default dan mengabaikan event apapun yang terjadi di wilayahnya. Agar lebih menarik, kita akan mulai menambahkan komponen dan mengubah pola pengembangan kita: menulis rangkaian instruksi sebagai program (tentu shell tetap sangat berguna untuk eksperimen dan membedah setiap komponen Python yang masih asing):
File uitest.py
from Tkinter import *
class MainFrame(Frame):
def __init__(self, master):
Frame.__init__(self, master)
self.btn1 = Button(text="Push me!", command=self.echo)
self.btn2 = Button(text="Don't push me!!", command=master.quit)
self.btn1.pack()
self.btn2.pack()
def echo(self):
print "Oy! You're pushing the button!"
if __name__ == '__main__':
root = Tk()
frm = MainFrame(root)
frm.pack()
root.mainloop()
Whew! Tebing yang didaki jadi lebih curam... Kode di atas
memperkenalkan lebih banyak konsep baru. Kita mulai bahasan dari
titik awal eksekusi. if __name__ == '__main__' adalah
perintah kondisional yang membuat program kecil ini siap menjadi
komponen dalam program yang lebih besar – bila ia dipanggil
secara langsung (sebagai program, langsung dalam namespace
utama, __main__) ia akan membuat objek induk sendiri dan
mengeksekusinya sendiri, namun bila dipanggil oleh program lain
(sebagai modul, dalam namespace program yang memanggil) ia
tidak akan bertindak sendiri, memungkinkan kelas MainFrame
di atasnya digunakan oleh program yang memanggil.

frm juga objek dari kelas dalam modul Tkinter,
seperti root. Bedanya, ia harus mempunyai pemuat (sebuah
frame harus berada dalam sebuah jendela!) yang ditentukan saat
inisialisasi komponen. Men-subclass-kan komponen (dalam hal
ini Frame kita spesialisasikan menjadi MainFrame)
biasanya menjadi prosedur rutin2
dalam pemrograman dengan Tkinter. Sementara pack() akan
dijelaskan pada bagian selanjutnya. Sementara ini cukuplah dijelaskan
bahwa fungsi ini adalah komponen dalam satu di antara tiga
pengelola-geometri utama dalam Tkinter (dua yang lain adalah grid
dan place); memanggil fungsi pengelola-geometri seperti
ini memberitahu objek yang bersangkutan dimana ia harus ditempatkan
dan bagaimana perilaku penempatannya.
Argumen dengan keyword command dalam perintah
inisialisasi Button adalah salah satu cara kita
menentukan sendiri fungsi callback-handler atau handler
saja (fungsi yang akan dipanggil oleh objek induk saat terjadi event
apapun pada komponen tertentu dalam wilayahnya). Handler yang
ditentukan melalui argumen harus tidak berargumen, dan setiap
return-value akan diabaikan. Ada kalanya bukan ini yang kita
inginkan; untuk kendali lebih besar, Tkinter menyediakan metode lain
yang lebih fleksibel, yaitu bind:
class MainFrame(Frame):
def __init__(self, master):
Frame.__init__(self, master)
self.btn1 = Button(self,text="Push me!")
self.btn2 = Button(self,text="Don't push me!!", command=master.quit)
self.btn1.bind("<Shift-Button-1>", self.echo1)
self.btn1.bind("<Button>", self.echo2)
self.btn1.pack()
self.btn2.pack()
def echo1(self, event):
print "Oy! You're pushing the button while pressing Shift!"
def echo2(self, event):
pressed = {1:'kiri', 2:'tengah', 3:'kanan'}
for i in dir(event): print "%s = %s" % (i, getattr(event, i))
print "Anda menekan tombol %s mouse." % (pressed[event.num])
Di sini kita memasang beberapa handler berbeda untuk kombinasi
event tertentu dalam tombol pertama dengan metode bind;
satu untuk tombol kiri mouse dengan modifier tombol Shift,
satu lagi untuk semua tombol mouse tanpa modifier. Handler
yang ditentukan dengan cara ini harus menerima satu argumen, yaitu
objek event. Baris kedua dalam metode MainFrame.echo2
memperlihatkan semua atribut tersebut beserta nilainya saat itu;
baris ketiga memberi contoh trivial pemanfaatannya.
Argumen-argumen dengan keyword memperlihatkan sifat semua
komponen Tkinter; masing-masing komponen memiliki atribut (yang,
untuk memudahkan, dapat kita imajinasikan sebagai dictionary,
meski sebenarnya tidak) yang ditentukan saat inisialisasi atau
melalui metode configure(). Setiap atribut tersebut
menentukan, antara lain, tampilan dan perilaku setiap komponen. Ada
atribut yang dimiliki oleh semua komponen, seperti yang menentukan
warna dan tampilan border, dan ada yang hanya dimiliki oleh
komponen tertentu, seperti besar langkah increment nilai dalam
Spinbox, dan semuanya memiliki nilai default yang
membuat kita tidak harus menentukan sendiri nilai semua atribut
komponen saat inisialisasi.
Geometry-Manager
Pustaka Tk menyediakan beberapa pengelola-geometri: Pack, Grid dan
Place. Semuanya memiliki set konfigurasi yang berbeda-beda, dan
memenuhi kebutuhan desain GUI yang berbeda pula. Satu instans
komponen hanya dapat menggunakan satu pengelola-geometri selama masa
hidup program; bila sebuah Frame berisi satu komponen
yang dikemas dengan, misalnya, Pack, dan komponen lain dengan Grid,
hasilnya adalah loop abadi (Anda dapat mencobanya sendiri,
meski tentu tidak ada efek visual yang menarik) karena algoritma
pengaturan geometri yang berbeda tidak dapat menyelesaikan komputasi
masing-masing.
Pack
Di antara ketiga pengelola-geometri, Pack adalah yang paling mudah digunakan sekaligus paling penuh trik karena algoritma pemenuhan ruangnya, yang dapat diringkas sebagai berikut:
Komponen pertama yang dikemas menempel di salah satu sisi pemuat. Ukuran komponen pemuatnya menyesuaikan, bila komponen meminta ukuran yang lebih besar dari ukuran awal si pemuat.
Apabila ada komponen lain yang masuk, Pack memperbesar si pemuat secukupnya agar komponen kedua ini dapat masuk. Komponen kedua hanya dapat meminta ruang yang belum dipakai komponen pertama: apabila komponen pertama sudah menempel di salah satu sisi dalam majikannya, penempatan komponen selanjutnya hanya memperhitungkan sisi ruang yang tidak digunakan komponen sebelumnya. Karenanya urutan perintah pengemasan berpengaruh pada ruang yang ditempati setiap komponen.
Sebagai contoh, kita mulai dengan snippet berikut:
class MainFrame(Frame):
def __init__(self, master):
Frame.__init__(self, master)
self.btn1 = Button(width=4,height=4,text="Oy!")
self.btn2 = Button(text="Hola!")
self.btn1.pack(side=LEFT,expand=1,fill=BOTH)
Dalam snippet di atas, MainFrame.btn1
diinisialisasi dengan ukuran tertentu, namun meminta dikemas dengan
mengisi seluruh ruang kosong yang ada (expand=1 membuat
komponen meminta ruang selebar-lebarnya, fill=BOTH
membuat komponen memanfaatkan seluruh ruang yang diberikan padanya).
Menambah perintah self.btn2.pack(side=TOP) akan
menempelkan btn2 di sisi atas ruang kosong di
sebelah kanan btn1:
menjadi

Karena sudah meminta sisi kiri luas
ruang, dan btn1 sudah meminta sisi atas
dari (luas ruang – luas btn2), maka
komponen selanjutnya hanya dapat menempati ruang kosong di bawah btn1btn2
(kalau ruang sisa terlalu kecil, komponen-komponen yang masuk
belakangan tidak akan dimuat, seakan-akan hilang). Satu-satunya cara
supaya dua buah komponen dapat bersama-sama berada di satu sisi ruang
adalah dengan memasukkan keduanya dalam sebuah Frame temporer, dan
mengemas Frame tersebut (bila layout GUI yang diinginkan cukup
kompleks biasanya kita sudah akan mencium gelagat terlebih dahulu,
dan lebih memilih Grid). Hanya dengan menggunakan Pack, dan Frame
temporer secukupnya, kita dapat membuat layout seperti di bawah:

Penggunaan Pack juga memudahkan kita merancang antarmuka yang tidak rigid. Memang tidak dapat diketahui dari gambar, namun jumlah baris File MPEG di atas dapat ditentukan secara dinamis (lewat validasi input Jumlah CD). Dengan C++, saya harus mati-matian mempelajari banyak fungsi dan makro dalam API Win32 dan MFC untuk mencapai efek seperti itu.
Grid
Grid membagi ruang menjadi kotak-kotak imajiner dengan indeks berbasis-0 yang ukuran masing-masing menyesuaikan diri dengan ada-tidaknya komponen di dalamnya. Snippet berikut:
class MainFrame(Frame):
def __init__(self, master):
Frame.__init__(self, master)
self.btn1 = Button(self,text="Oy!", width=10,height=2, relief=GROOVE, borderwidth=2)
self.btn2 = Button(self,text="Hola!", width=10,height=2, relief=RIDGE, borderwidth=2)
self.btn3 = Button(self,text="Tabik!", width=10,height=2, relief=GROOVE, borderwidth=2)
self.btn1.grid(column=0,row=0)
self.btn2.grid(column=1,row=1)
self.btn3.grid(column=2,row=3)akan menghasilkan tampilan:

Perhatikan bahwa baris ke-3 tidak diperhitungkan, karena tidak berisi satupun komponen. Layout berikut sepenuhnya menggunakan Grid:

Place
IMHO, quite an uninteresting algorithm, tapi tetap sangat
bermanfaat untuk situasi di mana ukuran komponen harus permanen,
seperti untuk pembuatan jendela gambar 3D atau progress bar
dengan komponen Canvas. Tidak seperti Pack atau Grid,
pengelola-geometri ini tidak menyesuaikan ukuran komponen
induk/pemuat. Snippet berikut:
class MainFrame(Frame):
def __init__(self, master):
Frame.__init__(self, master)
self.configure(width=100,height=100)
self.btn1 = Button( self,text="Oy!",
width=10,height=2,
relief=GROOVE, borderwidth=2)
self.btn2 = Button( self,text="Hola!",
width=10,height=2,
relief=RIDGE, borderwidth=2)
self.btn3 = Button( self,text="Tabik!",
width=10,height=2,
relief=GROOVE, borderwidth=2)
self.btn1.place(x=0,y=0)
self.btn2.place(x=10,y=20)
self.btn3.place(x=20,y=40)akan menghasilkan tampilan:

Progress bar berikut hanya menggunakan Canvas
dan Canvas.Rectangle, sehingga ukurannya tidak boleh
berubah-ubah dari saat inisialisasi. Place to the rescue!

Penutup
Uraian singkat ini baru menyentuh permukaan pemrograman dengan
Tkinter, namun sudah mencakup hampir semua informasi umum yang
memungkinkan Anda mengembangkan aplikasi GUI Anda sendiri dengan
Python. Hampir semua pilihan konfigurasi dan atribut komponen telah
dicantumkan dalam dokumentasi internal Tkinter (lihat file
Tkinter.py, atau help(Tkinter) lewat shell
interpreter Python), namun dokumentasi yang diberikan pencipta
Tkinter, Fredrik Lundh, dapat pula dijadikan referensi. Manual untuk
pustaka Tk sendiri (lihat di website Scriptics Corp., atau
ActiveState Corp. selaku pengelola hak cipta Tcl/Tk terakhir) adalah
referensi paling lengkap dan definitif. Artikel selanjutnya (baru sketsa dan
perencanaan) adalah elaborasi penggunaan setiap komponen Tkinter dan pembuatan
komponen kustom.
Referensi
Lundh, Fredrik. An Introduction to Tkinter. 1999, dokumen PDF (http://www.pythonware.com/library/an-introduction-to-tkinter.htm)
ActiveState Corporation. Tcl8.4.11/Tk8.4.11 Manual. 2004, dokumen WWW (www.activestate.com)
Python Software Foundation. Python Documentation Release 2.4. 2004, dokumen WWW (www.python.org)
Lutz, Mark. Programming Python, 2nd Edition. 2001, Sebastopol: O'Reilly & Associates, Inc.
1Namespace
adalah tempat tinggal sebuah identifier, memungkinkan
beberapa entitas memiliki identifier yang serupa. Identifier
yang sama namun berada dalam namespace berbeda dapat hidup
berdampingan; setiap panggilan terhadap identifier tersebut
tidak akan ambigu, selama masing-masing berada dalam namespace-nya
sendiri dan namespace tersebut ikut dirujuk dalam
pemanggilan.
Bila ada modul selain Tkinter –
sebut saja Foo – yang juga memiliki komponen
bernama Tk, setidaknya salah-satu harus diimpor dengan
sintaks import biasa (hanya memasukkan namespace
modul ke namespace utama, bukannya memasukkan setiap
identifier dalam namespace modul ke namespace
utama) atau mengubah salah satu identifier melalui import
... as ... .
2Meskipun dapat melanggar prinsip pemisahan logika-presentasi dalam OOP, bila tidak hati-hati (atau bila Anda tidak seberapa peduli...). Sebagai contoh, lihat kode untuk IDLE.
All contents of this site are made by me,
Adhi Hargo, unless noted otherwise.
Seluruh halaman dalam situs ini dibuat oleh Adhi Hargo,
kecuali orangnya bilang sebaliknya.