dal 2015 - visita n. 735
TkInter
TkInter

 

3. Collegamenti


3.1. Collegamento di Evento

In questo secondo esempio, sviluppato tramite l'uso delle classi per comodità, viene realizzato un piccolo applicativo GUI nel quale sono presenti tutti i 4 compiti della programmazione UI, descritta precedentemente.

Per evitare confusione tra i pulsanti dell'interfaccia GUI ed i pulsanti del mouse chiameremo questi ultimi con i seguenti nomi: click singolo --> Psx (sinistro), Pcx (centrale), Pdx (destro),        doppio click --> P2sx (sinistro), P2cx (centrale), P2dx (destro).

  1. Il programma presenta a video una interfaccia con un messaggio e due pulsanti denominati Cambia e Uscita.
  2. Cliccando con Psx sul pulsante Cambia si otterrà l'alternarsi del colore di sfondo (a riposo) del pulsante stesso. Mentre cliccando con Pdx sempre sul pulsante Cambia si otterrà l'alternarsi del messaggio visualizzato. Cliccando invece P2dx sul pulsante Uscita si otterrà il ripristino della condizione iniziale, ed infine con Psx sul pulsante Uscita si provocherà la chiusura del programma.
  3. E' stato realizzato un collegamento (binding) tra l'interfaccia (1.) e le funzioni desiderate (2.).
  4. E' stato attivato il ciclo degli eventi.

#!/usr/bin/env python
# -*- coding: iso8859-15 -*-
# tkex12.py - versione 1.0
from Tkinter import *

class AppBase:
    def __init__(self, Genitore):
        self.mioGenitore = Genitore                        # (10)
        self.st1="Seconda Prova di Classe"                 # (12)
        self.st2="Ciao Classe"                             # (14)
        self.quadro1 = Frame(Genitore)                     # (20)
        self.quadro1["background"] = "#FFFF7F"             # (22)
        self.quadro1.pack()                                # (24)
        self.label1 = Label(self.quadro1, text=self.st1,   # (30)
            background="cyan", foreground="blue")
        self.label1.pack(padx=30, pady=10)                 # (32)
        self.puls1 = Button(self.quadro1)                  # (40)
        self.puls1.configure(text = "Cambia",              # (42)
            background = "orange", borderwidth = 1)
        self.puls1.pack(side = LEFT)                       # (44)
        self.puls1.bind("<Button-1>", self.puls1Press1)    # (46)
        self.puls1.bind("<Button-3>", self.puls1Press3)    # (48)
	
        self.puls2 = Button(self.quadro1)                  # (50)
        self.puls2.configure(text = "Uscita",              # (52)
            background = "red", borderwidth = 1)
        self.puls2.pack(side = RIGHT)                      # (54)
        self.puls2.bind("<Button-1>", self.puls2Press1)    # (56)
        self.puls2.bind("<Double-Button-3>",               # (58)
	    self.puls2Press3D)
    def puls1Press1(self, evento):                         # (60)
        if self.puls1["background"] == "orange":
            self.puls1["background"] = "green"
        else:
            self.puls1["background"] = "orange"
    def puls1Press3(self, evento):                         # (70)
        if self.label1["text"] == self.st2:
            self.label1["text"] = self.st1
        else:
            self.label1["text"] = self.st2
    def puls2Press1(self, evento):                         # (80)
        self.mioGenitore.destroy()
    def puls2Press3D(self, evento):                        # (85)
        self.label1["text"] = self.st1
        self.puls1["background"] = "orange"

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

main()

Nella scrittura del programma, oltre all'uso della classe, sono stati introdotti alcuni particolari che vogliamo far notare.




3.2. Complementi e terminologia

