dal 2015 - visita n. 970
TkInter
TkInter

 

2. Basi della GUI


2.1. Esempio Base-01

In modalità Linea di Comando è stato abbastanza semplice costruire un esempio contenente i 4 compiti fondamentali per la gestione dalla UI. Per realizzare altrettanto in modalità grafica (GUI) occorrerà invece procedere per gradi. Per il momento vediamo come attivare una finestra Tkinter.

#!/usr/bin/env python
# -*- coding: iso8859-15 -*-
# tkex01.py - versione 1.0

from Tkinter import *                            # (10)

# Programma Principale
def main():
    finestra = Tk()                              # (20)
    finestra.title("Tk Prova")                   # (30)
    finestra.mainloop()                          # (90)

main()

Dei quattro compiti GUI fondamentali discussi in precedenza, il seguente programma ne realizza solo uno: il ciclo degli eventi.

  1. L'istruzione (10) importa il modulo Tkinter, affinché sia disponibile per l'uso. In questa particolare forma di importazione, con l'asterisco, non si ha la necessità di qualificare ciò che si usa del modulo Tkinter.
  2. L'istruzione (20) crea la finestra principale di più alto livello (toplevel). Tecnicamente si dice che si crea un'istanza della classe Tkinter.Tk. Questa finestra, principale, è la componente GUI di massimo livello per qualsiasi applicazione Tkinter. Per convenzione la finestra principale è detta radice (root).
  3. L'istruzione (30) imposta il titolo della finestra, in sua assenza verrebbe impostato un titolo di default ("tk"). Viene utilizzato il metodo title del widget.
  4. L'istruzione (90) esegue il ciclo principale, ossia il ciclo degli eventi, come metodo dell'oggetto finestra. Quando il ciclo principale si esegue esso attende gli eventi che accadono nell'oggetto radice. Se avviene un evento allora esso viene gestito e il ciclo prosegue, continuando la sua attesa per il successivo evento. L'esecuzione del ciclo continua fino a quando nella finestra radice non si verifica un evento destroy (distruggi), un tale evento chiude la finestra. Quando la radice è distrutta la finestra si chiude e il ciclo degli eventi ha termine.

Quando si esegue il programma, grazie alla libreria Tk si vede la finestra principale automaticamente decorata con i widget necessari per minimizzare, massimizzare e chiudere la finestra. Possono essere provati per verificarne il funzionamento previsto, attivando il widget di chiusura (close, rappresentato dalla x nel riquadro alla destra della barra del titolo) si genera un evento destroy. Tale evento termina il ciclo principale, e poiché non ci sono altre istruzioni dopo finestra.mainloop() il programma termina perchè non ha altri compiti da eseguire.




2.2. Esempio Base-02

Nella programmazione GUI l'aspetto con cui ci si presenta all'utente è di importanza fondamentale, altrimenti tanto varrebbe usare la Linea di Comando ed una semplice UI.
Alcuni dei widget, pur essendo visibili, non hanno una funzione attiva, ma servono semplicemente da contenitori di altri widget attivi, realizzando di fatto una sorta di vassoio portaoggetti. Il contenitore più usato è sicuramente il quadro (frame), mentre come supporto per disegni ed immagini si preferisce la tela (canvas).
Nell'esempio seguente sono state aggiunte due istruzioni: la (40) che crea il frame quadro1 (figlio di finestra), e la (42) che fissa le modalità con cui posizionare tale elemento all'interno di finestra.

#!/usr/bin/env python
# -*- coding: iso8859-15 -*-
# tkex02.py - versione 1.0

from Tkinter import *                            # (10)

# Programma Principale
def main():
    finestra = Tk()                              # (20)
    finestra.title("Tk Prova")                   # (30)
    quadro1 = Frame(finestra)                    # (40)
    quadro1.pack()                               # (42)
    finestra.mainloop()                          # (90)

main()

I quadri vengono messi a disposizione da Tkinter mediante una classe chiamata Frame. Un'espressione come la (40) crea un'istanza della classe Frame (ossia crea un quadro), e associa tale istanza con il suo genitore (parent), finestra. Di fatto l'espressione aggiunge un quadro figlio al componente finestra, creando un contenitore in cui si potranno mettere dei widget.
L'istruzione successiva (42) impacchetta (pack) quadro1, creando una relazione visuale fra una componente GUI e il suo genitore. Questa operazione è obbligatoria e Tkinter supporta tre gestori di geometria (geometry manager): pack, grid e place. Ognuno dei tre ha le sue modalità operative, i suoi punti di forza ed i suoi difetti. Per semplicità inizialmente useremo pack e per questo si utilizzerà il termine di impacchettamento.
L'interno di un contenitore (per es. frame) si chiama cavità, la cavità è elastica e si adatterà (di default) a ciò che in essa è contenuto. Ecco spiegato il motivo per cui la finestra risultante in questo secondo esempio è piccolissima: finestra contiene quadro1 che a sua volta non contiene nulla, poichè quadro1 è elastico si restringe al massimo trascinando con se finestra a cui è legata. Nel programma precedente, invece, poiché non era stato messo nulla in finestra essa veniva presentata con le sue dimensioni predefinite.




