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 contrassegna 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()
Questo file svolge due funzioni:
- Importa il metodo
maindall'applicazionehelloworld. - Cioè, importa il metodo
maindall'applicazionehelloworlde, 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.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.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)
Dov'è la mia finestra?
Se si è commesso un errore nel codice, la finestra principale dell'applicazione potrebbe non essere visualizzata. In questo caso, è possibile digitare Ctrl+C nel terminale in cui è stata avviata l'applicazione. Questo arresterà l'applicazione. È quindi possibile correggere l'errore e riavviare l'applicazione.
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¶
Facciamo qualcosa di più interessante con la nostra applicazione HelloWorld.
Nota
Non rimuovete le importazioni all'inizio del file o il main() in fondo. È
necessario aggiornare solo la classe HelloWorld.
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(direction=COLUMN)
name_label = toga.Label(
"Your name: ",
margin=(0, 5),
)
self.name_input = toga.TextInput(flex=1)
name_box = toga.Box(direction=ROW, margin=5)
name_box.add(name_label)
name_box.add(self.name_input)
button = toga.Button(
"Say Hello!",
on_press=self.say_hello,
margin=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}")
Vediamo nel dettaglio cosa è cambiato.
Stiamo ancora creando un riquadro principale, ma ora stiamo applicando uno stile:
main_box = toga.Box(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.
Nota
Per usi più avanzati, Toga supporta anche un oggetto stile separato, che si usa in questo modo:
from toga.style import Pack
main_box = toga.Box(style=Pack(direction=COLUMN))
Successivamente, definiamo un paio di widget:
name_label = toga.Label(
"Your name: ",
margin=(0, 5),
)
self.name_input = toga.TextInput(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(direction=ROW, margin=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,
margin=5,
)
Il pulsante ha anche 5px di margine 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...
===========================================================================
(beeware-venv) $ briefcase dev
[helloworld] Starting in dev mode...
===========================================================================
(beeware-venv) C:\...>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:



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.
Prima di continuare, chiudere l'applicazione. Come per l'esercitazione 1, è
possibile farlo premendo il pulsante di chiusura della finestra
dell'applicazione, selezionando Quit/Exit dal menu dell'applicazione o digitando
Ctrl+C nel terminale in cui è stato eseguito briefcase dev.
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.