paper.gifioProgrammo N°22, Febbraio 1999 ©Copyright DIEMME Editori

fire.gif

OGGETTI EVENTI E DATABASE
All'appello mancano ancora pochi strumenti. ListBox, barre di scorrimento e finestre text completeranno il gruppo di oggetti più usati negli script tcl/tk. Impareremo a gestire gli eventi e alla fine vedremo come usare tutte le conoscenze fin qui acquisite per creare un database manager grafico
Nel numero precedente sono stati illustrati gli oggetti basilari del toolkit tk. Con pochi comandi avevamo dato vita ad una simpatica applicazione che disegnava le sinuose curve di Lissajous. Si è visto inoltre come gli oggetti più interessanti fossero proprio quelli che permettevano l'inserimento o la selezione dei parametri della funzione. Al fianco di tali oggetti (per gli assenti ricordo che si trattava di oggetti entry, menubutton e scale) si pone lo strumento listbox. Graficamente questo strumento si presenta come un rettangolo al cui interno c'è una lista di opzioni selezionabili e disposte in senso verticale una dopo l'altra. Sulla destra o sulla sinistra del rettangolo compare una barra di scorrimento che consente di raggiungere le opzioni più profonde e poi risalire all'inizio della lista.
linea.gif
LISTBOX
Dalla descrizione si intuisce che una listbox è costituita da due strumenti separati: lo strumento listbox vero e proprio (quello che contiene tutte le opzioni) e lo strumento scrollbar.

img_articoloPer comodità entrambi gli oggetti vengono impacchettati con il comando pack all'interno di un frame in modo che la listbox sia un oggetto unico. I comandi utilizzati per la creazione dei due strumenti sono
listbox pathName ?opzioni?
scrollbar pathName ?opzioni?
dove pathName (non mi stancherò mai di ricordarlo) è il nome dello strumento in riferimento alla gerarchia degli altri strumenti ai quali appartiene. A questo punto direi di sospendere le divagazioni teoriche e passare sl codice con un esempio. Ci proponiamo di scrivere un breve programma che consenta la scelta di un colore da un listbox e che automaticamente dipinga lo sfondo della finestra con il colore appena scelto. I nomi dei colori verranno scelti dal file rgb.txt della directory /var/X11R6/lib. Si noti che in Linux questo file è l'unico nostro riferimento per conoscere i nomi delle varie combinazioni dei tre colori fondamentali: rosso, verde e blu.

set rgb [open "/var/X11R6/lib/rgb.txt" r]
# assegno a rgb l'identificatore per il file\
rgb.txt che verrà aperto in sola lettura
set k 0
while {[gets $rgb line] != -1} {
# inizio a leggere i file riga per riga fino alla fine\
e assegno il contenuto di una riga alla variabile line
  set colore [split $line "\t"]
  # per ogni riga creo la lista colore i cui elementi\
  sono le porzioni della riga separate dal tab
  if {[regexp {^[aA-zZ]} [lindex $colore 2]] && \
     ![regexp { } [lindex $colore 2]]} {
    set color($k) [lindex $colore 2]
    incr k
  }
  # se il terzo elemento della lista colore inizia\
  con una lettera e se non contiene spazi, creo un\
  nuovo elemento dell'arry color. Questo array alla\
  fine del ciclo while conterrà tutti i nomi validi dei\
  colori del file rgb.txt 
}
frame .frame
# definisco un frame per la listbox
scrollbar .frame.scroll -command ".frame.list yview"
# definisco la scrollbar appartente al frame\
-command specifica il comando yview usato per\
lo scrolling verticale della listbox .frame.list
listbox .frame.list -yscroll ".frame.scroll set" \
	-width 20 -height 16 -setgrid 1
# definisco la listbox larga 20 caratteri e alta 16\
-yscroll associa il comando di scrolling con la scrollbar
pack .frame.list .frame.scroll -side left -fill y -expand 1
# affianco la listbox e la scrollbar
bind .frame.list <Double-1> {
    . configure -bg [selection get]
}
# associo l'evento "doppio clic del pulsante di sinistra\
del mouse" alla listbox. Il doppio clic sul nome del colore\
scelto cambia il colore di sfondo della finestra
set i 0
while {$i < $k} {
  .frame.list insert $i $color($i)
  incr i
}
# riempie la listbox con i nomi dei\
colori dell'array color
pack .frame -padx 20 -pady 20
#posiziona l'oggetto frame con un margine\
di 20 pixel dai bordi della finestra

