Cara terbaik untuk menyusun aplikasi tkinter?

136

Berikut ini adalah struktur keseluruhan dari program tkinter python tipikal saya.

def funA():
    def funA1():
        def funA12():
            # stuff

    def funA2():
        # stuff

def funB():
    def funB1():
        # stuff

    def funB2():
        # stuff

def funC():
    def funC1():
        # stuff

    def funC2():
        # stuff


root = tk.Tk()

button1 = tk.Button(root, command=funA)
button1.pack()
button2 = tk.Button(root, command=funB)
button2.pack()
button3 = tk.Button(root, command=funC)
button3.pack()

funA funBdan funCakan memunculkan Topleveljendela lain dengan widget ketika pengguna mengklik tombol 1, 2, 3.

Saya bertanya-tanya apakah ini cara yang tepat untuk menulis program python tkinter? Tentu, ini akan berhasil walaupun saya menulis dengan cara ini, tetapi apakah ini cara terbaik? Kedengarannya bodoh tetapi ketika saya melihat kode yang ditulis orang lain, kode mereka tidak kacau dengan banyak fungsi dan sebagian besar mereka memiliki kelas.

Apakah ada struktur khusus yang harus kita ikuti sebagai praktik yang baik? Bagaimana saya harus merencanakan sebelum mulai menulis program python?

Saya tahu tidak ada yang namanya praktik terbaik dalam pemrograman dan saya juga tidak memintanya. Saya hanya ingin beberapa saran dan penjelasan agar saya tetap berada di arah yang benar karena saya belajar Python sendiri.

Chris Aung
sumber
2
Berikut ini adalah tutorial yang sangat baik tentang desain GUI tkinter, dengan beberapa contoh - python-textbok.readthedocs.org/en/latest/… Ini adalah contoh lain dengan pola desain MVC - sukhbinder.wordpress.com/2014/12/ 25 / ...
Bondolin
12
Pertanyaan ini mungkin luas, tetapi bermanfaat dan h sebagai jawaban yang relatif populer (relatif terhadap hampir semua jawaban [tkinter] lainnya). Saya mencalonkan untuk membuka kembali, karena saya melihat membuka itu lebih bermanfaat daripada menutupnya.
Bryan Oakley

Jawaban:

271

Saya menganjurkan pendekatan berorientasi objek. Ini adalah template yang saya mulai dengan:

# Use Tkinter for python 2, tkinter for python 3
import tkinter as tk

class MainApplication(tk.Frame):
    def __init__(self, parent, *args, **kwargs):
        tk.Frame.__init__(self, parent, *args, **kwargs)
        self.parent = parent

        <create the rest of your GUI here>

if __name__ == "__main__":
    root = tk.Tk()
    MainApplication(root).pack(side="top", fill="both", expand=True)
    root.mainloop()

Hal-hal penting yang perlu diperhatikan adalah:

  • Saya tidak menggunakan impor wildcard. Saya mengimpor paket sebagai "tk", yang mengharuskan saya mengawali semua perintah tk.. Ini mencegah pencemaran namespace global, ditambah lagi membuat kode itu sangat jelas ketika Anda menggunakan kelas Tkinter, kelas ttk, atau sebagian dari Anda sendiri.

  • Aplikasi utama adalah kelas . Ini memberi Anda ruang nama pribadi untuk semua panggilan balik dan fungsi pribadi Anda, dan secara umum membuatnya lebih mudah untuk mengatur kode Anda. Dalam gaya prosedural Anda harus kode top-down, mendefinisikan fungsi sebelum menggunakannya, dll. Dengan metode ini Anda tidak melakukannya karena Anda tidak benar-benar membuat jendela utama sampai langkah terakhir. Saya lebih suka mewarisi dari tk.Framehanya karena saya biasanya mulai dengan membuat bingkai, tetapi itu sama sekali tidak perlu.

