Il modo migliore per strutturare un'applicazione tkinter?


136

Quella che segue è la struttura generale del mio tipico programma tkinter python.

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 funBe funCfarà apparire un'altra Toplevelfinestra con i widget quando l'utente fa clic sul pulsante 1, 2, 3.

Mi chiedo se questo è il modo giusto di scrivere un programma python tkinter? Certo, funzionerà anche se scrivo in questo modo, ma è il modo migliore? Sembra stupido ma quando vedo i codici scritti da altre persone, il loro codice non è incasinato con un sacco di funzioni e per lo più hanno classi.

Esiste una struttura specifica che dovremmo seguire come buona pratica? Come devo pianificare prima di iniziare a scrivere un programma Python?

So che non esiste una buona pratica in programmazione e non lo sto chiedendo neanche. Voglio solo alcuni consigli e spiegazioni per tenermi nella giusta direzione mentre sto imparando Python da solo.


2
Ecco un eccellente tutorial sulla progettazione della GUI di tkinter , con un paio di esempi - python-textbok.readthedocs.org/en/latest/… Ecco un altro esempio con un modello di progettazione MVC - sukhbinder.wordpress.com/2014/12/12/ 25 /…
Bondolin,

12
Questa domanda può essere ampia, ma è utile eh come risposta relativamente popolare (rispetto a quasi tutte le altre risposte [tkinter]). Sto nominando per riaprire, poiché vedo che averlo aperto è più utile che averlo chiuso.
Bryan Oakley,

Risposte:


271

Sostengo un approccio orientato agli oggetti. Questo è il modello con cui inizio:

# 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()

Le cose importanti da notare sono:

  • Non utilizzo un'importazione con caratteri jolly. Importo il pacchetto come "tk", che richiede il prefisso di tutti i comandi tk.. Ciò previene l'inquinamento dello spazio dei nomi globale, inoltre rende il codice completamente ovvio quando si utilizzano classi Tkinter, classi tk o alcune delle proprie.

  • L'applicazione principale è una classe . Questo ti dà uno spazio dei nomi privato per tutti i tuoi callback e funzioni private, e in generale semplifica l'organizzazione del tuo codice. In uno stile procedurale devi scrivere il codice dall'alto verso il basso, definire le funzioni prima di usarle, ecc. Con questo metodo non lo fai poiché non crei effettivamente la finestra principale fino all'ultimo passaggio. Preferisco ereditare tk.Framesolo perché in genere inizio creando un frame, ma non è assolutamente necessario.

Se la tua app ha finestre di livello superiore aggiuntive, ti consiglio di rendere ciascuna di queste una classe separata, ereditando da tk.Toplevel. Questo ti dà tutti gli stessi vantaggi sopra menzionati: le finestre sono atomiche, hanno il loro spazio dei nomi e il codice è ben organizzato. Inoltre, semplifica l'inserimento di ciascuno nel proprio modulo una volta che il codice inizia a diventare grande.

Infine, potresti prendere in considerazione l'utilizzo delle classi per ogni parte principale della tua interfaccia. Ad esempio, se stai creando un'app con una barra degli strumenti, un riquadro di navigazione, una barra di stato e un'area principale, puoi creare ognuna di quelle classi. Questo rende il tuo codice principale abbastanza piccolo e facile da capire:

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)

Dato che tutte queste istanze condividono un genitore comune, il genitore diventa effettivamente la parte "controller" di un'architettura modello-view-controller. Quindi, ad esempio, la finestra principale potrebbe posizionare qualcosa sulla barra di stato chiamando self.parent.statusbar.set("Hello, world"). Ciò consente di definire una semplice interfaccia tra i componenti, contribuendo a mantenere l'accoppiamento con un minimo.


22
@Bryan Oakley conosci qualche buon codice di esempio su Internet che posso studiare la loro struttura?
Chris Aung,

2
Secondo, l'approccio orientato agli oggetti. Tuttavia, astenermi dall'utilizzare l'ereditarietà nella tua classe che chiama la GUI è una buona idea, secondo la mia esperienza. Offre maggiore flessibilità se gli oggetti Tk e Frame sono attributi di una classe che non eredita da nulla. In questo modo puoi accedere agli oggetti Tk e Frame più facilmente (e meno ambiguamente), e distruggere uno non distruggerà tutto nella tua classe se non lo desideri. Ho dimenticato il motivo esatto per cui questo è vitale in alcuni programmi, ma ti consente di fare più cose.
Brōtsyorfuzthrāx,

1
semplicemente avere una classe non ti darà uno spazio dei nomi privato? perché migliorare la sottoclasse del Frame?
gcb,