2.3. Esempio Base-03

Proviamo adesso ad aggiungere nel contenitore quadro1 un pulsante, ossia il widget Button. Per il momento senza associarlo ad alcuna azione specifica, ma esplorando solo la possibilità di definirne alcune proprietà. I widget hanno molte proprietà che vengono conservate in un dizionario locale al widget stesso, il quale costituisce lo spazio dei nomi del widget.

#!/usr/bin/env python
# -*- coding: iso8859-15 -*-
# tkex03.py - versione 1.0

from Tkinter import *                            # (10)

# Programma Principale
def main():
    finestra = Tk()                              # (20)
    finestra.title("Tk Prova")                   # (30)
    quadro1 = Frame(finestra)                    # (40)
    quadro1.pack()                               # (42)
    puls1 = Button(quadro1)                      # (50)
    puls1["borderwidth"] = 1                     # (52)
    puls1["text"] = "Evviva Python+Tkinter !"    # (54)
    puls1["background"] = "pink"                 # (56)
    puls1.pack()                                 # (58)
    finestra.mainloop()                          # (90)

main()

Il pulsante puls1 viene creato con l'istruzione (50) ed è figlio di quadro1. Con le istruzioni (52, 54, 56) si assegnano le proprietà borderwidth (spessore del bordo), text (scritta) e background (colore di sfondo). L'istruzione (58) infine provvede ad impacchettare il pulsante dentro quadro1. Eseguendo il programma si nota che cliccando sul pulsante si ottiene il classico movimento dello stesso, ma non accade nulla perchè non gli è stato associato nessun gestore di eventi. Per il momento si deve chiudere la finestra attivando l'icona x a destra sulla barra del titolo. Si nota ancora come quadro1 abbia adattato elasticamente le sue dimensioni a puls1.




2.4. Esempio Base-04

Aggiungiamo ulteriori dettagli. L'istruzione (41) colora di giallo lo sfondo del contenitore quadro1, mentre le istruzioni (60, 62) introducono un nuovo widget Label con il quale è possibile apporre delle semplici scritte. Le istruzioni (70, 72) aggiungono infine un secondo pulsante le cui proprietà vengono specificate subito, diversamente da quanto fatto con puls1 ed analogamente a quanto fatto con label1 (60). Nella (72) l'impacchettamento non avviene con i parametri predefiniti, ma specificando la posizione (side) del widget e la spaziatura orizzontale (padx) e verticale (pady).

#!/usr/bin/env python
# -*- coding: iso8859-15 -*-
# tkex04.py - versione 1.0

from Tkinter import *                            # (10)

# Programma Principale
def main():
    finestra = Tk()                              # (20)
    finestra.title("Tk Prova")                   # (30)
    quadro1 = Frame(finestra)                    # (40)
    quadro1["background"] = "yellow"             # (41)
    quadro1.pack()                               # (42)
    puls1 = Button(quadro1)                      # (50)
    puls1["borderwidth"] = 1                     # (52)
    puls1["text"] = "Evviva Python+Tkinter !"    # (54)
    puls1["background"] = "pink"                 # (56)
    puls1.pack()                                 # (58)
    label1 = Label(quadro1, text="Ciao")         # (60)
    label1.pack()                                # (62)
    puls2 = Button(quadro1, text="Uscita",       # (70)
            borderwidth=1, background="red",
            command=quadro1.quit)
    puls2.pack(side="right", padx=5, pady=10)    # (72)
    finestra.mainloop()                          # (90)

main()

Negli esempi precedenti la preparazione di un pulsante è stata fatta in due fasi: creazione del pulsante, definizione delle proprietà. E' però possibile specificare le proprietà del pulsante nello stesso momento della sua creazione (70). Il widget Button, come tutti i widget, si aspetta come suo primo argomento il genitore, ma non si tratta di un parametro inteso come parola chiave, bensì di un parametro posizionale; dopo di esso, se si vuole, è possibile aggiungere uno o più argomenti parole chiave per specificare le proprietà del widget (70, 60).
Tra le proprietà di puls2 (70) notiamo command=quadro1.quit che associa al pulsante il gestore di evento per uscire dal programma. Non è tantissimo, ma con questo troviamo, sia pure disordinatamente la presenza dei 4 compiti da realizzare in una interfaccia GUI: 1) aspetto, 2) funzionamento, 3) collegamento, 4) ciclo di attesa.




2.5. Usare le Classi

