1. Introduzione alle GUI con Tkinter

In questa lezione impareremo ad utilizzare la libreria Tkinter, il modulo della Standard Library di Python per la creazione di interfacce grafiche. Vedremo quali sono gli elementi base di Tkinter che ci permettono di personalizzare una finestra con diverse funzionalità tramite un’ampia scelta di widget predefiniti come etichette, campi per inserire il testo, pulsanti che eseguono azioni, scrollbar ed altro ancora.

Potete trovare il codice di questa lezione nel repository di GitHub.

Iniziamo a prendere dimestichezza con Tkinter creando un semplice programma provvisto di pulsanti che fanno comparire del testo quando vengono premuti e poi creeremo uno script un po’ più articolato che ci permetterà di scaricare ASCII Art tramite una REST API. Cominciamo!


Come creare una semplice interfaccia grafica con Tkinter

Importiamo il modulo tkinter (per convenzione e comodità utilizziamo l’abbreviazione tk) e creiamo una finestra che rappresenta il widget principale (detto “top level”) della nostra schermata assegnandolo alla classe Tk di tk. Non è necessario installare nulla perché Tkinter fa parte delle funzionalità standard di Python.

import tkinter as tk

window = tk.Tk()

Impostiamo le dimensioni della finestra utilizzando il metodo geometry e diamogli come argomento una stringa contenente larghezza e altezza in pixel, nel nostro caso “600x600”. Impostiamo anche il nome che la finestra mostrerà sulla barra in alto quando è in esecuzione tramite il metodo title.

window.geometry("600x600")
window.title("Hello TkInter!")

Se vogliamo che la nostra finestra non sia ridimensionabile dall'utente quando è in esecuzione chiamiamo il metodo resizable passando due argomenti (che rappresentano altezza e larghezza della finestra) come False:

window.resizable(False, False)

Per cambiare il colore della finestra possiamo chiamare il metodo configure e passare come argomento il colore desiderato:

window.configure(background="white")

Per avviare la nostra interfaccia (per il momento vuota) chiamiamo il metodo mainloop sulla nostra finestra tramite l’apposita istruzione condizionale:

if __name__ == "__main__":
    window.mainloop()


Come aggiungere un pulsante: il widget Button

Aggiungiamo ora il primo widget della nostra interfaccia. Inizializziamo un pulsante che una volta collegato ci permetterà di chiamare funzioni: assegnamo alla variabile first_button la classe Button di Tkinter e diamogli come argomento il testo che vogliamo venga mostrato su di esso.

first_button = tk.Button(text="Saluta!")

Posizioniamo il tasto nella finiestra chiamando il metodo grid che ci permette di specificare la posizione nel widget genitore, che nel nostro caso è la finestra principale window.

Ci sono molti modi per posizionare gli elementi grafici con Tkinter. Il primo che vedremo è il metodo grid, che si basa sulla geometria a griglia: lo spazio della finestra principale viene diviso in righe e colonne, dall'alto verso il basso a partire dalla parte superiore sinistra, che in questo sistema ha quindi coordinate (0,0). Posizioniamo quindi il nostro pulsante in alto a sinistra:

first_button.grid(row=0, column=0)


Come far eseguire un’azione al pulsante

Per definire il comportamento del pulsante che abbiamo creato passiamo anche il parametro command e assegniamolo alla funzione first_print che creeremo tra poco:

first_button = tk.Button(text="Saluta!", command=first_print)

Creiamo la funzione first_print assegnando una stringa testuale alla classe Label, che creerà un’etichetta prendendo come parametri la finestra che la conterrà, il testo che dovrà mostrare, il fg (ForeGround, il colore in primo piano, ovvero quello del testo stesso), il font da utilizzare e la sua grandezza in pixel. Specifichiamo anche la posizione della Label sulla nostra finestra nello stesso modo in cui abbiamo definito quella del bottone con grid, posizionandola in questo caso alla destra di esso nella colonna adiacente.

def first_print():
    text = "Hello World!"
    text_output = tk.Label(window, text=text, fg="red", font=("Helvetica", 16))
    text_output.grid(row=0, column=1)

Aggiungiamo anche un secondo pulsante che mostri un altro messaggio utilizzando lo stesso procedimento:

second_button = tk.Button(text="Seconda Funzione", command=second_function)
second_button.grid(row=1, column=0, pady=20)

def second_function():
    text = "Nuovo Messaggio! Nuova Funzione!"
    text_output = tk.Label(window, text=text, fg="green", font=("Helvetica", 16))
    text_output.grid(row=1, column=1, padx=50)