Impacchettamento
E' uno dei modi per controllare le relazioni visive fra i vari widget (componenti), è abbastanza facile da usare, ma ci si deve affidare ai criteri di posizionamento di Tkinter. L'utente può, tuttavia, interagire tramite costanti di convenienza da utilizzare con la proprietà side: LEFT, RIGHT, TOP (predefinita), BOTTOM.
Mentre tramite le proprietà padx e pady è possibile spaziare il componente rispetto all'ambiente circostante. Si può, quindi, condizionare il comportamento di Tkinter, che tenderebbe altrimenti ad impilare i vari widget. Con riferimento all'ultimo esempio:

self.puls1.pack(side = LEFT)                       # (44)
self.label1.pack(padx=30, pady=10)                 # (32)

Risulta chiaro allora che uno dei modi per controllare l'aspetto della GUI è attraverso l'ordine in cui si impacchettano i widget nei contenitori.
I lati TOP e BOTTOM determinano l'orientazione verticale, mentre i lati LEFT e RIGHT determinano l'orientazione orizzontale.
Durante l'impacchettamento è possibile mischiare le orientazioni, ad esempio un pulsante in verticale (TOP) e l'altro in orizzontale (LEFT). Ma mischiare così le orientazioni in un contenitore non è una buona idea. Usando orientazioni miste è difficile prevedere quale sarà il risultato visivo, con risultati particolarmente sorprendenti in caso di ridimensionamento della finesta.

NON mischiare le orientazioni nel medesimo contenitore.