Molto spesso un programma Tkinter inizia il suo sviluppo come uno script elementare, con tutto il codice in linea come abbiamo fatto nei precedenti esempi. Poi, man mano che si intuiscono aspetti nuovi del problema, il programma cresce e dopo un po' ci si trova a gestire molto codice. Probabilmente si sono introdotte delle variabili globali, forse molte variabili globali. A questo punto il programma comincia ad essere difficile da comprendere o da modificare. Quando questo accade è il momento di ristrutturare il programma mediante l'uso delle classi.
Chi ha una buona confidenza con il concetto di classe ed ha le idee chiare, fin dal principio, sull'aspetto generale del programma può scegliere di strutturarlo fin dall'inizio mediante le classi . Di fatto saranno l'esperienza, le circostanze e le preferenze individuali a determinare il tipo di strumenti da usare. È meglio procedere come meglio ci si sente, e soprattutto, a prescindere dal metodo prescelto, non si deve temere l'eventualità di procedere a sostanziale ristrutturazione in caso di necessità.

Un programma organizzato in classi è probabilmente più facile da capire, specialmente se ha dimensioni non trascurabili. Ma la considerazione più importante è forse che l'uso delle classi consente di evitare le variabili globali. Durante la crescita di un programma in sviluppo a un certo punto occorre quasi sempre condividere informazioni fra diversi gestori di eventi. Ciò è possibile mediante l'uso di variabili globali, ma si tratta di una tecnica non molto elegante ed utile per piccoli programmi. Un modo migliore è quello di usare le istanze (mediante le variabili self.), e quindi risulta necessario attribuire all'applicazione una struttura con le classi.




2.6. Esempio Base-05

Riprendiamo l'ultima versione del programma Base e riscriviamola utilizzando le classi. Aggiungiamo una classe denominata AppBase e spostiamo buona parte del codice dal main all'interno del metodo costruttore __init__, istruzione (15).
Dopo questa ristrutturazione si realizzano tre cose:

  1. Si definisce una classe (AppBase) che realizza l'aspetto della GUI e le cose che si desidera che la GUI faccia. Tutto il codice necessario si trova nel corpo del metodo costruttore (__init__) della classe. I numeri di riferimento usati sono gli stessi della versione precedente, per poter fare un paragone.
  2. Quando il programma va in esecuzione, per prima cosa viene creata un'istanza della classe. L'istruzione che crea l'istanza è la (80), l'unica ad essere stata aggiunta nel main rispetto alla versione precedente.
    Si osservi l'uso convenzionale delle lettere: AppBase (con l'iniziale maiuscola) è il nome della classe, mentre appBase (con l'iniziale minuscola) è il nome dell'istanza.
    Si noti altresì che l'istruzione passa finestra come parametro effettivo al metodo costruttore (__init__) di AppBase. Il metodo costruttore riconosce finestra come valore del parametro formale Genitore (15).
  3. Viene, infine, eseguito come al solito il ciclo principale su finestra.

#!/usr/bin/env python
# -*- coding: iso8859-15 -*-
# tkex11.py - versione 1.0

from Tkinter import *                                      # (10)

class AppBase:
    def __init__(self, Genitore):                          # (15)
        self.quadro1 = Frame(Genitore)                     # (40)
        self.quadro1["background"] = "yellow"              # (41)
        self.quadro1.pack()                                # (42)
        self.puls1 = Button(self.quadro1)                  # (50)
        self.puls1["borderwidth"] = 1                      # (52)
        self.puls1["text"] = "Evviva Python+Tkinter !"     # (54)
        self.puls1["background"] = "pink"                  # (56)
        self.puls1.pack()                                  # (58)
        self.label1 = Label(self.quadro1, text="Ciao")     # (60)
        self.label1.pack()                                 # (62)
        self.puls2 = Button(self.quadro1, text="Uscita",   # (70)
            borderwidth=1, background="red",
            command=self.quadro1.quit)
        self.puls2.pack(side="right", padx=5, pady=10)     # (72)

# Programma Principale
def main():
    finestra = Tk()                                        # (20)
    finestra.title("Tk Prova")                             # (30)
    appBase = AppBase(finestra)                            # (80)
    finestra.mainloop()                                    # (90)

main()

In fase di esecuzione il programma appare esattamente identico al precedente. Non sono state aggiunte nuove funzionalità, ma è stato soltanto ristrutturato il codice con l'uso delle classi. Da notare come tutti i riferimenti ai widget, all'interno della definizione di classe, siano preceduti dal prefisso self., come ad esempio nelle istruzioni (50, 60, 70).






Corso Base Linux
Asm Linux
Python
GnuPlot
TkInter
wxPython
py-qt4-designer



Non conosciamo il vero se non conosciamo la causa.
Aristotele

Valid CSS!
pagina generata in 0.001 secondi