Esercitazione 2 - Rendere interessante#

In Tutorial 1, abbiamo generato un progetto stub in grado di funzionare, ma non abbiamo scritto alcun codice. Diamo un’occhiata a ciò che è stato generato per noi.

Cosa è stato generato#

Nella cartella src/helloworld, si dovrebbero vedere 3 file: __init__.py, __main__.py e app.py.

__init__.py segna la cartella helloworld come un modulo Python importabile. È un file vuoto; il solo fatto che esista indica all’interprete Python che la cartella helloworld definisce un modulo.

__main__.py segna il modulo helloworld come un tipo speciale di modulo, un modulo eseguibile. Se si cerca di eseguire il modulo helloworld usando python -m helloworld, il file __main__.py è il punto in cui Python inizierà l’esecuzione. Il contenuto di __main__.py è relativamente semplice:

from helloworld.app import main

if __name__ == '__main__':
    main().main_loop()

Cioè, importa il metodo main dall’applicazione helloworld e, se viene eseguito come punto di ingresso, chiama il metodo main() e avvia il ciclo principale dell’applicazione. Il ciclo principale è il modo in cui un’applicazione GUI ascolta gli input dell’utente (come i clic del mouse e la pressione della tastiera).

Il file più interessante è app.py: contiene la logica che crea la finestra della nostra applicazione:

import toga
from toga.style import Pack
from toga.style.pack import COLUMN, ROW

class HelloWorld(toga.App):
    def startup(self):
        main_box = toga.Box()

        self.main_window = toga.MainWindow(title=self.formal_name)
        self.main_window.content = main_box
        self.main_window.show()

def main():
    return HelloWorld()

Esaminiamo questa riga per riga:

import toga
from toga.style import Pack
from toga.style.pack import COLUMN, ROW

Per prima cosa, importiamo il toolkit di widget toga e alcune classi e costanti di utilità legate allo stile. Il nostro codice non le usa ancora, ma le useremo a breve.

Quindi, definiamo una classe:

class HelloWorld(toga.App):

Ogni applicazione Toga ha una singola istanza toga.App, che rappresenta l’entità in esecuzione che è l’applicazione. L’applicazione può finire per gestire più finestre, ma per le applicazioni semplici ci sarà una sola finestra principale.

Quindi, definiamo un metodo startup():

def startup(self):
    main_box = toga.Box()

La prima cosa che il metodo di avvio fa è definire un riquadro principale. Lo schema di layout di Toga si comporta in modo simile all’HTML. Si costruisce un’applicazione costruendo un insieme di riquadri, ognuno dei quali contiene altri riquadri, o widget veri e propri. Si applicano poi degli stili a questi riquadri per definire il modo in cui consumeranno lo spazio disponibile della finestra.

In questa applicazione, definiamo una singola casella, ma non inseriamo nulla al suo interno.

Quindi, definiamo una finestra in cui inserire questa casella vuota:

self.main_window = toga.MainWindow(title=self.formal_name)

Questo crea un’istanza di toga.MainWindow, che avrà un titolo corrispondente al nome dell’applicazione. Una finestra principale è un tipo speciale di finestra in Toga: è una finestra strettamente legata al ciclo di vita dell’applicazione. Quando la finestra principale viene chiusa, l’applicazione esce. La finestra principale è anche la finestra che contiene il menu dell’applicazione (se si utilizza una piattaforma come Windows in cui le barre dei menu fanno parte della finestra)

Aggiungiamo quindi la nostra casella vuota come contenuto della finestra principale e istruiamo l’applicazione a mostrare la nostra finestra:

self.main_window.content = main_box
self.main_window.show()

Infine, definiamo un metodo main(). Questo è ciò che crea l’istanza della nostra applicazione:

def main():
    return HelloWorld()

Questo metodo main() è quello che viene importato e invocato da __main__.py. Crea e restituisce un’istanza della nostra applicazione HelloWorld.

Questa è l’applicazione Toga più semplice possibile. Inseriamo nell’applicazione alcuni contenuti personali e facciamo in modo che l’applicazione faccia qualcosa di interessante.

Aggiunta di contenuti propri#

Modificate la classe HelloWorld all’interno di src/helloworld/app.py in modo che abbia questo aspetto:

class HelloWorld(toga.App):
    def startup(self):
        main_box = toga.Box(style=Pack(direction=COLUMN))

        name_label = toga.Label(
            "Your name: ",
            style=Pack(padding=(0, 5))
        )
        self.name_input = toga.TextInput(style=Pack(flex=1))

        name_box = toga.Box(style=Pack(direction=ROW, padding=5))
        name_box.add(name_label)
        name_box.add(self.name_input)

        button = toga.Button(
            "Say Hello!",
            on_press=self.say_hello,
            style=Pack(padding=5)
        )

        main_box.add(name_box)
        main_box.add(button)

        self.main_window = toga.MainWindow(title=self.formal_name)
        self.main_window.content = main_box
        self.main_window.show()

    def say_hello(self, widget):
        print(f"Hello, {self.name_input.value}")