Jika aplikasi Anda memiliki jendela tingkat atas tambahan, saya sarankan untuk membuat masing-masing dari mereka menjadi kelas terpisah, diwarisi dari tk.Toplevel. Ini memberi Anda semua keuntungan yang sama yang disebutkan di atas - jendela adalah atom, mereka memiliki ruang nama sendiri, dan kode terorganisir dengan baik. Plus, membuatnya mudah untuk menempatkan masing-masing ke dalam modulnya sendiri setelah kode mulai menjadi besar.

Terakhir, Anda mungkin ingin mempertimbangkan menggunakan kelas untuk setiap bagian utama antarmuka Anda. Misalnya, jika Anda membuat aplikasi dengan bilah alat, panel navigasi, bilah status, dan area utama, Anda dapat membuat masing-masing kelas tersebut. Ini membuat kode utama Anda cukup kecil dan mudah dimengerti:

class Navbar(tk.Frame): ...
class Toolbar(tk.Frame): ...
class Statusbar(tk.Frame): ...
class Main(tk.Frame): ...

class MainApplication(tk.Frame):
    def __init__(self, parent, *args, **kwargs):
        tk.Frame.__init__(self, parent, *args, **kwargs)
        self.statusbar = Statusbar(self, ...)
        self.toolbar = Toolbar(self, ...)
        self.navbar = Navbar(self, ...)
        self.main = Main(self, ...)

        self.statusbar.pack(side="bottom", fill="x")
        self.toolbar.pack(side="top", fill="x")
        self.navbar.pack(side="left", fill="y")
        self.main.pack(side="right", fill="both", expand=True)

Karena semua instans tersebut memiliki induk yang sama, induk tersebut secara efektif menjadi bagian "pengontrol" dari arsitektur model-view-controller. Jadi, misalnya, jendela utama dapat meletakkan sesuatu di bilah status dengan memanggil self.parent.statusbar.set("Hello, world"). Ini memungkinkan Anda untuk mendefinisikan antarmuka sederhana antara komponen, membantu menjaga kopling ke minimun.

Bryan Oakley
sumber
22
@Bryan Oakley apakah Anda tahu kode sampel yang baik di internet yang dapat saya pelajari strukturnya?
Chris Aung
2
Saya kedua pendekatan berorientasi objek. Namun, menahan diri untuk tidak menggunakan warisan di kelas Anda yang menyebut GUI adalah ide yang bagus, menurut pengalaman saya. Ini menawarkan Anda lebih banyak fleksibilitas jika kedua objek Tk dan Frame adalah atribut kelas yang tidak mewarisi dari apa pun. Dengan cara ini Anda dapat mengakses objek Tk dan Frame lebih mudah (dan kurang ambigu), dan menghancurkan satu tidak akan menghancurkan segala sesuatu di kelas Anda jika Anda tidak menginginkannya. Saya lupa alasan pasti mengapa ini penting dalam beberapa program, tetapi ini memungkinkan Anda untuk melakukan lebih banyak hal.
Brōtsyorfuzthrāx
1
tidak akan hanya memiliki kelas memberi Anda namespace pribadi? mengapa subclassing Frame meningkatkannya?
gcb
3
@ gcb: ya, setiap kelas akan memberi Anda namespace pribadi. Mengapa subkelas Bingkai? Saya biasanya akan membuat bingkai, jadi ini adalah kelas yang kurang untuk dikelola (subkelas Frame, vs kelas yang diwarisi dari objek, dengan bingkai sebagai atribut). Saya telah mengulangi jawabannya sedikit untuk membuatnya lebih jelas. Terima kasih untuk umpan baliknya.
Bryan Oakley
2
@madtyn: tidak perlu menyimpan referensi parent, kecuali Anda akan menggunakannya nanti. Saya tidak menyimpannya karena tidak ada kode dalam contoh saya yang mengharuskannya disimpan.
Bryan Oakley
39

Menempatkan setiap jendela tingkat atas Anda ke dalam kelas terpisah itu sendiri memberi Anda penggunaan kembali kode dan organisasi kode yang lebih baik. Setiap tombol dan metode yang relevan yang ada di jendela harus didefinisikan di dalam kelas ini. Ini sebuah contoh (diambil dari sini ):

import tkinter as tk