In questo caso abbiamo aggiunto il parametro pady alla griglia che definisce la posizione del nostro secondo pulsante e padx a quella della Label che mostra il secondo testo: questi due parametri si riferiscono al padding (ovvero lo spazio tra il contenuto di una componente grafica e il suo contenitore) sugli assi x (padx: padding orizzontale) e y (pady: padding verticale) della finestra espresso in pixel. In questo modo il secondo pulsante verrà posizionato sotto al primo e l’etichettà che creerà quando verrà premuto verrà posizionata alla destra di esso e sotto la prima etichetta.


Come allineare i widget nella finestra

Se a questo punto proviamo ad avviare il codice i widget appaiono incolonnati ma non sono allineati tra di loro, inoltre la grandezza dei widget è determinata dalle dimensioni degli elementi contenuti in essi. Per specificare l’allineamento dei widget aggiungiamo il parametro sticky assegnato a uno dei punti cardinali in inglese (N, S, E, W e le loro combinazioni). Indichiamo quindi a tutti i metodi grid che i widget devono essere allineati a destra (ovvero ad ovest: W).

import tkinter as tk

window = tk.Tk()
window.geometry("600x600")
window.title("Hello TkInter!")
window.resizable(False, False)
window.configure(background="white")


def first_print():
    text = "Hello World!"
    text_output = tk.Label(window, text=text, fg="red", font=("Helvetica", 16))
    text_output.grid(row=0, column=1, sticky="W")

def second_function():
    text = "Nuovo Messaggio! Nuova Funzione!"
    text_output = tk.Label(window, text=text, fg="green", font=("Helvetica", 16))
    text_output.grid(row=1, column=1, padx=50, sticky="W")

first_button = tk.Button(text="Saluta!", command=first_print)
first_button.grid(row=0, column=0, sticky="W")

second_button = tk.Button(text="Seconda Funzione", command=second_function)
second_button.grid(row=1, column=0, pady=20, sticky="W")


if __name__ == "__main__":
    window.mainloop()

A questo punto i widget sono allineati a destra e abbiamo concluso il nostro programma!

Adesso abbiamo tutte le informazioni di base che ci servono per creare un programma un po’ più strutturato.


Creiamo un Downloader di ASCII Art

Creiamo insieme un programma che converta le parole inserite in ASCII Art, ovvero immagini prodotte a partire dai 95 caratteri ASCII con l’aiuto di una REST API.

ATTENZIONE: ci dispiace informarvi del fatto che la REST API utilizzata nel tutorial non risulta più disponibile: aggiorneremo gli script con una nuova API funzionante il prima possibile. Nel frattempo provate a dare uno sguardo a questo repository di API gratuite e divertitevi a sperimentare!

Creiamo innanzitutto un nuovo script di Python. Per comunicare con la REST API avremo bisogno del modulo requests: qualora non lo avessimo già fatto precedentemente, possiamo installarlo nel nostro ambiente virtuale tramite PIP.

Importiamo quindi tk e requests, creiamo una nuova finestra con il metodo geometry di Tkinter dandole come dimensioni 900x500 e utilizziamo anche il metodo grid_columnconfigure. Esso definisce la dimensione delle colonne della griglia in modo esplicito e imposta le proprietà di allineamento per gli elementi al suo interno: passiamogli il parametro 0 per indicare l’indice della colonna a cui ci riferiamo e il parametro weight per decidere come lo spazio disponibile viene distribuito tra le colonne della griglia quando la finestra viene ridimensionata. In questo modo la prima colonna si espanderà o si ridurrà in base allo spazio disponibile, seguendo le regole di distribuzione imposte dal valore di peso weight.

import tkinter as tk
import requests

window = tk.Tk()
window.geometry("900x550")
window.title("ASCII ART DOWNLOADER")
window.grid_columnconfigure(0, weight=1)


Come aggiungere un’etichetta: il widget Label

Creiamo una nuova etichetta, inizializziamola con una stringa di testo e tramite il metodo grid facciamo in modo che rimanga attaccata alla parte superiore della finestra con il parametro sticky impostato su N in modo che gli elementi rimangano centrati.

welcome_label = tk.Label(window,
                         text="Welcome! Aggiungi una parola o una frase da scaricare:",
                         font=("Helvetica", 15))
welcome_label.grid(row=0, column=0, sticky="N", padx=20, pady=10)

Aggiungiamo l’istruzione condizionale che chiama il metodo mainloop della nostra interfaccia per rendere il codice eseguibile:

if __name__ == "__main__":
    window.mainloop()


Come aggiungere un campo per inserire del testo: il widget Entry