Nota

Non rimuovete le importazioni all’inizio del file o il main() in fondo. È necessario aggiornare solo la classe HelloWorld.

Vediamo nel dettaglio cosa è cambiato.

Stiamo ancora creando un riquadro principale, ma ora stiamo applicando uno stile:

main_box = toga.Box(style=Pack(direction=COLUMN))

Il sistema di layout integrato di Toga si chiama «Pack». Si comporta in modo molto simile ai CSS. Si definiscono gli oggetti in una gerarchia: in HTML, gli oggetti sono <div>, <span> e altri elementi DOM; in Toga, sono widget e box. Si possono poi assegnare stili ai singoli elementi. In questo caso, stiamo indicando che si tratta di un riquadro COLUMN, cioè un riquadro che consumerà tutta la larghezza disponibile e si espanderà in altezza man mano che si aggiungono contenuti, ma cercherà di essere il più corto possibile.

Successivamente, definiamo un paio di widget:

name_label = toga.Label(
    "Your name: ",
    style=Pack(padding=(0, 5))
)
self.name_input = toga.TextInput(style=Pack(flex=1))

Qui definiamo una Label e un TextInput. A entrambi i widget sono associati degli stili; l’etichetta avrà un padding di 5px a sinistra e a destra e nessun padding in alto e in basso. Il TextInput è contrassegnato come flessibile, cioè assorbirà tutto lo spazio disponibile nel suo asse di layout.

Il TextInput è assegnato come variabile di istanza della classe. Questo ci consente di accedere facilmente all’istanza del widget, che utilizzeremo tra poco.

Quindi, definiamo un riquadro per contenere questi due widget:

name_box = toga.Box(style=Pack(direction=ROW, padding=5))
name_box.add(name_label)
name_box.add(self.name_input)

Il nome_box è un box come quello principale, ma questa volta è un box ROW. Ciò significa che il contenuto sarà aggiunto orizzontalmente e cercherà di avere una larghezza il più possibile ridotta. Il riquadro ha anche un padding di 5px su tutti i lati.

Ora definiamo un pulsante:

button = toga.Button(
    "Say Hello!",
    on_press=self.say_hello,
    style=Pack(padding=5)
)

Il pulsante ha anche 5px di padding su tutti i lati. Definiamo anche un handler, un metodo da invocare quando il pulsante viene premuto.

Quindi, aggiungiamo la casella del nome e il pulsante alla casella principale:

main_box.add(name_box)
main_box.add(button)

Questo completa il nostro layout; il resto del metodo di avvio è come in precedenza: definire una MainWindow e assegnare il riquadro principale come contenuto della finestra:

self.main_window = toga.MainWindow(title=self.formal_name)
self.main_window.content = main_box
self.main_window.show()

L’ultima cosa da fare è definire il gestore del pulsante. Un gestore può essere qualsiasi metodo, generatore o co-routine asincrona; accetta come argomento il widget che ha generato l’evento e sarà invocato ogni volta che il pulsante viene premuto:

def say_hello(self, widget):
    print(f"Hello, {self.name_input.value}")

Il corpo del metodo è una semplice istruzione di stampa, che però interroga il valore corrente dell’input name e utilizza il suo contenuto come testo stampato.

Ora che abbiamo apportato queste modifiche, possiamo vederne l’aspetto avviando nuovamente l’applicazione. Come prima, useremo la modalità sviluppatore:

(beeware-venv) $ briefcase dev

[helloworld] Starting in dev mode...
===========================================================================

Si noterà che questa volta non installa le dipendenze. Briefcase è in grado di rilevare che l’applicazione è già stata eseguita in precedenza e, per risparmiare tempo, eseguirà solo l’applicazione. Se si aggiungono nuove dipendenze alla propria applicazione, ci si può assicurare che vengano installate passando l’opzione -r quando si esegue briefcase dev`.

Si dovrebbe aprire una finestra dell’interfaccia grafica:

Finestra Hello World Tutorial 2, su macOS

Se si inserisce un nome nella casella di testo e si preme il pulsante GUI, si dovrebbe vedere l’output nella console in cui è stata avviata l’applicazione.

Prossimi passi#

Ora abbiamo un’applicazione che fa qualcosa di più interessante. Ma funziona solo sul nostro computer. Impacchettiamo questa applicazione per la distribuzione. In Tutorial 3, impacchetteremo la nostra applicazione come un programma di installazione autonomo da inviare a un amico, a un cliente o da caricare su un App Store.