Mastodon

Quando si vuole sviluppare una applicazione che abbia una interfaccia grafica (GUI), prima di iniziare a scrivere il codice dell’applicazione vera e propria è bene prendere carta e penna, oppure usare Excalidraw, per progettare l’interfaccia (interface design) in modo tale che sia quanto più intuitiva possibile e facile da utilizzare.

Progetto dell’interfaccia

Supponiamo di voler sviluppare una applicazione che converta i watt in dBm.

Formula: dBm = 30 + 10 * Log10(watt)

Come prima cosa facciamo uno schizzo degli elementi che la nostra interfaccia grafica dovrà contenere.

Bene. Vediamoli ora in dettaglio:

  1. La finestra (window) principale.
  2. Una casella di testo (text) per inserire il valore dei watt da convertire in dBm (input dell’utente).
  3. Una etichetta (label) per l’unità di misura “watt”.
  4. Altre tre etichette per, rispettivamente, il testo “is equivalent to”, il risultato della conversione e l’unità di misura “dBm”.
  5. Un pulsante (button) per avviare il calcolo.

Disposizione elementi (layout)

Se osserviamo con attenzione la disposizione dei nostri 6 elementi (widgets), ci accorgiamo che possiamo immaginarli racchiusi all’interno di celle di una griglia (grid) composta da 3 colonne e 3 righe.

Ora siamo pronti per iniziare a scrivere il nostro codice.

Scrittura del codice

Avviamo l’applicazione Terminale (o Prompt dei comandi su Windows) ed avviamo l’interprete Tcl con il comando:

tclsh
---
%

In alternativa, se non disponiamo ancora sul nostro computer dell’interprete Tcl e del pacchetto Tk, possiamo sperimentare il codice online utilizzando l’ambiente Playground presente sul sito ufficiale del linguaggio Tcl.

Ora dobbiamo informare l’interprete Tcl che è nostra intenzione incorporare il pacchetto (package) Tk.

Per farlo utilizziamo il comando Tcl package.

package require Tk

L’interprete valuta questo comando ed appare la nostra finestra principale.

Questo primo comando non è strettamente necessario, ma è buona norma inserirlo anche, eventualmente, per specificare una determinata versione del pacchetto. Se si sta utilizzando l’ambiente Playground si deve saltare questo passaggio.

A questa finestra possiamo dare un titolo sfruttando il comando wm (window manager) che appartiene al pacchetto Tk.

wm title . "watt to dBm"

Istantaneamente vedremo aggiornarsi il titolo della nostra finestra.

Bene, ora dobbiamo creare la struttura (frame) della finestra che dovrà contenere tutti gli elementi (widgets) necessari.

Possiamo rappresentare la struttura della nostra interfaccia grafica con una mappa mentale in cui i rami che collegano i vari elementi stabiliscono delle relazioni di parentela.

Ogni elemento avrà un suo nome e una sua relazione di parentela.

La sequenza delle relazioni viene separata dal carattere punto (.frameName.widgetName).

Quando creiamo un elemento possiamo passargli degli argomenti che andranno a sovrascrivere quelli di default.

Per esempio possiamo definire i margini (padding) della struttura (frame) rispetto alla finestra principale (main window) secondo la sequenza: margine sinistro, margine superiore, margine destro e margine inferiore.

ttk::frame .c -padding "3 3 12 12"

La prima parte del comando (ttk::frame) specifica che vogliamo utilizzare il widget frame appartenente al nuovo pacchetto Tk (ttk).

Al frame assegniamo il nome c e lo dichiariamo figlio della finestra principale anteponendo il carattere punto (.c).

A questo punto ridefiniamo l’argomento padding assegnando i valori desiderati (-padding "3 3 12 12").

Inserimento del widget frame all’interno della finestra principale

Una volta creato il widget frame e datogli un nome e definiti alcuni argomenti, lo dobbiamo inserire all’interno della finestra principale utilizzando il comando grid.

grid .c -column 0 -row 0 -sticky nwes

Il primo argomento del comando grid specifica il nome del widget e la sua relazione di parentela .c.

Il secondo ed il terzo argomento specificano la posizione della cella all’interno della griglia nei termini di numero di colonna e numero di riga (-column 0 -row 0).

I numeri delle colonne incrementano da sinistra verso destra, mentre i numeri delle righe dall’alto in basso. Non è necessario che le numerazioni siano contigue, né è prestabilito un valore di partenza: ciò significa che la numerazione delle colonne potrebbe essere 4, 9, 11 e quella delle righe 3, 8, 876 e comunque avremo a che fare con una griglia avente tre colonne e tre righe.

L’ultimo argomento (-sticky nwes) specifica che le dimensioni del widget frame devono rimanere attaccate a nord, ad ovest, ad est e a sud rispetto alle dimensioni dell’elemento contenitore che, nel nostro caso, è la finestra principale.

