ioProgrammo N°21, Gennaio 1999 ©Copyright DIEMME Editori
IL TOOLKIT GRAFICO TK
Dopo aver appreso le tecniche fondamentali di programmazione con il linguaggio tcl passiamo all'introduzione del toolkit tk. In questo numero verranno presentati i comandi e le proprietà degli oggetti grafici principali
Tcl senza la sua estensione migliore, rappresentata dal toolkit grafico tk, si riduce ad un normalissimo linguaggio di scripting. Con tk saremo in grado di dare nuova vita agli scripts tcl.
Da un programma usa e getta , come molto spesso viene pensato uno script, passeremo a delle vere e proprie applicazioni grafiche. Il tutto avverrà sotto l'occhio vigile dell'ambiente X-Window di Linux, il quale ci permetterà di sbizzarrirci con finestre, label, pulsanti e disegni .
Facciamo subito una precisazione. Anche se avremo a che fare con oggetti ed eventi (in realtà gli oggetti si chiamano widgets, cioè strumenti), non ci troviamo di fronte ad un tipo di programmazione orientata gli oggetti. Assolutamente.
Il più che possiamo immaginare è pensare ad un approccio di tipo event-driver, ovvero a delle applicazioni che producono risultati in base a come gli oggetti rispondono a determinate azioni, per esempio ad un input da tastiera o ad un clik del mouse.
Altro aspetto importante da considerare è che l'ambiente su cui lavoreremo, e non mi riferisco direttamente ad X, è dotato di una propria gerarchia che si deve rigorosamente rispettare. Tanto per essere chiari, dobbiamo pensare al fatto che una finestra principale (finestra madre) può essere divisa in più aree, chiamate frame, (proprio come i frame dell'html) figlie della finestra madre principale e dotate di singoli strumenti che a loro volta sono figli dei frame.
Nella sintassi dei comandi tk questa gerarchia viene indicata con il termine "Pathname" proprio perchè è simile alla struttura gerarchica di un albero di directory (un path per l'appunto).
I comandi usati per costruire nuovi strumenti e i comandi usati per modificarne le proprietà hanno le seguenti forme generali:
nomestrumento pathname ?opzioni?
pathname opzione ?argomenti?
Una volta creato lo strumento è necessario posizionarlo nella finestra. A tal proposito si usano i comandi pack, place o grid a seconda della precisione con cui si vuole disporre gli oggetti.
Siamo pronti per iniziare. Non c'è modo migliore per studiare un nuovo linguaggio che partire con qualche esempio. Scriveremo un programma che visualizza, sulla base di alcuni parametri, le famose figure di Lissajous, in nome di un forse poco noto matematico francese del secolo passato.
Le funzioni oggetto della nostra prova sono:
x=cos(t*i); y=sin(z*i+3.14/z)
Vi ricordo inoltre che per ovvi motivi di spazio non sarà possibile affrontare nei dettagli tutti i comandi. Negli articoli verranno proposte le tecniche principali, sarete voi poi, manuale alla mano, a consolidare le vostre conoscenze.
IL WINDOWS MANAGER
La nostra applicazione grafica gira all'interno di un gestore di finestre (window manager), dal quale "eredita" alcune proprietà che definiscono l'aspetto di tutti gli elementi del programma (cornici, pulsanti, menu, etc).
Il window-manager più famoso di Linux è l' FVWM. Per i nostalgici di Windows95 alcuni volenterosi hanno scritto un secondo wm che ricorda l'aspetto delle finestre del sistema operativo di Microsoft.
Inoltre come se ciò non bastasse, taluni altri hanno creato il coloratissimo e sofisticato afterstep, un window manager di nuova generazione, ispirato all'ambiente grafico di NeXT, e che a seconda dei gusti può piacere o non piacere. Il wm originale è abbastanza carino e forse meno "pastoso" degli altri: ai guru di Linux piace così :).
In TK è possibile interagire con il gestore delle finestre mediante il comando wm (wm opzione nomefinestra ?argomenti?) con cui potremo definire la geometria della finestra e il suo titolo.
Le prime righe di codice sono quindi le seguenti
#!/usr/bin/wish
# richiamo la windowing-shell, l'interprete dei comandi TCL/TK
wm title . "FIGURE DI LISSAJOUS"
# imposto il titolo della finestra dell'applicazione\
nella gerarchia delle finestre questa è la finestra madre principale\
e il suo pathname è il punto .
wm resizable . 0 0
# imposto dimensioni fisse per la finestra\
in modo che non si possa ridimensionarla
. configure -bg white
# la finestra ha colore di sfondo bianco
Bene, le caratteristiche della finestra del programma sono impostate.
POSIZIONARE GLI STRUMENTI
Adesso facciamo mente locale su come vorremmo posizionare gli strumenti che compongono il nostro programma. Se ci aiuta possiamo fare uno schizzo con carta e matita. Dopo il disegno abbiamo deciso che il layout sarà quello di figura1.

Notiamo che ci servono due frame: f1 e f2, entrambi usati per contenere una label e un campo di input. In questo modo, forse, ci sarà più facile usare i comandi di posizionamento come pack e frame.
Abituiamoci a disegnare la posizione dei componenti su carta, poi quando saremo bravi lo potremo fare anche mentalmente.
ETICHETTE
La prima cosa che dovrà comparire subito sotto il titolo della finestra, sarà quindi la funzione di Lissajous usata nel programma. Ci serve una etichetta. Il comando label è proprio quello che fa per noi.
label .l1 -text "x=cos(t*i); y=sin(z*i+3.14/z)" -bg white
# label di nome .l1 e con sfondo bianco\
(-bg significa background)
Ora definiamo i frame e le loro etichette
frame .f1 -bg white
frame .f2 -bg white
label .f1.l1 -text t= -bg white
# label t= per indicare l'input del parametro t
label .f2.l2 -text z= -bg white
# label z= per indicare l'input del parametro z
CAMPI DI INPUT
Le due funzioni di Lissajous hanno bisogno di due parametri: t e z. Il modo più semplice per implementare la loro richiesta consiste nell'usare due campi di input. Usiamo lo strumento entry.
entry .f1.e1 -textvariable t
entry .f2.e2 -textvariable z
# l'opzione -textvariable indica che ai campi di input\
sono associate le variabili t e z che riceveranno\
i valori immessi
PULSANTI
L'ultimo strumento prima di passare ai comandi di pack è un pulsante la cui pressione con il mouse esegue il disegno di una figura di Lissajous.
button .b -text lissajous! -command {draw}
# definisce un pulsante di nome .b con etichetta lissajoo!\
-command indica che al pulsante è associato un comando, in\
questo caso viene eseguita la procedura draw
DOVE SI DISEGNA?
Ci serve un ultimo oggetto altrimenti dove mai verrà disegnato il grafico della nostra funzione?
In pratica abbiamo bisogno di un canvas, lo strumento usato per disegnare: proprio come un foglio da disegno.
canvas .c -width 300 -height 300 -bg black
# area da disegno larga 300 pixel e alta 300 pixel\
Il colore di sfondo è nero
IL COMANDO PACK
Visto che tutti gli strumenti sono stati definiti procediamo con il loro posizionamento. Osservando lo schema di figura 1, si nota come gli oggetti .l1, .f1, .f2, .b e .c siano disposti uno dopo l'altro in senso verticale. Solo gli oggetti contenuti nei due frame sono affiancati in senso orizzontale.
Il comando pack è l'ideale.
pack .l1 .f1 .f2 .b -pady 3
# 4 oggetti uno dopo l'altro in senso verticale\
separati da un margine di 3
pack .c
pack .f1.l1 -side left
pack .f1.e1 -side left
# label ed entry per il parametro t\
-side left indica che gli oggetti saranno\
disposti da sinistra a destra
pack .f2.l2 -side left
pack .f2.e2 -side left
# label ed entry per il parametro z\
per default il comando pack centra tutti gli\
oggetti rispetto alla finestra dell'applicazione
LISSAJOUS!
Bene. L'interfaccia grafica è stata costruita. Ora scriviamo la procedura draw che traccerà il grafico della funzione
proc draw {} {
# dichiaro draw. Non riceve parametri
global t
global z
# rendo visibili le variabili t e z \
anche all'interno della procedura
.c delete all
# pulisco il foglio da disegno\
il comando delete cancella tutti gli elemeti presenti\
sull'oggetto canvas
set i 0
while {$i <= 360} {
set x($i) [expr 150+130*cos([expr $t*$i])]
set y($i) [expr 150+130*sin([expr $z*$i+3.14/$z])]
incr i
}
# finchè i e minore o uguale a 360\
creo gli array x(i) e y(i) con le coordinate\
restituite dalle due funzioni di lissajous
set i 0
while {$i < [expr 360-1]} {
.c create line $x($i) $y($i) $x([expr $i+1]) $y([expr $i+1]) -fill green
update; # disegna il grafico in tempo reale
incr i
}
# dsegno il grafico delle funzioni\
Traccio delle line di colore verde\
da una coordinata a quella subito successiva
}
CONTROLLI
Il programma per valori di z diversi da zero funziona regolarmente. Se immetto 0, oppure anche una stringa, va brutalmente in crash. Ovvio, la funzione y=sin(z*i+3.14/z) contiene una divisione per z e se z è zero dà errore. Si deve controllare il valore di questo parametro e restituire un messaggio di avvertimento senza far bloccare il programma con un messaggio di errore poco simpatico. La stessa cosa vale per valori di t di tipo letterale. Possiamo aggiungere alla procedura draw, subito dopo i due comandi global, il seguente codice
if {![regexp {[1-9]} $t] && ![regexp {[1-9]} $z]} {
# si noti l'uso delle graffe per evitare l'effetto\
sostituzione
tk_messageBox -icon error\
-type ok \
-message "Valore non valido!\nImmettere un valore\n\
compreso tra 0.1 e 1.0"
return 1
# esce dalla procedura e ritorna 1
}
# se t=0 e se t z sono stringhe\
richiama una finestra di dialogo\
-icon usa un'icona predefinita per il messaggio\
-type il tipo di pulsante della finestra\
-message il messaggio
L'implementazione di una simile finestra di dialogo poteva essere fatta anche con il comando message, ma il metodo da noi adottato è più comodo.
I MENU
Nel programma sono stai usati gli strumenti entry per permettere l'inserimento di t e z.
Ciò ha comportato come abbiamo appena visto la scrittura di un po' di codice di controllo.
Nulla ci vieta però di obbligare la scelta dei parametri da un intervallo di valori preconfezionato.

In questo caso potremo predisporre un menu a tendina con una serie di radiobutton che permettano la selezione di un preciso valore.
Poiché le curve più belle si ottengono con dei valori di t e z che variano da 0.1 a 1.0 impostiamo due menu che tengano conto di questo intervallo.
Manteniamo tutto il resto del programma intatto mentre rimpiazziamo solo le due righe di codice del paragrafo "CAMPI DI INPUT" con le seguenti
menubutton .f1.e1 -text "valori di t" -menu .f1.e1.m1 -bd 3 -relief raise
menubutton .f2.e2 -text "valori di z" -menu .f2.e2.m2 -bd 3 -relief raise
# predispone i due pulsanti di menu associati alle variabili t e z\
-text è l'etichetta del menu\
-menu indica il menu contenente tutte le opzioni\
-bd 3 è lo spessore del bordo del menu\
-relief il tipo di bordo (raise=in rilievo)
menu .f1.e1.m1
menu .f2.e2.m2
# crea i due menu a tendina
set i 0.1
while {$i <= 1.0} {
.f1.e1.m1 add radio -label $i -variable t -value $i
.f2.e2.m2 add radio -label $i -variable z -value $i
# riempie i due menu a tendina\
con degli oggetti radiobutton\
ognuno dotato di label e valore
set i [expr $i+0.1]
}
set t 0.1
set z 0.1
# i due set impostano i valori di default\
delle due serie di radiobutton
Si potrebbe usare anche il comando tk_optionMenu con la seguente sintassi
tk_optionMenu .f1.e1 t 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0
tk_optionMenu .f2.e2 z 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0
set t 0.1
set z 0.1
# i due set impostano i valori di default\
dei due pulsanti di menu
Con questo metodo avremo due pulsanti con etichetta 0.1. Cliccandoci sopra si aprirà un menu a tendina che permetterà la selezione dei valori ammessi. Il valore scelto comparirà al posto della etichetta iniziale. Provare per credere.
LE SCALE
Un altro modo abbastanza elegante per proporre la selezione da un intervallo di valori consiste nell'uso dello strumento scale. Avete presente le levette degli equalizzatori di un HI-FI? Ebbene le scale sono esattamente la stessa cosa: un cursore che scorre orizzontalmente o verticalmente ed imposta un valore per una variabile. Man mano che il cursore scorre si può notare il cambiamento in tempo reale del valore per la variabile, mentre sotto la scala viene disegnato l'intervallo di valori consentito.
Sostituiamo quindi il codice dei due strumenti entry o dei due menu con questo
scale .f1.e1 -orient horizontal -length 100 -from 0 -to 1 \
-tickinterval 0.5 -variable t -resolution 0.1
scale .f2.e2 -orient horizontal -length 100 -from 0 -to 1 \
-tickinterval 0.5 -variable z -resolution 0.1
# -orient dispone la scala in senso orizzontale o verticale\
-lenght è la lunghezza della scala\
-from il valore di partenza della scala\
-to il valore finale\
-tickinterval la frequenza dei segnaposto per la scala\
-variable associa una variabile per i valori deala scala\
-resolution il valore di ogni passo della scala
Siamo arrivati ai saluti. Ci rileggiamo la prossima volta con tre argomenti tosti. Concluderemo lo studio deli strumenti di TK, impareremo a controllare gli eventi, e per finire in bellezza (l'articolo, non la serie) costruiremo una GUI per un gestore di database sql.
Ci sarà da divertirsi.
TEORIA E TECNICA
Il plugin di Tcl
Che ne dite di pubblicare in Internet il nostro programma? Lo possiamo fare grazie al plugin di tcl, compatibile con le versioni numero 3 e successive di Netscape ed Internet Explorer.
Il programma, giunto alla versione 2, è inoltre disponibile per i sistemi operativi più diffusi come Windows95/NT, Linux/Unix e Macintosh. Sorgenti ed eseguibili del plugin sono liberamente scaricabili all'indirizzo http://sunscript.sun.com/plugin/download.html.

Questa simpatica estensione ai browser più usati è fatta in modo da interpretare quasi tutti i comandi di tcl e di tk. Ho detto quasi tutti perchè per motivi di sicurezza alcuni comandi non sono supportati per default. Vediamo quali sono:
Comandi tcl disabilitati
cd, exec, fconfigure, file, glob, pwd, socket.
Comandi tk disabilitati
bell, clipboard, grab, menu, send, tk, toplevel, wm.
E' tuttavia possibile ripristinare tali comandi agendo nella sezione "policies" del file di configurazione plugin.cfg in cui sono contenute tutte le "politiche di sicurezza". In Linux tale file risiede nella directory ~/.netscape/tclplug/2.0/config mentre in windows è all'interno di \config nella directory di installazione.
Per il momento manteniamo la configurazione originale del file plugin.cfg, con l'accortezza di commentare i comandi wm perché non supportati.
La pagina html che richiama lo script tcl deve contenere il tag
<embed src="nomefile.tcl" width="largehezza in pixel" height="altezza in pixel">
Nel nostro caso "lissajous.htm" sarà
<html>
<head>
<title>FIGURE DI LISSAJOUS</title>
</head>
<body>
<embed src="lissajous.tcl" width="300" height="490">
</body>
</html>
Salviamo lo script con nome lissajous.tcl e da netscape apriamo il file htm appena creato. Il risultato in figura 3.
Data creazione HTML: Marzo 1999
Autore: Francesco Munaretto
E-mail: NoSpam@thank.you