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:

  1. Baris pertama mengimpor semua modul dalam Tkinter, sehingga kita dapat memanggil Tk alih-alih Tkinter.Tk (Semua komponen dasar Tkinter berada dalam namespace1 Tkinter, sehingga impor namespace seperti ini akan banyak meringkas kode).

  2. 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 mainloop tersebut hanya dijalankan saat jendela utama ditutup = saat aplikasi selesai), sehingga meskipun kita dapat meringkas dua baris terakhir menjadi Tk().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:

  1. 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.

  2. 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 btn1 sudah meminta sisi kiri luas ruang, dan btn2 sudah meminta sisi atas dari (luas ruang – luas btn1), maka komponen selanjutnya hanya dapat menempati ruang kosong di bawah btn2 (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.