linea.gif
GLI EVENTI
Nell'esempio precedente è stato usato un comando apparentemente misterioso :bind. Si tratta di una fondamentale istruzione di tk con la quale è possibile assegnare agli eventi collegati con uno strumento, l'esecuzione di una porzione di codice. In verità abbiamo già visto gli effetti di un evento, proprio quando è stato usato lo strumento button con l'opzione -command. In quel caso il bind di default del pulsante, trasformava un clic del mouse nell'esecuzione del comando specificato dall'argomento -command. Con bind, quindi, si possono superare gli eventi predefiniti e l'esempio che segue ne è una dimostrazione

frame .f -width 100 -height 100
bind .f <Button-1> {puts "Button 1 premuto"}
bind .f <Button-2> {puts "Button 2 premuto"}
bind .f <Button-3> {puts "Button 3 premuto"}
bind .f <ButtonRelease-1> {puts "Button 1 rilasciato"}
bind .f <Double-Button-1> {puts "Doppio click Button 1"}
bind .f <Any-Motion> {puts "Posizione %x,%y"}
pack .f

Abbiamo infatti associato ad un frame tutti gli eventi del mouse. Cliccando o spostano il mouse sopra il frame verranno stampate sulla finestra Xterm alcune note curiose.
linea.gif
XmSQL Users
Supponiamo di essere nei panni di un diligente amministratore di sistema Linux. Il nostro pallino è mantenere in perfetto ordine l'archivio degli account del server. Dai foglietti volanti, dimenticati in chissà quale cassetto, siamo passati all'agenda personale. Ora è giunto il momento di memorizzare i dati dei nostri utenti in un vero archivio elettronico. Coadiuvati da mSQL, msqltcl e tcl/tk ci proponiamo di scrivere una applicazione per X-Window che consenta le seguenti operazioni: memorizzare, aggiornare, cancellare e cercare i dati di un utente visualizzare e cancellare i dati di tutti gli utenti Ci serve il database. Entriamo nella directory /usr/local/Minerva/bin e creiamo il database "users" con il comando:
msqladmin create users
Poi con il comando "msql users" creiamo la tabella "users". Al prompt mSQL> si immette il comando \e. Verrà aperto un editor in cui scriveremo le seguenti linee:

img_articolo
create table users (
  cognome char(30)
  nome char(30)
  indirizzo char(50)
  telefono char(15)
  data char(8)
  username char(8)
  password char(10)
)

Salviamo il contenuto dell'editor e al prompt mSQL> immettiamo il comando \g che eseguirà la query "create table users".
Ottimo, il database è stato creato.
Ora passiamo al codice tcl/tk ma prima diamo un'occhiata al layout del nostro programma (Figura 2).
Iniziamo dalla geometria della finestra dell'applicazione


#!/usr/bin/wish
package require msqltcl
# include la libreria msqltcl
wm geometry . 320x450
# finestra del programma larga 320 e alta 450 pixel
wm title . "XmSQL Users"
# titolo della finestra
. configure -bg #aaffcc 
# colore di sfondo della finestra
wm resizable . 0 0
# finetra non ridimensionabile

Poi passiamo alla barra dei menu rappresentata dal frame .f1

frame .f1 -bd 2 -relief groove
# imposto la barra dei menu
menubutton .f1.m1 -text "File" -menu .f1.m1.mm1 
menubutton .f1.m2 -text "Help" -menu .f1.m2.mm2
# predispone i due menu \
-text e' l'etichetta del menu\
-menu indica il menu contenente tutte le opzioni
menu .f1.m1.mm1
menu .f1.m2.mm2
# imposta i due menu a tendina 
.f1.m1.mm1 add command -command lista_tutto -label "Lista tutto"
.f1.m1.mm1 add command -command cancella_tutto -label "Cancella tutto"
.f1.m1.mm1 add command -command exit -label "Esci" 
.f1.m2.mm2 add command -command help -label "Guida"
.f1.m2.mm2 add command -command help -label "Informazioni"
# riempie i due menu a tendina\
-command associa ad ogni voce di menu un comando tcl
pack .f1.m1 -side left 
pack .f1.m2 -side right 
pack .f1 -fill x
# posiziona i due menu, uno alla sinistra e l'altro\
alla detra della barra dei menu