3
@gcb: sì, qualsiasi classe ti darà uno spazio dei nomi privato. Perché sottoclasse un frame? In genere ho intenzione di creare comunque un frame, quindi è una classe in meno da gestire (sottoclasse di Frame, rispetto a una classe che eredita dall'oggetto, con un frame come attributo). Ho riformulato leggermente la risposta per renderla più chiara. Grazie per il feedback.
Bryan Oakley,

2
@madtyn: non è necessario salvare un riferimento parent, a meno che non lo utilizzerai in seguito. Non l'ho salvato perché nessuno dei codici nel mio esempio ha richiesto che fosse salvato.
Bryan Oakley,

39

Mettere ciascuna delle finestre di livello superiore nella propria classe separata ti consente di riutilizzare il codice e una migliore organizzazione del codice. Eventuali pulsanti e metodi pertinenti presenti nella finestra devono essere definiti all'interno di questa classe. Ecco un esempio (preso da qui ):

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()

Vedi anche:

Spero che aiuti.


6

Questa non è una cattiva struttura; funzionerà bene. Tuttavia, devi avere funzioni in una funzione per eseguire comandi quando qualcuno fa clic su un pulsante o qualcosa del genere

Quindi quello che potresti fare è scrivere classi per questi quindi avere metodi nella classe che gestiscono i comandi per i clic sui pulsanti e così via.

Ecco un esempio:

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()

Di solito i programmi tk con più finestre sono più classi grandi e in __init__tutte le voci, le etichette ecc. Vengono create e quindi ogni metodo è quello di gestire gli eventi clic sui pulsanti

Non c'è davvero un modo giusto per farlo, qualunque cosa funzioni per te e svolga il lavoro finché è leggibile e puoi spiegarlo facilmente perché se non riesci a spiegare facilmente il tuo programma, probabilmente c'è un modo migliore per farlo .

Dai un'occhiata a Thinking in Tkinter .


3
"Thinking in Tkinter" sostiene le importazioni globali, che ritengo sia un pessimo consiglio.
Bryan Oakley,

1
È vero che non ti suggerisco di usare i globi solo alcune delle principali strutture methos della classe che hai ragione :)
Seriale

2

OOP dovrebbe essere l'approccio e framedovrebbe essere una variabile di classe anziché una variabile di istanza .

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()

inserisci qui la descrizione dell'immagine

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


2
Puoi usare solo TKintersu Python 2. Consiglierei di usare tkinterper Python 3. Metterei anche le ultime tre righe di codice in una main()funzione e la chiamerei alla fine del programma. Eviterei sicuramente l' uso from module_name import *poiché inquina lo spazio dei nomi globale e può ridurre la leggibilità.
Zac

1
Come hai potuto distinguere tra button1 = tk.Button(root, command=funA)e button1 = ttk.Button(root, command=funA)se anche il tkintermodulo di estensione fosse stato importato? Con la *sintassi, entrambe le righe di codice sembrano essere button1 = Button(root, command=funA). Non consiglierei di usare quella sintassi.
Zac,

0

Organizzare la tua applicazione usando class rendi facile a te e agli altri che lavorano con te il debug dei problemi e migliorare facilmente l'app.

Puoi organizzare facilmente la tua applicazione in questo modo:

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()

-2

Probabilmente il modo migliore per imparare a strutturare il tuo programma è leggere il codice di altre persone, specialmente se si tratta di un grande programma a cui molte persone hanno contribuito. Dopo aver esaminato il codice di molti progetti, dovresti avere un'idea di come dovrebbe essere lo stile di consenso.

Python, come lingua, è speciale in quanto vi sono alcune linee guida su come formattare il codice. Il primo è il cosiddetto "Zen of Python":

  • Bello è meglio che brutto.
  • Esplicito è meglio che implicito.
  • Semplice è meglio di complesso.
  • Complesso è meglio che complicato.
  • Flat è meglio di nidificato.
  • Sparse è meglio che denso.
  • La leggibilità conta.
  • I casi speciali non sono abbastanza speciali da infrangere le regole.
  • Sebbene la praticità superi la purezza.
  • Gli errori non dovrebbero mai passare silenziosamente.
  • A meno che non sia esplicitamente messo a tacere.
  • Di fronte all'ambiguità, rifiuta la tentazione di indovinare.
  • Dovrebbe esserci uno - e preferibilmente solo un - modo obsoleto di farlo.
  • Anche se in quel modo all'inizio potrebbe non essere ovvio a meno che tu non sia olandese.
  • Adesso è meglio che mai.
  • Anche se non è spesso meglio di destra ora.
  • Se l'implementazione è difficile da spiegare, è una cattiva idea.
  • Se l'implementazione è facile da spiegare, potrebbe essere una buona idea.
  • Gli spazi dei nomi sono un'ottima idea per suonare il clacson: facciamo di più!

A un livello più pratico, c'è PEP8 , la guida di stile per Python.

