Note GUI Tkinter

 ☗ 

Creazione applicazione grafica:


Strutturata con le classi

try: import Tkinter as tk                   # Py2
except ImportError: import tkinter as tk    # Py3

class Applicazione:

    def __init__ (self):
        self.finestra = tk.Tk()
        self.finestra.title("Hello world")

    def start(self):
        self.finestra.mainloop()

app = Applicazione()
app.start()

Strutturata senza classi

try: import Tkinter as tk                   # Py2
except ImportError: import tkinter as tk    # Py3

finestra = tk.Tk()
finestra.title("Hello world")
finestra.mainloop()

Aspetto della finestra

finestra.resizable(False, False)    # rende la finestra non ridimensionabile
finestra.overrideredirect(True)     # toglie i bordi, non si puo' chiudere con la X

Normalmente la finestra è elastica e diventa grande quanto il suo contenuto, tuttavia è possibile indicarne a priori la grandezza e la posizione:
finestra.geometry("640x480")
finestra.geometry("%dx%d" % (WIDTH, HEIGHT))              # solo dimensioni
finestra.geometry("%dx%d+%d+%d" % (WIDTH, HEIGHT, X, Y))  # anche posizione

Conoscendo le dimensioni dello schermo è possibile centrare la finestra nello schermo stesso:
larghezza = finestra.winfo_screenwidth()    # larghezza schermo in pixel
altezza = finestra.winfo_screenheight()     # altezza schermo in pixel
x = larghezza//2 - WIDTH//2
y = altezza//2 - HEIGHT//2
finestra.geometry("%dx%d+%d+%d" % (WIDTH, HEIGHT, x, y))

Impostare l'icona della finestra su sistemi Windows:
finestra.wm_iconbitmap("icona.ico")
(si possono creare icone 16x16 su: www.favicon.cc/)


Oggetto Frame, il contenitore:

All'interno della finestra si possono disporre dei widget (window-objects). L'oggetto Frame (riquadro/cornice) permette di raggruppare un certo numero di widget all'interno di esso. Questo serve principalmente per il posizionamento, infatti disponendo il frame in un altro punto della finestra si spostano assieme ad esso anche tutti i widget contenuti, ma è utile anche per creare elementi decorativi, come separatori o riquadri tridimensionali.

Ogni elemento di una applicazione grafica ha un oggetto "genitore" ed eventualmente uno o più oggetti "figli". Un oggetto Frame di base ha come genitore la finestra stessa:

frame1 = tk.Frame(form1)