class Demo1:
    def __init__(self, master):
        self.master = master
        self.frame = tk.Frame(self.master)
        self.button1 = tk.Button(self.frame, text = 'New Window', width = 25, command = self.new_window)
        self.button1.pack()
        self.frame.pack()
    def new_window(self):
        self.newWindow = tk.Toplevel(self.master)
        self.app = Demo2(self.newWindow)

class Demo2:
    def __init__(self, master):
        self.master = master
        self.frame = tk.Frame(self.master)
        self.quitButton = tk.Button(self.frame, text = 'Quit', width = 25, command = self.close_windows)
        self.quitButton.pack()
        self.frame.pack()
    def close_windows(self):
        self.master.destroy()

def main(): 
    root = tk.Tk()
    app = Demo1(root)
    root.mainloop()

if __name__ == '__main__':
    main()

Lihat juga:

Semoga itu bisa membantu.

alecxe
sumber
6

Ini bukan struktur yang buruk; itu akan bekerja dengan baik. Namun, Anda harus memiliki fungsi dalam fungsi untuk melakukan perintah ketika seseorang mengklik tombol atau sesuatu

Jadi yang dapat Anda lakukan adalah menulis kelas untuk ini kemudian memiliki metode di kelas yang menangani perintah untuk klik tombol dan semacamnya.

Ini sebuah contoh:

import tkinter as tk

class Window1:
    def __init__(self, master):
        pass
        # Create labels, entries,buttons
    def button_click(self):
        pass
        # If button is clicked, run this method and open window 2


class Window2:
    def __init__(self, master):
        #create buttons,entries,etc

    def button_method(self):
        #run this when button click to close window
        self.master.destroy()

def main(): #run mianloop 
    root = tk.Tk()
    app = Window1(root)
    root.mainloop()

if __name__ == '__main__':
    main()

Biasanya program tk dengan banyak jendela adalah beberapa kelas besar dan dalam __init__semua entri, label dll dibuat dan kemudian masing-masing metode adalah untuk menangani peristiwa klik tombol

Sebenarnya tidak ada cara yang tepat untuk melakukannya, apa pun yang bekerja untuk Anda dan menyelesaikan pekerjaan selama dapat dibaca dan Anda dapat dengan mudah menjelaskannya karena jika Anda tidak dapat dengan mudah menjelaskan program Anda, mungkin ada cara yang lebih baik untuk melakukannya .

Lihatlah Thinking in Tkinter .

Serial
sumber
3
"Thinking in Tkinter" mendukung impor global, yang menurut saya merupakan saran yang sangat buruk.
Bryan Oakley
1
Itu benar saya tidak menyarankan Anda menggunakan global hanya beberapa struktur meto kelas utama Anda benar :)
Serial
2

OOP harus menjadi pendekatan dan frameharus menjadi variabel kelas dan bukan variabel instan .

from Tkinter import *
class App:
  def __init__(self, master):
    frame = Frame(master)
    frame.pack()
    self.button = Button(frame, 
                         text="QUIT", fg="red",
                         command=frame.quit)
    self.button.pack(side=LEFT)
    self.slogan = Button(frame,
                         text="Hello",
                         command=self.write_slogan)
    self.slogan.pack(side=LEFT)
  def write_slogan(self):
    print "Tkinter is easy to use!"

root = Tk()
app = App(root)
root.mainloop()

masukkan deskripsi gambar di sini

Referensi: http://www.python-course.eu/tkinter_buttons.php

Trevor
sumber
2
Anda hanya dapat menggunakan TKinterpada Python 2. Saya akan merekomendasikan menggunakan tkinteruntuk Python 3. Saya juga akan menempatkan tiga baris kode terakhir di bawah main()fungsi dan menyebutnya di akhir program. Saya pasti akan menghindari penggunaan from module_name import *karena mencemari namespace global dan dapat mengurangi keterbacaan.
Zac
1
Bagaimana Anda bisa tahu perbedaan antara button1 = tk.Button(root, command=funA)dan button1 = ttk.Button(root, command=funA)jika tkintermodul ekstensi juga sedang diimpor? Dengan *sintaks, kedua baris kode akan muncul button1 = Button(root, command=funA). Saya tidak akan merekomendasikan menggunakan sintaks itu.
Zac
0