Ora è il turno degli strumenti che permettono la ricerca per cognome, impacchettati nel frame .f2

frame .f2 -bg #aaffcc 
label .f2.l1 -text "Cognome:"  -bg #aaffcc 
entry .f2.e2 -bg white -textvariable cerca
button .f2.b2 -text "Cerca" -command {cerca}
pack .f2.l1 -side left 
pack .f2.e2 -side left 
pack .f2.b2 -side left
pack .f2

Proseguiamo con i frame centrali, quelli con le etichette ed i campi di input usati per immettere o modificare i dati degli utenti

frame .f3 -bg #aae5e5 -bd 3 -relief raised
# Il frame delle etichette
frame .f3.fsx -bg #aae5e5 
set etic {Cognome: Nome: Indirizzo: Telefono: \
         "Data login:" USERNAME: PASSWORD:}
set k 1
foreach i $etic {
  label .f3.fsx.l$k -text $i -bg #aae5e5
  pack .f3.fsx.l$k -anchor nw -pady 4 
  incr k
}
unset i
# e quello dei campi di input
frame .f3.fdx -bg #aae5e5 
set var {cognome nome indirizzo telefono \
        data username password}
set k 1
foreach i $var {
  entry .f3.fdx.e$k -bg white -textvariable $i 
  pack .f3.fdx.e$k -pady 2 -anchor nw 
  incr k
}
# ora li impacchettiamo
pack .f3.fsx -side left -ipadx 5 -padx 5 -pady 3
pack .f3.fdx -side left -padx 5 -pady 3
pack .f3

Ci serve un altro frame per posizionare i pulsanti "Nuovo", "Aggiorna" e "Cancella", ai quali verranno associate le procedure che permetteranno l'inserimento, la modifica e la cancellazione dei dati di un utente

frame .f4 -bg #aaffcc 
button .f4.b1 -text "Nuovo" -command nuovo  
button .f4.b2 -text "Aggiorna" -command aggiorna
button .f4.b3 -text "Cancella" -command cancella
pack .f4.b1 -side left -padx .4c
pack .f4.b2 -side left -padx .4c
pack .f4.b3 -side left -padx .4c 
pack .f4 -pady 3

Gli ultimi strumenti sono un text e due scrollbar, posizionati nel frame .f5. L'oggetto text di solito viene usato nella programmazione di editor poiché consente la scrittura di testo su un area della finestra. Noi invece lo useremo per l'output di tutti i dati del database.

frame .f5 
text .f5.text -wrap none \
     -xscrollcommand [list .f5.xscroll set] \
     -yscrollcommand [list .f5.yscroll set] \
     -bg #f0f0f0
scrollbar .f5.xscroll -orient horizontal \
          -command [list .f5.text xview]
scrollbar .f5.yscroll -orient vertical \
          -command [list .f5.text yview]
pack .f5.xscroll -side bottom -fill x 
pack .f5.yscroll -side right -fill y 
pack .f5.text -side left -fill x -expand 1
pack .f5

Benissimo. La nostra applicazione è quasi pronta. Ora ci concentriamo sulle procedure che eseguiranno le query sql.

set db [msqlconnect localhost]
msqluse $db users

proc lista_tutto {} {
# visualizza i dati di tutti gli utenti
  global db
  global cognome nome indirizzo telefono data username password
  .f5.text delete 0.0 end
  # cancella il contenuto del widget text
  msqlsel $db {select * from users order by cognome}
  msqlmap $db { cognome nome indirizzo telefono data username password } {
    .f5.text insert 0.0 \
    "$username $cognome $nome $indirizzo $telefono $data $password\n"
  }
}

proc cancella_tutto {} {
# svuota il db
  global db
  msqlsel $db "delete from users"
}

proc cerca {} {
# visualizza i dati di un utente con una\
ricerca per cognome
  global db
  global cerca
  set k 1
  while {$k <= 7} {
    .f3.fdx.e$k delete 0 end
    # cancella il contenuto di tutti i campi di input
    incr k
  }
  msqlsel $db "select * from users where cognome like '%$cerca%'"
  set result [msqlnext $db]
  set k 1
  foreach i $result {
    .f3.fdx.e$k insert 0 $i
    # riempie tutti i campi di input
    incr k
  }
}