Con questa riga si crea un oggetto di tipo Frame chiamato frame1, appartenente alla finestra di nome form1. Un frame, come tutti gli altri widget, dispone di un grande numero di attributi visuali (dimensioni, colori, tipo e dimensione dei bordi, font ecc. Per impostare (configurare) questi attributi (chiamati anche option / opzioni) ci sono tre modi, si possono specificare nell'operazione di creazione dell'oggetto (vengono passati automaticamente al costruttore dell'oggetto quando viene istanziato), si può usare il metodo configure (dopo aver già istanziato l'oggetto), oppure si possono impostare manualmente uno per uno accedendo direttamente al dizionario interno dei widget (sempre dopo aver istanziato l'oggetto):

frame1 = tk.Frame(form1, bg="yellow", width=300, height=200)

frame1.configure(bg="yellow", width=300, height=200)

frame1["bg"] = "yellow"
frame1["width"] = 300
frame1["height"] = 200

Per rendere visibile il tutto si deve chiamare uno dei tre metodi possibili di posizionamento: pack, grid o place:
try: import Tkinter as tk                   # Py2
except ImportError: import tkinter as tk    # Py3

class Applicazione:

    def __init__ (self):
        self.form1 = tk.Tk()
        self.form1.title("Titolo finestra")
        self.form1.resizable(False, False)

        self.frame1 = tk.Frame(self.form1)
        self.frame2 = tk.Frame(self.form1)
        self.frame3 = tk.Frame(self.form1)
        self.frame4 = tk.Frame(self.form1)

        self.frame1.configure(bg="yellow", width=150, height=100)
        self.frame2.configure(bg="red",    width=150, height=100)
        self.frame3.configure(bg="blue",   width=150, height=100)
        self.frame4.configure(bg="green",  width=150, height=100)

        self.frame1.grid(row=0, column=0)
        self.frame2.grid(row=0, column=1)
        self.frame3.grid(row=1, column=0)
        self.frame4.grid(row=1, column=1)


    def start(self):
        self.form1.mainloop()

app = Applicazione()
app.start()




Frame di grandezza fissa o elastici

Normalmente un frame è un oggetto "elastico", può estendersi per accettare elementi interni più grandi, ma può anche "collassare" attorno agli oggetti, se vengono inseriti in esso con i metodi di posizionamento pack e grid. Nell'esempio precedente si vedono 4 frames rettangolari di identiche dimensioni (specificate per ciascuno con width e height). Le dimensioni visualizzate sono quelle volute perchè i frames non contengono nulla. Provando ad inserire un qualsiasi widget al loro interno, e a posizionarlo con i metodi pack o grid, i frames si restringono alle dimensioni del contenuto, e la finestra si restringe eventualmente alle dimensioni residue dei frames.
        self.frame1 = tk.Frame(self.form1)
        self.frame2 = tk.Frame(self.form1)
        self.frame3 = tk.Frame(self.form1)
        self.frame4 = tk.Frame(self.form1)

        self.bottone1 = tk.Button(self.frame1, text="PULSANTE")

        self.frame1.configure(bg="yellow", width=150, height=100)
        self.frame2.configure(bg="red",    width=150, height=100)
        self.frame3.configure(bg="blue",   width=150, height=100)
        self.frame4.configure(bg="green",  width=150, height=100)

        self.frame1.grid(row=0, column=0)
        self.frame2.grid(row=0, column=1)
        self.frame3.grid(row=1, column=0)
        self.frame4.grid(row=1, column=1)

        self.bottone1.grid(row=0, column=0)




Per evitare questo si può usare il metodo grid_propagate(False): sul frame che vogliamo "bloccare".
        self.frame1.grid_propagate(False)

        self.frame1.grid(row=0, column=0)
        self.frame2.grid(row=0, column=1)
        self.frame3.grid(row=1, column=0)
        self.frame4.grid(row=1, column=1)

        self.bottone1.grid(row=0, column=0)




Dalla versione 2.6 di Python in poi il metodo grid non centra più di default i widget nelle celle. Per fare questo bisogna scrivere:
        self.frame1.grid_rowconfigure(0, weight=2)
        self.frame1.grid_columnconfigure(0, weight=2)
        self.bottone1.grid(row=0, column=0)

(Info tratte da: stackoverflow.com/questions/14946963)



Gli attributi visivi comuni


Dimensioni

Sono specificate con width e height (larghezza e altezza). Per oggetti non testuali come i Canvas e i Frame le dimensioni si intendono in pixel, per oggetti testuali come bottoni, listbox, label le dimensioni si intendono in caratteri.


Colore

Lo sfondo si identifica con bg, mentre il primo piano, cioè il colore del testo, si identifica con fg.
I colori possono essere scritti con il loro nome ("yellow" "blue" ecc) o specificandoli in RGB: bg="#FF0000"


Bordi

I bordi hanno una dimensione (spessore in pixel) specificato con bd=..., e uno stile specificato con relief="stile". Gli stili possibili per i bordi sono:



Testo

Molti widget (come bottoni e label) possono rappresentare del testo. Lo stile del testo si può specificare con font = ("nomedelfont", dimensioni, "stile"). Per essere il più possibile indipendenti dal sistema operativo usato si dovrebbero specificare solo famiglie generiche come "serif", "monospace" ecc, oppure caratteri comuni come "helvetica", "courier", "times" ecc. Lo stile può essere: "normal", "bold", "roman", "italic", "underline", "overstrike".
self.label1 = tk.Label(self.form1, font=("helvetica", 12, "bold"))

Immagini

Alcuni widget, come label e bottoni, permettono di rappresentare immagini al posto o assieme al testo. Si deve creare un oggetto PhotoImage e assegnarlo alla proprietà image dei widget.
self.immagine1 = tk.PhotoImage(file="logo.gif")
self.bottone1.configure(image=self.immagine1)


Il posizionamento


La griglia

Il metodo grid permette di posizionare ogni widget all'interno di una cella immaginaria di una griglia creata sull'elemento genitore. Il metodo grid applicato ai frames principali di una finestra li allinea in una griglia relativa all'intera finestra, mentre grid applicato a widget contenuti in un frame allinea questi widget su una griglia interna a quel frame. Per posizionare un oggetto è sufficiente indicare oggetto.grid(row=riga, column=colonna). I valori di riga e colonna partono da zero.


Celle estese su più righe o colonne

Può essere necessario estendere un oggetto per più colonne o righe, in questo caso, oltre ad indicare la sua cella, si indicano anche i parametri rowspan = numerodirighe o columnspan = numerodicolonne.
try: import Tkinter as tk                   # Py2
except ImportError: import tkinter as tk    # Py3

class Applicazione:

    def __init__ (self):

        self.form1 = tk.Tk()
        self.form1.title("Titolo finestra")
        self.form1.resizable(False, False)

        self.frame1 = tk.Frame(self.form1)
        self.frame2 = tk.Frame(self.form1)
        self.frame3 = tk.Frame(self.form1)

        self.frame1.configure(bg="yellow", width=150, height=100)
        self.frame2.configure(bg="red",    width=150, height=100)
        self.frame3.configure(bg="blue",   width=300, height=100)

        self.frame1.grid(row=0, column=0)
        self.frame2.grid(row=0, column=1)
        self.frame3.grid(row=1, column=0, columnspan=2)

    def start(self):
        self.form1.mainloop()

app = Applicazione()
app.start()




        self.frame1.grid(row=0, column=0)
        self.frame2.grid(row=0, column=1, rowspan=2)
        self.frame3.grid(row=1, column=0)






Padding

Ogni oggetto visuale dispone di parametri per regolare il margine esterno (attorno all'oggetto) o interno (tra il bordo e il contenuto). Con padx e pady regoliamo il padding esterno, con ipadx e ipady quello interno. Il margine lasciato dal padding esterno ha il colore di fondo dell'elemento genitore:
         self.frame1.grid(row=0, column=0, padx=10, pady=10)
         self.frame2.grid(row=0, column=1, rowspan=2, padx=10, pady=10)
         self.frame3.grid(row=1, column=0, padx=10, pady=10)





Estensione

Con il parametro sticky possiamo fare in modo che un frame si estenda per riempire lo spazio vuoto nella finestra causato da frame vicini più grandi. Sticky accetta come valori delle coordinate n (nord) s (sud) w (ovest) e (est), anche combinate "ns" vuol dire estendi in tutto lo spazio verticale, "ew" in tutto quello orizzontale, "nsew" tutto attorno:
        self.frame1.grid(row=0, column=0, padx=10, pady=10)
        self.frame2.grid(row=0, column=1, rowspan=2, padx=10, pady=10, sticky="ns")
        self.frame3.grid(row=1, column=0, padx=10, pady=10)





Label e pulsanti

Due widget molto comuni in un'interfaccia grafica sono le Label (per rappresentare del testo o delle piccole immagini) e i pulsanti (Button) da cliccare col mouse (anche loro possono mostrare delle piccole immagini al loro interno). Nel seguente esempio si crea una finestra dall'aspetto più classico usando tre frames orizzontali. Il primo in alto contiene due pulsanti (nella loro dichiarazione si indica che sono figli di frame1), quello al centro ha il colore di fondo bianco e una label (chiamata label1) che serve per mostrare un'immagine (nella dichiarazione della label è indicato che è figlia di frame2). La label è posizionata con grid, quindi il frame deve essere "bloccato" per non collassare attorno alla label stessa (frame2.grid_propagate(0)). Inoltre affinché non venga visualizzato un bordo attorno all'immagine mostrata dalla label, il bordo della label stessa deve essere impostato a 0 (bd=0). Infine il frame in basso (frame3) contiene solo una label testuale. L'immagine visualizzata dalla label1 nel frame2 appare nel frame centrale in quanto va ricordato che grid si applica riferendosi all'oggetto genitore, la label1 è figlia di frame2, come frame2 è figlio di form1, quindi grid applicato sulla label1 si riferisce all'area di frame2, mentre grid applicato a frame2 si rifersice all'area di form1, cioè all'intera finestra).

try: import Tkinter as tk                   # Py2
except ImportError: import tkinter as tk    # Py3

class Applicazione:

    def __init__ (self):

        self.form1 = tk.Tk()
        self.form1.resizable(False, False)


        # Frame alto con pulsanti ---------------------------------------------
        self.frame1 = tk.Frame(self.form1, bd=1, relief="raised")
        self.frame1.grid(row=0, column=0, sticky="we")

        self.bottone1 = tk.Button(self.frame1, text="Bottone1")
        self.bottone1.grid(row=0, column=0, padx=4, pady=4)

        self.bottone2 = tk.Button(self.frame1, text="Bottone2")
        self.bottone2.grid(row=0, column=1, pady=4)


        # Frame centrale con immagine centrata --------------------------------
        self.frame2 = tk.Frame(
            self.form1, height=300, width=400, 
            bd=1, relief="raised", bg="white")
        self.frame2.grid_propagate(False)
        self.frame2.grid_rowconfigure(0, weight=2)
        self.frame2.grid_columnconfigure(0, weight=2)
        self.frame2.grid(row=1, column=0)

        self.immagine1 = tk.PhotoImage(file="bissio.gif")
        self.label1 = tk.Label(self.frame2, image=self.immagine1, bd=0)
        self.label1.grid(row=0, column=0)


        # Frame basso con label -----------------------------------------------
        self.frame3 = tk.Frame(self.form1, bd=1, relief="raised")
        self.frame3.grid(row=2, column=0, sticky="we")

        self.label2 = tk.Label(self.frame3, text="Barra di stato")
        self.label2.grid(row=0, column=0, padx=4, pady=4)


    def start(self):
        self.form1.mainloop()

app = Applicazione()
app.start()





Canvas

Il canvas è una superficie su cui creare oggetti grafici come linee, poligoni, rettangoli, ovali, cerchi, testi, immagini ecc. Ogni elemento grafico disegnato è un oggetto dotato di un identificatore interno al canvas e di proprietà modificabili (posizione, colori ecc). Il canvas di tk non è un'area bitmap dove lavorare bit per bit, se serve lavorare con i singoli bit si può usare un oggetto PhotoImage.
try: import Tkinter as tk                   # Py2
except ImportError: import tkinter as tk    # Py3

# Crea una finestra grafica 640x480
form1 = tk.Tk()
form1.resizable(False,False)
canvas1 = tk.Canvas(
    form1, width=640, height=480, 
    bg="white", highlightthickness=0)
canvas1.grid()
form1.mainloop()
L'area grafica utile è pari alle dimensioni impostate con width e height (il punto in alto a sinistra ha coordinate x e y pari a 0,0). Se non si imposta highlightthickness a zero, viene automaticamente disegnato un bordo di due pixel interno all'area grafica stessa.

I colori predefiniti sono "white", "black", "red", "green", "blue", "yellow", "cyan", "magenta" (vedere anche infohost.nmt.edu/tcc/help/pubs/tkinter/colors.html)


Tracciare una linea o più segmenti

(vedere anche infohost.nmt.edu/tcc/help/pubs/tkinter/create_line.html):
canvas1.create_line(x1, y1, x2, y2)
Il plottaggio di un singolo pixel si può simulare tracciando una riga lunga 2 pixel (di cui solo il primo viene disegnato).

Se si usa una variabile per identificare un oggetto grafico, questo può essere modificato a runtime:
identificatore = canvas1.create_line(2, 25, 101, 25, arrow="both", dash=1)
canvas1.itemconfigure(identificatore, fill="red", width=3)
canvas1.coords(identificatore, 2, 2, 101, 51)


Esempio di tracciamento funzione animato:




Cancellare tutti gli oggetti dal canvas

canvas1.delete("all")


Ottenere lista identificatori di tutti gli oggetti dal canvas

(gli id sono numeri interi non necessariamente consecutivi):
lista_id = canvas1.find_all()


Esportare il canvas in formato postscript

canvas1.postscript(
    colormode="color",
    file="nomefile.ps", 
    rotate=False, x=2, y=2,
    width=100, height=50)
(Visualizzatore postscript online: view.samurajdata.se/)



Metodi PhotoImage

È possibile leggere e modificare i singoli pixel di un oggetto PhotoImage tramite i metodi get e put. Put nella forma più semplice accetta un colore specificato con una stringa RGB e una coordinata x,y, mentre nel caso più complesso accetta un'intera stringa di colori suddivisa in righe e colonne formattata con dei delimitatori {}.

Esempio di creazione di una semplice immagine 8x8 ingrandita con un fattore specificato in una costante (SCALE). L'immagine viene posizionata all'interno di un canvas e ne viene periodicamente cambiato il colore agendo direttamente sui pixel.



(info tratte da: tkinter.unpythonic.net/wiki/PhotoImage)



Funzioni temporizzate

È possibile chiamare funzioni a tempo (metodo after), gestire gli eventi pendenti (metodo update_idletasks), aggiornare un widget (metodo update):
finestra.after(millisecondi, funzione)      ESEMPIO
finestra.update_idletasks()
widget.update()  

(info tratte da: infohost.nmt.edu/tcc/help/pubs/tkinter/universal.html)


Rimuovere e riposizionare widget

Un widget può essere rimosso visualmente dalla finestra (senza distruggerlo) con il metodo forget. Se ci si sono salvate preventivamente le informazioni di posizionamento è possibile rivisualizzarlo:
info = widget.grid.info()
widget.grid.forget()
widget.grid(info)



Può essere utile intercettare l'evento di chiusura per chiamare una funzione:
finestra.protocol("WM_DELETE_WINDOW", funzione)        ESEMPIO     

(info tratte da: effbot.org/tkinterbook/tkinter-events-and-bindings.htm)



Intercettare evento di chiusura

Una finestra si può chiudere da programma chiamando il metodo destroy o cliccando sulla X di chiusura:
finestra.destroy()

Può essere utile intercettare l'evento di chiusura per chiamare una funzione:
finestra.protocol("WM_DELETE_WINDOW", funzione)        ESEMPIO     

(info tratte da: effbot.org/tkinterbook/tkinter-events-and-bindings.htm)