Pengorganisasian aplikasi Anda menggunakan kelas memudahkan Anda dan orang lain yang bekerja dengan Anda untuk men-debug masalah dan meningkatkan aplikasi dengan mudah.

Anda dapat dengan mudah mengatur aplikasi Anda seperti ini:

class hello(Tk):
    def __init__(self):
        super(hello, self).__init__()
        self.btn = Button(text = "Click me", command=close)
        self.btn.pack()
    def close():
        self.destroy()

app = hello()
app.mainloop()
muyustan
sumber
-2

Mungkin cara terbaik untuk mempelajari bagaimana menyusun program Anda adalah dengan membaca kode orang lain, terutama jika itu adalah program besar yang telah dikontribusikan banyak orang. Setelah melihat kode banyak proyek, Anda harus mendapatkan ide tentang gaya konsensus yang seharusnya.

Python, sebagai bahasa, adalah spesial karena ada beberapa panduan kuat tentang bagaimana Anda harus memformat kode Anda. Yang pertama adalah apa yang disebut "Zen Python":

  • Cantik lebih baik dari yang jelek.
  • Eksplisit lebih baik daripada implisit.
  • Sederhana lebih baik daripada kompleks.
  • Kompleks lebih baik daripada rumit.
  • Flat lebih baik daripada bersarang.
  • Jarang lebih baik daripada padat.
  • Jumlah keterbacaan diperhitungkan.
  • Kasus khusus tidak cukup istimewa untuk melanggar aturan.
  • Meskipun kepraktisan mengalahkan kemurnian.
  • Kesalahan tidak boleh terjadi secara diam-diam.
  • Kecuali secara eksplisit dibungkam.
  • Dalam menghadapi ambiguitas, tolak godaan untuk menebak.
  • Harus ada satu - dan lebih disukai hanya satu - cara yang jelas untuk melakukannya.
  • Meskipun demikian mungkin tidak jelas pada awalnya kecuali Anda orang Belanda.
  • Sekarang lebih baik daripada tidak sama sekali.
  • Meskipun tidak pernah sering lebih baik daripada yang tepat sekarang.
  • Jika implementasinya sulit dijelaskan, itu ide yang buruk.
  • Jika implementasinya mudah dijelaskan, mungkin itu ide yang bagus.
  • Namespaces adalah salah satu ide bagus - mari kita lakukan lebih dari itu!

Pada tingkat yang lebih praktis, ada PEP8 , panduan gaya untuk Python.

Dengan semua itu dalam pikiran, saya akan mengatakan bahwa gaya kode Anda tidak benar-benar cocok, terutama fungsi bersarang. Temukan cara untuk meratakannya, baik dengan menggunakan kelas atau memindahkannya ke modul terpisah. Ini akan membuat struktur program Anda lebih mudah dipahami.

Inbar Rose
sumber
12
-1 untuk menggunakan Zen Python. Meskipun ini semua saran yang bagus, itu tidak langsung menjawab pertanyaan yang diajukan. Keluarkan paragraf terakhir dan jawaban ini dapat diterapkan untuk hampir setiap pertanyaan python di situs ini. Itu bagus, saran positif, tetapi tidak menjawab pertanyaan.
Bryan Oakley
1
@BryanOakley Saya tidak setuju dengan Anda tentang itu. Ya, Zen dari Python luas dan dapat digunakan untuk menjawab banyak pertanyaan. Dia menyebutkan dalam paragraf terakhir untuk memilih kelas atau menempatkan fungsi dalam modul terpisah. Dia juga menyebutkan PEP8, panduan gaya untuk Python, dengan referensi untuk itu. Meskipun bukan jawaban langsung, saya pikir jawaban ini kredibel dalam kenyataan bahwa ia menyebutkan banyak rute berbeda yang dapat diambil. Itu hanya pendapat saya
Zac
1
Saya datang ke sini mencari jawaban untuk pertanyaan khusus ini. Bahkan untuk pertanyaan terbuka, saya tidak bisa melakukan apa pun dengan respons ini. -1'd dari saya juga.
Jonathan
Tidak mungkin, pertanyaannya adalah tentang menyusun aplikasi tkinter , tidak tentang pedoman styling / coding / zen. Semudah mengutip @Arbiter "Meskipun bukan jawaban langsung", jadi, BUKAN jawaban. Ini seperti "mungkin ya dan mungkin tidak", dengan zen didahulukan.
m3nda
-7