Con quelli in mente, direi che il tuo stile di codice non si adatta davvero, in particolare le funzioni nidificate. Trova un modo per appiattire quelli, usando le classi o spostandole in moduli separati. Ciò renderà la struttura del tuo programma molto più facile da capire.


12
-1 per l'utilizzo dello Zen di Python. Sebbene sia un buon consiglio, non affronta direttamente la domanda che è stata posta. Prendi l'ultimo paragrafo e questa risposta potrebbe applicarsi a quasi tutte le domande di Python su questo sito. È un consiglio positivo e positivo, ma non risponde alla domanda.
Bryan Oakley,

1
@BryanOakley Non sono d'accordo con te su questo. Sì, lo Zen di Python è ampio e può essere usato per rispondere a molte domande. Nel paragrafo finale ha menzionato la scelta delle classi o l'inserimento delle funzioni in moduli separati. Ha anche citato PEP8, una guida di stile per Python, con riferimenti ad esso. Sebbene non sia una risposta diretta, penso che questa risposta sia credibile nel fatto che menziona molti percorsi diversi che possono essere intrapresi. Questa è solo la mia opinione
Zac,

1
Sono venuto qui in cerca di risposte a questa domanda specifica. Anche per una domanda a risposta aperta, non posso fare nulla con questa risposta. -1 anche da me.
Jonathan,

In nessun caso, la domanda sta per strutturare un'app tkinter , niente sulle linee guida di styling / coding / zen. Facile come citare @Arbiter "Anche se non è una risposta diretta", quindi NON è una risposta. Questo è come "forse sì e forse no", con zen anteposto.
m3nda,

-7

Personalmente non uso l'approccio orientato agli oggetti, soprattutto perché a) mi intrometto; b) non lo riutilizzerai mai come modulo.

ma qualcosa che non è discusso qui, è che è necessario utilizzare il threading o il multiprocessing. Sempre. altrimenti la tua applicazione sarà terribile.

fai un semplice test: avvia una finestra, quindi recupera un po 'di URL o qualsiasi altra cosa. le modifiche apportate all'interfaccia utente non verranno aggiornate durante la richiesta di rete. Ciò significa che la finestra dell'applicazione verrà rotta. dipende dal sistema operativo in cui ti trovi, ma la maggior parte delle volte non verrà ridisegnato, tutto ciò che trascini sulla finestra verrà intonacato su di esso, fino a quando il processo non tornerà al mainloop TK.


4
Quello che dici non è semplicemente vero. Ho scritto moltissime applicazioni basate su tk, sia personali che commerciali, e quasi mai ho dovuto usare i thread. I thread hanno il loro posto, ma semplicemente non è vero che devi usarli quando scrivi i programmi di tkinter. Se hai funzioni di esecuzione lunghe potresti aver bisogno di thread o multiprocessing, ma ci sono molti, molti tipi di programmi che puoi scrivere che non hanno bisogno di thread.
Bryan Oakley,

Penso che se riformulassi la tua risposta per renderla un po 'più chiara, sarebbe una risposta migliore. Sarebbe anche di grande aiuto avere un esempio canonico di utilizzo dei thread con tkinter.
Bryan Oakley,

non mi importava di essere la migliore risposta qui perché è un po 'fuori tema. ma tieni presente che iniziare con thread / multiplo è molto semplice. se devi aggiungere più tardi, è una battaglia persa. e al giorno d'oggi, non esiste assolutamente alcuna applicazione che non parlerà mai alla rete. e anche se ignori e pensi "ho solo un piccolo I / O del disco", domani il tuo client decide che il file vivrà su NFS e stai aspettando l'IO della rete e la tua app sembra morta.
gcb,

2
@ erm3nda: "ogni app connessa alla rete o che sta scrivendo IO sarà molto più veloce usando thread o sottoprocessi" - semplicemente non è vero. Il threading non renderà necessariamente il tuo programma più veloce e in alcuni casi lo rallenterà. Nella programmazione della GUI, il motivo principale per usare i thread è quello di poter eseguire del codice che altrimenti bloccherebbe la GUI.
Bryan Oakley,

2
@ erm3nda: no, io non dico le discussioni non sono necessari a tutti . Sono sicuramente necessari (beh, thread o multiprocessing) per molte cose. È solo che esiste una classe molto ampia di applicazioni GUI in cui tkinter è adatto ma in cui i thread semplicemente non sono necessari. E sì, "programmi di installazione, blocchi note e altri semplici strumenti" rientrano in quella categoria. Il mondo è composto da più di questi "strumenti facili" di quanto non lo sia da cose come Word, Excel, Photoshop, ecc. Inoltre, ricorda che il contesto qui è tkinter . Tkinter non viene in genere utilizzato per applicazioni molto grandi e complesse.
Bryan Oakley,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.