Il modo corretto per gestire GUI complicate, in cui è effettivamente necessario avere orientazioni multiple, è attraverso l'annidamento dei contenitori.

  • Collegamento
    Si intende tipicamente la definizione di una connessione fra le seguenti entità: un evento su un widget, un gestore di eventi. Questa realizzazione prende il nome di collegamento per evento (event binding).

    Nella figura si vede che quando accade l'evento Psx (click pulsante sinistro del mouse) sul widget pulsante (Uscita) si ha come conseguenza l'esecuzione del gestore di evento metodo puls2Press1. Ogni widget di Tkinter ha un metodo bind() con cui realizza il collegamento. La forma di utilizzo del metodo è:

    widget.bind(nome_evento, nome_gestore_evento)     self.puls2.bind("<Button-1>", self.puls2Press1)    # (56)
    

    Un gestore di eventi è un frammento di codice, un metodo nel caso di una classe, una funzione, o altro che gestisce gli eventi al loro accadere.

  • Focus
    Indica quale è il widget attivo, ovvero quello al quale devono essere rivolti gli eventi di input da tastiera. Solitamente le GUI risolvono il problema tramite la selezione: cliccando su qualcosa una volta, questo qualcosa diventa attivo e prende il focus. Ogni successivo input da tastiera viene rivolto ad esso. Ad esempio nel caso delle icone di un desktop, si effettua la selezione di una icona con un singolo click e la si aziona con un Enter, oppure la si aziona direttamente con un doppio click.
    Ci sono molte maniere di spostare il fuoco da un widget all'altro: mediante il tasto sinistro del mouse o con l'uso del tasto TAB sulla tastiera, per esempio. Solamente un widget alla volta può essere a fuoco (o avere il focus); il widget che è a fuoco è quello che vede, e reagisce agli eventi. Mettere a fuoco un widget è il processo di dare il fuoco al widget.
    L'insieme dei widget che sono in grado di ricevere il fuoco viene mantenuto in una lista circolare, detta ordine di attraversamento, che inizialmente è la sequenza con cui i widget sono stati creati. Premendo il tasto TAB si sposta il fuoco dalla posizione attuale (eventualmente nessuna) al successivo widget della lista. Al termine della lista il fuoco ritorna al widget in testa alla lista. La combinazione Shift+TAB sposta il fuoco in senso opposto all'interno della lista.

    Quando un pulsante è a fuoco si nota una piccola cornice tratteggiata attorno al pulsante oppure al testo del pulsante. Per verificarlo basta rieseguire il programma precedente. All'avvio nessuno dei pulsanti è a fuoco, perciò non si vede nessuna cornice tratteggiata. Se si preme TAB si osserva che appare la cornice attorno al pulsante a sinistra (Cambia), il che indica che è stato messo a fuoco. Premendo ripetutamente TAB il fuoco passa al pulsante successivo (Uscita), e quando raggiunge l'ultimo ritorna al primo. (In realtà il programma ha solamente due pulsanti, perciò il fuoco si alterna fra essi.)
    Si potrebbe apportare una modifica al programma, per fare in modo che il pulsante Cambia sia a fuoco sin dall'inizio, basta usare il metodo focus_force():

    self.puls1.focus_force()

    All'esecuzione del programma il pulsante Cambia sarà a fuoco sin dal primo momento.




  • 3.3. Eventi della tastiera

    Il programma precedente era stato costruito per reagire agli eventi del mouse, ma finestra ha solo ereditato da Tk() il comportamento di reagire all'evento di tastiera pressione del tasto TAB, il quale provoca l'alternarsi del fuoco fra i due pulsanti. Invece la pressione del tasto INVIO non provoca alcun effetto, perchè nel programma sono stati collegati solamente eventi relativi al mouse e non eventi della tastiera. Raggiungere questo obiettivo è abbastanza semplice, basta conoscere il nome dell'evento (in questo caso <Return>) e scrivere qualcosa di simile:

    self.puls1.bind("<Return>", self.puls1Press)
    

    L'unica differenza è che il nome dell'evento è quello di un evento di tastiera invece che di un evento del mouse. Volendo ottenere che la pressione del tasto Invio sulla tastiera abbia lo stesso effetto dell'evento Psx (click pulsante sinistro del mouse) basta collegare ad entrambi i tipi di evento lo stesso gestore di eventi.

    Il programma seguente illustra come sia possibile collegare più tipi di evento allo stesso widget (ad esempio un pulsante), e come quindi sia possibile collegare più coppie widget/evento al medesimo gestore di evento. Volutamente, questa volta, ci si è orientati più agli eventi di tastiera che a quelli del mouse, pertanto la descrizione procederà supponendo di utilizzare il programma per mezzo della tastiera.
    All'avvio vengono presentati 2 widget pulsante, con il focus forzato sul primo di essi (38). Al primo pulsante Cambia fg/bg sono associati 2 eventi: cambio del colore di sfondo (attivabile con il tasto F1 o con il mouse Psx) e cambio del colore del testo (attivabile con il tasto F2 o con il mouse Pdx). Al secondo pulsante Uscita/Reset sono associati 2 eventi: uscita dal programma (attivabile con il tasto Return o con il mouse Psx) e ripristino della situazione iniziale (attivabile con il tasto Escape o con il mouse Pdx).
    Le figure allegate si riferiscono ad una verifica del funzionamento del programma effettuata tramite la tastiera. L'uso del mouse, infatti, introduce un elemento di disturbo causato dall'evento passaggio del mouse sopra il pulsante widget, a parte questo fatto però anche il mouse svolge egregiamente i compiti assegnatigli.

    #!/usr/bin/env python
    # -*- coding: iso8859-15 -*-
    # tkex13.py - versione 1.0
    from Tkinter import *
    
    class AppBase:
        def __init__(self, Genitore):
            self.mioGenitore=Genitore                        # (10)
            self.quadro1=Frame(Genitore)                     # (20)
            self.quadro1.configure(bg="#7FFFFF",             # (22)
    	    padx=4, pady=4)
            self.quadro1.pack()                              # (24)
            self.puls1=Button(self.quadro1)                  # (30)
            self.puls1.configure(text="Cambia fg/bg",        # (32)
                bg="orange", fg="white", bd=1, height=1,
    	    font=("Lucida", 10, "bold"))
            self.puls1.pack(side=LEFT)                       # (33)
            self.puls1.bind("<Button-1>", self.puls1Press1)  # (34)
            self.puls1.bind("<Button-3>", self.puls1Press3)  # (35)
            self.puls1.bind("<F1>", self.puls1Press1)        # (36)
            self.puls1.bind("<F2>", self.puls1Press3)        # (37)
    	self.puls1.focus_force()                         # (38)
            self.puls2=Button(self.quadro1)                  # (40)
            self.puls2.configure(text="Uscita/Reset",        # (42)
                bg="red", fg="white", bd=1, height=1,
    	    font=("Lucida", 10, "bold"))
            self.puls2.pack(side=RIGHT)                      # (43)
            self.puls2.bind("<Button-1>", self.puls2Press1)  # (44)
            self.puls2.bind("<Button-3>", self.puls2Press3)  # (45)
            self.puls2.bind("<Return>", self.puls2Press1)    # (46)
            self.puls2.bind("<Escape>", self.puls2Press3)    # (47)
        def puls1Press1(self, evento):                       # (50)
    	descr_ev(evento)
            if self.puls1["bg"] == "orange":
                self.puls1["bg"]="brown"
            else:
                self.puls1["bg"]="orange"
        def puls1Press3(self, evento):                       # (60)
    	descr_ev(evento)
            if self.puls1["fg"] == "white":
                self.puls1["fg"]="black"
            else:
                self.puls1["fg"]="white"
        def puls2Press1(self, evento):                       # (70)
    	descr_ev(evento)
            self.mioGenitore.destroy()
        def puls2Press3(self, evento):                       # (80)
    	descr_ev(evento)
            self.puls1["bg"]="orange"
            self.puls1["fg"]="white"
    
    def descr_ev(e):                                         # (90)
    # Descrive l'evento in base ai suoi attributi
      nome_ev={"2": "Tasto Tastiera",                        # (92)
                     "4": "Pulsante Mouse"}
      print "Tempo:", str(e.time)                            # (94)
      print "Tipo di evento: " + str(e.type),\
        "(" + nome_ev[str(e.type)] + ")",\
        "-- Id. widget collegato: " + str(e.widget),\
        "-- Simbolo tasto: " + str(e.keysym)
    
    # Programma Principale
    def main():
        finestra=Tk()
        finestra.title("Tk Prova")
        appBase=AppBase(finestra)
        finestra.mainloop() 
    
    main()
    
    

    Nella struttura del programma ci sono alcuni particolari che vogliamo far notare:




    3.4. Collegamento di Comando

    Con riferimento alla Tavola Eventi del Mouse, si nota che l'evento <ButtonPress> corrisponde all'atto di premere un tasto del mouse, senza però rilasciarlo, mentre l'evento <ButtonRelease> corrisponde all'atto di rilascio del tasto stesso. In alcune situazioni è indispensabile poter distinguere i due tipi di evento, come nel caso del trascinamento (drag and drop) di un componente grafico. Nel caso dei widget pulsante, invece, non è necessaria tale distinzione, però si tende a considerare avvenuto l'evento click sul widget al rilascio del pulsante del mouse.

    Il collegamento di comando, detto anche command binding è un altro modo per collegare un gestore di eventi a un widget. Esso collega una molteplicità di eventi al gestore, anzichè un singolo evento. Nel caso di un pulsante, ad esempio, esso collega al gestore la sequenza ButtonPress ButtonRelease; se dovesse passare esplicitamente un evento al gestore, potrebbe passare o ButtonPress o ButtonRelease. Ma nessuno dei due realizzerebbe la condizione desiderata, ecco il motivo per cui nel collegamento di comando non viene passato al gestore alcun evento oggetto.
    Il collegamento di comando usa l'opzione command dei widget:

    self.puls1 = Button(self.quadro1, command=self.puls1Press)

    Nel programma seguente il collegamento di comando, applicato ai due widget pulsante, li rende attivi tramite la barra spaziatrice (SPACE) o il click del pulsante sinistro del mouse (Psx). Ogni evento è rilevabile, oltre che dagli effetti che produce, da un messaggio visualizzato sul widget label o sulla console.

    #!/usr/bin/env python
    # -*- coding: iso8859-15 -*-
    # tkex14.py - versione 1.0
    
    from Tkinter import *
    
    class AppBase:
        def __init__(self, Genitore):
            self.mioGenitore=Genitore                        # (10)
    	self.st1 = "Confermare con la barra spaziatrice\n\
    e spostare il focus con il tasto TAB.\n\
    Con il mouse usare il pulsante sinistro."
            self.st2 = "Attivato Gestore puls1Press - Giallo"
            self.st3 = "Attivato Gestore puls1Press - Verde"
            self.st4 = "Attivato 'puls2Press' - Uscita"
            self.quadro1 = Frame(Genitore)                   # (20)
            self.quadro1.configure(bg="#FFFFCF",             # (22)
    	    padx=4, pady=4)
            self.quadro1.pack()                              # (24)
            self.label1 = Label(self.quadro1, text=self.st1, # (30)
                bg="#FFFFCF", fg="blue")
            self.label1.pack(padx=10, pady=4)                # (32)
    
            self.puls1 = Button(self.quadro1,                # (40)
                command=self.puls1Press)
            self.puls1.configure(text="Cambia", bg="green")  # (42)
            self.puls1.pack(side = LEFT)                     # (44)
            self.puls1.focus_force()                         # (46)
    
            self.puls2 = Button(self.quadro1,                # (50)
                command=self.puls2Press)
            self.puls2.configure(text="Uscita", bg="red")    # (52)
            self.puls2.pack(side = RIGHT)                    # (54)
    
        def puls1Press(self):                                # (60)
            if self.puls1["bg"] == "green":
                self.puls1["bg"] = "yellow"
                self.label1.configure(text=self.st2)         # (64)
            else:
                self.puls1["bg"] = "green"
                self.label1.configure(text=self.st3)         # (66)
            self.label1.pack(padx=10, pady=4)                # (68)
    
        def puls2Press(self):                                # (70)
            print self.st4                                   # (72)
            self.mioGenitore.destroy()
    
    # Programma Principale
    def main():
          finestra = Tk()
          appBase = AppBase(finestra)
          finestra.title("Tk Prova")
          finestra.mainloop()
    
    main()

    Questi i particolari rilevanti nella struttura del programma:




    3.5. Collegamento Misto

    Abbiamo intuito che per quanto riguarda il collegamento di Comando è il widget a reagire ad un gruppo di eventi propri e predefiniti. Non tutti i widget hanno l'opzione command: i vari tipi di pulsanti (RadioButton, CheckButton, ecc.) ce l'hanno, altri widget prevedono opzioni simili, tipo scrollcommand. E' quindi opportuno approfondire lo studio dei diversi tipi di widget per stabilire il relativo supporto al collegamento di comando. Conoscere a fondo l'opzione command dei widget usati permette di migliorare il funzionamento della GUI, semplificando il lavoro del programmatore. Purtroppo le informazioni al riguardo sono frammentarie, eccettuato il codice sorgente delle librerie Tk. Informazioni più dettagliate si possono trovare su alcuni libri relativi a Tk o a Tkinter. La documentazione su Tk è un po' disorganica, ma è disponibile in linea.

    Nel caso del pulsante (button) l'opzione command fornisce al widget sensibilità sia alla tastiera sia al mouse, esso infatti rimane in ascolto di pressioni del tasto [Spazio] e di click Psx del mouse. Pertanto, come verificato nel caso precedente non si ha alcuna reazione nei confronti del tasto [Invio]. Questo comportamento può sembrare insolito, specialmente a chi è abituato ai sistemi Microsoft, ma si può ovviare rendendo il widget pulsante (button) sensibile anche al tasto [Invio].

    Una soluzione a questo problema è il collegamento misto che si ottiene scrivendo semplicemente due gestori di eventi. Il gestore vero e proprio (60, 70) verrà utilizzato per il collegamento di comando, ed ovviamente viene scritto senza il passaggio di alcun oggetto evento. L'altro gestore (80, 90) è solo un aggancio verso il gestore vero e proprio. Questo gestore (80, 90), che riceverà un oggetto evento come parametro effettivo (di fatto ignorato), ha la vera funzione di invocare il vero gestore (60, 70). Per comodità il gestore aggancio lo abbiamo chiamato con il medesimo nome del vero gestore di eventi, ma con l'aggiunta di un suffisso _b (bind) per ricordarci che viene attivato da un collegamento di evento.

    #!/usr/bin/env python
    # -*- coding: iso8859-15 -*-
    # tkex15.py - versione 1.0
    
    from Tkinter import *
    
    class AppBase:
        def __init__(self, Genitore):
            self.mioGenitore=Genitore                        # (10)
    	self.st1 = "Confermare con la barra spaziatrice\n\
    e spostare il focus con il tasto TAB.\n\
    Con il mouse usare il pulsante sinistro."
            self.st2 = "Attivato Gestore puls1Press - Giallo"
            self.st3 = "Attivato Gestore puls1Press - Verde"
            self.st4 = "Attivato 'puls2Press' - Uscita"
            self.quadro1 = Frame(Genitore)                   # (20)
            self.quadro1.configure(bg="#FFFFCF",             # (22)
    	    padx=4, pady=4)
            self.quadro1.pack()                              # (24)
            self.label1 = Label(self.quadro1, text=self.st1, # (30)
                bg="#FFFFCF", fg="blue")
            self.label1.pack(padx=10, pady=4)                # (32)
    
            self.puls1 = Button(self.quadro1,                # (40)
                command=self.puls1Press)
            self.puls1.bind("<Return>", self.puls1Press_b)   # (41)
            self.puls1.configure(text="Cambia", bg="green")  # (42)
            self.puls1.pack(side = LEFT)                     # (44)
            self.puls1.focus_force()                         # (46)
    
            self.puls2 = Button(self.quadro1,                # (50)
                command=self.puls2Press)
            self.puls2.bind("<Return>", self.puls2Press_b)   # (51)
            self.puls2.configure(text="Uscita", bg="red")    # (52)
            self.puls2.pack(side = RIGHT)                    # (54)
    
        def puls1Press(self):                                # (60)
            if self.puls1["bg"] == "green":
                self.puls1["bg"] = "yellow"
                self.label1.configure(text=self.st2)         # (64)
            else:
                self.puls1["bg"] = "green"
                self.label1.configure(text=self.st3)         # (66)
            self.label1.pack(padx=10, pady=4)                # (68)
    
        def puls2Press(self):                                # (70)
            print self.st4                                   # (72)
            self.mioGenitore.destroy()
    
        def puls1Press_b(self, evento):                      # (80)
            print "Attivato 'puls1Press_b'"                  # (81)
            self.puls1Press()                                # (82)
        
        def puls2Press_b(self, evento):                      # (90)
            print "Attivato 'puls2Press_b'"                  # (91)
            self.puls2Press()                                # (92)
    
    # Programma Principale
    def main():
          finestra = Tk()
          appBase = AppBase(finestra)
          finestra.title("Tk Prova")
          finestra.mainloop()
    
    main()

    Il programma è praticamente identico al precedente, tranne che per alcuni particolari:






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



    Per vedere ciò che vi sta di fronte, bisogna avere una percezione chiarissima, incontaminata, senza pregiudizi, in cui non ci sia il desiderio di superarlo, ma solamente di osservarlo.
    Jiddu Krishnamurti (1895-1986)

    Valid CSS!
    pagina generata in 0.001 secondi