Saya pribadi tidak menggunakan pendekatan yang berorientasi pada keberatan, sebagian besar karena itu a) hanya menghalangi; b) Anda tidak akan pernah menggunakannya kembali sebagai modul.

tetapi sesuatu yang tidak dibahas di sini, adalah Anda harus menggunakan threading atau multiprocessing. Selalu. jika tidak aplikasi Anda akan mengerikan.

lakukan saja tes sederhana: mulai jendela, lalu ambil beberapa URL atau apa pun. perubahan adalah UI Anda tidak akan diperbarui saat permintaan jaringan sedang terjadi. Artinya, jendela aplikasi Anda akan rusak. tergantung pada OS yang Anda gunakan, tetapi sebagian besar waktu, itu tidak akan redraw, apa pun yang Anda seret jendela akan terpampang di atasnya, sampai proses kembali ke mainlo TK.

gcb
sumber
4
Apa yang Anda katakan tidak benar. Saya telah menulis banyak aplikasi berbasis tk, baik pribadi maupun komersial, dan hampir tidak pernah harus menggunakan utas. Utas ada di tempatnya, tetapi tidak benar bahwa Anda harus menggunakannya saat menulis program tkinter. Jika Anda memiliki fungsi runnng yang lama, Anda mungkin perlu utas atau multiprosesing, tetapi ada banyak, banyak jenis program yang bisa Anda tulis yang tidak memerlukan utas.
Bryan Oakley
Saya pikir jika Anda mengulangi jawaban Anda menjadi sedikit lebih jelas tentang itu, itu akan menjadi jawaban yang lebih baik. Ini juga akan sangat membantu untuk memiliki contoh kanonik menggunakan utas dengan tkinter.
Bryan Oakley
tidak peduli menjadi jawaban terbaik di sini karena itu agak di luar topik. tetapi perlu diingat bahwa memulai dengan threading / multip sangat mudah. jika Anda harus menambahkan nanti, itu adalah pertempuran yang hilang. dan saat ini, sama sekali tidak ada aplikasi yang tidak akan pernah berbicara dengan jaringan. dan bahkan jika Anda mengabaikan dan berpikir 'saya hanya punya sedikit disk IO', besok klien Anda memutuskan bahwa file akan hidup di NFS dan Anda sedang menunggu jaringan IO dan aplikasi Anda tampaknya sudah mati.
gcb
2
@ erm3nda: "setiap aplikasi yang terhubung dengan jaringan atau melakukan penulisan IO akan jauh lebih cepat menggunakan utas atau subproses" - itu tidak benar. Threading tidak harus membuat program Anda lebih cepat, dan dalam beberapa kasus akan membuatnya lebih lambat. Dalam pemrograman GUI, alasan utama untuk menggunakan utas adalah untuk dapat menjalankan beberapa kode yang jika tidak akan memblokir GUI.
Bryan Oakley
2
@ erm3nda: tidak, saya tidak mengatakan benang tidak diperlukan sama sekali . Mereka pasti dibutuhkan (baik, utas atau multiprosesor) untuk banyak hal. Hanya saja ada kelas aplikasi GUI yang sangat besar di mana tkinter cocok tetapi di mana utas tidak diperlukan. Dan ya, "installer, notes, dan alat mudah lainnya" termasuk dalam kategori itu. Dunia terdiri dari lebih dari "alat-alat mudah" ini daripada hal-hal seperti kata, excel, photoshop, dll. Plus, ingatlah bahwa konteks di sini adalah tkinter . Tkinter biasanya tidak digunakan untuk aplikasi yang sangat besar dan kompleks.
Bryan Oakley