Aggiungiamo un altro widget utilizzando la classe Entry di Tkinter e posizioniamolo sotto l’etichetta che abbiamo appena creato passando row=1 e col=0 come parametri del metodo grid e impostiamo il parametro sticky su WE per indicare che la colonna deve estendersi da destra a sinistra per tutto lo spazio disponibile. Per quanto riguarda il padding, impostiamo solo padx in modo da lasciare lo spazio per inserire il testo attaccato all’etichetta dandogli 10px di spazio sui lati.

text_input = tk.Entry()
text_input.grid(row=1, column=0, sticky="WE", padx=10)

Creiamo un nuovo widget Button, un pulsante che utilizzeremo per eseguire il download. Nella griglia posizioniamolo esattamente sotto al widget Entry specificando row=2:

download_button = tk.Button(text="DOWNLOAD ASCII ART", command=download_ascii)
download_button.grid(row=2, column=0, sticky="WE", pady=10, padx=10)

Creiamo la nostra funzione download_ascii con cui verifichiamo che il nostro utente abbia inserito una parola o una frase con il metodo get:

def download_ascii():
    if text_input.get():
        text_response = text_input.get()
    else:
        text_response = "Aggiungi una parola o una frase al campo input!"


Come aggiungere un'area per contenere un testo lungo: Il widget Text

Creiamo quindi un widget testuale utilizzando la classe Text di Tkinter per ospitare l’ASCII Art scaricata e utilizziamo il metodo insert passandogli come parametri tk.END e il nostro testo text_reponse. tk.END è una costante predefinita che rappresenta la fine di un testo o di una stringa all'interno dei widget. Posizioniamo il nuovo widget di tipo Text nella terza row a partire dall’alto, tramite il parametro sticky facciamo in modo che si espanda verso i lati e diamogli lo stesso padding che abbiamo dato al pulsante.

def download_ascii():
    if text_input.get():
        text_response = text_input.get()
    else:
        text_response = "Aggiungi una parola o una frase al campo input!"

    textwidget = tk.Text()
    textwidget.insert(tk.END, text_response)
	textwidget.grid(row=3, column=0, sticky="WE", padx=10, pady=10)

Abbiamo usato i widget Entry e Text per acquisire l’input dell’utente, ma quali sono le differenze tra l’uno e l’altro? Entry consente all’utente di inserire una sola riga di testo, mentre Text è paragonabile allo spazio di un editor di testo perché è multiriga e accetta input molto più lunghi.


Come comunicare con la REST API: il modulo requests

Comunichiamo ora con le API tramite il modulo requests in modo da ottenere l’ASCII Art sulla base dell’input dell’utente. Dichiariamo una variabile payload con un dizionario che registri l’input dell’utente e una variabile response che tramite il modulo requests si colleghi alla REST API specificando l’endpoint come parametro.

def download_ascii():
    if text_input.get():
        user_input = text_input.get()
        payload = {"text": user_input}
        response = requests.get("http://artii.herokuapp.com/make",
                                params=payload)
        text_response = response.text
    else:
        text_response = "Aggiungi una parola o una frase al campo input!"

A questo punto funziona! Aggiungiamo i crediti verso il creatore dell’API tramite un ulteriore widget di tipo Label assegnato alla variabile credits_label e il nostro programma è completo:

ascii_art_downloader

import tkinter as tk
import requests

window = tk.Tk()
window.geometry("900x550")
window.title("ASCII ART DOWNLOADER")
window.grid_columnconfigure(0, weight=1)


def download_ascii():
    if text_input.get():
        user_input = text_input.get()
        payload = {"text": user_input}
        response = requests.get("http://artii.herokuapp.com/make",
                                params=payload)
        text_response = response.text
    else:
        text_response = "Aggiungi una parola o una frase al campo input!"

    textwidget = tk.Text()
    textwidget.insert(tk.END, text_response)
    textwidget.grid(row=3, column=0, sticky="WE", padx=10, pady=10)

    credits_label = tk.Label(window, text="ascii art by artii.herokuapp.com")
    credits_label.grid(row=4, column=0, sticky="S", pady=10)


welcome_label = tk.Label(window,
                         text="Welcome! Aggiungi una parola o una frase da scaricare:",
                         font=("Helvetica", 15))
welcome_label.grid(row=0, column=0, sticky="N", padx=20, pady=10)

text_input = tk.Entry()
text_input.grid(row=1, column=0, sticky="WE", padx=10)

download_button = tk.Button(text="DOWNLOAD ASCII ART", command=download_ascii)
download_button.grid(row=2, column=0, sticky="WE", pady=10, padx=10)


if __name__ == "__main__":
    window.mainloop()