proc nuovo {} {
# nuovi dati nel db
  global db
  global cognome nome indirizzo telefono data username password
  msqlsel $db "insert into users (cognome, nome, indirizzo, \
  telefono, data, username, password) values ('$cognome', '$nome', \
  '$indirizzo', '$telefono', '$data', '$username', '$password')"
}

proc aggiorna {} {
# modifica i dati di un utente
  global db
  global cognome nome indirizzo telefono data username password 
  msqlsel $db "update users set cognome = '$cognome', nome = '$nome',\
  indirizzo = '$indirizzo', telefono = '$telefono', data = '$data',\
  username='$username', password='$password' where username = '$username'"
}

proc cancella {} {
# cancella i dati di un utente
  global db
  global username 
  msqlsel $db "delete from users where username = '$username'"
}

proc help {} {
# il menu help dà alcune informazioni sul programma
  tk_messageBox -icon info -type ok -message \
  "XmSQL Users\nper i lettori di ioProgrammo"
}

Il nostro programma è pronto e sarà uguale a quello visualizzato in figura 3. Vi sarete accorti che nel codice mancano i controlli sugli errori. Li potreste scrivere voi, almeno sulle istruzioni sql: che ne dite?
linea.gif
TEORIA E TECNICA
La libreria mSQLTcl

I linuxiani veterani di ioProgrammo sicuramente ricorderanno mSQL. Si tratta del più conosciuto gestore di database relazionali SQL prodotto dalla Hughes Technologies.

img_articoloQualche volenteroso, viste le possibilità offerte da mSQL, ha deciso di creare una libreria di comandi per i programmatori tcl/tk in modo da permettere ad uno script tcl di interagire con un database sql. La nostra scelta è caduta su msqltcl-1.99. Si tratta della libreria più stabile, pienamente compatibile con le versioni 7.5/4.1 e 7.6/4.2 di tcl/tk. Purtroppo tale compatibilità viene meno nel caso in cui si decidesse di usare l'ultimo mSQL: il 2.0.3 (la compilazione si pianta di brutto lasciando poche speranze di intervento nei sorgenti in C). Lo stesso autore comunque ci avverte che msqltcl è stata testata con successo solo con mSQL 1.0.16. Poco male, l'esempio di questo articolo dimostra che anche con un database manager un po' più datato si possono ottenere ottimi risultati. D'altra parte le capacità di adattamento di noi linuxiani sono strepitose, vero? Vero!

Supponendo di aver già installato mSQL 1.0.16 (per default /usr/local/Minerva/bin) la compilazione di msqltcl-1.99 non dovrebbe essere problematica. E' sufficiente leggere con attenzione i file README ed INSTALL e fornire i parametri corretti al comando configure. Sei parametri specificano il path delle librerie di tcl, tk e mSQL, mentre il settimo abiliterà il caricamento dinamico della libreria. Dopo di che possiamo eseguire in successione i comandi make e make install. Se siamo stai bravi nella directory /usr/local/lib troveremo i file di libreria msqltcl, msqlwish e msqltcl.so. A questo punto non ci resta che informare tcl della presenza di una nuova libreria con l'esecuzione della seguente riga di codice

pkg_mkIndex /usr/local/lib msqltcl.so
Questo comando aggiorna il file indice delle librerie supportate da tcl. Successivamente saremo in grado di includere la libreria nello script usando il comando "package require msqltcl.so". Quest'ultima istruzione è valida solo nel caso in cui abbiamo compilato una libreria dinamica (il settimo parametro del comando configure), altrimenti bisogna usare il comando di sistema "load msqltcl.so". Nell'esempio di questo articolo useremo solo alcuni dei comandi di msqltcl e precisamente

msqlconnect ?hostname? (apre una connessione al server msqld e restituisce un handle).
msqluse handle dbname (abilita l'uso di un database associato all'handle).
msqlsel handle sql-statement (esegue una query sql sul database e restituisce il numero di record prodotti dalla query).
msqlnext handle (restituisce una lista con i valori dei campi del record successivo della query).
msqlmap handle binding-list script (itera l'esecuzione di codice tcl per ogni record della query. Ogni elemento della binding-list punta al nome di ogni campo della tabella prodotta dalla query).

fire.gif

Data creazione HTML: Marzo 1999
Autore: Francesco Munaretto
E-mail: NoSpam@thank.you
exclaim.gif