In linea di principio sarebbe possibile disporre i vari widgets (text, label, ecc.) direttamente sulla finestra principale, ma è una buona pratica utilizzare come contenitore generale il widget frame.

Inserimento del widget entry all’interno del widget frame

Creiamo ora la casella di testo per permettere all’utente di inserire il valore dei watt.

grid [ttk::entry .c.watt -width 7 -textvariable watt] -column 2 -row 1 -sticky we

L’attributo -textvariable rende la variabile watt una variabile globale. Ogni volta che l’utente modificherà il contenuto del widget watt, la variabile globale watt verrà aggiornata di conseguenza.

Come è possibile notare, questa volta abbiamo realizzato in un’unica riga di codice sia la creazione del widget entry che il suo inserimento nella griglia.

Da notare che questo widget, come i prossimi, è figlio del widget frame e non della finestra principale.

Infine è stato ancorato a sinistra e a destra (-sticky we).

Inserimento degli altri widgets

Ed ora creiamo e posizioniamo gli altri widgets.

grid [ttk::label .c.dbm -textvariable dbm] -column 2 -row 2 -sticky we
grid [ttk::button .c.calc -text "Calculate" -command calculate] -column 3 -row 3 -sticky w
grid [ttk::label .c.wlbl -text "watt"] -column 3 -row 1 -sticky w
grid [ttk::label .c.islbl -text "is equivalent to"] -column 1 -row 2 -sticky e
grid [ttk::label .c.dlbl -text "dbm"] -column 3 -row 2 -sticky w

L’attributo -textvariable rende la variabile dbm una variabile globale. Ogni volta che il programma di conversione verrà eseguito, la variabile globale dbm verrà aggiornata di conseguenza così come il widget associato.

Ottimizzazione della interfaccia grafica

grid columnconfigure . 0 -weight 1; grid rowconfigure . 0 -weight 1
grid columnconfigure .c 0 -weight 1
foreach w [winfo children .c] {grid configure $w -padx 5 -pady 5}
focus .c.watt
bind . <Return> {calculate}

La prima riga di codice informa Tk che il widget frame deve espandersi se la finestra principale viene ridimensionata dall’utente.

La terza riga spazzola tutti i widget figli del widget frame valorizzando alcuni loro attributi.

La quarta riga specifica che il widget entry sia quello su cui si troverà il cursore al momento dell’avvio del programma.

L’ultima riga lega il pulsante alla funzione che andremo a scrivere

Scrittura della funzione

Con la scrittura della funzione di conversione da watt in dBm usciamo dalla parte relativa alla progettazione dell’interfaccia ed entriamo nella parte relativa al cuore del programma: ovvero al compito che il programma deve svolgere.

Nell’ambito dello sviluppo del software spesso si segue una specifica metodologia di progettazione: nel nostro caso seguiremo il pattern architetturale Model-View-Controller (MVC) che separa la parte relativa alla presentazione dei dati (View) da quella relativa al compito (Model). La parte che invece dovrà gestire l’input dell’utente prende il nome di Controller.

proc calculate {} {
   if {[catch {
       set ::dbm [format "%.1f" [expr {30 + (log10($::watt)*10)}]]
   }]!=0} {
       set ::dbm ""
   }
}

Ora soffermiamo la nostra attenzione sulla riga di codice evidenziata e cerchiamo di descrivere per passi cosa farà l’interprete Tcl:

  • esegue il comando log10 sul valore del widget watt: log10($::watt)
  • esegue il comando expr applicando la formula completa ci conversione da watt a dBm
  • esegue il comando format per fare in modo che il valore del risultato abbia una sola cifra decimale
  • esegue il comando set per assegnare alla variabile dbm (e quindi al widget omonimo) il risultato ottenuto

Avvio del ciclo degli eventi

Non resta ora che scrivere il codice per fare in modo che si avvii il ciclo degli eventi (event loop), il quale si occuperà di rendere visibili tutti i widgets e, soprattutto, permetterà al programma di mettersi in ascolto delle azioni dell’utente.

vwait forever

Questo comando non è strettamente necessario in quanto lo stesso pacchetto Tk si occupa di avviare il ciclo degli eventi una volta letto il programma.

Cosa manca?

Sebbene il programma che abbiamo scritto faccia ciò che ci eravamo ripromessi, non possiamo certo dire che sia completo.

Potremmo infatti aggiungere le seguenti parti:

  1. Gestire meglio l’input dell’utente al fine di evitare errori nel calcolo.
  2. Migliorare il layout nel caso in cui l’utente ridimensioni la finestra principale.
  3. Rendere il programma capace di funzionare su diversi sistemi operativi.

Nel prossimo articolo vedremo alcuni concetti di base del pacchetto Tk.