(Parte 1)
Come muovere i primi passi con Microsoft Visual Basic, lo
strumento da molti considerato ideale per la realizzazione di applicazioni di
piccola e media entità in ambiente Windows.
Un ringraziamento per la collaborazione a Maurizio Crespi
La nascita di Microsoft Visual Basic ha comportato una rivoluzione nel mondo
degli strumenti di sviluppo per Windows, lanciando un nuovo modo di programmare,
che vede per la prima volta prevalere l'uso del mouse nei confronti di quello
della tastiera.
Tale approccio, detto visuale, si basa sull'uso di oggetti, ovvero di
elementi attivi selezionabili su una barra degli strumenti (toolbox),
caratterizzati dall'essere descritti da un insieme di valori, detti
proprietà ed in grado di eseguire dei metodi, ovvero delle azioni
che ne possono influenzare lo stato.
Un'ulteriore particolarità degli oggetti consiste nella capacità di generare
degli eventi, a cui il programmatore può associare come risposta degli insiemi
di istruzioni, detti procedure (ad esempio quando viene premuto un
bottone il programmatore può associare la chiusura di una finestra).
Il linguaggio con cui esse sono scritte si basa sulle regole sintattiche
previste dal BASIC, di cui mantiene la semplicità.
La creazione di un form
Un'applicazione Visual Basic è costituita da una o più finestre, dette
form, sulle quali si trovano gli oggetti che formano l'interfaccia fra
l'applicazione e l'utente.
Su un form è possibile inserire pressoché qualsiasi elemento, sia che si
tratti di un'immagine, di un box di testo, di una lista o di un pulsante.
Il form principale dell'applicazione è generato automaticamente ogni volta
che si agisce sul menu File per creare un nuovo progetto ed è
visualizzato all'avvio dell'applicazione, di cui, in genere, rappresenta la
schermata principale.
La sua chiusura determina la fine dell'esecuzione del programma.
Si supponga di voler realizzare un'applicazione che, alla pressione di un
pulsante, faccia apparire in un punto preciso della finestra la scritta
"Benvenuto in Visual Basic". Il primo passo consiste nel posizionare sul
form gli elementi grafici necessari.
Si deve dapprima inserire un contenitore per il testo da visualizzare.
Visual Basic prevede due tipi di elementi adatti a questo scopo: si tratta
delle label e delle textbox.
Le prime, come indica il nome, sono dedicate alla visualizzazione di
"etichette", ovvero di porzioni di testo non modificabili dall'utente. Le
seconde, invece, consentono la digitazione di un testo al proprio interno e si
rivelano pertanto adatte all'acquisizione di dati.
Nel caso dell'esempio, dovendo visualizzare un testo modificabile solo dal
programma, è conveniente optare per una label, che può essere inserita
sul form rendendo visibile la barra degli strumenti di disegno, operazione
effettuabile agendo sulla voce Toolbox del menu View, selezionando
su di essa l'elemento contraddistinto dalla lettera "A" e tracciando con il
mouse un rettangolo sull'area destinata ad ospitare l'etichetta.
Al rilascio del pulsante sinistro del mouse appare l'elemento desiderato,
contenente la scritta "Label1".
Con questa operazione si crea il primo oggetto. Per adeguarlo alle proprie
esigenze, è necessario variarne le proprietà. Ciò è possibile facendo clic su di
esso con il mouse e visualizzando la finestra delle proprietà agendo sulla voce
Properties Window del menu View.
Appare così una tabella, in cui la colonna di sinistra contiene i nomi di
tutte le proprietà che descrivono l'elemento e la colonna di destra ne indica i
rispettivi valori. Nel caso dell'etichetta testuale appena creata, si nota che
due sue proprietà contengono il testo "Label1".
Esse sono denominate, rispettivamente, Name e Caption. Come è
facile immaginare, esse definiscono rispettivamente il nome dell'oggetto, ovvero
l'identificatore da usare ogniqualvolta si desideri fare riferimento ad esso e
il testo visualizzato nell'etichetta.
Si noti che Visual Basic provvede ad assegnare a queste proprietà un valore
predefinito. Spesso, però, è necessario fare in modo che la label
visualizzi un dato diverso.
Nell'esempio, l'etichetta deve visualizzare del testo solo dopo la pressione
del pulsante. È pertanto necessario che all'avvio del programma il suo contenuto
sia nullo. Affinché ciò avvenga, si deve fare clic con il mouse sulla colonna di
destra in corrispondenza della proprietà Caption e si deve sostituirne il
contenuto con uno spazio.
Sebbene sia possibile mantenere per gli oggetti i nomi predefiniti, è
consigliabile, al fine di rendere più facilmente leggibile il programma,
assegnare ad essi degli identificatori che permettano ad un altro programmatore,
o allo stesso autore dopo che sia trascorso un considerevole lasso di tempo, di
poterne facilmente comprendere la funzione. Ciò rende più semplice ogni
eventuale modifica successiva.
Nel caso dell'esempio, è pertanto conveniente assegnare all'etichetta il nome
lblMessaggio. Si noti l'uso del prefisso "lbl", abbreviazione di "label".
Il ricorso a un prefisso è assolutamente facoltativo, ma si rivela estremamente
utile nel caso di applicazioni complesse per fare sì che a colpo d'occhio sia
possibile comprendere il tipo di componente a cui si sta facendo riferimento.
La modifica del nome dell'oggetto avviene digitando il nuovo identificatore
nella colonna di destra della finestra Properties, in corrispondenza
della riga denominata Name.
In modo analogo a quanto visto per la label, è possibile inserire il pulsante
all'interno del form selezionando sulla toolbox l'apposita icona e tracciando un
rettangolo avente le dimensioni dell'oggetto da creare.
Queste ultime possono essere modificate in qualsiasi momento trascinando i
bordi dell'elemento con il mouse. Sempre con l'ausilio di questo strumento, è
possibile spostare qualsiasi oggetto in una diversa posizione. Agendo sulla
finestra delle proprietà, è possibile denominare l'oggetto btScrivi (ove
"bt" rappresenta l'abbreviazione di bottone) e associare alla proprietà
Caption, che contiene il testo che deve apparire sul pulsante, il
valore "Scrivi".
Gli eventi
Il form appare come visualizzato in Figura 1. A
questo punto è necessario rendere "attivo" il bottone, ovvero fare in modo che
al verificarsi dell'evento costituito dalla sua pressione sia eseguito del
codice che provveda a modificare il contenuto della label. A tal fine, occorre
selezionare il pulsante e fare su di esso un doppio clic con il mouse.
Si provoca così l'apertura di una finestra caratterizzata da una barra nella
parte superiore, in cui è possibile osservare due liste a scomparsa.
Quella posta a sinistra contiene il nome dell'oggetto, mentre in quella di
destra sono elencati tutti i possibili eventi che esso può generare.
Volendo fare in modo che la risposta avvenga in occasione della pressione del
pulsante, occorre selezionare nella lista l'evento Click. Si provoca così
la creazione della procedura btScrivi_Click, di cui è automaticamente
visualizzata l'intestazione nella parte inferiore della finestra, seguita dalla
frase End Sub, che indica la fine del blocco di codice.
Le istruzioni che devono essere eseguite in risposta all'evento vanno
inserite nello spazio compreso fra le righe generate automaticamente, avendo
l'accortezza di indicare un solo comando per linea. Per fare in modo che il
programma visualizzi per mezzo della label lblMessaggio il testo
"Benvenuto in Visual Basic", è necessario variare la proprietà
Caption di quest'ultima. La procedura da associare al pulsante è quindi
la seguente: Sub btScrivi_Click ()
lblMessaggio.Caption = "Benvenuto in Visual Basic"
End Sub
Si noti che il nome della proprietà segue quello dell'oggetto, da cui è
separato per mezzo di un punto.
Per verificare il funzionamento dell'applicazione, è necessario selezionare
la voce Start del menu Run, oppure premere il tasto F5.
È così possibile osservare che, pur digitando una sola riga di codice, è
stato creato un vero programma operante in ambiente Windows con un'interfaccia
grafica.
I metodi
Come si è già accennato, ogni oggetto è in grado di ricevere dei comandi che
possono provocare la variazione di alcune proprietà o la generazione di eventi.
Si tratta dei metodi. L'invocazione di un metodo avviene secondo la
sintassi:
<oggetto>.<metodo> [<parametro>, ...
,<parametro>]
Al nome dell'oggetto è necessario far seguire, separato da un punto, quello
del metodo e gli eventuali parametri.
Ad esempio, l'aggiunta della riga
lblMessaggio.Move 0,0
nella procedura vista in precedenza, provoca l'esecuzione del metodo
Move da parte dall'oggetto lblMessaggio. La coppia 0, 0
rappresenta l'insieme dei parametri.
L'effetto che si ottiene consiste nello spostamento dell'etichetta testuale
nell'angolo superiore sinistro del form, ovvero nel punto di cui i parametri
rappresentano le coordinate.
Le variabili
Anche Visual Basic, come tutti i linguaggi di programmazione, prevede l'uso
delle variabili, mediante le quali è possibile memorizzare dei valori testuali o
numerici in strutture a cui il programma può accedere grazie a un nome assegnato
loro in fase di creazione.
Una variabile è detta locale quando è definita all'interno di una
procedura; la sua creazione avviene quando si fa riferimento ad essa per la
prima volta, oppure quando è eseguita l'istruzione Dim, che presenta la
seguente sintassi:
Dim <nome> [As <tipo>]
in cui <nome> rappresenta il nome da assegnare alla variabile.
Esso non si sottrae alla regola valida per tutti gli identificatori, ivi
compresi i nomi degli oggetti, che impone loro di essere costituiti da delle
sequenze di caratteri alfabetici o numerici prive di spazi ed aventi per
iniziali delle lettere dell'alfabeto.
È inoltre possibile, anche se non indispensabile (come sottolineato dalla
presenza delle parentesi quadre), aggiungere all'istruzione Dim
un'indicazione del tipo di dato da creare. Visual Basic prevede numerosi tipi
standard. I più utilizzati sono integer, usato per rappresentare i dati
numerici interi compresi fra -32,768 a 32,767, string, che può contenere
delle sequenze (stringhe) alfanumeriche composte al massimo da circa 65500
caratteri, long, in grado di ospitare numeri interi compresi fra
-2,147,483,648 e 2,147,483,647, single e double, con cui è
possibile memorizzare numeri reali anche di notevole entità.
Se invece è omessa la dichiarazione del tipo, la variabile è definita
variant. Questo formato non è previsto dalla maggior parte dei linguaggi
di programmazione tradizionali. La sua caratteristica fondamentale è
l'universalità. Una variabile variant, infatti, può contenere dei dati
aventi qualsiasi formato.
È buona norma, tuttavia, non fare largo uso di strutture di questo tipo, in
quanto la loro gestione da parte dell'interprete è poco efficiente.
Una variabile locale può anche non essere dichiarata. In questo caso, la sua
creazione avviene la prima volta in cui si fa riferimento ad essa.
In assenza di dichiarazione, è necessario aggiungere al nome un
identificatore di tipo, costituito da un carattere. In caso di sua omissione, la
variabile è creata di tipo Variant. I principali identificatori di tipo
sono i seguenti: $ (String), % (Integer), & (Long), !
(Single), # (Double).
Ad esempio, è possibile creare una variabile di tipo numerico intero ed
assegnarle il valore 5 sia scrivendo
Dim A as Integer
A = 5
sia con la riga
A% = 5
Dopo l'esecuzione dell'ultima istruzione presente nella procedura in cui sono
state create, le variabili locali sono automaticamente distrutte.
Per fare in modo che il loro valore sia conservato anche in occasione delle
chiamate successive, è necessario dichiarare le variabili sostituendo
Static alla parola chiave Dim.
In alcuni casi, si rivela necessario fare in modo che una variabile non sia
mai distrutta e sia accessibile anche dalle altre procedure presenti in un form.
In questo caso, è necessario selezionare la sezione General nella
lista a discesa posta nella parte superiore sinistra della finestra contenente
il codice associato al form, la voce Declarations nella lista di destra e
dichiarare la variabile per mezzo dell'istruzione Dim.
Quando invece si presenta la necessità di fare in modo che una struttura sia
accessibile da tutte le procedure presenti nell'applicazione, indipendentemente
dal form in cui esse si trovano, è necessario utilizzare per la dichiarazione la
parola chiave Global.
Una variabile dichiarata in questo modo è detta globale. La sua
dichiarazione è impossibile all'interno di un form. Essa deve essere effettuata
in un modulo, ovvero in un file di testo caratterizzato dall'estensione .BAS,
che è aggiunto al progetto selezionando la voce Add Module del menu
Project. La riga di codice rappresenta un esempio di dichiarazione di una
variabile globale:
Global NomeCognome as String
Un esempio di utilizzo di una variabile
Si consideri l'applicazione precedentemente realizzata. Si desidera
modificarla per creare un semplice strumento in grado di visualizzare il tempo
trascorso dall'ultima pressione del pulsante btScrivi.
Per fare ciò, è necessario aggiungere al form un timer. Si tratta di
un oggetto in grado di generare un evento ad intervalli regolari, la cui
frequenza è decisa dal programmatore.
Per inserire tale elemento, occorre selezionare sulla toolbox l'icona
a forma di cronografo e disegnare un rettangolo sul form. Si provvede poi a
selezionare il nuovo oggetto, a visualizzare la finestra Properties e ad
assegnare il valore 1000 alla proprietà Interval.
In tal modo si definisce il periodo (in millisecondi) che intercorre fra due
eventi. L'impostazione effettuata provoca la generazione di un evento ogni
secondo.
Anche in questo caso, è possibile associare al verificarsi di questi eventi
un insieme di istruzioni da eseguire. Ciò è possibile facendo doppio clic
sull'oggetto.
Si provoca così l'apertura della finestra del codice sorgente e la creazione
della procedura Timer1_Timer, in cui è possibile
scrivere:
Sub Timer1_Timer ()
NumSecondi = NumSecondi + 1
lblMessaggio.Caption = Str$(NumSecondi)
End Sub
Essa ha lo scopo di incrementare di un'unità il valore della variabile
NumSecondi, usata per conteggiare i secondi e di visualizzare tale valore
all'interno della label. A tal fine, il risultato del conteggio è convertito in
testo tramite la funzione Str$ ed è assegnato alla proprietà
Caption dell'oggetto che provvede a visualizzarlo sullo schermo.
Per consentire l'azzeramento del contasecondi in seguito alla pressione del
pulsante btScrivi, è necessario modificare la procedura associata a tale
evento.
Ciò è possibile facendo doppio clic sul bottone e sostituendo le istruzioni
precedentemente inserite con le seguenti:
Sub btScrivi_Click ()
NumSecondi = 0
End Sub
Affinché la variabile NumSecondi possa essere utilizzata da entrambe
le procedure, è necessario che sia accessibile in tutto il form. Come visto in
precedenza, perché ciò sia possibile, si deve digitare nella sezione
Global/Declarations la riga
Dim NumSecondi as Long
Conclusioni
Come dimostrato dagli esempi sopra descritti, si può realizzare
un'applicazione funzionante in ambiente Windows in un batter d'occhio,
semplicemente facendo uso del mouse e scrivendo un numero davvero ridotto di
righe di codice.
È proprio grazie alla notevole facilità d'uso, oltre che all'affidabilità
dell'interprete di cui è dotato, che Visual Basic è da molti considerato lo
strumento ideale per la realizzazione di programmi di piccola e media (Parte 2)
La seconda parte del corso introduttivo alla programmazione in
Visual Basic si pone come scopo di insegnare l'uso della struttura di controllo
If e delle proprie varianti.
Dopo aver studiato come si creano delle semplici applicazioni ed aver preso
confidenza con i tipi di dati fondamentali, è ora giunto il momento di occuparsi
delle strutture di controllo, mediante le quali si possono scrivere dei
programmi in grado di comportarsi in modo diverso a seconda dei valori assunti
dai dati da elaborare.
Le soluzioni degli esercizi proposti nella prima
parte
Prima di affrontare nuovi argomenti, è bene rivedere ciò che è stato oggetto
di studio nella scorsa puntata. Lo spunto è offerto dalla correzione degli
esercizi in essa proposti.
Il primo esercizio
Il primo esercizio prevede la creazione di un programma in grado di simulare
il funzionamento di un interruttore mediante due pulsanti, riportanti
rispettivamente le scritte "ON" e "OFF" e un'etichetta di testo, nella quale
devono essere visualizzate in alternativa le indicazioni "Acceso" e "Spento".
La prima fase consiste nel creare un nuovo progetto e nell'inserire
all'interno del form principale i tre elementi grafici. Si provvede poi, per
mezzo dell'apposita finestra, ad assegnare ad essi i valori della proprietà
Caption, affinché sui pulsanti appaiano le scritte "ON" e "OFF".
L'interfaccia appare come nella Figura 1.
È buona norma assegnare anche dei nomi agli oggetti. Ciò comporta
l'impostazione per ognuno di essi della proprietà Name. Si supponga di
utilizzare i nomi btnON, btnOFF, per i pulsanti e lblValore
per l'etichetta testuale.
L'ultimo passo consiste nello scrivere il codice da associare alla pressione
dei bottoni. Essi devono semplicemente variare il testo visualizzato dalla
label, ovvero modificare la sua proprietà Caption. Dopo aver premuto due
volte il tasto sinistro del mouse sul pulsante btnON ed aver quindi
aperto la finestra che ospita il codice di risposta all'evento Click, è
necessario scrivere quanto segue:
Private Sub btnON_Click()
lblValore.Caption = "Acceso"
End Sub
Si noti che l'indicazione Private, il cui significato sarà analizzato
in uno dei prossimi numeri, deve essere omessa sulle versioni di Visual Basic
precedenti la 4.0. In modo perfettamente analogo si può completare
l'applicazione associando al pulsante btnOFF la
procedura:
Private Sub btnOFF_Click()
lblValore.Caption = "Spento"
End Sub
Il secondo esercizio
Nel secondo esercizio è richiesta la modifica del funzionamento di un
programma che simula un cronografo. L'interfaccia si compone di un pulsante di
azzeramento e di una label che visualizza il numero dei secondi trascorsi. Si
desidera fare in modo che l'indicazione del tempo sia rappresentata nel formato
HH:MM:SS.
Il conteggio avviene per mezzo di un timer regolato, mediante l'impostazione
della proprietà Interval al valore 1000, in modo da generare un evento ogni
secondo. La procedura da modificare è pertanto quella che gestisce gli eventi
Timer da esso prodotti. Devono essere eseguite delle divisioni successive per
determinare il numero delle ore, dei minuti e dei secondi. Il codice diventa
pertanto il seguente:
Private Sub Timer1_Timer()
Dim Ore, Minuti, Secondi, Resto As Integer
Dim StringaOre, StringaMinuti, StringaSecondi,
Tempo As String
NumSecondi = NumSecondi + 1
Ore = Int(NumSecondi / 3600)
Resto = NumSecondi - (Ore * 3600)
Minuti = Int(Resto / 60)
Secondi = Resto - (Minuti * 60)
StringaOre = Str$(Ore)
StringaMinuti = Str$(Minuti)
StringaSecondi = Str$(Secondi)
Tempo = StringaOre + ":"
Tempo = Tempo + StringaMinuti + ":"
Tempo = Tempo + StringaSecondi
lblMessaggio.Caption = Tempo
End Sub
Si noti che, per estrarre la parte intera del risultato di ogni divisione, si
è fatto ripetutamente uso della funzione Int.
Il numero delle ore è calcolato dividendo per 3600 il totale dei secondi,
contenuto nella variabile globale NumSecondi. Il resto è poi diviso per
60 al fine di quantificare i minuti. Questa operazione genera un nuovo resto,
che rappresenta il numero di secondi. I valori ottenuti sono successivamente
trasformati in sequenze alfanumeriche e copiati nella variabile Tempo,
anch'essa di tipo String, avendo cura di interporre fra di loro il
carattere ":". Si noti che, per concatenare le stringhe, si fa uso
dell'operatore di addizione "+". Il valore della variabile Tempo è infine
visualizzato per mezzo della label.
La struttura If
Avviando l'applicazione sopra descritta, è possibile osservare che il
contenuto dell'etichetta non è esattamente espresso nel formato HH:MM:SS.
Infatti, l'indicazione appare come in Figura 2, ossia
i valori indicanti il numero di ore, minuti e secondi non sono obbligatoriamente
composti da due cifre. Si supponga di voler modificare il programma affinché
provveda ad anteporre uno zero non significativo al contenuto delle variabili
StringaOre, StringaMinuti e StringaSecondi ogniqualvolta esse risultino composte
da una sola cifra. L'applicazione, in questo caso, deve essere in grado di
riconoscere una condizione, nella fattispecie la presenza di un valore inferiore
a dieci e di intervenire anteponendo uno zero solo quando essa è verificata. A
differenza degli esempi fino ad ora esaminati, il flusso delle istruzioni
all'interno della procedura non è più rigidamente definito, ma può variare in
base al verificarsi di alcune condizioni. Il programma deve pertanto essere in
grado di prendere delle decisioni. Come la quasi totalità dei linguaggi di
programmazione, Visual Basic prevede a tal fine la struttura If, caratterizzata
dalla seguente sintassi:
If <condizione> Then
<istruzioni da eseguire se la condizione è vera>
[Else
<istruzioni da eseguire se la condizione è falsa>]
End If
La condizione può essere rappresentata da pressoché qualsiasi espressione
booleana. Si ricorda che un'espressione è così detta se può assumere solo due
valori distinti, cioè vero o falso. Se essa è verificata,
l'interprete esegue il blocco di istruzioni indicato fra le parole chiave
Then e Else. In caso contrario, è eseguito il secondo gruppo di
comandi. Si noti che quest'ultimo può anche essere assente. In tal caso, la
parola chiave Else non va utilizzata e non è eseguito alcun codice in
caso di mancata verifica della condizione.
La frase End If delimita la struttura. Tutte le istruzioni che la
seguono sono eseguite in modo indipendente dal valore dell'espressione booleana.
La procedura da associare all'evento Timer può essere pertanto modificata
come indicato:
Private Sub Timer1_Timer()
Dim Ore, Minuti, Secondi, Resto As Integer
Dim StringaOre, StringaMinuti, StringaSecondi,
Tempo As String
NumSecondi = NumSecondi + 1
Ore = Int(NumSecondi / 3600)
Resto = NumSecondi - (Ore * 60)
Minuti = Int(Resto / 60)
Secondi = Resto - (Minuti * 60)
If Ore < 10 Then
StringaOre = "0" & Ore
Else
StringaOre = Str$(Ore)
End If
If Minuti < 10 Then
StringaMinuti = "0" & Minuti
Else
StringaMinuti = Str$(Minuti)
End If
If Secondi < 10 Then
StringaSecondi = "0" & Secondi
Else
StringaSecondi = Str$(Secondi)
End If
Tempo = StringaOre + ":"
Tempo = Tempo + StringaMinuti + ":"
Tempo = Tempo + StringaSecondi
lblMessaggio.Caption = Tempo
End Sub
Essa si differenzia dalla precedente per l'introduzione di tre strutture di
controllo pressoché analoghe. Si osservi ad esempio la prima; la condizione è
vera se la variabile Ore contiene un numero inferiore a dieci. In questo
caso, alla variabile StringaOre è associato un testo composto dal
carattere "0" e dal valore contenuto nella variabile Ore. Si noti che in
questo caso non si è fatto uso della funzione Str$. Quando un dato
numerico è assegnato ad una variabile di tipo stringa, infatti, la conversione
di formato è eseguita automaticamente, a condizione che il codice utilizzato non
possa generare delle ambiguità. È per questo motivo che la concatenazione delle
stringhe è stata effettuata mediante il simbolo "&", anziché con l'operatore
di addizione che, essendo applicabile anche ai valori numerici, avrebbe reso
l'interprete incapace di dedurre le reali intenzioni del programmatore. Il
carattere "&" può essere utilizzato esclusivamente per unire due stringhe
alfanumeriche e quindi pone al riparo da ogni dubbio.
La funzione Val
Nel seguito di questa trattazione si farà uso delle caselle di testo
(textbox). Esse risultano estremamente simili alle label, ma si
differenziano da esse in quanto rendono possibile la modifica del proprio
contenuto all'utente finale. Sono pertanto utilizzate ogniqualvolta si presenti
la necessità di richiedere un dato alfanumerico all'utilizzatore del programma.
Per verificare o modificare il loro contenuto è necessario accedere alla
proprietà Text, che è sempre di tipo String. La sua conversione in
una valore numerico, quando possibile, può essere effettuata per mezzo della
funzione Val, la cui sintassi è:
<var1> = Val(<var2>)
dove var1 è di tipo numerico e var2 rappresenta una stringa.
Occorre notare che la funzione esegue una scansione del testo fornitole come
parametro a partire dal carattere posto all'estrema sinistra. Il controllo
termina quando è incontrato un elemento non numerico. Pertanto, la condizione
Val("1234") = Val("1234abc")
è verificata. Inoltre, è vera anche la condizione:
Val("abcde") = 0
Facendo tesoro di ciò che è stato appena appreso, si provi a realizzare una
semplice applicazione in grado di visualizzare in una label il valore assoluto
di un numero posto in una casella di testo, senza far uso della funzione
Abs. Il calcolo può essere eseguito in corrispondenza della pressione di
un pulsante.
Le strutture If nidificate
Si supponga di voler realizzare un programma in grado di calcolare il valore
massimo fra due numeri forniti in ingresso mediante delle caselle di testo e di
visualizzare il risultato in una label. L'interfaccia è rappresentata nella
Figura
3. Si supponga di assegnare i nomi
txtPrimoValore e txtSecondoValore alle textbox. La procedura da
associare come risposta alla pressione del pulsante di calcolo è la
seguente:
Private Sub btCalcola_Click()
Dim Valore1, Valore2 As Double
Dim Risultato As String
Valore1 = Val(txtPrimoValore.Text)
Valore2 = Val(txtSecondoValore.Text)
If Valore1 > Valore2 Then
Risultato = "Valore massimo: " & Valore1
Else
If Valore1 < Valore2 Then
Risultato = "Valore massimo: " & Valore2
Else
Risultato = "I valori sono uguali"
End If
End If
lblMessaggio.Caption = Risultato
End Sub
Dopo aver copiato i dati in ingresso in variabili numeriche, l'applicazione
procede al confronto fra i valori. Dapprima il programma verifica se prevale il
contenuto della variabile Valore1 rispetto a quello di Valore2. Se
la condizione è verificata, il massimo è copiato nella variabile
Risultato, che costituisce il testo da visualizzare per mezzo della label
lblMessaggio. Altrimenti, è eseguito l'altro blocco della struttura
If, ovvero è verificata la prevalenza del secondo valore rispetto al
primo. In caso negativo, è segnalata l'uguaglianza dei dati in ingresso. È
possibile osservare che è stata posta una struttura If all'interno di
un'altra. L'annidamento di più blocchi di programma è una pratica molto comune
quando si fa uso di linguaggi moderni come Visual Basic. Si noti che se fosse
stato necessario eseguire il confronto fra un numero più elevato di valori,
l'uso di strutture nidificate avrebbe influenzato negativamente la leggibilità
del programma. Per ridurre tali effetti negativi è possibile utilizzare la
parola chiave ElseIf, che permette di mantenere un'organizzazione a due
livelli anche in presenza di un numero elevato di confronti. Ecco come si
presenta la procedura modificata:
Private Sub btCalcola_Click()
Dim Valore1, Valore2 As Double
Dim Risultato As String
Valore1 = Val(txtPrimoValore.Text)
Valore2 = Val(txtSecondoValore.Text)
If Valore1 > Valore2 Then
Risultato = "Valore massimo: " & Valore1
ElseIf Valore1 < Valore2 Then
Risultato = "Valore massimo: " & Valore2
Else
Risultato = "I valori sono uguali"
End If
lblMessaggio.Caption = Risultato
End Sub
L'uso delle parole riservate ElseIf permette di aumentare a piacimento
il numero di condizioni da controllare all'interno della stessa struttura.
Se nessuna di esse risulta verificata, allora è eseguito il codice
specificato dopo la parola chiave Else. Per verificarne l'utilità, si
provi a realizzare un'applicazione in grado di visualizzare un messaggio
indicante se il dato inserito in una textbox rappresenta il nome di uno dei
giorni della settimana.
Conclusioni
Le strutture di controllo, oggetto di questa trattazione, danno alle
applicazioni la flessibilità necessaria per svolgere operazioni non banali. Per
questo motivo, la loro conoscenza è di importanza fondamentale. Al lettore è
pertanto rivolto l'invito ad esercitarsi nell'uso di tali costrutti. Buon
lavoro, dunque! (Parte 3)
Il corso dedicato alla programmazione con Microsoft Visual
Basic continua con l'analisi della struttura di controllo Select Case e degli
operatori logici fondamentali
Spesso si rivela necessario sviluppare dei programmi caratterizzati dalla
capacità di eseguire istruzioni diverse in base al valore assunto da una o più
variabili. A tal fine, Visual Basic prevede la struttura di selezione multipla
Select Case, al centro dell'attenzione in questa terza parte del corso
dedicato alla programmazione con il noto strumento Microsoft. Continua pertanto
lo studio delle strutture di controllo, delle quali difficilmente
un'applicazione può fare a meno.
Le soluzioni agli esercizi della scorsa puntata
Prima di affrontare lo studio di nuovi argomenti, è opportuno rivedere quanto
esposto nello scorso numero. Come di consueto, lo spunto è offerto dalla
correzione degli esercizi in esso proposti.
Primo esercizio
Il primo esercizio prevede la realizzazione di un programma in grado di
leggere un numero mediante una textbox e di visualizzarne il valore assoluto. A
tal fine, dopo aver creato un nuovo progetto, occorre aggiungere al form
principale la casella di testo, a cui è possibile assegnare il nome
txtNumero, nonché il pulsante btnCalcola, a cui è affidata la
funzione di provocare l'aggiornamento del contenuto della label
lblRisultato con il frutto dell'elaborazione. Ciò è ottenuto per mezzo
della seguente procedura:
Private Sub btnCalcola_Click()
Dim StringaNumero As String
Dim Numero As Integer
StringaNumero = txtNumero.Text
Numero = Val(StringaNumero)
If Numero > 0 Then
lblRisultato.Caption = "Valore assoluto: " & Numero
Else
lblRisultato.Caption = "Valore assoluto: " & Str(-1 *
Numero)
End If
End Sub
Il contenuto della textbox è posto nella variabile StringaNumero, che
è convertita in un dato numerico per mezzo della funzione Val. Per fare
in modo che il valore sia mantenuto invariato qualora risulti positivo, o ne sia
calcolato l'opposto altrimenti, si impone il ricorso ad una struttura If,
associata alla condizione
Numero > 0
Quando essa è verificata, il valore numerico fornito in ingresso è
visualizzato senza subire modifiche. In caso contrario, la procedura provvede ad
effettuare l'inversione di segno e ad assegnare il risultato, opportunamente
trasformato in una stringa, alla label.
Secondo esercizio
Anche il secondo esercizio prevede la creazione di un programma in grado di
ricevere un valore in ingresso per mezzo di una textbox e di visualizzare un
risultato mediante una label. L'applicazione da sviluppare deve controllare che
il dato inserito dall'utente rappresenti uno dei nomi dei giorni della
settimana. Si desidera fare in modo che la verifica sia svolta automaticamente
ogniqualvolta il contenuto della casella di testo subisca una variazione. Questa
condizione è segnalata dall'oggetto, a cui si suppone di aver assegnato il nome
txtTestoDigitato, con la generazione dell'evento Change. Per
associare ad esso del codice, è sufficiente fare doppio clic con il mouse sulla
textbox. In tal modo si crea la procedura txtTestoDigitato_Change, di
seguito descritta.
Private Sub txtTestoDigitato_Change()
Dim Testo As String
Dim GiornoSettimana As Integer
Testo = UCase(txtTestoDigitato.Text)
If Testo = "LUNEDI" Then
GiornoSettimana = True
ElseIf Testo = "MARTEDI" Then
GiornoSettimana = True
ElseIf Testo = "MERCOLEDI" Then
GiornoSettimana = True
ElseIf Testo = "GIOVEDI" Then
GiornoSettimana = True
ElseIf Testo = "VENERDI" Then
GiornoSettimana = True
ElseIf Testo = "SABATO" Then
GiornoSettimana = True
ElseIf Testo = "DOMENICA" Then
GiornoSettimana = True
Else
GiornoSettimana = False
End If
If GiornoSettimana Then
lblRisultato.Caption = "E' un giorno della settimana"
Else
lblRisultato.Caption = "Non è un giorno della settimana"
End If
End Sub
Si noti l'uso della funzione UCase, che provvede a restituire la
stringa derivante dalla conversione in lettere maiuscole del dato in ingresso.
Ciò permette di verificare la corrispondenza del testo digitato dall'utente al
nome di un giorno della settimana in modo indipendente dall'uso che è stato
effettuato durante la sua scrittura delle lettere maiuscole. Il numero dei
confronti, in questo caso, risulta elevato. Si ricorre pertanto all'uso della
parola chiave ElseIf, al fine di semplificare la struttura di controllo,
che in tal modo risulta organizzata in un unico livello. Essa provvede alla
modifica della variabile intera GiornoSettimana, che può assumere
solamente due valori. Il primo, corrispondente al valore logico True, è
assegnato nel caso in cui il confronto con uno dei nomi dei giorni della
settimana abbia fornito un esito positivo. In caso contrario, la variabile
prende il valore False. Tale dato è oggetto di valutazione nella seconda
struttura If che visualizza, facendo uso di una label a cui è stato
assegnato il nome lblRisultato, un messaggio indicante l'esito della
verifica. Si noti che la condizione è rappresentata dalla sola variabile
GiornoSettimana, a cui non è associato alcun operatore di confronto. Ciò
a prima vista può lasciare sconcertati. In realtà, è perfettamente lecito, in
quanto è noto che la condizione che regola una struttura If deve essere
di tipo booleano, ovvero deve poter assumere esclusivamente i valori True
o False. Proprio come la variabile GiornoSettimana.
La struttura Select Case
L'esercizio precedente rappresenta un esempio di come una struttura If
possa assumere dimensioni notevoli. In questi casi, la leggibilità del programma
rischia di essere gravemente intaccata. Per evitare che questo accada, è
possibile ricorrere ad una diversa struttura di controllo denominata Select
Case, la cui sintassi è la seguente:
Select Case <variabile>
Case <valore 1>:
<blocco istruzioni 1>
[Case <valore 2>:
<blocco istruzioni 2>]
.
.
.
[Case <valore n>:
<blocco istruzioni n>]
[Case Else:
<istruzioni da eseguire se tutti i confronti
falliscono>]
End Select
La struttura Select Case è adatta ad essere utilizzata ogniqualvolta
si desideri variare il flusso del programma in base al risultato del confronto
fra il valore di una variabile e uno o più dati costanti. Ognuno di essi deve
essere preceduto dalla parola chiave Case e seguito dai due punti, nonché
dal gruppo di istruzioni da eseguire quando il confronto ha esisto positivo.
Inoltre, è possibile utilizzare la clausola Case Else, che va
obbligatoriamente posta alla fine della struttura, per definire un gruppo di
istruzioni che deve essere eseguito solo se tutti i confronti hanno ottenuto un
esito negativo. La procedura che costituisce la soluzione del secondo esercizio
può quindi essere riscritta nel modo seguente:
Private Sub txtTestoDigitato_Change()
Dim Testo as string
Dim GiornoSettimana as integer
Testo = Ucase(txtTestoDigitato.text)
Select Case Testo
Case "LUNEDI":
GiornoSettimana = True
Case "MARTEDI":
GiornoSettimana = True
Case "MERCOLEDI":
GiornoSettimana = True
Case "GIOVEDI":
GiornoSettimana = True
Case "VENERDI":
GiornoSettimana = True
Case "SABATO":
GiornoSettimana = True
Case "DOMENICA":
GiornoSettimana = True
Case Else
GiornoSettimana = False
End Select
If GiornoSettimana Then
lblRisultato.caption = "E' un giorno della settimana"
Else
lblRisultato.caption = "Non è un giorno della settimana"
End If
End Sub
Il codice risulta in questo caso più leggibile. Tuttavia, c'è spazio per un
ulteriore miglioramento, grazie alla possibilità di utilizzare la virgola per
separare i valori a cui devono essere fatte corrispondere sequenze analoghe di
istruzioni. La struttura Select Case può quindi essere nuovamente
riscritta in modo estremamente più sintetico:
Select Case Testo
Case "LUNEDI", "MARTEDI", "MERCOLEDI", "GIOVEDI",
"VENERDI", "SABATO", "DOMENICA":
GiornoSettimana = True
Case Else
GiornoSettimana = False
End Select
È possibile pertanto notare come talvolta l'impiego della struttura
Case possa agevolare notevolmente il compito del programmatore. Al fine
di verificare ciò, si provi a realizzare un'applicazione che, ricevuto in
ingresso un numero composto da una sola cifra, restituisca una stringa
contenente l'indicazione dello stesso valore indicato in lettere.
Quando utilizzare la struttura Select Case
La struttura Select Case, potendo agire su una sola variabile per
volta, presenta alcune limitazioni rispetto alla struttura If, in quanto
non rende possibile l'esecuzione di valutazioni complesse. Si osservi il
seguente esempio:
If (valore1 = 2) Then
Risultato = 5
ElseIf (valore2 = 3) Then
Risulato = 7
End if
Non è possibile eseguire le stesse operazioni mediante una sola struttura
Case. Ne sono necessarie due nidificate, come illustrato di
seguito:
Select Case Valore1
Case 2:
Risultato = 5
Case Else:
Select Case Valore2
Case 3:
Risultato = 7
End Select
End Select
La leggibilità del codice è in questo caso peggiorata. Inoltre, il numero
delle righe digitate è aumentato. Da ciò appare evidente che l'uso della
struttura Case non è sempre consigliabile. La sua opportunità deve essere
valutata attentamente in fase di progettazione.
Confronti Multipli
Fino ad ora si è fatto uso della struttura di selezione multipla per
confrontare il valore della variabile di controllo con alcuni dati costanti. In
realtà, ci sono altre potenzialità nell'uso del costrutto Case che
andiamo ora ad esplorare.
L'uso della struttura Case per valutare l'appartenenza a degli intervalli di
valori.
Per mezzo della struttura Case diventa estremamente semplice valutare
l'appartenenza del contenuto di una variabile a un intervallo di valori. Si
supponga ad esempio di disporre di un form, caratterizzato anche questa volta
dalla presenza di una label, denominata lblRisultato, di una casella di
testo, a cui è assegnato il nome txtValore e di un pulsante avente la
funzione di avviare l'elaborazione. Si ipotizzi di voler avvisare mediante la
label quando il valore indicato nella textbox, presunto numerico, risulta
compreso fra 1 e 10, oppure se appartiene all'intervallo avente per estremi 11 e
100. Supponendo che il pulsante sia denominato btnVerifica, è possibile
digitare la procedura:
Private Sub btnVerifica_Click()
Dim Testo As String
Dim Valore As Integer
If IsNumeric(txtValore.Text) Then
Valore = Val(txtValore.Text)
Select Case Valore
Case 1 To 10:
Testo = "E' compreso fra 1 e 10"
Case 11 To 100:
Testo = "E' compreso fra 11 e 100"
Case Else:
Testo = "Non appartiene agli intervalli"
End Select
Else
Testo = "Non è un valore numerico"
End If
lblRisultato.Caption = Testo
End Sub
Si noti l'uso della funzione IsNumeric atta a controllare che la
stringa contenuta nella textbox, contenga effettivamente un numero (convertibile
successivamente in un valore numerico per mezzo della funzione Val).
Infatti, solo in questo caso sono effettuati i confronti. Altrimenti, si
provvede a visualizzare nella label un opportuno messaggio di avviso. Questa
volta, la struttura Select Case non fa riferimento a dei valori costanti,
bensì a degli intervalli, ognuno dei quali è specificato per mezzo dei propri
estremi, inframmezzati dalla parola chiave To.
L'uso della struttura Case per valutare se un valore è superiore ad una data
soglia.
Si supponga di voler modificare l'esempio precedente per valutare se il
valore inserito è superiore a 100. La struttura case deve essere modificata come
segue:
Select Case Valore
Case 1 To 10:
Testo = "E' compreso fra 1 e 10"
Case 11 To 100:
Testo = "E' compreso fra 11 e 100"
Case Is > 100:
Testo = "E' maggiore di 100"
Case Else:
Testo = "Non appartiene agli intervalli"
End Select
Si noti l'introduzione della parola chiave Is, seguita da
un'espressione di confronto. Se quest'ultima ha esito positivo, è eseguita
l'istruzione associata.
Gli operatori logici elementari
Si ipotizzi di voler scrivere una struttura If per verificare se il
valore di una variabile numerica intera, denominata Numero, appartiene
all'intervallo compreso fra 10 e 100. È necessario valutare contemporaneamente
due condizioni:
il numero deve essere maggiore di 10
il numero deve essere inferiore a 100
Per far sì che sia prodotta una stringa indicante l'esito del confronto,
occorre digitare il seguente codice:
If Numero > 10 then
If Numero < 100 then
Testo = "Il numero è compreso fra 10 e 100"
Else
Testo = "Il numero non appartiene all'intervallo"
End if
End if
Si noti che l'uso della struttura Case avrebbe consentito la creazione
di un listato più semplice e leggibile. È però possibile operare una
semplificazione combinando i due confronti, ovvero creando un'unica espressione
booleana in grado di stabilire l'appartenenza all'intervallo specificato. A tal
fine, è necessario ricorrere agli operatori logici.
L'operatore And
Spesso si rivela necessario valutare la contemporanea validità di due o più
condizioni.
È il caso dell'esempio precedente, in cui si desidera verificare se la
variabile Numero contiene un valore maggiore di 10 e nel contempo minore
di 100.
L'operatore And assolve questo compito, restituendo il valore logico
True solo se le condizioni a cui è applicato sono contemporaneamente
verificate. Il codice può pertanto essere riscritto come segue:
If (Numero > 10) And (Numero < 100) Then
Testo = "Il numero è compreso fra 10 e 100"
Else
Testo = "Il numero non appartiene all'intervallo"
End if
Come è possibile notare, la struttura risulta più semplice, in quanto
composta da un solo livello.
L'operatore Or
A differenza del precedente, l'operatore Or restituisce il valore
logico True se almeno una delle condizioni specificate è vera. Ad
esempio, la condizione
(Numero = 5) Or (Numero > 11)
è verificata quando la variabile Numero assume un valore maggiore di
11 o uguale a 5.
L'operatore Not
Un altro utile operatore logico è quello di negazione (Not). Come è
facile dedurre, esso restituisce il valore True se la condizione a cui è
applicato non è verificata, mentre restituisce False in caso contrario. A
titolo di esempio, si supponga di voler realizzare una struttura If in
grado di generare una stringa indicate se il valore della variabile
Numero risulta contemporaneamente diverso da 5 e da 10. Il codice da
digitare è il seguente:
If Not ((Numero = 5) Or (Numero = 10)) Then
Testo = "Il numero è diverso da 5 e da 10"
End if
Si osservi la condizione. Essa risulta dalla combinazione di due confronti.
Si tratta di
Numero = 5
e
Numero = 10
L'uso dell'operatore OR permette di verificare se almeno uno di essi
ha esito positivo. In tal caso, non deve essere fornita alcuna indicazione. La
stringa deve essere creata, infatti, solo quando entrambi i confronti hanno
esito negativo, ovvero quando l'espressione
(Numero = 5) Or (Numero = 10)
restituisce il valore False, ovvero quando è verificata la
condizione:
Not ((Numero = 5) Or (Numero = 10))
Come esercizio, si provi a realizzare un programma che riceva in ingresso una
stringa contenente il nome di un mese e restituisca il numero dei giorni di cui
esso si compone. Si preveda la possibilità di specificare l'anno a cui fare
riferimento, al fine di offrire una corretta indicazione della lunghezza del
mese di febbraio anche negli anni bisestili.
Conclusioni
Lo studio della struttura Select Case ha completato la panoramica
sulle strutture di controllo iniziata nello scorso numero. Tali istruzioni
ricoprono un ruolo fondamentale nella quasi totalità delle applicazioni. La loro
conoscenza è inoltre indispensabile per la comprensione degli argomenti che
verranno trattati nelle prossime puntate di questo corso. Al lettore va pertanto
l'invito ad esercitarsi sui concetti illustrati e a non trascurare gli esercizi
proposti. (Parte 4) Oggetto di studio della quarta puntata del corso dedicato alla programmazione
in Visual Basic sono i cicli e in particolare la struttura For, mediante la
quale è possibile far eseguire al calcolatore delle operazioni
ripetitive
Uno dei punti di forza dei calcolatori è rappresentato dalla possibilità di
eseguire in modo estremamente veloce delle operazioni ripetitive. A tal fine, i
linguaggi di programmazione mettono a disposizione delle strutture che prendono
il nome di cicli. Naturalmente, Visual Basic non si sottrae a questa regola. La
prima struttura ad essere presa in esame è rappresentata dal ciclo For, a cui è
dedicata questa lezione.
Le soluzioni degli esercizi dello scorso
numero
Come sempre, prima di procedere allo studio dei nuovi argomenti, saranno
illustrate le soluzioni degli esercizi proposti nello scorso numero.
Primo esercizio
Il primo esercizio prevede la realizzazione di un programma in grado di
leggere un numero composto da una sola cifra e di offrire come risultato la sua
indicazione in lettere. Si tratta di una tipica applicazione della struttura
Select Case, che opera una scelta fra dieci valori. Come interfaccia
utente si fa uso di un form costituito da una textbox, denominata
txtCifra, a cui è affidata la ricezione dei dati in ingresso. Il
risultato è restituito per mezzo della label lblMessaggio, che deve
essere aggiornata ogniqualvolta la cifra in ingresso subisca una variazione,
ovvero in corrispondenza del verificarsi dell'evento change sulla
textbox. La gestione di quest'ultimo è possibile per mezzo della seguente
procedura:
Private Sub txtCifra_Change()
Dim Cifra As Integer
Dim DatoIngresso As String
DatoIngresso = txtCifra.Text
If IsNumeric(DatoIngresso) Then
Cifra = Val(DatoIngresso)
Select Case Cifra
Case 0
lblMessaggio.Caption = "ZERO"
Case 1
lblMessaggio.Caption = "UNO"
Case 2
lblMessaggio.Caption = "DUE"
Case 3
lblMessaggio.Caption = "TRE"
Case 4
lblMessaggio.Caption = "QUATTRO"
Case 5
lblMessaggio.Caption = "CINQUE"
Case 6
lblMessaggio.Caption = "SEI"
Case 7
lblMessaggio.Caption = "SETTE"
Case 8
lblMessaggio.Caption = "OTTO"
Case 9
lblMessaggio.Caption = "NOVE"
Case Else
lblMessaggio.Caption = "Il numero non è composto
da una sola cifra"
End Select
Else
lblMessaggio.Caption = "Dato non numerico"
End If
End Sub
La prima operazione effettuata consiste nella lettura del dato in ingresso
attraverso la textbox. Se esso risulta convertibile in un numero, ovvero se la
funzione IsNumeric restituisce il valore logico True, il dato è
copiato nella variabile intera Cifra ed è utilizzato per controllare la
struttura Select Case, che provvede ad effettuare i confronti necessari
per verificare l'uguaglianza con i numeri compresi fra 0 e 9 ed a visualizzare
la stringa corretta mediante la label.
Secondo esercizio
Il secondo esercizio prevede la creazione di un programma che, ricevendo in
ingresso il nome di un mese, sia in grado di indicarne il numero dei giorni in
un anno specificato. La soluzione comporta nuovamente l'uso della struttura
Select Case. Per l'acquisizione dei dati si provvede, come di consueto, ad
utilizzare delle caselle di testo, denominate txtMese e txtAnno.
Il risultato è visualizzato in seguito alla pressione di un pulsante per mezzo
della label lblMessaggio. Dopo aver creato un form ed aver posto su di
essi gli oggetti grafici, è possibile implementare la procedura destinata al
calcolo. Supponendo che al tasto sia assegnato il nome btnCalcola, si può
scrivere:
Private Sub btnCalcola_Click()
Dim Anno As Integer
Dim Giorni As Integer
Dim Mese As String
Anno = Val(txtAnno.Text)
Mese = UCase(txtMese.Text)
Select Case Mese
Case "GENNAIO", "MARZO", "MAGGIO", "LUGLIO", "AGOSTO",
"OTTOBRE", "DICEMBRE":
Giorni = 31
Case "APRILE", "GIUGNO", "SETTEMBRE", "NOVEMBRE":
Giorni = 30
Case "FEBBRAIO":
If ((Anno Mod 4) = 0) And ((Anno Mod 100) <> 0) Then
Giorni = 29
Else
Giorni = 28
End If
Case Else
Giorni = 0
End Select
If Giorni > 0 Then
lblMessaggio.Caption = "Numero giorni :" & Giorni
Else
lblMessaggio.Caption = "Mese non corretto"
End If
End Sub
La struttura di selezione è controllata dalla variabile Mese, il cui
contenuto è opportunamente convertito in maiuscolo per mezzo della funzione
UCase e confrontato con i nomi dei mesi, anch'essi composti solo da
lettere maiuscole. Ciò fa sì che il confronto non sia case sensitive. Si noti,
che nel caso in cui il mese selezionato sia febbraio, è eseguito un ulteriore
controllo per verificare se l'anno è bisestile. Tale verifica è effettuata
testando la contemporanea validità di due condizioni, ovvero la divisibilità
dell'anno per quattro e la non divisibilità per cento. La condizione risulta
vera se entrambi i resti delle divisioni, calcolati per mezzo della funzione
Mod, sono nulli. Si ricorre pertanto all'operatore logico And, in grado
di restituire il valore True solo in caso di contemporanea verifica delle
condizioni che ne costituiscono gli operandi.
Il ciclo For
Si supponga di voler realizzare un programma in grado di calcolare il
fattoriale di un numero, ovvero il prodotto di tutti i valori interi positivi
minori o uguali ad esso. Gli strumenti illustrati nelle puntate precedenti di
questo corso non si rivelano sufficienti a tal fine, in quanto è necessaria la
capacità di ripetere per un numero variabile di volte l'operazione di
moltiplicazione. Si impone pertanto il ricorso alle strutture di iterazione. Nel
caso dell'esempio, occorre applicare la moltiplicazione a tutti i numeri
naturali minori o uguali a quello di cui si desidera calcolare il fattoriale.
Appare così evidente la necessità di disporre di una struttura in grado di
permettere la ripetizione di una porzione di codice per un numero finito di
volte; si tratta della classica struttura For, che nel caso di Visual
Basic è caratterizzata dalla seguente sintassi:
For <contatore> = <valore iniziale> To <valore finale>
[Step <passo>]
<istruzione 1>
...
<istruzione n>
Next [<contatore>]
Dopo la parola chiave For è necessario far seguire una variabile
intera, che funge da contatore. Il suo valore è incrementato ad ogni ripetizione
di un numero di unità pari a quello specificato dopo la parola chiave
Step, fino al raggiungimento del valore finale; questa condizione
determina la fine delle iterazioni e il passaggio all'istruzione seguente la
parola riservata Next. Volendo quindi realizzare un'applicazione in grado
di calcolare il fattoriale di un numero, occorre creare un form ed inserire su
di esso una textbox, usata per leggere il dato in ingresso, una label, destinata
ad accogliere il risultato e un pulsante, a cui è affidato il compito di avviare
il calcolo. Alla pressione di quest'ultimo deve essere associata la seguente
procedura:
Private Sub btnCalcola_Click()
Dim Contatore As Integer
Dim Fattoriale As Integer
Dim Numero As Integer
If IsNumeric(txtNumero.Text) Then
Numero = Val(txtNumero.Text)
If Numero >= 0 Then
Fattoriale = 1
For Contatore = 1 To Numero
Fattoriale = Fattoriale * Contatore
Next Contatore
lblRisultato.Caption = Fattoriale
Else
lblRisultato.Caption = "Errore: valore negativo"
End If
Else
lblRisultato.Caption = "Errore: dato non numerico"
End If
End Sub
Essa dapprima verifica che nella textbox txtNumero sia presente un
dato numerico. In tal caso, quest'ultimo è assegnato alla variabile
Numero dopo la necessaria conversione per mezzo della funzione
Val. Tale valore è quindi oggetto di un secondo controllo volto a
verificarne la positività. La funzione fattoriale, infatti, non è definita per i
numeri negativi e vale 1 se il dato in ingresso è nullo. Successivamente, la
procedura provvede ad effettuare le moltiplicazioni. A tal fine fa uso di un
ciclo For, avente come valore iniziale 1 e come valore finale il numero
di cui si desidera calcolare il fattoriale. Si noti che è stata omessa la parola
chiave Step. In questo caso, il passo adottato è quello predefinito, cioè
1. All'interno della struttura è presente una sola riga di codice, che ha il
fine di moltiplicare il contenuto della variabile Fattoriale per il
valore assunto dal contatore. La sua ripetizione per tutti i valori compresi fra
1 e il numero fornito in ingresso fa sì che alla fine del ciclo la variabile
Fattoriale contenga il risultato desiderato, che può essere visualizzato per
mezzo della label. Si noti ciò che avviene se in ingresso è fornito il
numero 0. La variabile Fattoriale è comunque inizializzata al valore 1.
L'istruzione all'interno del ciclo, invece, non è mai eseguita. Infatti, come si
è detto in precedenza, la ripetizione termina quando il contatore raggiunge il
valore finale. In questo caso, la condizione è immediatamente soddisfatta, per
cui non si verificano iterazioni.
Esercizio
Per esercitarsi sull'uso della struttura For, si provi a realizzare un
programma che, dato in ingresso un numero intero, visualizzi la somma di tutti i
numeri naturali pari minori o uguali ad esso.
Cicli nidificati
Si supponga ora di voler modificare l'applicazione dell'esempio precedente
per fare in modo che fornisca all'utente la somma dei fattoriali dei primi
n numeri interi positivi, dove n rappresenta il dato in ingresso
acquisito mediante la casella di testo txtNumero.
Per il calcolo del fattoriale è necessario ricorrere nuovamente a un ciclo,
che è sostanzialmente identico a quello visto in precedenza. Tale operazione
deve essere però ripetuta per tutti i numeri naturali minori o uguali al dato
n fornito dall'utente, ovvero per tutti i valori interi compresi fra 1 e
n.
Si impone pertanto l'uso di un ulteriore ciclo, che deve risultare più
esterno rispetto al precedente. Al pulsante btnCalcola si può quindi
associare la seguente procedura:
Private Sub btnCalcola_Click()
Dim Contatore As Integer
Dim Fattoriale As Integer
Dim n As Integer
Dim Risultato As Long
If IsNumeric(txtNumero.Text) Then
n = Val(txtNumero.Text)
If n > 0 Then
Risultato = 0
For Contatore1 = 1 To n
Fattoriale = 1
For Contatore2 = 1 To Contatore1
Fattoriale = Fattoriale * Contatore2
Next Contatore2
Risultato = Risultato + Fattoriale
Next Contatore1
lblRisultato.Caption = Risultato
Else
lblRisultato.Caption = "Errore: valore negativo o
nullo"
End If
Else
lblRisultato.Caption = "Errore: dato non numerico"
End If
End Sub
Anche questa volta è per prima cosa eseguito un controllo della validità del
dato digitato dall'utente. Se si tratta di un numero intero positivo, la
procedura provvede ad avviare il calcolo, dopo aver posto a zero il valore della
variabile Risultato. Si noti che tale operazione non è in realtà
necessaria, in quanto Visual Basic provvede automaticamente ad azzerare le
variabili intere in fase di creazione. La sua presenza, però, contribuisce a
rendere leggibile il codice, soprattutto a coloro che sono abituati all'uso di
altri linguaggi che non presentano questa caratteristica. Si osservino ora i due
cicli nidificati. La variabile usata come contatore in quello più esterno funge
da valore finale nel ciclo interno. Ad ogni iterazione è pertanto calcolato il
fattoriale del numero contenuto nella variabile Contatore1. Tale valore
va ad incrementare quello della variabile Risultato, che alla fine è
visualizzato mediante la label. Allo scopo di mantenere una buona leggibilità
del codice, è necessario fare in modo che alle istruzioni che si trovano
all'interno di un ciclo sia associato un rientro sinistro maggiore. In tal modo
risulta semplice riconoscere l'inizio e la fine della struttura. Tale necessità
è ancora più evidente allorquando vi siano più cicli nidificati. Per lo stesso
motivo, sebbene sia possibile farne a meno, è preferibile far seguire ogni
parola chiave Next dal nome della variabile di conteggio che caratterizza
il ciclo. In caso di omissione, l'interprete comunque assume che la struttura da
delimitare sia quella posta nel blocco più interno.
Le funzioni Len e Mid$
Si supponga di voler scrivere un programma che sia in grado di invertire una
stringa fornita in ingresso. Per la sua realizzazione occorre far uso di due
funzioni specifiche per il trattamento dei dati alfanumerici. Si tratta della
funzione Len, che restituisce la lunghezza della stringa passatale come
parametro e della più complessa funzione Mid$. Quest'ultima permette di
estrarre da una stringa una sequenza composta da un dato numero di caratteri
consecutivi. La sua sintassi è la seguente:
<risultato> = Mid$(<stringa>, <posizione>, [<numero
caratteri>])
I parametri rappresentano rispettivamente la stringa fornita in ingresso, la
posizione del primo carattere da estrarre e il numero dei caratteri oggetto
dell'operazione. Si noti che quest'ultimo è facoltativo. In sua assenza, è
restituita tutta la porzione della stringa compresa fra la posizione indicata e
il carattere posto all'estrema destra. Ad esempio, la riga
A$ = Mid$("Developing Software Solutions", 12, 8)
Assegna alla variabile A$ la stringa "Software". Scrivendo
invece
A$ = Mid$("Developing Software Solutions", 12)
si provoca l'assegnamento della sequenza alfanumerica "Software
Solutions".
Cicli con decremento
Ora si dispone dei mezzi necessari per la realizzazione dell'applicazione in
grado di invertire una stringa. Supponendo di utilizzare un form analogo a
quello degli esempi precedenti, caratterizzato da una textbox destinata
all'acquisizione dei dati in ingresso, denominata txtStringa, dalla label
lblRisultato, utilizzata per visualizzare la stringa risultante e dal
pulsante btnInverti, che ha lo scopo di provocare l'inversione della
sequenza, la procedura da associare ad esso è la seguente:
Private Sub btnInverti_Click()
Dim i As Integer
Dim Lunghezza As Integer
Dim Risultato As String
Dim Carattere As String * 1
Lunghezza = Len(txtStringa.Text)
Risultato = ""
For i = Lunghezza To 1 Step -1
Carattere = Mid$(txtStringa.Text, i, 1)
Risultato = Risultato + Carattere
Next i
lblRisultato.Caption = Risultato
End Sub
Si fa uso della funzione Len per calcolare la lunghezza della stringa
digitata dall'utente. Il valore restituito determina la posizione del primo
carattere che deve essere aggiunto alla variabile Risultato. Esso
rappresenta pertanto il valore di partenza in un ciclo For caratterizzato
da un decremento, ovvero da un passo di segno negativo. Questa volta, quindi, il
valore iniziale risulta maggiore di quello finale e le iterazioni terminano
quando è raggiunto il valore minimo, nella fattispecie 1. La funzione
Mid$ è utilizzata per estrarre il carattere posto nella posizione
indicata dal contatore. Si noti che la variabile atta a contenerlo è di tipo
stringa ed è stata dichiarata di dimensione pari a un elemento per mezzo della
riga
Dim Carattere As String*1
Visual Basic, infatti, a differenza di molti altri linguaggi di
programmazione, non dispone di un tipo di dati idoneo alla memorizzazione dei
singoli caratteri. Per ovviare a questo inconveniente, è necessario ricorrere ad
una stringa di dimensione pari ad un'unità, come in questo esempio. Per mezzo
dell'operatore di concatenazione (+), ogni carattere estratto è aggiunto in coda
al contenuto della variabile Risultato, che al termine delle iterazioni contiene
la stringa invertita. Quest'ultima può essere quindi assegnata alla proprietà
Caption della label lblRisultato per essere visualizzata.
Esercizio
Per esercitarsi sui concetti esposti, si provi a realizzare un programma che,
ricevuto in ingresso un numero intero positivo, sia in grado di restituire il
maggior numero naturale per cui esso è divisibile.
Conclusioni
Il ciclo For è estremamente usato in ogni applicazione. Esso non
rappresenta però l'unica struttura di iterazione. Ne esistono delle altre, che
saranno oggetto di studio nella prossima lezione. Per comprendere il loro
funzionamento è necessario aver assimilato quanto esposto in questa trattazione.
Al lettore è pertanto rivolto il consueto invito ad esercitarsi
nell'applicazione dei concetti illustrati. (Parte 5) Oggetto di studio della quinta parte del corso dedicato alla
programmazione in Visual Basic sono ancora i cicli. Questa volta l'obbiettivo è
puntato sull'uso delle strutture regolate da una condizione booleana
La necessità di poter eseguire ripetitivamente un gruppo di istruzioni è
sentita durante la stesura di pressoché qualsiasi applicazione non banale. Per
questo motivo, tutti gli strumenti di sviluppo prevedono il supporto per le
strutture di iterazione. Dopo aver soffermato l'attenzione sulla struttura
For, si procederà ora a studiare l'uso dei cicli basati sulla parola
chiave Do, che permettono di legare il numero delle ripetizioni non più
al valore assunto da un contatore, bensì al verificarsi di una condizione
logica.
Le soluzioni degli esercizi dello scorso numero
Come ormai consuetudine, la prima parte della lezione è
dedicata alla correzione degli esercizi proposti nello scorso numero.
Primo esercizio
Il primo esercizio prevede la realizzazione di un programma che, dato in
ingresso un numero intero, visualizzi la somma di tutti i numeri naturali pari
minori o uguali ad esso. Dopo aver disegnato un form dotato di una casella di
testo, a cui può essere assegnato il nome txtNumero, di una label
(lblRisultato) e di un pulsante (btnCalcola), è possibile scrivere
la procedura da associare alla pressione di quest'ultimo:
Private Sub btnCalcola_Click()
Dim Numero As Integer
Dim Contatore As Integer
Dim Somma As Long
Numero = Val(txtNumero.text)
If Numero > 0 Then
Somma = 0
For Contatore = 2 To Numero Step 2
Somma = Somma + Contatore
Next Contatore
lblRisultato.Caption = Somma
Else
lblRisultato.Caption = "Numero negativo o nullo"
End If
End Sub
Il suo scopo è di trasformare in un dato numerico la stringa posta nella
casella di testo e, se tale valore risulta positivo, di creare un ciclo per
calcolare tutti i numeri interi pari minori ad esso. A tal fine fa uso di una
struttura For avente come valore iniziale 2, ovvero il più piccolo numero
pari, e come passo ancora 2. In tal modo, sono esclusi dal conteggio i valori
dispari, cioè non multipli di 2. Il limite massimo è rappresentato dal numero
fornito dall'utente. Si noti che, nel caso in cui quest'ultimo sia dispari, la
struttura fa sì che il contatore assuma in realtà un valore massimo inferiore di
un'unità rispetto a quello indicato. Tutti i valori assunti dal contatore sono
aggiunti alla variabile Somma, che al termine delle iterazioni contiene
il risultato desiderato.
Secondo esercizio
Il secondo esercizio richiede la creazione di un programma che, dato in
ingresso un numero intero positivo, restituisca il più grande numero naturale
per cui esso è divisibile. Supponendo di riutilizzare il form descritto in
precedenza, è possibile associare al pulsante la seguente
procedura:
Private Sub btnCalcola_click()
Dim Contatore As Integer
Dim Divisore As Integer
Dim Numero As Double
Numero = Val(txtNumero.Text)
If Numero > 0 Then
If Numero = Int(Numero) Then
Divisore = 1
For Contatore = 2 To (Numero - 1)
If (Numero Mod Contatore) = 0 Then
Divisore = Contatore
End If
Next Contatore
lblRisultato.Caption = Divisore
Else
lblRisultato.Caption = "Numero non intero"
End If
Else
lblRisultato.Caption = "Numero negativo o nullo"
End If
End Sub
Si fa ancora uso di un ciclo For in cui il contatore è inizializzato a
2 (è superfluo verificare la divisibilità per 1). Il valore finale è pari al
numero diminuito di un'unità. Al il programma accede solo se il dato fornito
dall'utente, memorizzato nella variabile Numero, risulta intero positivo.
Ad ogni iterazione, si fa uso dell'operatore Mod per verificare se il
numero fornito in ingresso è divisibile per il valore della variabile
Contatore. In caso affermativo, quest'ultimo è memorizzato nella
variabile Divisore che, completato il ciclo, contiene il risultato
desiderato.
Il comando Exit For
Si noti che, con la procedura sopra descritta, il numero delle ripetizioni
aumenta all'aumentare del valore fornito in ingresso dall'utente. L'applicazione
di tale algoritmo a un numero molto grande potrebbe pertanto causare, almeno in
linea di principio, dei problemi di prestazioni. E' possibile scrivere una
procedura più efficiente realizzando un ciclo con decremento. In questo caso, la
struttura For ha come valore iniziale il dato fornito in ingresso
diminuito di un'unità.
Private Sub btnCalcola_click()
Dim Contatore As Integer
Dim Divisore As Integer
Dim Numero As Double
Numero = Val(txtNumero.Text)
If Numero > 0 Then
If Numero = Int(Numero) Then
Divisore = Numero
For Contatore = (Numero - 1) To 1 Step -1
If (Numero Mod Contatore) = 0 Then
Divisore = Contatore
Exit For
End If
Next Contatore
lblRisultato.Caption = Divisore
Else
lblRisultato.Caption = "Numero non intero"
End If
Else
lblRisultato.Caption = "Numero negativo o nullo"
End If
End Sub
Ad ogni iterazione è effettuato un controllo atto a stabilire se il contenuto
della variabile Contatore è in grado di dividere il numero fornito
dall'utente. Il primo valore di cui è rilevata la capacità di soddisfare questa
condizione è sicuramente il maggior divisore. Una volta trovatolo, è pertanto
inutile proseguire con le iterazioni. Per provocare l'uscita prematura dal
ciclo, si fa uso del comando Exit For. In questo caso, il numero delle
ripetizioni è quello strettamente necessario. Il vantaggio in termini di
efficienza può essere notevole. E' tuttavia importante non abusare dell'uso di
questo comando, in quanto ha influenza negativa sulla leggibilità del
codice.
Il ciclo While
Una soluzione più elegante prevede l'uso del ciclo While, che permette
la ripetizione di un segmento di codice per tutto il tempo in cui una condizione
risulta vera. La sua sintassi è la seguente:
While <condizione>
<istruzione 1>
<istruzione 2>
...
<istruzione n>
Wend
Le istruzioni comprese fra le parole chiave While e Wend sono
ripetute per un numero di volte non stabilito rigidamente a priori, bensì
dipendente dalle stesse istruzioni, che devono essere in grado di fare in modo
che la condizione ad un certo punto smetta di verificarsi. In caso contrario, si
incappa in un errore molto diffuso fra i programmatori alle prime armi, ovvero
si crea ciò che è usualmente detto ciclo infinito. Gli effetti di
un'iterazione senza fine sono evidenti. Il programma di fatto si blocca e per
terminarlo occorre far ricorso al task manager del sistema operativo.
La procedura che costituisce la soluzione del secondo esercizio può essere
riscritta utilizzando la struttura While nel modo
seguente:
Private Sub btnCalcola_click()
Dim Contatore As Integer
Dim Divisore As Integer
Dim Numero As Double
Numero = Val(txtNumero.Text)
If Numero > 0 Then
If Numero = Int(Numero) Then
Contatore = Numero - 1
While (Numero Mod Contatore) <> 0
Contatore = Contatore - 1
Wend
lblRisultato.Caption = Contatore
Else
lblRisultato.Caption = "Numero non intero"
End If
Else
lblRisultato.Caption = "Numero negativo o nullo"
End If
End Sub
Il suo funzionamento è estremamente semplice: un contatore è inizializzato al
massimo numero intero inferiore al dato fornito in ingresso ed è decrementato
all'interno del ciclo più volte per tutto il tempo che il resto della divisione
per il contatore del numero digitato dall'utente si mantiene diverso da zero.
Quando il resto si annulla, ovvero quando la variabile Contatore contiene
il divisore desiderato, la condizione
(Numero Mod Contatore)<>0
diventa falsa e le iterazioni terminano. Il risultato può quindi essere
visualizzato per mezzo dell'apposita label. Si noti che il ciclo non può mai
essere infinito. La condizione infatti assume sicuramente un valore falso quando
la variabile Contatore vale 1.
Le parole chiave Do e Loop
Il ciclo While può anche essere descritto in modo più elegante per
mezzo delle parole chiave Do e Loop. In questo caso la sintassi
diventa:
Do While <condizione>
<istruzione 1>
<istruzione 2>
...
<istruzione n>
Loop
Il comportamento è analogo a quello visto in precedenza: le istruzioni
contenute all'interno della struttura sono ripetute fintanto che la condizione
indicata accanto alla parola While si verifica. La procedura dell'esempio
precedente può quindi essere riscritta come segue:
Private Sub btnCalcola_click()
Dim Contatore As Integer
Dim Divisore As Integer
Dim Numero As Double
Numero = Val(txtNumero.Text)
If Numero > 0 Then
If Numero = Int(Numero) Then
Contatore = Numero - 1
Do While (Numero Mod Contatore) <> 0
Contatore = Contatore - 1
Loop
lblRisultato.Caption = Contatore
Else
lblRisultato.Caption = "Numero non intero"
End If
Else
lblRisultato.Caption = "Numero negativo o nullo"
End If
End Sub
Il ciclo Do Until
Pressoché analogo è il ciclo Do Until, caratterizzato dalla seguente
sintassi:
Do Until <condizione>
<istruzione 1>
<istruzione 2>
...
<istruzione n>
Loop
In questo caso, le iterazioni avvengono quando la condizione è falsa e
terminano quando essa si avvera. Il ciclo su cui si basa la soluzione del
secondo esercizio può essere riscritto utilizzando la struttura Do Until
nel modo seguente:
Do Until (Numero Mod Contatore) = 0
Contatore = Contatore - 1
Loop
Si noti che l'uso di questa struttura in alternativa alla precedente non
presenta dei vantaggi significativi. La scelta può pertanto essere effettuata
caso per caso in base alla comprensibilità del codice e alle proprie preferenze
personali.
Si consideri ora il seguente esempio. Si desidera realizzare un programma
che, ricevendo in ingresso due stringhe, sia in grado di indicare la posizione
in cui la seconda è eventualmente contenuta nella prima. In pratica, si tratta
di realizzare una procedura in grado di simulare il comportamento della funzione
Instr.
Per prima cosa si provvede a disegnare un form dotato di due caselle di
testo, a cui è possibile dare i nomi txtStringa e txtDaCercare, e
di un pulsante, a cui è dato il nome btnCerca. Ad esso è possibile
associare la procedura che effettua la ricerca di una stringa nell'altra. Il
codice è il seguente:
Private Sub btnCerca_click()
Dim Stringa As String
Dim DaCercare As String
Dim LunghStringa As Integer
Dim LunghDaCercare As Integer
Dim Posizione As Integer
Dim Trovato As Boolean
Stringa = txtStringa.Text
DaCercare = txtDaCercare.Text
LunghStringa = Len(Stringa)
LunghDaCercare = Len(DaCercare)
Posizione = 0
Trovato = False
Do Until Trovato Or (Posizione + LunghDaCercare) > LunghStringa
Posizione = Posizione + 1
Trovato = (Mid$(Stringa, Posizione, LunghDaCercare) = DaCercare)
Loop
If Trovato Then
lblMessaggio.Caption = "Posizione = " & Str$(Posizione)
Else
lblMessaggio.Caption = "Stringa non trovata"
End If
End Sub
La procedura dapprima calcola la lunghezza della stringa da cercare, poi
estrae tutte le possibili sequenze di tale dimensione dal testo posto nella
casella txtStringa. Il processo termina quando è estratta una sequenza
uguale a quella oggetto di ricerca; in questo caso la variabile Trovato,
contenente il risultato del confronto, assume il valore logico True. La
condizione
Trovato Or (Posizione + LunghDaCercare) > LunghStringa
pertanto si verifica e il ciclo si conclude. La variabile Posizione,
usata come contatore, contiene allora la posizione in cui la sequenza è stata
localizzata. L'informazione in essa contenuta rappresenta il dato che il
programma deve visualizzare per mezzo della label.
Come si può facilmente notare, le iterazioni possono terminare anche quando
il numero dei caratteri presenti nella stringa a partire dalla posizione
indicata dal contatore risulta inferiore alla lunghezza della sequenza da
cercare. In questo caso, il ciclo termina senza che sia stata trovata alcuna
occorrenza; tale eventualità provoca la visualizzazione di un opportuno
messaggio di avviso per mezzo della label. Si noti che se la stringa da cercare
ha lunghezza inferiore a quella entro cui deve essere effettuata la ricerca, le
istruzioni poste all'interno del ciclo non sono mai eseguite.
Esercizio
Si provi a realizzare un'applicazione che, ricevuta in ingresso una stringa
alfabetica, indichi la posizione in essa occupata dalla prima lettera maiuscola,
se presente.
Ripetizione di un blocco di istruzioni per almeno una
volta
Spesso si rivela utile poter fare in modo che, indipendentemente dal
verificarsi della condizione che regola il ciclo, il blocco di istruzioni in
esso contenuto sia eseguito almeno una volta. Ad esempio, si supponga di voler
realizzare un'applicazione in grado di estrarre a sorte dei numeri compresi fra
1 e 10 mentre la loro somma si mantiene inferiore a 15. A tal fine si utilizza
la funzione Rnd, che restituisce un numero pseudocasuale (ovvero generato
in modo da sembrare estratto a sorte) compreso fra 0 e 1. E' evidente che almeno
una volta deve essere effettuata l'estrazione di un numero. Per fare in modo che
ciò avvenga, è possibile posizionare la condizione che regola il ciclo alla fine
di esso anziché all'inizio. In questo caso la sintassi diventa:
Do
<istruzione 1>
<istruzione 2>
...
<istruzione n>
Loop Until <condizione>
Per consentire all'estrazione di avere inizio in seguito alla pressione del
pulsante btnEstrai e per far sì che i numeri generati siano visualizzati
in una textbox, denominata txtNumeri, è necessario scrivere la seguente
procedura:
Private Sub btnEstrai_Click()
Dim Somma As Integer
Dim Numero As Integer
Randomize
Somma = 0
txtNumeri.Text = ""
Do
Numero = Int(10 * Rnd) + 1
Somma = Somma + Numero
txtNumeri.Text = txtNumeri.Text + Str$(Numero) + "-"
Loop Until Somma > 15
End Sub
Il ciclo provvede a calcolare un numero casuale compreso fra 1 e 10 per mezzo
della funzione Rnd, moltiplicando il valore da essa restituito per 10,
estraendone la parte intera e sommando il valore 1. Siccome, come già accennato
in precedenza, la funzione restituisce un numero decimale maggiore o uguale a 0
e minore di 1, la parte intera del prodotto risulta compresa fra 0 e 9. Per fare
in modo che alla variabile Numero sia assegnato un valore compreso fra 1
e 10 occorre pertanto aggiungere un'unità. Il valore ottenuto è aggiunto alla
variabile Somma. I numeri sono visualizzati, separati da un trattino,
nella textbox txtNumeri. Le iterazioni terminano quando il valore della
variabile Somma diventa maggiore di 15.
Si noti l'uso dell'istruzione Randomize all'inizio della procedura.
Esso ha lo scopo di inizializzare il generatore di numeri pseudocasuali. In
assenza di tale operazione, i valori generati sarebbero sempre gli stessi ogni
volta che è lanciato il programma.
Anche quando la condizione è posta alla fine del ciclo è possibile sostituire
la parola chiave Until con While. In questo caso, le iterazioni
avvengono fintanto che si verifica la condizione indicata.
Esercizio
Per valutare la propria comprensione degli argomenti trattati, si realizzi
un'applicazione che, ricevendo in ingresso una stringa per mezzo di una textbox,
restituisca la lunghezza della prima parola in essa contenuta.
Conclusioni
I calcolatori sono nati allo scopo di agevolare l'utente nello svolgimento
delle operazioni ripetitive. Per questo motivo, le strutture che permettono di
eseguire delle iterazioni rivestono una notevole importanza, tale da rendere la
loro conoscenza imprescindibile per qualsiasi programmatore. Ancora una volta,
pertanto, è rivolto al lettore l'invito ad esercitarsi per acquisire la
necessaria dimestichezza con i concetti esposti.
Nella sesta parte, il corso dedicato alla programmazione in ambiente
Microsoft Visual Basic punta il proprio obbiettivo su delle strutture dati di
importanza fondamentale: i vettori.
La fortuna dei calcolatori si deve alla capacità di effettuare in breve tempo
delle operazioni ripetitive su una grande quantità di informazioni. Fino ad ora,
in questo corso, i dati da elaborare sono sempre stati considerati contenuti
all'interno di comuni variabili. Quando tuttavia il numero dei valori da gestire
diventa elevato, esse si rivelano inadeguate a soddisfare le esigenze del
programmatore. Lo scopo della lezione consiste nell'introdurre il concetto di
vettore e di dimostrare quanto una struttura di questo tipo possa agevolare il
compito dello sviluppatore in presenza di un'elevata mole di dati di tipo
omogeneo. Prima di illustrare i nuovi concetti, è opportuno verificare la
comprensione degli argomenti esposti nella precedente lezione. Lo spunto è come
sempre rappresentato dalla discussione delle soluzioni degli esercizi in essa
proposti.
Nella scorsa lezione sono state descritte le strutture di iterazione basate
sull'uso della parola chiave Do. Gli esercizi in essa proposti hanno
perciò lo scopo di invitare il lettore a farne uso.
Primo esercizio
Il primo esercizio richiede la realizzazione di un programma che, ricevuta in
ingresso una stringa alfabetica, indichi la posizione in essa occupata dalla
prima lettera maiuscola, se presente. È evidente la necessità di implementare un
ciclo che effettui una scansione della stringa, la cui lunghezza può variare
indefinitamente e può anche essere nulla. Per questo motivo, occorre utilizzare
una struttura che sia in grado di evitare le iterazioni qualora non siano
necessarie. La scelta ricade sul ciclo Do While. Dopo aver realizzato un
form dotato di una casella di testo, utilizzata per l'inserimento della stringa
e di una label, mediante la quale è visualizzato il risultato dell'elaborazione,
è possibile aggiungere un pulsante, avente lo scopo di avviare la ricerca. Esso
deve pertanto essere in grado di rispondere all'evento Click con la
seguente procedura:
Private Sub btnCalcola_Click()
Dim Stringa As String
Dim Carattere As String * 1
Dim i As Integer
Dim Lunghezza As Integer
Dim Fine As Boolean
Stringa = txtStringa.Text
Lunghezza = Len(Stringa)
i = 1
Fine = False
Do While Not (Fine) And (i <= Lunghezza)
Carattere = Mid$(Stringa, i, 1)
Select Case Carattere
Case "A" To "Z":
Fine = True
Case Else:
i = i + 1
End Select
Loop
If Fine Then
lblRisultato.Caption = "Posizione: " & i
Else
lblRisultato.Caption = "Nessuna lettera maiuscola"
End If
End Sub
Il contenuto della casella di testo costituisce la stringa nella quale deve
essere effettuata la ricerca. Per mezzo della funzione Mid$ è estratto un
carattere per volta. La sua posizione è definita dalla variabile i. Se il
carattere risulta maiuscolo, la variabile booleana Fine è posta al valore
logico True e la ricerca termina. In caso contrario, è incrementato il
contatore di un'unità ed è eseguita una nuova iterazione per verificare il
carattere seguente. Si noti che se il contatore assume un valore superiore al
numero dei caratteri di cui è composta la stringa, condizione immediatamente
verificata se la lunghezza è nulla, il ciclo termina. Si noti altresì l'uso
della struttura Select Case per verificare se il carattere estratto
risulta maiuscolo. Permettendo di definire degli intervalli, infatti, essa rende
estremamente agevole l'operazione di controllo, rendendo inoltre il codice molto
semplice da comprendere.
Secondo esercizio
Anche il secondo esercizio richiede la scansione di una stringa fornita
dall'utente. La sua soluzione consiste infatti nella realizzazione di un
programma in grado di ricevere in ingresso una stringa per mezzo di una textbox
e di restituire la lunghezza della prima parola in essa contenuta. È possibile
far uso di un form analogo a quello dell'esercizio precedente. Anche in questo
caso, l'elaborazione è avviata dalla pressione del pulsante. Il codice che la
descrive è quindi ancora contenuto nella procedura btnCalcola_Click:
Private Sub btnCalcola_Click()
Dim Stringa As String
Dim Carattere As String * 1
Dim i As Integer
Dim Lunghezza As Integer
Dim PosInizio As Integer
Dim Fine As Boolean
Stringa = txtStringa.Text
Lunghezza = Len(Stringa)
If Lunghezza > 0 Then
i = 1
Fine = False
Rem Ricerca della posizione
Rem del primo carattere alfabetico
Do
Carattere = Mid$(Stringa, i, 1)
Select Case Carattere
Case "A" To "Z", "a" To "z":
Fine = True
Case Else:
i = i + 1
End Select
Loop Until Fine Or (i > Lunghezza)
If Fine Then
Fine = False
PosInizio = i
Rem Ricerca di uno spazio o di
Rem un carattere di punteggiatura
Rem che delimiti la parola
Do While Not (Fine) And (i <= Lunghezza)
Carattere = Mid$(Stringa, i, 1)
Select Case Carattere
Case " ", ",", ";", ".":
Fine = True
Case Else:
i = i + 1
End Select
Loop
lblRisultato.Caption = "Lunghezza prima parola:" &
(i - PosInizio)
Else
lblRisultato.Caption = "La stringa non contiene parole"
End If
Else
lblRisultato.Caption = "Nessuna lettera maiuscola"
End If
End Sub
Dopo l'avvio, la procedura provvede a copiare il contenuto della textbox
nella variabile denominata Stringa, di cui è calcolata la lunghezza per
mezzo della funzione Len. Mediante un ciclo, è effettuata la ricerca del
primo carattere alfabetico contenuto nella stringa. Se l'esito è positivo,
ovvero se essa contiene almeno una parola, è utilizzato un ulteriore ciclo per
ricercare il carattere che ne determina la fine. La differenza fra la posizione
di quest'ultimo e quella del primo carattere costituisce la lunghezza della
parola. Il valore è visualizzato per mezzo dell'etichetta testuale
lblRisultato.
Si supponga di voler realizzare una semplice applicazione in grado di
ordinare in modo crescente tre valori numerici. Non si tratta di un compito
gravoso. È infatti sufficiente disegnare un form e porre su di esso tre textbox,
necessarie per l'acquisizione dei valori, nonché una label destinata ad ospitare
l'elenco ordinato. Per avviare l'elaborazione, è possibile ancora una volta
utilizzare una procedura associata all'evento Click generato dalla
pressione di un pulsante. Il codice è il seguente:
Private Sub btnOrdina_Click()
Dim Valore1 As Double
Dim Valore2 As Double
Dim Valore3 As Double
Dim temp As Double
Valore1 = Val(txtValore1.Text)
Valore2 = Val(txtValore2.Text)
Valore3 = Val(txtValore3.Text)
If Valore2 < Valore1 Then
temp = Valore2
Valore2 = Valore1
Valore1 = temp
End If
If Valore3 < Valore1 Then
temp = Valore3
Valore3 = Valore1
Valore1 = temp
End If
If Valore3 < Valore2 Then
temp = Valore3
Valore3 = Valore2
Valore2 = temp
End If
lblRisultato.Caption = Valore1 & " - " & Valore2 & " - "
& Valore3
End Sub
I dati forniti dall'utente, dopo essere stati convertiti in valori numerici,
sono posti all'interno delle variabili Valore1, Valore2,
Valore3. È successivamente effettuato un confronto fra i dati contenuti
nelle variabili Valore1 e Valore2. Se il numero contenuto nella seconda è
maggiore di quello memorizzato nella prima, i due valori sono scambiati. In modo
analogo, è effettuato il confronto fra i numeri contenuti nelle variabili
Valore2 e Valore3 ed è effettuato un ulteriore scambio qualora
esso si riveli necessario per fare in modo che la variabile Valore3
contenga un dato maggiore di quello posto in Valore2. Un ulteriore
verifica è eseguita per fare in modo che il dato contenuto in Valore3 sia
maggiore di quello memorizzato in Valore1. In seguito all'esecuzione
delle tre strutture If, si ottiene che:
il dato contenuto nella variabile Valore2 risulta maggiore o uguale a
quello posto in Valore1
il dato posto nella variabile Valore3 risulta maggiore o uguale a
quelli posti in Valore2 e Valore1
La sequenza Valore1, Valore2, Valore3, risulta pertanto
ordinata in modo crescente. I valori possono allora essere visualizzati
utilizzando il carattere "-" come separatore. Sarebbe sicuramente molto meno
agevole la realizzazione di un'applicazione analoga in grado di gestire 100
numeri. Ancor più ardua si rivelerebbe la creazione di un programma in grado di
operare su 1000 o 10000 valori. È evidente che le comuni variabili non
permettono di sviluppare in modo ragionevolmente semplice dei programmi in grado
di gestire un numero elevato di informazioni dello stesso tipo. Per queste
realizzazioni, è necessario far ricorso ai vettori.
Fino ad ora, le variabili sono stare considerate come delle strutture in
grado di associare a un nome simbolico un solo valore, secondo una relazione
uno a uno. Saranno descritte ora delle speciali variabili, in grado di
associare ad un unico nome uno o più valori, secondo una relazione uno a
molti. Esse prendono il nome di vettori, o Array. Un array si
presenta quindi come una variabile in grado di ospitare diversi valori in
opportuni spazi numerati.
L'accesso ad un elemento contenuto in un vettore non può pertanto essere
effettuato indicando il solo nome della struttura, bensì richiede che sia
specificato anche il numero corrispondente alla posizione, che prende il nome di
indice. Per meglio comprendere il concetto di vettore, si provi ad
immaginare una variabile come una sorta di cassetto in cui è possibile riporre
un'informazione.
Si supponga di applicare su questo cassetto un'etichetta su cui è riportata
la scritta "Valori". Nel linguaggio naturale, per indicare a una persona di
prendere il contenuto del cassetto caratterizzato dall'etichetta "Valori" e di
porlo in un altro denominato "Risultato" si usa la frase: "Prendi il contenuto
del cassetto Valori e ponilo nel cassetto Risultato". In Visual
Basic, ricordando che tali cassetti sono in realtà delle variabili, è possibile
scrivere:
Risultato = Valori
Si supponga ora di disporre di un'intera cassettiera, in cui ogni singolo
cassetto è numerato in modo univoco e di spostare su di essa l'etichetta. In
questo caso, per accedere a un oggetto, occorre specificare il nome della
cassettiera e il numero che identifica il cassetto che lo contiene. Ad esempio,
è possibile dire "Prendi il contenuto del cassetto numero 3 della cassettiera
Valori e ponilo nel cassetto di nome Risultato". Ciò in Visual
Basic è tradotto con la riga:
Risultato = Valori(3)
Si noti che l'indice dell'elemento è posto fra parentesi tonde. Grazie ai
vettori, è ora possibile realizzare in modo molto semplice un'applicazione in
grado di richiedere 100 numeri e di visualizzare in un'etichetta di testo
l'elenco dei valori ordinato in modo crescente. Il codice può essere associato
all'evento Load del form di avvio, affinché sia eseguito durante la fase
di creazione.
Private Sub Form_Load()
Dim Stringa As String
Dim Valori(100) As Double
Dim Temp As Double
Dim i As Integer
Dim j As Integer
Rem Acquisizione dei dati
For i = 1 To 100
Stringa = InputBox("Inserire il numero")
Valori(i) = Val(Stringa)
Next i
Rem Ordinamento con algoritmo di selezione diretta.
For i = 1 To 99
For j = i + 1 To 100
If Valori(i) > Valori(j) Then
Temp = Valori(i)
Valori(i) = Valori(j)
Valori(j) = Temp
End If
Next j, i
Rem Inserimento dei risultati nella textbox
lblRisultato.Caption = ""
For i = 1 To 100
lblRisultato.Caption = lblRisultato.Caption &
Valori(i) & Chr$(10)
Next i
End Sub
Si noti che la dichiarazione del vettore Valori è eseguita per mezzo
dell'istruzione Dim, in modo pressoché analogo a quanto previsto per una
qualsiasi variabile. L'unica differenza è costituita dalla presenza delle
parentesi contenenti un numero che rappresenta la dimensione, ovvero il massimo
indice utilizzabile. Il valore minimo per l'indice è sempre 0, tranne quando si
provvede ad indicare nella sezione destinata delle dichiarazioni delle variabili
globali la clausola
Option Base 1
In questo caso, l'indice minimo dei vettori è 1. Osservando la dichiarazione,
si nota la presenza di un unico identificatore di tipo. Ciò rende immediatamente
intuibile la necessità che tutti i dati presenti nel vettore siano omogenei.
Nell'esempio, i valori sono acquisiti per mezzo della funzione InputBox,
la cui sintassi è la seguente:
<stringa>=InputBox(<Messaggio di richiesta>
[, <titolo della finestra>]
[, <valore di default>]
[, <posizione orizzontale>]
[, <posizione verticale>])
Essa visualizza una finestra di dialogo che invita l'utente a digitare una
stringa. Come è possibile notare, il programmatore deve specificare il messaggio
di richiesta. Inoltre, egli può provvedere a un ulteriore personalizzazione
indicando il titolo della finestra (in caso di omissione è utilizzato quello
dell'applicazione), il valore di default, che è restituito se l'utente agisce
sul pulsante Annulla e le coordinate orizzontale e verticale del punto di
origine. Si noti che il riempimento del vettore avviene per mezzo di un ciclo
For, che provvede a richiedere il valore di ogni singolo elemento
richiamando 100 volte la funzione InputBox.
Dopo l'acquisizione dei dati, è possibile provvedere al loro ordinamento. A
tal fine, si fa uso dell'algoritmo detto di selezione diretta. Non si
tratta del metodo più efficiente, ma si presta molto bene agli scopi didattici,
in quanto è caratterizzato da un'estrema semplicità. Eccone una breve
descrizione. Si supponga che i sia l'indice di un elemento del vettore.
Affinché l'ordinamento sia crescente, è necessario che tutti gli oggetti
caratterizzati dal possedere un indice superiore a i abbiano un valore
maggiore di quello dell'elemento di indice i. Per fare in modo che ciò
avvenga, per ogni posizione j maggiore di i è effettuato un
confronto. Se l'elemento di indice j contiene un valore inferiore a
quello dell'oggetto di posizione i, i dati sono scambiati. Al termine
dell'iterazione, l'elemento di indice i contiene sicuramente un valore
inferiore a tutti quelli che lo seguono. La ripetizione di questa operazione per
tutti gli elementi del vettore ne provoca l'ordinamento. L'implementazione è
basata sull'uso di due cicli For nidificati. Il più interno effettua la
scansione del vettore alla ricerca dei valori inferiori a quello contenuto
nell'elemento di indice i. Il più esterno varia i per fare in modo
che tale scansione sia ripetuta per tutti gli elementi del vettore, tranne
l'ultimo, per il quale l'operazione sarebbe evidentemente inutile. La terza
parte del programma è ancora una volta costituita da un ciclo, la cui funzione è
quella di copiare nella label i dati contenuti nel vettore ordinato, separandoli
con il carattere caratterizzato dal codice ASCII 10, che corrisponde al
ritorno a capo. In tal modo i valori sono visualizzati su righe separate. Si
noti l'uso della funzione Chr$ per generare un carattere a partire dal
suo codice ASCII. L'applicazione descritta si caratterizza per la
presenza di ben quattro cicli. Ciò non è casuale. I vettori, infatti, essendo
costituiti da dati di tipo omogeneo, sui quali in genere devono essere
effettuate le stesse operazioni, si prestano molto bene ad essere utilizzati
nelle procedure basate sull'uso delle strutture di iterazione.
Per verificare la comprensione degli argomenti esposti, si provi ad eseguire
i seguenti esercizi:
Primo esercizio
Si modifichi l'applicazione dell'esempio affinché effettui l'ordinamento in
modo decrescente.
Secondo esercizio
Si realizzi un'applicazione in grado di estrarre a sorte 30 numeri, di
calcolarne la media aritmetica e di visualizzare all'interno di una textbox solo
quelli che risultano inferiori ad essa.
I vettori sono strutture di dati dall'importanza fondamentale, il cui uso si
rivela spesso indispensabile per la realizzazione di programmi in grado di
gestire grandi quantità di dati di tipo omogeneo. Nel prossimo numero si parlerà
ancora degli array e saranno illustrati alcuni concetti più avanzati. Per la
loro comprensione è quindi importante esercitarsi su quanto appreso questo mese.
Questa lezione si chiude in un modo un po' diverso, con una serie di domande
aventi lo scopo di consentire un'autoverifica del livello di comprensione degli
argomenti esposti in questi sei mesi. Le risposte corrette sono elencate di
seguito, in corsivo.
Qual è la differenza che intercorre fra una textbox e una label?
La label permette solo di visualizzare una stringa. La textbox permette
anche l'inserimento del dato da parte dell'utente.
A quale evento occorre associare del codice per fare in modo che sia eseguito
in corrispondenza della pressione di un pulsante?
L'evento generato dalla pressione di un pulsante è denominato
Click.
Può una variaibile di tipo Integer contenere il dato 23.45?
No, perché le variabili di tipo Integer non possono contenere numeri con
cifre decimali.
Quale istruzione permette di dichiarare una variabile?
La dichiarazione di una variabile è possibile per mezzo dell'istru- zione
Dim.
È consentito utilizzare più strutture If nidificate?
Si, in Visual Basic non ci sono limiti al livello di nidificazione delle
strutture If.
Quali istruzioni sono eseguite se la condizione che regola una struttura If è
falsa?
Sono eseguite le istruzioni comprese fra le parole chiave Else e End
If
A cosa serve la funzione Val?
La funzione Val converte una stringa in un valore numerico
Cosa restituisce l'operatore logico And applicato a due condizioni?
L'operatore logico And, se applicato a due condizioni, restituisce il
valore True se e solo se entrambe sono verificate.
A cosa serve l'operatore logico Not?
L'operatore logico Not serve per negare una condizione.
A cosa serve la parola chiave Next?
La parola chiave Next è utilizzata per delimitare il blocco di istruzioni
contenuto in un ciclo For.
A cosa serve la funzione Len?
La funzione Len restituisce la lunghezza della stringa fornitale come
parametro
A cosa serve la parola chiave Step?
La parola chiave Step permette di specificare l'incremento della variabile
di controllo ad ogni iterazione in un ciclo For. Se è omessa, l'incremento è di
un'unità.
È possibile interrompere prematuramente un ciclo For?
Si, per mezzo dell'istruzione Exit For
Un ciclo While può ripetere un blocco di istruzioni per un qualsiasi numero
di volte. Se la condizione non è verificata, può non eseguire alcuna
iterazione.
Quando un ciclo Do Until ripete un blocco di istruzioni?
Un ciclo Do Until ripete un blocco di istruzioni fino a quando si verifica
la condizione specificata dopo la parola Until
La clausola Option Base 1 serve per fare in modo che l'indice minimo dei
vettori all'interno del progetto sia 1, anziché 0. (Parte 7) Continua lo studio dei vettori; questo mese sono protagonisti quelli a più
dimensioni, le strutture caratterizzate dal poter variare dinamicamente il
numero degli elementi che le compongono e gli array di controlli
Nella scorsa lezione è stata introdotta la nozione di vettore, o array. Per
mezzo di questa struttura di dati, è possibile associare un unico nome a un
insieme di posizioni di memoria dello stesso tipo, ognuna delle quali può
contenere un dato diverso. In questa settima puntata del corso dedicato alla
programmazione in Visual Basic sarà esteso il concetto e ne saranno descritte le
evoluzioni, ovvero saranno studiati i vettori caratterizzati dal possedere più
di una dimensione e gli utilissimi array di controlli, che talvolta
contribuiscono notevolmente a semplificare il compito del programmatore.
Il primo passo, prima di procedere allo studio dei nuovi argomenti, prevede
come sempre la verifica della comprensione di quelli esposti nel numero
precedente, mediante la correzione degli esercizi in esso proposti.
Primo esercizio
Il primo esercizio richiede la modifica dell'applicazione, descritta nello
scorsa puntata, in grado di leggere 100 numeri e di elencarli in ordine
crescente all'interno di una casella di testo. È infatti richiesto di far sì che
effettui l'ordinamento in modo decrescente. La soluzione è banale, in quanto
richiede semplicemente l'inversione dell'operatore di confronto all'interno del
ciclo che provvede all'ordinamento. Il codice necessario per ordinare un vettore
contenente 100 valori numerici in modo decrescente per mezzo dell'algoritmo di
selezione diretta è pertanto quello indicato di seguito:
For i = 1 To 99
For j = i + 1 To 100
If Valori(i) < Valori(j) Then
Temp = Valori(i)
Valori(i) = Valori(j)
Valori(j) = Temp
End If
Next j, i
Secondo esercizio
Il secondo esercizio presenta una complessità leggermente superiore. Esso
richiede infatti di realizzare un'applicazione in grado di estrarre a sorte 30
numeri, di calcolarne la media aritmetica e di visualizzare all'interno di una
label solo quelli che risultano inferiori ad essa. Per la sua realizzazione,
l'applicazione richiede la creazione di un form, in cui va posta un'etichetta
testuale denominata lblRisultato. Il codice che provvede al suo
riempimento deve essere eseguito all'avvio. Può pertanto essere associato
all'evento Load del form. La procedura, che è possibile osservare nel Listato 1, è costituita da 3 cicli. Il primo ha lo
scopo di estrarre a sorte 30 valori interi compresi fra 1 e 100. Di essi è
calcolata la media aritmetica per mezzo della seconda struttura di iterazione.
Tutti i valori che risultano inferiori a questo numero sono elencati, uno sotto
l'altro, all'interno della casella di testo. Ciò avviene per mezzo del terzo
ciclo.
Si noti che la dichiarazione del vettore risulta leggermente diversa da
quelle viste nella scorsa lezione. In questo caso, infatti, è stato specificato
anche l'indice minimo, secondo la sintassi
Dim <nome> (<indice_minimo> To <indice_massimo>) As
<tipo>
Ciò consente di creare dei vettori in cui gli indici possono essere anche
negativi, oppure appartenere a un campo di valori molto diverso da quello
usuale. Ad esempio, supponendo di voler creare un array in grado di ospitare un
dato numerico indicante il prodotto interno lordo dello Stato italiano negli
anni compresi fra il 1989 ad oggi, è possibile dichiarare la variabile PIL come
segue:
Dim PIL(1989 To 1998) As Double
Naturalmente, il vettore è del tutto equivalente a uno di 10 elementi
dichiarato con il metodo usuale, però l'uso di indici strettamente correlati
alle informazioni contenute nella struttura offre il vantaggio di migliorare la
leggibilità del codice.
Si supponga di voler scrivere un'applicazione in grado di leggere un dato
numerico corrispondente alla temperatura massima per ogni giorno di un mese, di
calcolare la media di tutti i valori e di indicare in quali giorni la
temperatura si è mantenuta al di sotto del valore medio calcolato. In pratica,
l'applicazione risulta estremamente simile a quella descritta in precedenza. In
questo caso, tuttavia, i valori non sono casuali, bensì inseriti dall'utente.
Inoltre, il loro numero non è fisso, in quanto dipendente dal mese a cui si
riferiscono i dati. Una possibile soluzione è quella indicata nel Listato 2.
ReDim [Preserve] <nome> (<dimensione>)
Il suo scopo è di variare la dimensione del vettore di cui è specificato il
nome. L'istruzione può essere ripetuta all'interno del programma per un numero
di volte teoricamente infinito. La procedura descritta nel Listato 2 può essere resa più efficiente mediante
l'uso di un vettore dinamico, come indicato nel Listato 3. Si noti che è cambiata la
dichiarazione del vettore, che nella nuova procedura costituita dalla riga
Dim V() As Integer
La dimensione questa volta non è stata specificata; il compito di allocare la
giusta quantità di memoria per le informazioni è demandato alla riga
ReDim V(NumGiorni)
che provvede a dimensionare il vettore in modo che il massimo indice sia pari
al valore della variabile intera NumGiorni, il cui valore è fornito
dall'utente.
Si supponga ora di voler modificare la procedura sopra descritta per fare in
modo che non richieda all'avvio il numero dei valori da elaborare, bensì
acquisisca ciclicamente dei dati fino alla digitazione da parte dell'utente
della stringa "Fine".
In questo caso, non è possibile conoscere la dimensione del vettore prima
dell'inserimento dei dati. Esso quindi deve essere in grado di crescere
continuamente. Il codice necessario per svolgere il compito richiesto è
descritto nel Listato 4. Si noti la
presenza di un ciclo delimitato dalle parole chiave Do e Loop.
Esso risulta privo delle clausole While o Until. L'uscita è
infatti causata dall'istruzione Exit For, che è eseguita quando l'utente
digita la parola "Fine". Osservando il codice, è possibile constatare che la
stringa inserita dall'utente è convertita in maiuscolo per mezzo della funzione
UCase ed è privata degli eventuali spazi iniziali e finali per mezzo
della funzione Trim. In tal modo, il confronto risulta indipendente
dall'uso delle lettere maiuscole.
L'istruzione ReDim è invocata ad ogni iterazione, al fine di
incrementare la dimensione del vettore di un'unità per fare posto al dato appena
letto. Dovendo intervenire su una struttura già contenente dei dati e volendo
fare in modo che essi non siano persi, occorre utilizzare l'istruzione
ReDim con la clausola Preserve; in sua assenza, il
ridimensionamento dell'array comporterebbe la cancellazione di tutti i valori
contenuti. All'interno della procedura si è fatto uso anche della funzione
UBound, che ha lo scopo di calcolare il massimo indice previsto dal
vettore, ovvero la sua dimensione.
Esercizio
Si provi a realizzare un'applicazione in grado di simulare lo spoglio delle
schede elettorali. Essa deve leggere per ogni scheda il numero del candidato
votato. Alla fine, deve essere in grado di indicare la classifica in ordine
decrescente.
Si supponga ora di voler modificare il programma per fare in modo che sia in
grado di effettuare le proprie valutazioni facendo riferimento sia alle
temperature massime, sia alle minime e che indichi i giorni in cui almeno uno
dei due valori è risultato al di sotto della media.
In questo caso, quindi, per ogni giorno è necessario acquisire due valori.
Una soluzione consiste nell'utilizzare due vettori, uno dedicato alle
temperature massime e l'altro alle minime. Il codice è descritto nel Listato 5. Esso non costituisce un esempio di
compattezza ed efficienza. Infatti, le istruzioni utilizzate per gestire le
temperature minime sono praticamente duplicate per gestire le massime. La
lunghezza del codice può essere ridotta facendo ricorso a una matrice costituita
da due colonne, di cui una è dedicata alle temperature minime e l'altra alle
massime. La procedura così modificata è descritta nel Listato 6. Il primo indice determina la colonna della
matrice.
Il valore 1 fa riferimento alle temperature minime, mentre il valore 2 si
riferisce alle massime. Il secondo, ridimensionabile per mezzo dell'istruzione
ReDim, è invece utilizzato per far riferimento al giorno. Si osservi che
l'uso dell'istruzione ReDim non è possibile sul primo indice.
Infatti, solo l'indice posto più a destra nella dichiarazione di una matrice
può essere ridimensionato. L'introduzione di una seconda dimensione comporta una
semplificazione del codice.
Per operazioni più complesse, è possibile incrementare ulteriormente il
numero delle dimensioni di un vettore, purché rimanga inferiore a 60.
Nella procedura appena descritta, si è fatto nuovamente uso della funzione
UBound, che in questo caso richiede un ulteriore parametro indicante la
posizione della dimensione di cui si desidera conoscere la grandezza.
Si supponga ora di voler modificare l'applicazione per fare in modo che sia
in grado di richiedere su un unico form i valori delle temperature minime e
massime rilevate in tutti i giorni di una settimana. L'interfaccia deve
prevedere l'acquisizione dei dati che avviene per mezzo di caselle di testo. Si
tratta di creare un form e di aggiungere le textbox necessarie, che si
supporranno denominate LunediMin, LunediMax, MartediMin,
MartediMax, ecc. Occorre poi modificare la procedura precedentemente
descritta sostituendo la parte dedicata all'inserimento dei dati nella matrice
con il seguente codice:
V(1,1)=Val(LunediMin.Text)
V(2,1)=Val(LunediMax.Text)
V(1,2)=Val(MartediMin.Text)
V(2,2)=Val(MartediMax.Text)
V(1,3)=Val(MercolediMin.Text)
V(2,3)=Val(MercolediMax.Text)
V(1,4)=Val(GiovediMin.Text)
V(2,4)=Val(GiovediMax.Text)
V(1,5)=Val(VenerdiMin.Text)
V(2,5)=Val(VenerdiMax.Text)
V(1,6)=Val(SabatoMin.Text)
V(2,6)=Val(SabatoMax.Text)
V(1,7)=Val(DomenicaMin.Text)
V(2,7)=Val(DomenicaMax.Text)
La sequenza di istruzioni presentata non rappresenta un esempio di
semplicità. Nel caso in cui le caselle di testo, anziché essere 14, fossero in
un numero sensibilmente maggiore, il programma potrebbe diventare poco
leggibile. Si osservi che di fatto le righe sono estremamente simili fra loro.
Ciò che varia è il nome della textbox. Grazie alle caratteristiche di Visual
Basic, è possibile operare una notevole semplificazione del codice utilizzando
un vettore di controlli, nella fattispecie rappresentati da caselle di testo. Un
array di questo tipo non richiede alcuna dichiarazione. È sufficiente far uso
della proprietà Index dei controlli. Ad esempio, si supponga di voler
realizzare il form in grado di acquisire tutte le temperature rilevate nell'arco
della settimana. Dopo aver agito sul menu Progetto ed aver selezionato la
voce Inserisci form, si provvede ad inserire sul modulo creato una
casella di testo e ad assegnarle il nome txtMinima. Ad essa possono
successivamente essere assegnati i valori desiderati delle proprietà che
definiscono il carattere, il colore e la dimensione. Si crea così l'elemento
campione, che può essere duplicato per mezzo di una normale operazione di copia
e incolla. Si noti che, nel momento in cui si incolla il primo elemento, appare
un messaggio che segnala la presenza sul form di un altro oggetto denominato
txtMinima e presenta una finestra di dialogo che ha lo scopo di chiedere
al programmatore se desidera creare un array. Rispondendo Si, si crea un
vettore di caselle di testo denominato txtMinima, in cui gli elementi che
lo compongono sono caratterizzati dal possedere un diverso valore della
variabile Index.
Quest'ultima costituisce l'indice che identifica in modo univoco l'elemento
nel vettore. È tuttavia possibile realizzare una matrice anche riunendo dei
controlli aventi dei nomi diversi, purché siano dello stesso tipo. In questo
caso, occorre inserire tutti gli elementi nel form e successivamente associare
ad essi dei diversi valori della proprietà Index. Dopo aver fatto ciò, è
possibile variare il contenuto delle proprietà Name ed assegnare a tutti
la stessa stringa. Tornando all'esempio, si supponga di creare 7 copie
dell'oggetto txtMinima. Si supponga altresì di agire in modo analogo con
un'altra casella di testo, denominata txtMassima. In tal modo, si creano
2 vettori, destinati a contenere rispettivamente le temperature minime e massime
rilevate. Grazie ad essi, il codice necessario per riempire il vettore V si
riduce a poche righe:
For i=1 to 7
V(1,i)=val(txtMinima(i).Text)
V(2,i)=val(txtMassima(i).Text)
Next i
Si noti la notevole compattezza che lo caratterizza e la possibilità di
adattarlo alla gestione di un numero maggiore di textbox semplicemente variando
il valore massimo della variabile di controllo del ciclo. L'accesso alle
proprietà e ai metodi di ogni elemento del vettore richiede obbligatoriamente
l'indicazione dell'indice fra parentesi tonde. Un'interessante caratteristica
dei vettori di controlli consiste nell'utilizzo della stessa procedura per
rispondere agli eventi generati da ogni singolo elemento.
Ad esempio, si supponga di disporre di un form dotato di 3 pulsanti,
etichettati Primo, Secondo, Terzo, che costituiscono un
vettore denominato btnBottoni. Volendo generare un messaggio alla
pressione di un pulsante che indichi quale di essi è stato premuto, è possibile
scrivere la seguente procedura:
Private Sub btnBottoni_Click(Index As Integer)
MsgBox btnBottoni(Index).Caption
End Sub
Essa è associata all'evento Click di tutti gli elementi del vettore.
Si noti che fra parentesi è specificata la variabile Index. Essa ha
valore solo all'interno della procedura ed identifica l'indice dell'elemento che
ha generato l'evento.
Per mezzo della funzione MsgBox, in grado di far apparire sullo
schermo una finestra contenente un messaggio, è possibile visualizzare
l'etichetta testuale del pulsante premuto, facendo riferimento alla proprietà
Caption dell'elemento di indice corrispondente al valore della variabile
Index.
La procedura descritta risulta adatta a gestire vettori di dimensione
qualsiasi. Ancora una volta appare evidente la caratteristica fondamentale degli
array: lo stesso codice può gestire allo stesso modo e con la stessa facilità
delle strutture sia di piccole, sia di grandi dimensioni.
Esercizio
Si provi a realizzare un'applicazione che preveda una pulsantiera costituita
da tasti numerati da 1 a 9. La pressione di uno di essi deve provocare
l'incremento di un contatore numerico di un'entità pari all'indicazione presente
sulla sua etichetta. Il valore del contatore deve essere visualizzato per mezzo
di una label.
L'uso dei vettori permette di aumentare notevolmente la flessibilità e il
livello di astrazione del proprio codice.
Per mezzo di quelli a più dimensioni e agli array di controlli, diventa
spesso possibile ridurre notevolmente la quantità di codice necessaria per
realizzare un'applicazione, a tutto vantaggio della leggibilità.
Data l'importanza degli argomenti proposti, l'invito rivolto al lettore è
come sempre di esercitarsi su di essi, al fine di raggiungere un buon livello di
comprensione. (Parte 8)
Questo mese il corso
di programmazione in Visual
Basic
focalizza la propria attenzione sulle
procedure, talvolta
dette subroutine
Loggetto dellottava puntata del corso
dedicato alla programma-
zione in ambiente Microsoft
Visual Basic è
rappresentato dalle
procedure definibili dal programmatore.
Come si potrà osservare
in seguito, si tratta
di strutture di fondamentale
importanza, in
quanto il loro uso permette di rendere
notevolmente più compatto
e leggibile il codice di unapplicazione.
LE SOLUZIONI DEGLI ESERCIZI DELLA SCORSA
LEZIONE
Come sempre, prima di affrontare i nuovi
argomenti sarà fornito
al lettore uno spunto per rinfrescare
la propria memoria su quanto
appreso nella scorsa lezione.
Loccasione è data
dalla correzione
degli esercizi in essa
proposti.
Primo esercizio
Il primo esercizio richiede la realizzazione
di un programma in
grado di simulare lo
spoglio delle schede elettorali.
Per ogni
scheda, lapplicazione deve
leggere il numero del
candidato
votato e redigere, a scrutinio terminato,
una classifica in ordine
decrescente. Il compito può
essere svolto dalla seguente
proce-
dura, che è eseguita nella fase
di caricamento di un form conte-
nente unetichetta di testo (lblClassifica)
destinata a contenere la
classifica finale.
Private Sub Form_load()
Dim ElencoVoti()
As Integer
Dim Classifica()
As Integer
Dim Stringa As String
Dim Candidato As Integer
Dim NumeroCandidati As Integer
Dim i As Integer
Dim j As Integer
Dim x As Integer
Dim y As Integer
Stringa = InputBox(Numero
candidati)
NumeroCandidati
= Int(Val(Stringa))
ReDim ElencoVoti(NumeroCandidati)
ReDim Classifica(NumeroCandidati)
Rem Acquisizione dei dati
Do
Stringa = InputBox(N.
candidato votato
(0 per finire))
Candidato
= Int(Val(Stringa))
If Candidato
> 0 Then
ElencoVoti(Candidato)
= ElencoVoti(Candidato) + 1
End If
Loop Until Candidato = 0
Rem Preparazione
vettore classifica
For i = 1 To NumeroCandidati
Classifica(i)
= i
Next i
For i = 1 To NumeroCandidati
- 1
For j = i + 1 To NumeroCandidati
x
= Classifica(i)
y
= Classifica(j)
If
ElencoVoti(x) < ElencoVoti(y) Then
Temp
= Classifica(i)
Classifica(i)
= Classifica(j)
Classifica(j)
= Temp
End
If
Next j, i
Rem Visualizzazione classifica
lblClassifica.Caption =
For i = 1 To NumeroCandidati
lblClassifica.Caption
= lblClassifica.Caption &
Classifica(i) & Chr$(10)
Next i
End Sub
La lettura dei dati
è, per semplicità,
affidata a delle finestre
di
input, realizzate per mezzo della funzione
InputBox.
Sono previsti due vettori;
il primo, denominato ElencoVoti,
associa allelemento di indice n il numero
dei voti collezionati dal
candidato numero n. Ogni
elemento contiene un numero
che è
incrementato ogniqualvolta è estratta
una scheda sui cui è espressa
una preferenza riferita al
candidato corrispondente. Un
secondo
vettore, denominato Classifica,
ha lo scopo di memorizzare
i
numeri associati ai candidati
in modo che leggendo
sequenzial-
mente la struttura si possa leggere la
classifica in ordine decrescen-
te di voti. Entrambi
i vettori sono dimensionati
allavvio del
programma per prevedere un elemento per
ogni candidato.
La procedura provvede a
leggere tutti i voti
di preferenza,
aggiornando volta per volta il vettore
ad essi dedicato. Dopo aver
fatto ciò, è inizializzato
il vettore Classifica con i numeri corrispon-
denti a tutti i candidati; tali valori
sono in seguito scambiati fra loro
in modo tale per cui
al termine del ciclo
il vettore contenga la
classifica in ordine decrescente
di preferenze. Si noti
che per
effettuare ciò è applicata
una variante allalgoritmo di ordinamento
per selezione diretta visto nella scorsa
lezione, in cui lordinamento
è eseguito sullarray Classifica
ma i confronti sono effettuati fra gli
elementi del vettore ElencoVoti.
Si desidera infatti fare
in modo
PROGRAMMO SUBITO n.8 - SETTEMBRE 1998
a a a a a a a a a a a a a a a a a a a a
a a a a a a a a a a a a a a a a a a a a
a a a a a a a a a a a a a a a a 87
che, dati due elementi
qualsiasi x e y del vettore
Classifica, x
preceda y se ElencoVoti(x)>ElencoVoti(y).
Secondo esercizio
Il secondo esercizio prevede
la realizzazione di un
programma
dotato di una pulsantiera costituita
da tasti numerati da 1 a 9. La
pressione di uno di essi deve provocare
lincremento di un conta-
tore numerico di unentità pari
allindicazione presente sulla sua
etichetta. Il valore del
contatore deve essere
visualizzato per
mezzo di una label.
Se si fa uso di
un vettore di controlli,
la
soluzione diventa estremamente semplice,
al punto da non neces-
sitare di alcun commento. Si supponga
di disegnare un form dotato
di nove pulsanti, che costituiscono gli
elementi del vettore btnNu-
meri. Alla pressione di uno di essi,
ovvero al verificarsi dellevento
Click su un elemento del vettore, può
essere associata la seguente
procedura:
Private Sub btnNumeri_click(index
As Integer)
Dim Contatore As Integer
Dim Incremento As Integer
Contatore = Val(lblContatore.Caption)
Incremento = Val(btnNumeri(index).Caption)
Contatore = Contatore +
Incremento
lblContatore.Caption = Contatore
End Sub
LE PROCEDURE
Si supponga di voler
realizzare unapplicazione in
grado di
calcolare la somma di due valori numerici
inseriti dallutente dopo
aver verificato che siano positivi e,
rispettivamente, inferiori a 10
e 50. Una possibile implementazione è
la seguente:
Private Sub Form_Load()
Dim Stringa As String
Dim n1 As Double
Dim n2 As Double
Dim Somma As Double
Rem Primo valore
Do
Stringa = InputBox(Primo
valore:)
n1 = Val(Stringa)
Loop Until (n1 > 0) And
(n1 < 10)
Rem Secondo valore
Do
Stringa
= InputBox(Secondo valore:)
n2 = Val(Stringa)
Loop Until (n2 > 0) And
(n2 < 50)
Somma = n1 + n2
MsgBox (La somma è
& Somma)
End Sub
Lacquisizione dei valori
avviene per mezzo di
due cicli. Il loro
scopo consiste nel richiedere la digitazione
di una stringa allutente
e nelleffettuare la conversione in un
valore numerico per mezzo
della funzione Val. Tale
dato è successivamente
sottoposto a
verifica per determinarne la conformità
alle condizioni richieste.
Qualora essa non fosse riscontrata, la
presenza del ciclo fa sì che sia
richiesto nuovamente il dato allutente.
Si noti che le due strutture
di iterazione sono estremamente simili.
Ciò che cambia è rappre-
sentato solamente dalla variabile da
acquisire e dal valore massimo
consentito. Se Visual Basic disponesse
di unistruzione in grado di
richiedere un dato e di verificarne lappartenenza
ad un intervallo,
il codice sarebbe notevolmente più
semplice e privo di ripetizioni.
Una simile istruzione non è prevista
nella libreria standard dello
strumento di sviluppo. Tuttavia, come
tutti i linguaggi di program-
mazione moderni, Visual Basic permette
la creazione di procedure,
o subroutine. Esse rappresentano gruppi
di istruzioni racchiuse in
strutture identificate univocamente dai
propri nomi. Una sequenza
di questo tipo può
essere eseguita più
volte allinterno di un
programma senza che sia
necessario ripeterne il
codice; basta
infatti richiamarla indicandone il nome.
La definizione di una procedura prevede
la seguente sintassi:
[Public|Private] Sub <nome> [(<definizione_parametro_1>,
...
<definizione_parametro_n>)]
[<dichiarazione_variabili_locali>]
<istruzione_1>
...
<istruzione_n>
End Sub
Dopo la parola chiave Sub è necessario
specificare un identifica-
tore che costituisce il
nome della struttura.
Come tutti gli altri
identificatori, deve essere
composto da lettere, numeri
o dal
carattere di sottolineatura
(undescore) e il primo
carattere deve
essere necessariamente costituito
da una lettera dellalfabeto.
Si
noti che non è
la prima volta che
si usa la parola
chiave Sub
allinterno di questo corso. Infatti,
le porzioni di codice da associa-
re come risposta agli
eventi sono anchesse
delle procedure.
Quando un controllo genera
un evento, linterprete
provvede
automaticamente a verificare lesistenza
di una procedura avente
un nome composto dallidentificatore
del controllo seguito
dal
carattere di sottolineatura
e dal tipo di evento
(ad esempio
btnPulsante_Click). Se tale struttura
esiste, è eseguita. Le proce-
dure oggetto di trattazione in questa
lezione sono invece definite
dallutente con lo scopo di migliorare
la leggibilità del programma
e riciclare il codice
già scritto, evitando
la ripetizione di linee
pressoché identiche. Le
procedure, analogamente alle
variabili,
sono soggette alle regole
di scope. Pertanto, se
sono definite
allinterno di un form,
risultano locali a questultimo,
quindi
Linter faccia utente
della soluzione del
secondo esercizio
proposto nella
scorsa lezione
FIGURA 1
Mediante luso delle
procedure è possibile
migliorare la leggibilità del
codice
a a a a a SETTEMBRE 1998 - PROGRAMMO
SUBITO n.8 a a a a a a a a a a a a a a a
a a a a a a a a a a a a a a a a a a a a
a a a a a a a a a a a a a a a a 88
pressoché invisibili agli altri
moduli. Qualora si desiderasse creare
delle procedure globali,
da utilizzare indifferentemente
in tutti i
form come se si trattasse di comandi
standard previsti dal linguag-
gio di programmazione, è
necessario creare un nuovo
modulo
globale, agendo sul menu Progetto
alla voce Inserisci modulo.
Luso dei moduli globali permette anche
di creare delle librerie di
procedure che possono essere
agevolmente riutilizzate per
lo
sviluppo di altre applicazioni,
semplicemente includendo il
file
allinterno dei progetti.
UNA FUNZIONE IN UN MODULO
Per inserire una procedura in un modulo
occorre selezionare il
file nella finestra Progetto e premere
il pulsante di visualizzazione
del codice. A questo punto è possibile
utilizzare la voce Inserisci
routine del menu Strumenti. Appare così
una finestra che permette
di specificare il nome della procedura,
unitamente ad altre opzioni
che per ora non saranno descritte.
Agendo in questo modo
sul form di avvio
dellapplicazione de-
scritta nellesempio precedente,
è possibile inserire
in esso la
procedura LeggiValore, in grado di leggere
un dato e di convertirlo
in un valore numerico.
La sua definizione è la seguente:
Sub LeggiValore()
Dim Stringa As String
Do
Stringa = InputBox(MessaggioRichiesta)
n = Val(Stringa)
Loop Until (n > 0) And
(n < Massimo)
End Sub
Si noti la dichiarazione della variabile
Stringa, che risulta locale
alla procedura, essendo stata
definita al suo interno.
Pertanto, è
creata quando il programma
inizia ad eseguire la
routine ed è
distrutta in corrispondenza delluscita
dal blocco di codice. Risulta
quindi evidente che il suo valore non
può essere utilizzato al di fuori
della procedura. Inoltre, non è
conservato nelle chiamate succes-
sive, a meno che non si sostituisca la
parola chiave Dim con Static.
In questo caso, la variabile è
detta statica.
Tornando alla procedura LeggiValore,
si nota che essa
fa riferi-
mento anche alle variabili n, MessaggioRichiesta
e Massimo, di cui
tuttavia non è specificata la
definizione. Ciò è corretto
se si tratta
di due variabili globali,
ovvero se sono definite
allinterno della
sezione Generale del form o in un modulo
globale facendo uso della
parola chiave Global. Nel primo caso
esse risultano visibili a tutte
le procedure definite allinterno del
form.
Nel secondo, possono
essere utilizzate addirittura
da tutte le
routine contenute nellapplicazione.
Con lintroduzione della pro-
cedura LeggiValore, il codice da associare
allevento Load del form
principale, diventa il seguente:
Private Sub Form_Load()
Dim Stringa As String
Dim n1 As Double
Dim n2 As Double
Dim Somma As Double
Rem Primo valore
MessaggioRichiesta = Primo valore:
Massimo = 10
LeggiValore
n1 = n
Rem Secondo valore
MessaggioRichiesta = Secondo
valore:
Massimo = 50
LeggiValore
n2 = n
Somma = n1 + n2
MsgBox (La somma è & Somma)
End Sub
Si noti luso delle variabili globali
per comunicare alla procedura
il valore massimo accettabile, nonché
il messaggio da visualizzare.
Si noti altresì che la routine
provvede ad utilizzare unaltra varia-
bile globale (n) per restituire il valore
letto. Questo primo esempio
di utilizzo di una procedura ha sortito
un effetto pressoché delu-
dente, in quanto non ha comportato una
sensibile semplificazione
del codice. Ciò a causa della
necessità di utilizzare delle variabili
globali per scambiare le informazioni
fra i due blocchi di istruzioni
e dalla conseguente presenza di numerose
operazioni di assegna-
mento. Al fine di ottenere
un consistente miglioramento
della
leggibilità del codice, nonché
una sua semplificazione, è necessario
modificare la procedura LeggiValore
per fare in modo
che sia in
grado di accettare dei parametri, ovvero
che permetta di inizializ-
zare le variabili MessaggioRichiesta
e Massimo nel momento della
sua invocazione, senza che
sia richiesto luso delle
istruzioni di
assegnamento.
I PARAMETRI
Il metodo più semplice ed efficace
per comunicare delle informa-
zioni a una procedura consiste nelluso
dei parametri, ognuno dei
quali deve essere dichiarato
secondo la sintassi:
[ByVal|ByRef] <nome> As
<Tipo>
Un parametro, talvolta detto argomento,
per il codice contenuto
allinterno della routine equivale a
una variabile. Da essa si differen-
zia per il fatto che la sua inizializzazione
non avviene allinterno
della procedura, bensì quando
essa è richiamata. Si supponga ad
esempio di definire la seguente subroutine:
Sub Prova(Param1 As String,
Param2 As Integer)
MsgBox(Param1 &
- & Param2)
End Sub
Se si digita la riga
Prova Dev, 1998
si richiama la procedura Prova
e si inizializzano i
parametri
Param1 e Param2 rispettivamente
con la stringa Dev
e con il
numero 1998. Il messaggio visualizzato
è pertanto il seguente:
Dev-1998
Si noti che un parametro
può essere di un
qualsiasi tipo base
previsto da Visual Basic. Come accade
per le variabili, se il formato
non è dichiarato, il dato è
considerato Variant.
La procedura LeggiValore può
pertanto essere riscritta in modo
da accettare come parametri
il messaggio da visualizzare
al mo-
mento della richiesta del dato e il valore
massimo consentito.
Sub LeggiValore(MessaggioRichiesta
As String,
Massimo As Integer)
Dim Stringa As String
Do
Stringa = InputBox(MessaggioRichiesta)
n = Val(Stringa)
Loop Until (n > 0) And
(n < Massimo)
End Sub
Per effetto di questa modifica, il codice
da associare al caricamen-
to del form principale si semplifica
notevolmente:
Sub Form_Load()
Dim n1 As Double
Dim n2 As Double
Dim Somma As Double
LeggiValore Primo valore:, 10
n1 = n
LeggiValore Secondo valore:,
50
n2 = n
Somma = n1 + n2
MsgBox (La somma è & Somma)
End Sub
Le modifiche apportate fanno
sì che non sia
più necessario
dichiarare le variabili globali
MessaggioRichiesta e Massimo.
Unulteriore semplificazione potrebbe
essere introdotta renden-
do la procedura in grado di aggiornare
automaticamente le varia-
bili n1 e n2. Ciò è
possibile introducendo un terzo parametro.
La
procedura LeggiValore diventa
pertanto la seguente:
Sub LeggiValore(MessaggioRichiesta
As String,
Massimo As Integer, n As Double)
Dim Stringa As String
Do
Stringa = InputBox(MessaggioRichiesta)
n = Val(Stringa)
Loop Until (n > 0) And
(n < Massimo)
End Sub
Il codice da eseguire in fase di caricamento
del form diventa:
Sub Form_Load()
Dim n1 As Double
Dim n2 As Double
Dim Somma As Double
LeggiValore Primo
valore:, 10, n1
LeggiValore Secondo valore:,
50, n2
Somma = n1 + n2
MsgBox (La somma è
& Somma)
End Sub
Lapplicazione ora non necessità
più di alcuna variabile globale.
LE MODALITÀ DI PASSAGGIO DEI PARAMETRI
I parametri possono essere passati a
una procedura secondo due
diverse modalità: per valore e
per riferimento; la scelta è effettuata
utilizzando per ogni singolo
parametro in alternativa
le clausole
ByVal o ByRef.
Passaggio dei parametri per
riferimento
Nellesempio precedente, la
variabile n è stata
passata alla
procedura LeggiValore non
per fornire ad essa
uninformazio-
ne, bensì per fare in modo che
sia la routine a restituire un valore
al blocco di istruzioni chiamante. Infatti,
nel momento in cui è
eseguita la riga
LeggiValore Primo valore:,
10, n1
alla variabile n1 non
è stato ancora
assegnato un valore.
Questo compito spetta alla
procedura. Si dice allora
che la
variabile n1 è stata
passata per riferimento,
ovverosia che è
stato fornito alla procedura lindirizzo
della locazione di memo-
ria in cui risiede
n1 per fare
in modo che il dato
in essa
contenuto sia modificato.
Questo è metodo
di passaggio delle
informazioni predefinito in Visual Basic.
Quando alla dichiara-
zione di un parametro non è anteposta
la clausola ByVal, esso è
considerato dallinterprete come
passato per riferimento.
È
quindi evidente che la clausola ByRef
risulta di fatto superflua,
in quanto ha solo lo scopo di rendere
il codice più rapidamente
comprensibile.
Passaggio dei parametri per
valore
Se nella dichiarazione di un parametro
si antepone la clausola
ByVal, si stabilisce che linformazione
è passata per valore. Ciò
significa che la procedura riceve un
valore ma non la facoltà di
variarlo. In pratica il dato deve essere
considerato dalla routine
alla stregua di una costante.
In realtà, la procedura
può effettuare delle modifiche al
valore
del parametro, ma esse non hanno effetto
nel blocco chiamante.
Ad esempio, si supponga
di dichiarare le seguenti
procedure:
Sub Prova1(ByVal Testo As
String)
Testo = Testo & 5.0
MsgBox Testo
End Sub
Sub Prova2(ByRef Testo as
String)
Testo = Testo & 5.0
MsgBox Testo
End Sub
Si supponga altresì di dichiarare
una variabile di tipo stringa
denominata Messaggio e di inserirvi la
frase Visual Basic. La
riga
Prova1 Messaggio
provoca pertanto la visualizzazione
di una finestra in
cui
appare la scritta Visual Basic 5.0.
Il contenuto della variabile
Messaggio, tuttavia, non
varia, in quanto essa
è stata passata
per valore.
Al contrario, lesecuzione della riga
Prova2 Messaggio
provoca la visualizzazione di una finestra
perfettamente iden-
tica a quella descritta
in precedenza, ma comporta
anche la
modifica del contenuto della
variabile Messaggio.
DUE SEMPLICI ESERCIZI
Al fine di verificare la comprensione
degli argomenti esposti,
si provi a realizzare
una procedura in grado
di restituire il
valore assoluto di un
numero intero, usando
a tal fine un
parametro passato per riferimento. Si
realizzi poi una seconda
procedura che richiami la
prima per calcolare la
somma dei
valori assoluti di 3 numeri passati come
parametri per valore.
CONCLUSIONI
Un uso corretto delle procedure è
di fondamentale importanza
per la scrittura di
codice leggibile e di
facile manutenzione.
Inoltre, il loro raggruppamento
in librerie permette di
creare
dei moduli che possono essere riutilizzati
nella realizzazione di
applicazioni da parte di uno o più
utenti come se si trattasse di
estensioni del linguaggio
di programmazione.
(Parte 9) La
nona lezione del corso dedicato alla programmazione in Visual Basic si pone lo
scopo di illustrare le funzioni definibili dall'utente e il concetto di
ricorsione
Nella scorsa lezione sono state
descritte le subroutine e sono stati evidenziati i benefici che il loro uso dà
al programmatore. Questo mese l'argomento sarà ulteriormente approfondito con
l'introduzione delle funzioni, ovvero di procedure in grado di restituire un
valore senza l'ausilio di una variabile globale o di un parametro passato per
riferimento, nonché con la descrizione dell'uso avanzato che è possibile fare di
esse. Prima però è opportuno compiere un passo indietro per riportare alla mente
i concetti esposti nella precedente puntata del corso. Come sempre, il fine è
raggiunto mediante l'illustrazione delle soluzioni degli esercizi in essa
proposti.
Le soluzioni degli esercizi della
scorsa lezione Si tratta
di due semplicissimi esercizi, il cui scopo è quello di aiutare il lettore a
riconoscere la differenza fra la modalità di passaggio dei parametri per
valore e quella per riferimento.
Primo esercizio
Il primo esercizio prevede la realizzazione di una procedura in grado di
restituire il valore assoluto di un numero intero per mezzo di un parametro
passato per riferimento. Dai testi scolastici è noto che il valore assoluto
di un numero è pari ad esso se è positivo oppure, in caso contrario, al suo
opposto. Volendo fare a meno di ricorrere alla funzione Abs prevista
dalla libreria standard, è possibile effettuare il calcolo per mezzo di una
struttura If. La soluzione dell'esercizio è pertanto la seguente:
Sub ValAssoluto(ByVal Numero As
Double, ByRef Risultato As Double) If Numero > 0 Then Risultato =
Numero Else Risultato = -1 * Numero End If End Sub
Si noti che il numero di cui
deve essere calcolato il valore assoluto è fornito alla procedura tramite un
parametro passato per valore. Si supponga ora di voler eseguire la seguente
porzione di codice:
r=0 n=45 ValAssoluto n, r
MsgBox(Str$(r))
Un eventuale tentativo da parte
della routine di modificare il dato fornitole come primo parametro non ha
effetto sulla variabile n posta nel blocco di codice chiamante. Diverso è
invece il discorso per quanto riguarda la variabile r. Essa, infatti, è
passata come parametro per riferimento; la procedura può quindi intervenire
direttamente sul suo valore. Ciò permette alla routine di restituire un dato
senza far uso di variabili globali.
Secondo esercizio
Il secondo esercizio richiede la realizzazione di una procedura in grado di
richiamare quella appena illustrata per calcolare la somma dei valori assoluti
di 3 numeri passati come parametri per valore. La soluzione è banale ed è
rappresentata dal codice riportato di seguito:
Sub SommaVAssoluti(ByVal n1 As Double, ByVal n2 As Double,
ByVal n3 As Double, ByRef Somma As Double) Dim r1 As Double Dim r2 As Double Dim r3 As Double
ValAssoluto n1, r1 ValAssoluto n2, r2 ValAssoluto n3, r3 Somma =
r1 + r2 + r3 End Sub
Anche in questo caso, il
risultato è restituito al blocco di codice chiamante per mezzo di un parametro
passato per riferimento.
Le funzioni
Un sensibile
miglioramento della leggibilità del codice e della comodità d'uso dello
strumento di sviluppo deriva dalla possibilità di restituire un valore evitando
l'uso di una variabile globale o di un parametro passato per riferimento. Ciò è
possibile per mezzo delle funzioni. Il linguaggio prevede una quantità
elevatissima di funzioni standard. Ad esempio, per calcolare il valore assoluto
di un numero, esiste la funzione Abs, che riceve in ingresso un dato
numerico e ne restituisce il modulo. La riga
x = Abs(y)
fa sì che alla variabile x sia
assegnato il valore assoluto del numero contenuto nella variabile y. Come
accade per le procedure, è possibile incrementare l'insieme delle funzioni
disponibili creandone ad hoc per soddisfare le proprie esigenze. A tal fine è
necessario racchiudere le istruzioni in strutture dichiarate per mezzo della
parola chiave Function, secondo la sintassi di seguito riportata:
[Public|Private] Function <nome>
[(<definizione_parametro_1>, ... <definizione_parametro_n>)] As
<tipo> [<dichiarazione_variabili_locali>]
<istruzione_1> ... <istruzione_n> <nome>=<valore> End
Function
Com'è possibile notare,
l'analogia con le procedure è notevole. A differenza di esse, è necessario
indicare un tipo di dati standard al termine della riga di dichiarazione. Esso
identifica il formato in cui deve essere restituito il risultato. È inoltre
necessario fare in modo che all'interno del blocco di codice sia presente una
riga che preveda l'assegnamento di un valore a una variabile avente lo stesso
nome della funzione. Tale dato è quello restituito al blocco chiamante. Si
supponga di voler realizzare una semplice funzione in grado di moltiplicare un
numero intero per 4. Il codice necessario è il seguente:
Function Quadruplo(Numero As Integer)
Quadruplo = Numero * 4 End Function
Il valore assunto dal parametro
Numero è moltiplicato per 4 e restituito dalla funzione. È possibile
utilizzare la struttura appena creata in un'istruzione di assegnamento scrivendo
il suo nome seguito dai parametri posti fra parentesi tonde, come nell'esempio
che segue, in cui il risultato della valutazione della funzione Quadruplo
è assegnato alla variabile x.
x = Quadruplo(y)
Lo stesso scopo può essere
ottenuto anche per mezzo di una procedura dotata di un parametro passato per
riferimento. In questo caso il codice è il seguente:
Sub Quadruplo(Numero As Integer,
Risultato As Integer) Risultato = Numero * 4 End Sub
In questo caso, volendo
assegnare alla variabile x il risultato, è necessario scrivere:
Quadruplo(y,x)
Pur essendo le due soluzioni del
tutto equivalenti, appare evidente la maggiore semplicità della prima. Si noti
che una funzione può restituire un solo valore. Qualora si presentasse la
necessità di fornire al blocco chiamante più di un'informazione, è necessario
ricorrere ai parametri passati per riferimento.
Esercizio Si
provi a realizzare una funzione in grado di ricevere come parametro una stringa
alfanumerica e restituire il valore logico True se essa contiene il nome
di un giorno della settimana.
La ricorsione
Si supponga di voler
realizzare una funzione in grado di calcolare il fattoriale di un numero intero.
Ricordando che il fattoriale è definito solo per valori numerici positivi o
nulli (in quest'ultimo caso giova ricordare che il fattoriale di 0 è 1), è
possibile prevedere la seguente implementazione:
Function Fattoriale(ByVal n As
Integer) As Long Dim i As Integer Dim Risultato As Long If n >= 0
Then Risultato = 1 For i = 2 To n Risultato = Risultato * i Next
i Else Risultato = 0 End If Fattoriale = Risultato End
Function
Si tratta di una funzione in
grado di ricevere come parametro (per valore) un dato di tipo numerico intero e
di restituire un long. L'algoritmo prevede dapprima la verifica che il
numero oggetto di elaborazione sia positivo o nullo; successivamente, esegue un
ciclo che provvede ad effettuare la serie di moltiplicazioni necessaria per il
calcolo del risultato. Tale valore è infine assegnato alla funzione per fare in
modo che essa lo restituisca. Si noti che se il dato fornito in ingresso è
negativo, la funzione restituisce il valore 0. Ciò è accettabile, non essendo
possibile che il fattoriale di un numero sia nullo; si tratta quindi di un
valido indicatore della presenza di un errore. L'algoritmo appena descritto
non è l'unico in grado di effettuare il calcolo del fattoriale di un numero. Ad
esempio, è possibile scrivere la seguente implementazione:
Function Fattoriale(ByVal n As
Integer) As Long If n >= 0 Then If n = 0 Then Fattoriale = 1
Else Fattoriale = n * Fattoriale(n - 1) End If End If End
Function
Il numero delle righe di codice
si è ridotto, a vantaggio della leggibilità. Tuttavia, a prima vista il lettore
può rimanere sconcertato dall'uso della funzione Fattoriale all'interno
della propria definizione. Ciò può apparire come un errore. In realtà, tale
tecnica è perfettamente lecita e prende il nome di ricorsione.
Per rendersi conto del corretto
funzionamento, si provi ad osservare ciò che avviene calcolando il fattoriale di
un numero qualsiasi, ad esempio 3.
Essendo n pari a 3, la funzione
esegue il calcolo
Fattoriale(3)=3*Fattoriale(2)
Ma, analogamente
Fattoriale(2)=2*Fattoriale(1)
e
Fattoriale(1)=1*Fattoriale(0)
Per mezzo di una struttura
If , è distinto il caso in cui n è nullo. In questa condizione la
funzione restituisce il valore 1. Quindi,
Fattoriale(3)=3*(2*(1*(1)))
Ciò concorda con la definizione
matematica del fattoriale. L'uso della ricorsione permette in alcuni casi di
semplificare notevolmente la scrittura di una funzione e di migliorarne al tempo
stesso la leggibilità. Tuttavia, presenta numerose insidie. Si provi a valutare
il risultato prodotto dalla riga
Calcola(7)
dove la funzione Calcola
è definita come segue:
Function Calcola(n As Integer) As
Integer Calcola = n + Calcola(n - 1) End Function
Il risultato è costituito da un
errore di sistema. Infatti, non essendo prevista alcuna condizione di uscita,
ovvero non esistendo un valore del parametro per cui è restituito un risultato
non dipendente da una successiva chiamata della funzione, si genera una
successione di invocazioni di quest'ultima destinata a non avere fine, almeno
sino all'esaurimento dello spazio di memoria dedicato allo stack del
sistema. Appare quindi evidente una condizione fondamentale che deve essere
soddisfatta da tutte le funzioni ricorsive: deve sempre essere prevista una
condizione di uscita. È bene tuttavia non abusare della ricorsione. Infatti,
sebbene in alcuni casi semplifichi notevolmente il compito del programmatore,
talvolta può comportare un sensibile aumento della richiesta di risorse da parte
del sistema, data la necessità che esso ha di mantenere contemporaneamente
attive più istanze della stessa funzione.
Esercizio Si
provi a realizzare una funzione che, dato un parametro n intero positivo
passato per valore, sia in grado di restituire la somma dei primi n
numeri naturali. A tal fine si faccia uso della ricorsione.
La procedura Main
Lo studio delle
subroutine termina con una procedura un po' particolare, la cui importanza è
tutt'altro che trascurabile. Si tratta della procedura Main.
Un'applicazione realizzata in Visual Basic è generalmente dotata di
un'interfaccia utente grafica, in cui l'interazione con gli elementi attivi
determina il flusso del programma. Tuttavia, in alcuni casi, soprattutto per la
realizzazione di semplici utility in grado di operare in modo invisibile
all'utente, è necessario sopprimere l'interfaccia grafica. Quando ciò avviene,
il progetto risulta privo di form. Il codice deve pertanto essere posto altrove,
ovvero all'interno di moduli. Affinché una sequenza di istruzioni sia eseguita
all'avvio di un'applicazione è necessario inserirla all'interno di una procedura
denominata Main. Occorre inoltre selezionare tale routine come oggetto di
avvio nella finestra delle proprietà del progetto in luogo di un form. Una
semplice procedura Main è la seguente:
Sub Main() If Date$ = "12-25-1998"
Then MsgBox "Buon Natale" End If End Sub
Si tratta di una piccola
applicazione che può essere eseguita automaticamente all'avvio di Windows e che
normalmente non fornisce alcun feedback all'utente. Solo il giorno di Natale del
1998 essa ha un effetto visibile, in quanto provvede a visualizzare un messaggio
di auguri. Si noti l'uso della funzione standard Date$ che restituisce
una stringa contenente la data corrente. Il ricorso alla procedura Main
non è tuttavia riservato solo alle applicazioni prive di form. A volte può
essere utile fare in modo che un programma all'avvio sia in grado di valutare
una condizione e in base ad essa scegliere fra i form che lo compongono quello
che deve essere visualizzato. Si supponga di disporre di una funzione denominata
SelezionaForm, di cui saranno trascurati i dettagli implementativi, in
grado di leggere il registro di sistema per determinare la lingua utilizzata
dalla versione in uso del sistema operativo e di restituire una stringa
contenente una delle seguenti sequenze alfanumeriche: "Italiano", "Inglese",
"Tedesco", "Francese". Si supponga altresì di aver realizzato un progetto
caratterizzato dalla presenza di 4 form, differenti fra loro per la lingua in
cui sono scritte le etichette che identificano i controlli e di aver dato ad
essi rispettivamente i nomi Ita, Ing, Ted, Fra. È
allora possibile scrivere una procedura Main in grado di avviare il form
corrispondente alla lingua presente nel sistema. Il codice è il seguente:
Sub Main() Select Case
SelezionaForm() Case "Italiano": Ita.Show vbModal Case "Inglese":
Ing.Show vbModal Case "Tedesco": Ted.Show vbModal Case
"Francese" Fra.Show vbModal End Select End Sub
Si notino alcune particolarità;
la funzione SelezionaForm, ad esempio, non prevede parametri. Affinché
sia utilizzabile correttamente, è comunque necessario richiamarla specificando
le parentesi, sebbene fra esse non sia posto alcun dato o nome di variabile. Si
noti inoltre l'uso del metodo Show, che permette di avviare il
caricamento e la successiva visualizzazione di un form. L'uso della costante
predefinita vbModal accanto ad esso stabilisce che la procedura in fase
di esecuzione non può passare all'istruzione successiva finché il form risulta
visibile. Solo dopo la sua chiusura il flusso delle istruzioni può riprendere e,
non essendo presente altro codice, il programma può terminare.
Conclusioni
Le procedure e le
funzioni sono strumenti estremamente efficaci, in grado di fornire un notevole
aiuto al programmatore. I moderni linguaggi di programmazione orientati agli
eventi, come Visual Basic, si fondano in modo pressoché totale sulla presenza di
tali strutture. L'acquisizione della necessaria dimestichezza nel loro uso
diventa pertanto un obbligo per colui che desidera sfruttare al meglio le
potenzialità dello strumento. Per questo motivo, ancora una volta la lezione si
chiude rivolgendo al lettore un invito ad esercitarsi sugli argomenti trattati,
servendosi a tal fine anche degli esercizi proposti.
(Parte 10) La maggior parte delle applicazioni
deve essere in grado di memorizzare delle informazioni su un supporto di memoria
di massa, costituito in genere da un'unità a disco, in modo tale da renderle
reperibili nelle sessioni successive. La struttura destinata ad accogliere
questi dati, oggetto di studio in questa lezione, prende il nome di
file.
Le soluzioni degli esercizi proposti
nella scorsa lezione Prima di avventurarsi nello studio dei file, è bene
focalizzare come di consueto l'attenzione sugli esercizi proposti nello scorso
numero. Eccone, in breve, le soluzioni.
Primo esercizio Il primo
esercizio prevede lo sviluppo di una funzione booleana in grado di ricevere come
parametro una stringa alfanumerica e di restituire True se essa contiene
il nome di un giorno della settimana. In caso contrario, il valore ritornato
deve essere False. La soluzione, pressoché banale, è la
seguente:
Function Giorno(Stringa As String) As
Boolean Dim Risultato As Boolean Select Case UCase(Stringa) Case
"LUNEDI", "MARTEDI", "MERCOLEDI", "GIOVEDI", "VENERDI", "SABATO",
"DOMENICA" Risultato = True Case Else Risultato = False End
Select Giorno = Risultato End Function
Il valore restituito corrisponde a
quello della variabile Risultato, che è aggiornato da una semplice
struttura Select Case effettuata sulla stringa convertita in
maiuscolo.
Secondo esercizio Il secondo
esercizio prevede la realizzazione di una funzione ricorsiva che, dato un
parametro n intero positivo passato per valore, sia in grado di
restituire la somma dei primi n numeri naturali. La soluzione è
rappresentata dalla funzione Calcola descritta di seguito.
Function Calcola(n As Integer)As
Long Dim Somma As Long If n > 0 Then Somma = n + Calcola(n -
1) End If Calcola = Somma End Function
Se il parametro n ha un valore
non nullo, è sommato al risultato dell'applicazione della funzione a n-1.
Se invece n è uguale a zero, si verifica la condizione di uscita.
I file Un file è un agglomerato
di informazioni residente su un supporto di memorizzazione di massa, ovvero su
un dispositivo caratterizzato dalla capacità di mantenere i dati anche dopo lo
spegnimento e il successivo riavvio del sistema. Di solito si tratta di un'unità
a disco fisso o rimovibile. Le informazioni contenute in un file non devono
essere necessariamente di tipo omogeneo. Esiste infatti la possibilità di creare
dei file strutturati, in grado cioè di contenere elementi di pari
caratteristiche, oppure composti da informazioni di natura anche profondamente
diversa. Visual Basic permette di creare 3 diversi tipi di file, con accesso
sequenziale o casuale. In questa lezione sono oggetto di studio le strutture ad
accesso sequenziale.
I file ad accesso sequenziale Le
strutture più elementari sono i file ad accesso sequenziale, il cui uso è in
genere riservato alla memorizzazione delle informazioni testuali. Si tratta di
strutture organizzate in righe, da utilizzare quando si desidera salvare dei
documenti da stampare oppure per la memorizzazione di limitate quantità di dati
(come ad esempio quelle riguardanti le impostazioni di un programma). Il
vantaggio offerto dai file ad accesso sequenziale è rappresentato dalla
semplicità con cui è possibile accedere al loro contenuto mediante un qualsiasi
editor di testo, come il Blocco Note di Windows. Sono di questo tipo, ad
esempio, i file Win.ini e System.ini, contenenti le informazioni
riguardanti la configurazione dell'ambiente Windows a 16 bit. L'organizzazione
dei dati, tuttavia, fa sì che le strutture di questo tipo non siano adatte a
contenere una grande quantità di informazioni, a causa del dilatarsi dei tempi
di accesso dovuto al fatto che la ricerca di un particolare dato può comportare
la lettura dell'intero file.
L'apertura di un file
sequenziale Un file, di qualsiasi tipo esso sia, per poter essere
utilizzato deve necessariamente essere aperto. È possibile effettuare ciò per
mezzo del comando Open, la cui sintassi, per le strutture ad accesso
sequenziale, è la seguente:
Open
<percorso_file> [For <modalità>] As
[#]<numero_identificatore> [Len=<lunghezza_record>]
dove <percorso_file> è una
stringa che identifica il percorso del file che si desidera creare o leggere. Se
si sta usando una versione a 32 bit dello strumento e quindi si sta realizzando
un'applicazione funzionante solo su Windows 95, 98 o NT, è possibile far uso dei
nomi di file lunghi. Ciò non è consentito invece alle applicazioni destinate ad
essere compilate con una versione a 16 bit di Visual Basic. In questo caso,
occorre adeguarsi al formato previsto da MS-DOS e dalla macchina virtuale a 16
bit di Windows, che prevede un massimo di 8 caratteri per i nomi dei file e di 3
per le estensioni. Il carattere separatore è in tutti i casi costituito dalla
barra retroversa "\".Dopo aver specificato il nome del file da gestire, è
necessario indicare la modalità con cui si desidera accedere ad esso. La scelta
è fra Input, Output e Append. Nel primo caso, il file è
aperto in lettura, ovvero è utilizzato esclusivamente per reperire delle
informazioni senza effettuare su di esse alcuna modifica. È evidente che
l'apertura di un file in questa modalità richiede che esso sia stato in
precedenza creato sul disco, altrimenti provoca la notifica di un
errore.Diversamente accade invece per i file aperti in output. In questo caso,
infatti, si provoca la generazione di una nuova struttura e la sovrascrittura
del file eventualmente già presente sul disco con il nome specificato dopo la
parola chiave Open. Com'è facile intuire, l'accesso in output ha il fine
di permettere la scrittura delle informazioni sui file. In alcuni casi,
tuttavia, risulta necessario inserire all'interno di uno di essi dei nuovi dati
senza distruggere quelli inseriti in una o più sessioni precedenti. La modalità
da utilizzare per raggiungere tale scopo è denominata
Append.
Il numero identificatore Dopo
aver dichiarato l'uso che si desidera fare del file, è necessario assegnare ad
esso un numero, che ne costituisce un identificatore univoco. Tale valore deve
essere un intero compreso fra 1 e 511 e va specificato dopo la parola chiave
As. Per le strutture non destinate alla condivisione con altre
applicazioni, è opportuno utilizzare i numeri compresi fra 1 e 255, riservando i
rimanenti ai file che devono essere resi accessibili da più processi nello
stesso momento. Si noti che, per compatibilità con il linguaggio BASIC classico,
il numero di identificazione si può far precedere dal carattere
#.
Il parametro Len L'ultimo
parametro che resta da esaminare è quello che definisce la lunghezza di ogni
blocco di informazioni interessato dalle operazioni di lettura o scrittura. Esso
è opzionale e rappresenta la dimensione dello spazio di memoria temporanea
(denominato buffer) allocato dal sistema. Tale valore non può essere
superiore a 32767.
L'istruzione Close Se il
comando Open rende possibile accedere a un file e crea per esso un
identificatore, l'effetto contrario, ovvero il rilascio della struttura e
l'eliminazione del numero di riferimento, è sortito dall'istruzione
Close. Si supponga di aver aperto un file e di aver assegnato ad esso
l'identificatore 1. La riga
Close #1
determina la fine dell'uso della
struttura da parte del programma, pur rendendone possibile la successiva
riapertura. Il numero di riferimento ritorna così ad essere utilizzabile per la
gestione di un altro file.
La lettura di un file di
testo Si supponga di voler realizzare un'applicazione in grado di
visualizzare in una casella di testo il contenuto del file di sistema
Win.ini. In primo luogo, è necessario disegnare un form analogo a quello
rappresentato nella figura 1, in cui è presente una textbox, che si
suppone denominata txtTesto. È poi necessario fare in modo che all'avvio
del programma essa sia riempita con il testo contenuto nel file da leggere. A
tal fine, si può associare il seguente codice all'evento Load del
form:
Private Sub Form_Load() Dim Riga As
String txtTesto.Text = "" Open "c:\windows\win.ini" For Input As #1 Do
Until EOF(1) Line Input #1, Riga txtTesto.Text = txtTesto.Text & Riga
& Chr$(13) & Chr$(10) Loop Close #1 End Sub
Dopo aver eliminato i caratteri
eventualmente presenti nella textbox, la procedura provvede ad aprire in input
il file, che si suppone contenuto nella cartella "C:\Windows" e ad associargli
il numero identificatore 1. Tutte le operazioni che in seguito sono effettuate
sul file sono prodotte da istruzioni riceventi tale valore come parametro.Il
file da leggere è notoriamente composto da testo organizzato in righe. Lo scopo
dell'applicazione illustrata in questo esempio è di leggere tutte le righe e di
copiarle, una di seguito all'altra, nella textbox. A tal fine è necessario
creare un ciclo in grado di svolgere le operazioni di lettura. La condizione che
deve causare il termine delle iterazioni è rappresentata dal raggiungimento
della fine del file. Ciò si verifica quando tutti i dati sono stati letti. Il
file, tuttavia, potrebbe anche essere vuoto. È pertanto necessario fare in modo
che il ciclo preveda il test all'ingresso, ovvero che la condizione sia
specificata accanto alla parola chiave Do, al fine di evitare le
iterazioni quando essa risulti vera già immediatamente dopo l'esecuzione del
comando Open.
La funzione EOF Un file
sequenziale può essere paragonato a un nastro magnetico. Per sapere se
l'ipotetica testina di lettura ha raggiunto la posizione di fine, è necessario
ricorrere alla funzione EOF (End Of File), alla quale, come di
consueto, va passato come parametro il numero identificatore della struttura a
cui si desidera far riferimento. Il valore restituito è di tipo logico e vale
True se è raggiunta la fine del file; ovviamente, in caso contrario, il
risultato è False.
La funzione Line Input La
lettura di una riga di un file di testo è effettuata per mezzo dell'istruzione
Line Input. Essa prevede due parametri; il primo è costituito ancora una
volta dall'identificatore del file su cui è eseguita l'operazione. Il secondo,
invece, è rappresentato dalla variabile di tipo stringa destinata ad ospitare la
riga di testo da leggere. Nell'esempio è stata utilizzata la variabile
denominata Riga. In esso, il dato letto è aggiunto al contenuto della
casella testuale per mezzo dell'operatore di concatenazione di stringhe
(&). Si noti che per passare alla riga successiva della textbox si fa
uso di una sequenza composta dal carattere di ritorno del carrello
(Chr$(13)) e da quello di avanzamento di una riga
(Chr$(10)).
La scrittura su un file di
testo Si supponga ora di voler realizzare un programma in grado di
effettuare l'esatto contrario del precedente, ovvero di trasferire su un file di
testo il contenuto di una textbox. La sua implementazione risulta estremamente
semplice. In pratica richiede esclusivamente l'uso di un'istruzione
Print.Si consideri il form rappresentato in figura 2,
caratterizzato dalla presenza di 3 pulsanti. Il primo è denominato
btnCarica ed ha lo scopo di caricare nella textbox txtTesto il
contenuto del file di cui è specificato il nome nella casella
txtNomeFile. Un secondo pulsante, denominato btnCancella, ha lo
scopo di eliminare il contenuto della textbox, mentre il terzo serve per
memorizzare in un file le informazioni presenti all'interno del controllo
txtTesto. Il codice è riportato nel listato 1. Si noti che la
procedura che si occupa del caricamento dei dati è pressoché identica a quella
dell'esempio precedente; si differenzia solo perché il nome del file da leggere
è acquisito dal controllo txtNomeFile, anziché essere fisso. Il
salvataggio del file sul disco è eseguito in modo estremamente semplice; in
pratica il programma non deve far altro che aprire il la struttura in output ed
eseguire su di essa l'istruzione Print, la quale riceve come parametri,
rispettivamente, il numero identificatore e la stringa da scrivere, nella
fattispecie costituita dal contenuto del controllo
txtTesto.
Un semplice esercizio Per
esercitarsi sugli argomenti esposti, si provi a realizzare un'applicazione in
grado di leggere, per mezzo di opportune textbox, tre valori numerici interi e
una stringa. Si preveda poi la possibilità di salvarli in un file in seguito
alla pressione di un pulsante. Mediante un altro bottone, si faccia in modo che
l'utente possa ripristinare all'interno delle caselle di testo i valori salvati
sul disco. (Parte 11) Questo mese il corso di Visual Basic focalizza l’attenzione sui file ad
accesso casuale, che si distinguono per essere dotati di una struttura rigida;
ciò li rende in grado di offrire una maggiore velocità di accesso alle
informazioni
Nella scorsa lezione sono
stati presi in considerazione i file di testo, in cui l’accesso alle
informazioni avviene in modo sequenziale. Essi presentano un limite, ovvero la
scarsa idoneità alla memorizzazione di grandi quantità di dati, a causa dei
talvolta elevati tempi di ricerca. In queste condizioni sono spesso
preferibili delle strutture più sofisticate, quali ad esempio i file ad accesso
casuale, che sono oggetto di studio in questa puntata del corso. Come sempre,
tuttavia, prima di affrontare i nuovi argomenti, è opportuno un ripasso dei
concetti esposti in precedenza; lo spunto è fornito dalla correzione
dell’esercizio proposto nella scorsa lezione.
La soluzione
dell’esercizio proposto nella scorsa lezione
L’esercizio proposto
nello scorso numero prevede la realizzazione di un programma in grado di
leggere, per mezzo di opportune textbox, tre valori numerici interi e una
stringa, nonché di offrire la possibilità di salvare questi dati su richiesta
dell’utente. La pressione di un pulsante deve provocare il ripristino
all’interno delle caselle di testo dei valori salvati. Il codice è contenuto nel
Listato 1. L’applicazione è composta da due procedure; la prima,
associata alla pressione del pulsante btnSalva, ha lo scopo di scrivere
le informazioni sul file Archivio.dat. Si noti che è copiata sul
disco solo la parte intera dei numeri; l’eventuale parte decimale, non prevista
dall’esercizio, è troncata. I dati sono letti dalla procedura associata alla
pressione del pulsante btnCarica, che fa uso dell’istruzione Line
Input.
I file ad accesso
casuale
I file ad accesso casuale
sono caratterizzati dall’organizzazione molto rigida in strutture dette
record. Un file è quindi composto da un numero variabile di record
accodati. Al fine di comprendere meglio il concetto, si pensi a uno schedario,
quale ad esempio l’anagrafica dei clienti di un’azienda. L’archivio è composto
da schede aventi tutte la stessa dimensione e contenenti lo stesso tipo di
informazione. Ogni scheda rappresenta un record.
I
record
Per poter inserire in un
file più informazioni, anche di tipo non omogeneo, raggruppate in "schede", è
necessario riunirle in un’unica struttura. Si deve quindi creare un nuovo tipo
di dati. Tale operazione è eseguita per mezzo della struttura Type, la
cui sintassi è:
Type
<nome> <nome_campo_1> As <tipo_1> <nome_campo_2>
As <tipo_2> … <nome_campo_n> As <tipo_n> End
Type
All’interno della
struttura devono essere dichiarati gli elementi che la compongono, che sono
denominati campi. Per ognuno di essi deve essere specificato il tipo di
dati che lo caratterizza. I campi possono essere anche di tipo totalmente
diverso. Si possono infatti definire dei record contenenti contemporaneamente
delle informazioni di tipo testuale, numerico e logico. Ad esempio, la seguente
dichiarazione è corretta:
Type
Automobile Marca As String*50 Modello As String*50 Cilindrata As
Integer Diesel As Boolean End Type
Si noti che sono stati
dichiarati due campi di tipo alfanumerico, uno di tipo logico e uno numerico
intero. La struttura è denominata Automobile. In questo modo si è
provveduto ad aggiungere un nuovo tipo di dati a quelli standard previsti da
Visual Basic. È quindi possibile dichiarare la variabile MiaVettura, di
tipo Automobile digitando:
Dim MiaVettura As
Automobile
Riempimento dei
campi di un record
Le variabili definite
come record prevedono, data la propria conformazione, una modalità di
assegnamento dei valori leggermente diversa rispetto a quella prevista dalle
strutture convenzionali. In genere, si esegue un’operazione di assegnamento per
ogni campo, secondo la sintassi:
<variabile>.<campo> = <valore>
Ad esempio, volendo
assegnare il valore 2499 al campo Cilindrata della variabile
MiaVettura, definita in precedenza, occorre scrivere:
MiaVettura.Cilindrata = 2499
L’apertura di un
file ad accesso casuale
Come per le strutture
sequenziali, l’apertura di un file ad accesso casuale avviene per mezzo
dell’istruzione Open che, in questo caso, assume la forma:
Open
<percorso_file> For Random As [#]<identificatore> Len =
<lunghezza_record>
dove percorso_file
indica il percorso completo del file che si desidera aprire, mentre
identificatore costituisce un numero utilizzato per contraddistinguere in
modo univoco tale struttura e pertanto va fornito come parametro a tutti i
comandi che sono destinati a gestirla. Si noti che la modalità di accesso è
indicata per mezzo della parola chiave Random, che deve essere
obbligatoriamente specificata, indipendentemente dal tipo di operazione che si
desidera effettuare sul file, sia essa di lettura o scrittura. Si noti altresì
che anche il parametro Len è obbligatorio. Esso deve contenere l’esatta
dimensione del record che costituisce l’unità di informazione memorizzata nel
file. Qualora essa non sia nota, può essere determinata per mezzo della funzione
Len. Ad esempio, volendo assegnare alla variabile DimRec la
dimensione del record MiaVettura, è necessario digitare:
DimRec =
Len(MiaVettura)
Il contenuto della
variabile DimRec costituisce il valore da passare come ultimo parametro
all’istruzione Open per consentire la gestione di un file composto da
elementi di tipo Automobile. Analogamente ai file sequenziali, anche le
strutture ad accesso casuale devono essere chiuse dopo l’uso per mezzo
dell’istruzione Close.
La lettura di un
record
La lettura di un record
contenuto in un file ad accesso casuale avviene per mezzo dell’istruzione
Get, caratterizzata dalla seguente sintassi:
Get
[#]<identificatore>, <posizione>, <variabile>
dove
<identificatore> rappresenta il numero che univocamente identifica
il file oggetto dell’operazione di lettura e <variabile> è il nome
della variabile in cui i dati letti devono essere posti. Il parametro
<posizione> indica la posizione del record da leggere. Si tratta di
un valore intero compreso fra 1 e il numero dei record presenti nel file. Ad
esempio, si supponga di voler accedere al quarto record presente nel file di
identificatore 1 e di voler porre il suo contenuto nella variabile Dato.
Ciò è possibile per mezzo della riga:
Get #1, 4,
Dato
Solo in un caso il valore
del parametro <posizione> può essere omesso; ciò si verifica in
occasione dell’effettuazione di operazioni di lettura in sequenza; l’assenza del
numero indicante la posizione provoca infatti l’accesso al record successivo a
quello corrente. Non possono tuttavia essere omesse le virgole di separazione.
Ad esempio, la sequenza
Get #1, 4,
Dato Get #1,, Dato1
provoca la lettura del
quarto e del quinto record del file identificato dal numero 1.
La scrittura di
un record
Per scrivere il contenuto
di un record in un file ad accesso casuale è possibile utilizzare l’istruzione
Put, la cui sintassi è pressoché identica a quella del comando
Get:
Put
[#]<identificatore>, <posizione>, <variabile>
In questo caso, la
variabile indicata come terzo parametro contiene il dato da scrivere. Ad
esempio, la riga
Put #1, 5,
Dato
scrive il contenuto della
variabile Dato nel quinto elemento del file identificato dal numero 1. Il
valore assunto dal parametro <posizione> assume un’importanza
fondamentale, in quanto determina se è aggiunto un nuovo record all’archivio o
se ne è sovrascritto uno già esistente. Quest’ultima evenienza si verifica
quando è indicata una posizione già occupata da un elemento. Per aggiungere un
nuovo record al file, invece, è necessario indicare un valore pari al numero
totale dei record incrementato di un’unità.
La cancellazione
logica di un record
Il metodo più semplice
per cancellare un record consiste nel sovrascriverlo con un elemento vuoto. In
questo modo, tuttavia, non è possibile recuperare lo spazio da esso occupato sul
disco. Si tratta cioè di una cancellazione logica, non fisica, in
quanto Visual Basic non dispone di un’istruzione in grado di rimuovere un record
e di recuperare automaticamente lo spazio da esso occupato. È possibile
sfruttare a proprio vantaggio la possibilità di effettuare solo una
cancellazione logica dei record contenuti in un file per fare in modo che degli
elementi eventualmente eliminati per sbaglio possano essere agevolmente
recuperati. Ciò è possibile aggiungendo un campo booleano alla struttura dei
record e facendo in modo che il programma che accede all’archivio consideri
cancellati tutti gli elementi caratterizzati dal contenere il valore logico
True all’interno di questo campo. L’eliminazione di un record comporta
quindi la semplice variazione del valore di un suo campo. Analogamente, è
possibile recuperare un elemento cancellato per errore impostando nuovamente al
valore False il campo booleano. La struttura Automobile può
pertanto essere modificata come segue:
Type
Automobile1 Marca As String*50 Modello As String*50 Cilindrata As
Integer Diesel As Boolean Cancellato As Boolean End Type
La cancellazione
fisica di un record
Quando la quantità di
informazioni da gestire diventa elevata, la necessità di recuperare lo spazio
occupato dai record cancellati diventa evidente, sia per evitare lo spreco di
spazio sul disco, sia per non ridurre drasticamente i tempi di accesso alle
informazioni costringendo il programma a leggere dei dati inutili. Come già
osservato in precedenza, Visual Basic non dispone di un’istruzione in grado di
provvedere automaticamente alla cancellazione fisica di un record. Tuttavia, la
scrittura di una simile procedura non presenta un livello di difficoltà elevato.
Essa deve solo creare un nuovo file e copiare al suo interno tutti i record non
vuoti. Successivamente, deve eliminare il primo file ed assegnare il suo nome
alla nuova struttura. È ciò che fa la procedura di seguito descritta, che riceve
come parametro il nome del file da compattare, che si suppone composto da record
di tipo Automobile1:
Sub
CompattaFile(ByVal NomeFile As String) Dim ID_old As Integer Dim ID_new As
Integer Dim Dato As Automobile1 Dim Lunghezza As Integer Lunghezza =
Len(Dato) ID_old = FreeFile Open NomeFile For Random As ID_old Len =
Lunghezza ID_new = FreeFile Open "Temp.dat" For Random As ID_new Len =
Lunghezza Do While Not EOF(ID_old) Get ID_old, , Dato If Not
Dato.Cancellato Then Put ID_new, , Dato End If Loop Close ID_old,
ID_new Kill NomeFile Name "Temp.dat" As NomeFile End Sub
Si noti l’uso della
funzione FreeFile, che restituisce un numero adatto ad essere utilizzato
come identificatore di file ed evita così il rischio di utilizzare degli
identificatori già in uso in altre parti del programma. La procedura provvede a
leggere in modo sequenziale il file di cui è specificato il nome come parametro
e a copiare in un file denominato Temp.dat tutti i record per i quali il
campo Cancellato assume il valore False. Si noti che le operazioni
di lettura e scrittura sono eseguite sequenzialmente, in quanto è stato omesso
il valore indicante la posizione nelle istruzioni Get e Put. Il
ciclo di copiatura termina quando sono esauriti i record da leggere. Quando ciò
avviene, la funzione EOF (End Of File), già descritta nella scorsa
lezione, restituisce il valore True. Dopo aver copiato tutti i record non
cancellati logicamente, la procedura provvede a chiudere entrambi i file. Si
noti che a tal fine utilizza un’unica istruzione Close, in cui gli
identificatori dei file da chiudere sono separati da una virgola. Il passo
successivo consiste nel sostituire il file originale con quello creato. Ciò
comporta l’esecuzione di due operazioni: la cancellazione del file di origine e
la ridenominazione di quello generato dalla procedura. L’eliminazione avviene
per mezzo dell’istruzione Kill, la cui sintassi è
Kill
<Nome_file>
Il file Temp.dat è
quindi rinominato per mezzo dell’istruzione Name, che è caratterizzata
dalla seguente sintassi:
Name
<Vecchio_nome> As <Nuovo_nome>
Un
esempio…
Il Listato 2
contiene il codice di un’applicazione in grado di archiviare i dati relativi a
dei siti Internet. Per ognuno di essi, è possibile indicare l’indirizzo e una
breve descrizione. È possibile creare dei nuovi record vuoti in grado di
ospitare i dati relativi a nuovi siti, oppure modificare le informazioni
riferite a quelli esistenti, nonché scorrere l’archivio nei due sensi. Il
file è composto da record del tipo DatiURL, definito all’inizio. Si noti
che la definizione è preceduta dalla parola chiave Private per indicare
che il suo campo di validità è limitato al form che la contiene. I record sono
letti e scritti rispettivamente per mezzo delle procedure LeggiRecord e
ScriviRecord. La variabile globale Posizione stabilisce il numero
del record da leggere o aggiornare. Si noti la presenza della funzione
ContaRecord, che ha lo scopo di calcolare il numero dei record presenti
in archivio come rapporto fra la dimensione del file, restituita dalla funzione
LOF (Length Of File) e la lunghezza di un singolo
elemento.L’apertura del file avviene in corrispondenza dell’avvio
dell’applicazione, quindi al verificarsi dell’evento Load. Analogamente,
per assicurare la chiusura dell’archivio quando cessa l’uso del programma,
l’istruzione Close è stata posta fra il codice associato all’evento
Unload.
… e un
esercizio
Per esercitarsi sui
concetti esposti, si provi a modificare l’esempio sopra descritto aggiungendo la
possibilità di cancellare logicamente e, su richiesta, fisicamente dei
record.
Conclusioni
I file rivestono un’importanza
fondamentale nella maggior parte delle applicazioni, in quanto consentono di
trattare una quantità di dati superiore a quella che può essere contenuta nella
memoria dell’elaboratore. Inoltre, trattandosi di strutture permanenti,
permettono di mantenere tali informazioni anche dopo lo spegnimento o il riavvio
del sistema. L’utilizzo di strutture ad accesso casuale rende ancora più
evidenti i vantaggi offerti dai file, permettendo una maggiore flessibilità
d’uso.
Un database è una struttura residente su un’unità a disco che si caratterizza
per la capacità di contenere ingenti quantità di dati organizzati in record. Ciò
che fa la differenza fra un database e un normale file ad accesso casuale è la
presenza di un modulo, detto generalmente database engine, che permette
di effettuare ricerche e inserimenti all’interno dell’archivio in modo semplice
e veloce. Esiste quindi un componente che si fa carico di tutta la gestione dei
dati, lasciando al programmatore il solo compito di definire i parametri in base
ai quali effettuare le operazioni di ricerca. Visual Basic, fin dalle sue
origini, ha sempre annoverato fra i suoi punti di forza la capacità di agevolare
al massimo lo sviluppo delle applicazioni in grado di gestire dei database.
Questa sua caratteristica è andata evolvendosi nel tempo al punto che oggi è uno
degli strumenti più utilizzati nel campo della realizzazione di applicazioni
gestionali che accedono ad archivi locali o posti su un sistema
remoto.
Le soluzioni
dell’esercizio proposto nella scorsa lezione
Prima di affrontare lo
studio dei database, è opportuno come sempre effettuare un breve ripasso di ciò
che si è appreso nella scorsa lezione. Lo spunto è dato dalla correzione
dell’esercizio in essa proposto, che prevede la modifica del programma descritto
come esempio, in grado di visualizzare un elenco di siti Internet, per
consentirgli di gestire la cancellazione logica e, su richiesta, fisica dei
record. Questi ultimi sono del tipo DatiURL, di cui è di seguito
riportata la definizione:
Private Type
DatiURL Indirizzo As String * 100 Descrizione As String * 100 End
Type
Per consentire la
cancellazione degli elementi, è necessario modificare la struttura aggiungendole
un campo booleano, che è utilizzato per contrassegnare i record logicamente
rimossi. La definizione del tipo DatiURL diventa pertanto la
seguente:
Private Type
DatiURL Indirizzo As String * 100 Descrizione As String *
100 Cancellato As Boolean End Type
Per consentire la
cancellazione del record corrente è necessario modificare la procedura
ScriviRecord in modo da tenere conto del nuovo campo.
Sub
ScriviRecord(Cancella As Boolean) Dim Dato As DatiURL Dato.Indirizzo =
txtURL.Text Dato.Descrizione = txtDescrizione.Text Dato.Cancellato =
Cancella Put #1, Posizione, Dato End Sub
Si noti che ora accetta
un parametro booleano che stabilisce se il record che deve essere scritto sul
disco deve essere cancellato logicamente. Il codice da associare alla pressione
del pulsante btnCancella, è pertanto il seguente:
Private Sub
btnCancella_Click() ScriviRecord True End Sub
È inoltre possibile
inserire un pulsante in grado di recuperare tutti gli elementi cancellati. La
sua pressione deve semplicemente richiamare la procedura Ripristina,
composta da un ciclo in grado di leggere tutti i record presenti in archivio e
di riscriverli dopo aver modificato il valore del campo Cancellato.
Private Sub
Ripristina() Dim i As Long Dim NumElementi As Long Dim Dato As
DatiURL NumElementi = ContaRecord() For i = 1 To NumElementi Get #1, i,
Dato Dato.Cancellato = False Put #1, i, Dato Next i End
Sub
La rimozione fisica di
tutti gli elementi cancellati può invece avvenire per mezzo della funzione
CompattaFile, che provvede a copiare in un nuovo file tutti i record
caratterizzati dal contenere il valore False all’interno del campo
Cancellato. Dopo essere stato creato, il nuovo file sostituisce quello di
partenza.
Private Sub
CompattaFile() Dim Dato As DatiURL Dim Lunghezza As Long Dim
NumElementi As Long Dim i As Long NumElementi = ContaRecord() Open
"temp.dat" For Random As #2 Len = LunghezzaRecord For i = 1 To
NumElementi Get #1, i, Dato If Not Dato.Cancellato Then Put #2, ,
Dato End If Next i Close #1, #2 Kill "archivio.dat" Name
"temp.dat" As "archivio.dat" Open "archivio.dat" For Random As #1 Len =
LunghezzaRecord End Sub
Il codice completo del
programma sopra descritto è riportato nel Listato 1. La sua lunghezza si
sarebbe notevolmente ridotta se fosse stato utilizzato un database in luogo del
file ad accesso casuale. Nel seguito di questa lezione si vedrà come è possibile
fare ciò.
I tipi di
database utilizzabili da Visual Basic
Esistono sul mercato
molti sistemi di gestione dei database. Ognuno di essi è caratterizzato da un
diverso modo di organizzare le informazioni sul disco. Per questo motivo, i file
prodotti con tali strumenti presentano spesso dei formati molto diversi. I
prodotti più noti sono Microsoft Access e FoxPro, nonché Borland DBase e
Paradox. Visual Basic è in grado di gestire tutti questi formati, grazie a
dei moduli supplementari denominati ISAM (Indexed Sequential Access
Method) che possono essere utilizzati dal motore di gestione dei database su
richiesta dell’applicazione. Oltre ai formati gestibili per mezzo dei moduli
ISAM, Visual Basic è in grado di leggere e scrivere dei dati anche in strutture
di diverso tipo, eventualmente poste su server remoti, grazie al supporto per la
tecnologia ODBC (Open Database Connectivity). Ciò estende notevolmente il
campo di utilizzo dello strumento, facendo sì che esso sia in grado di
utilizzare qualunque formato di database per il quale esista un driver
ODBC.
La struttura di
un database
Le informazioni contenute
all’interno di un database, essendo spesso in grande quantità, sono raggruppate
in tabelle, al fine di minimizzare lo spreco di spazio sul disco e di agevolare
le operazioni di ricerca. Ad esempio, supponendo di voler gestire l’inventario
di una libreria, sarà necessario conoscere per ciascun libro almeno il titolo,
l’autore, l’editore e lo scaffale che lo contiene. È tuttavia verosimile
attendersi che molti editori abbiano pubblicato più di un libro. Se ad ogni
opera è dedicato un record e se in ognuno di essi sono presenti anche i dati
relativi all’editore, è evidente che le informazioni riguardanti gli editori che
hanno pubblicato più di un libro sono inutilmente duplicate. L’ingombro degli
archivi non risulta pertanto ottimizzato in relazione ai dati contenuti. Ciò si
traduce in uno spreco di spazio sul disco e in una maggior quantità di
informazioni da leggere durante l’effettuazione delle operazioni di ricerca.
Inoltre, la presenza di più copie dello stesso dato può portare a problemi di
inconsistenza dovuti ad errori di inserimento o alla mancata sincronizzazione in
caso di modifica. Per evitare ciò, è conveniente suddividere l’archivio in più
strutture, dette tabelle, raggruppando fra loro le informazioni dello stesso
tipo. Fra le tabelle è possibile stabilire delle relazioni. Nel caso della
libreria, ad esempio, è possibile inserire i dati relativi agli editori in una
tabella a parte ed associare ad ognuno di essi un identificatore univoco. Le
altre informazioni relative ai libri possono essere contenute in una seconda
tabella, costituita da record in cui in luogo dei dati relativi agli editori
sono presenti solo i loro identificatori. Ciò fa sì che un’eventuale modifica
delle informazioni relative a una casa editrice, dovute ad esempio ad un cambio
di indirizzo o di ragione sociale, comporti la variazione del contenuto di un
solo record e non metta a repentaglio la consistenza dei dati.
L’oggetto
data
La gestione dei database
in Visual Basic è resa estremamente semplice da uno speciale controllo:
l’oggetto data. Si tratta di un componente che presenta il caratteristico
aspetto illustrato nella Figura 1; come è possibile osservare, è composto
da 4 pulsanti, su cui sono presenti i simboli tipici dei registratori a nastro,
posti alle estremità di un’etichetta testuale. Tutte le operazioni che
devono essere effettuate sull’archivio devono far riferimento all’oggetto
data ad esso associato. Per poter utilizzare il suddetto componente,
occorre assegnare dei valori ad alcune sue proprietà. In primo luogo è
necessario specificare il tipo di database che si prevede di utilizzare. Ciò è
possibile agendo, per mezzo dell’apposita finestra, sulla proprietà
Connect. Il valore predefinito è Access. Infatti, il motore di
gestione dei database utilizzato da Visual Basic, denominato Jet, prevede
come formato nativo quello di Microsoft Access, che ha la caratteristica di
incorporare tutte le informazioni all’interno di un file di estensione
mdb. Scorrendo la lista che appare nella finestra delle proprietà, è
possibile osservare l’elenco degli altri formati supportati per mezzo dei driver
ISAM. La lunghezza di questa lista è variabile in base al numero dei moduli
installati nel sistema. Si noti che la decisione di utilizzare un formato
diverso da quello standard comporta l’aggiunta al disco di distribuzione del
necessario modulo ISAM. Dopo averne definito il tipo, è possibile indicare il
nome del database. Ciò è possibile agendo sull’attributo DatabaseName. Si
noti che, facendo doppio clic sulla voce nella casella delle proprietà, si
provoca l’apertura di una finestra che invita a selezionare un file. Nel caso di
archivi contenuti interamente all’interno di un unico file, come quelli in
formato Access, esso rappresenta il database da utilizzare. In altri casi, quali
ad esempio le strutture di tipo xBase (DBase o FoxPro), in
cui ogni tabella è memorizzata sul disco in un file a sé stante caratterizzato
dall’estensione dbf, il database è costituito dalla directory che
contiene i dati. Un’altra proprietà a cui è necessario assegnare un valore è
denominata RecordSource. Essa deve contenere l’espressione che permette
al motore di gestione degli archivi di scegliere la fonte dei dati da
visualizzare. Il valore da indicare è il nome della tabella da cui si desidera
prelevare le informazioni.
La realizzazione
di un programma in grado di accedere a un database
Si supponga di voler
realizzare un’applicazione analoga a quella dell’esercizio proposto nella scorsa
lezione, in grado di visualizzare le informazioni relative a un elenco di siti
Internet. Questa volta, tuttavia, si desidera far uso di un database. L’archivio
può essere creato mediante un prodotto come Microsoft Access. Con questo
strumento è possibile definire una tabella costituita da record aventi due campi
di tipo alfanumerico, denominati Indirizzo e Descrizione. Si
supponga di assegnarle il nome TabellaURL e di salvare il database nel
file Web.mdb. Il passo successivo consiste nel creare un form, su cui
occorre trascinare dalla casella degli strumenti un oggetto di tipo Data.
Ad esso si suppone di assegnare il nome dbArchivio. Si procede poi a
scegliere il formato da utilizzare assegnando alla proprietà Connect il
valore Access e ad assegnare il corretto valore alla proprietà
DatabaseName. A tal fine si seleziona il file Web.mdb per
mezzo dell’apposita opzione presente nella finestra delle proprietà. L’ultimo
attributo da impostare riguarda la fonte dei dati. Essendo il database
composto da una sola tabella, la scelta del valore della proprietà
RecordSource è pressoché obbligata. La lista posta nella finestra delle
proprietà è infatti composta dalla sola voce TabellaURL. Dopo aver
assegnato i valori alle proprietà fondamentali dell’oggetto di tipo data,
non resta che inserire sul form le caselle testuali che permettono la
visualizzazione e la modifica dei dati presenti in archivio. Il loro
collegamento al database può essere effettuato senza bisogno di scrivere del
codice, in quanto avviene semplicemente impostando le proprietà
DataSource e DataField. La proprietà DataSource permette
di indicare l’oggetto di tipo data da cui una textbox deve attingere i
valori da visualizzare. Nell’apposita finestra è possibile selezionare l’unico
valore proposto, ovvero dbArchivio. La proprietà DataField,
invece, permette di indicare il nome del campo da visualizzare. Nel caso
dell’esempio, i valori possibili sono Indirizzo e
Descrizione. Senza scrivere alcuna riga di codice, bensì semplicemente
seguendo le istruzioni sopra riportate, è possibile realizzare un’applicazione
in grado di visualizzare un archivio di siti Internet, caratterizzata dalla
possibilità di scorrere i record agendo sui pulsanti posti all’interno del
controllo di tipo data.
Il metodo
UpdateControls
L’applicazione creata
consente anche la modifica dei dati presenti in archivio. Se il contenuto di
almeno una delle textbox subisce delle variazioni, la pressione di uno dei
pulsanti di cui è dotato il controllo dbArchivio provoca il salvataggio
nel database delle informazioni modificate. Si supponga ora di voler
aggiungere all’applicazione un pulsante, a cui si dà il nome
btnRipristina, in grado di annullare le eventuali modifiche effettuate
accidentalmente. Si tratta in pratica di costringere l’oggetto dbArchivio
a rileggere il record corrente e a sovrascrivere le informazioni poste nelle
textbox collegate. Ciò è possibile semplicemente invocando il metodo
UpdateControls. Il codice da associare al pulsante è il
seguente:
Private Sub
btnRipristina_Click() dbArchivio.UpdateControls End Sub
La proprietà
Recordset
L’insieme dei record
presenti in archivio è identificato dalla proprietà Recordset. Si tratta
in sintesi di un oggetto su cui è possibile invocare dei metodi per aggiungere,
togliere o modificare gli elementi posti nella base di dati.
L’aggiunta di un
record all’archivio
L’aggiunta di un nuovo
record all’archivio richiede l’invocazione del metodo AddNew dell’oggetto
Recordset. Supponendo di voler inserire nel form il pulsante
btnNuovoRecord, il codice da associare alla sua pressione è il
seguente:
Private Sub
btnNuovoRecord_Click() dbArchivio.Recordset.AddNew End Sub
Dopo aver inserito nelle
caselle di testo il contenuto del nuovo record, è necessario trasferirlo nel
database. Per fare ciò, occorre eseguire il metodo Update dell’oggetto
Recordset. Questa operazione può essere effettuata come risposta alla
pressione di un pulsante che si suppone denominato
btnAggiorna.
Private Sub
btnAggiorna_Click() dbArchivio.Recordset.Update End Sub
La cancellazione
di un record
Anche per cancellare un
record è sufficiente utilizzare un semplice metodo dell’oggetto
Recordset. Si tratta del metodo Delete. Volendo inserire nel form
un pulsante in grado di rimuovere il record corrente, è necessario scrivere il
seguente codice:
Private Sub
btnCancella_Click() dbArchivio.Recordset.Delete End Sub
Un semplice
esercizio
Per esercitarsi sui
concetti esposti, si provi a realizzare un programma in grado di gestire una
semplice rubrica telefonica.
Conclusioni
L’uso di un database
consente il trattamento di cospicue quantità di informazioni in modo molto
semplice e veloce. Per questo motivo, la stragrande maggioranza delle
applicazioni gestionali ne fa uso. A questo tipo di strutture, data l’importanza
che le caratterizza, sarà dedicata anche la prossima lezione. Per agevolarne la
comprensione, il lettore è quindi invitato ad esercitarsi sui concetti sopra
esposti.
database sono insiemi di dati organizzati secondo strutture ben definite, aventi
lo scopo di rendere agevole la gestione e la ricerca delle informazioni. Lo
studio delle modalità con cui è possibile localizzare dei record all’interno di
un archivio costituisce lo scopo di questa lezione, che ha come protagonista
l’oggetto Recordset.
La soluzione dell’esercizio proposto nella scorsa lezione
Come sempre, prima dell’introduzione dei nuovi concetti, è offerta
un’occasione di ripasso di quelli già acquisiti in precedenza. Lo spunto è dato
dalla descrizione del programma che costituisce la soluzione dell’esercizio
proposto nella scorsa lezione. L’applicazione rappresenta una semplice
rubrica telefonica, in grado di consentire l’inserimento di nuove voci e la
cancellazione di quelle eventualmente esistenti. La sua realizzazione prevede
dapprima la creazione di un database per mezzo di un apposito strumento (ad
esempio Microsoft Access) e l’inserimento in esso di una tabella, a cui
si dà il nome Rubrica, caratterizzata dal possedere 7 campi di tipo
alfanumerico, denominati Cognome, Nome, Indirizzo, Comune, Telefono, Fax,
eMail. Il passo successivo prevede la creazione di un form, su cui va
inserito un oggetto di tipo Data, a cui è assegnato il nome
dbArchivio. Esso è collegato al database assegnando il nome del file alla
proprietà DatabaseName. Inoltre, deve essere collegato alla tabella. Per
fare ciò occorre impostare la stringa "Rubrica" come valore della proprietà
RecordSource. Il form deve poi essere completato con le caselle di testo
destinate ad accogliere le stringhe contenute nei campi dei record. Ad ogni
textbox deve essere assegnata la stringa "dbArchivio" come valore della
proprietà DataSource. Per mezzo dell’attributo DataField è inoltre
possibile specificare il campo contenente le informazioni da visualizzare.
L’applicazione è completata con l’inserimento nel form dei pulsanti che
permettono l’aggiunta di un nuovo record, la memorizzazione dei dati in esso
inseriti e la cancellazione di un elemento. Per consentire ciò, alla pressione
dei tasti deve corrispondere l’invocazione, rispettivamente, dei metodi
AddNew, Update e Delete dell’oggetto Recordset.
Quest’ultimo, come si è osservato nella scorsa lezione, costituisce una
proprietà del componente di tipo Data. Le procedure associate ai pulsanti
btnNuovo, btnCancella e btnAggiorna sono pertanto le
seguenti:
Private Sub btnNuovo_Click() dbArchivio.Recordset.AddNew End Sub
Private Sub btnCancella_Click() dbArchivio.Recordset.Delete End
Sub Private Sub btnAggiorna_Click() dbArchivio.Recordset.Update
End Sub
Il passaggio al primo o all’ultimo record presente in
archivio
A volte, per motivi di estetica, si sente la necessità di rendere invisibile
all’utente l’oggetto di tipo Data. In questi casi occorre inserire nel
form dei pulsanti che consentano la selezione degli elementi posti in archivio.
Per mezzo del metodo MoveFirst dell’oggetto Recordset, è
possibile fare in modo che il primo elemento presente in archivio diventi il
record corrente. In pratica, il suddetto metodo provoca un effetto analogo a
quello della pressione del pulsante posto all’estrema sinistra dell’oggetto
Data. Volendo quindi inserire nel form il pulsante btnPrimo, in
grado di selezionare il primo record presente in archivio, occorre digitare il
seguente codice:
Private Sub btnPrimo_Click() dbArchivio.Recordset.MoveFirst End
Sub
Un metodo analogo a quello appena descritto consente invece il posizionamento
sull’ultimo record. Il suo nome è MoveLast. La procedura da associare al
pulsante btnUltimo, in grado di selezionare l’elemento posto in coda
all’archivio, è la seguente:
Private Sub btnUltimo_Click() dbArchivio.Recordset.MoveLast End
Sub
Il passaggio al record successivo o precedente
Anche per accedere ad un record adiacente è necessario utilizzare l’oggetto
Recordset. Il metodo MoveNext, infatti, provvede a fare del record
successivo il record corrente. Analogamente, il passaggio all’elemento
precedente avviene invocando il metodo MovePrevious. Volendo inserire
nel form i pulsanti btnPrecedente e btnSeguente, in grado di
consentire lo scorrimento del database in entrambi i sensi, occorre digitare il
seguente codice:
Private Sub btnPrecedente_Click() If not dbArchivio.Recordset.BOF
Then dbArchivio.Recordset.MovePrevious End If End Sub Private Sub
btnSeguente_Click() If not dbArchivio.Recordset.EOF
Then dbArchivio.Recordset.MoveNext End If End Sub
Le proprietà BOF e EOF
Si noti che il frammento di codice sopra riportato provvede a verificare i
valori delle proprietà BOF (Beginning Of File) e EOF (End Of
File) prima di accedere rispettivamente al record precedente o al
successivo. Ciò ha il fine di evitare che siano effettuati dei tentativi di
lettura di elementi inesistenti.
La ricerca delle informazioni
Si supponga di voler dotare la rubrica telefonica della possibilità di
ricercare dei nominativi. A tal fine, si aggiunge al form il pulsante
btnCerca, a cui è associato il seguente codice:
Private Sub btnCerca_Click() Dim Ripeti As Boolean Dim Trovato As
Boolean Dim DaCercare As String DaCercare = InputBox("Stringa da
cercare:") dbArchivio.Recordset.MoveFirst Do Trovato = (txtCognome.Text
= DaCercare) Ripeti = Not (dbArchivio.EOF Or Trovato) If Ripeti
Then dbArchivio.Recordset.MoveNext End If Loop While Ripeti If Not
Trovato Then MsgBox "Stringa non trovata" End If End
Sub
La stringa da cercare, richiesta per mezzo della funzione InputBox, è
confrontata con il contenuto del campo Cognome, associato all’elemento
txtCognome. La ricerca avviene in modo sequenziale a partire dal primo
record. La soluzione presentata permette di ricercare il primo record
caratterizzato dall’uguaglianza del contenuto del campo Cognome rispetto
alla stringa inserita dall’utente. Tuttavia, il numero delle righe di codice
necessarie per la sua realizzazione è elevato. Ciò è in contrasto con una della
caratteristiche che fanno dei database delle strutture pressoché onnipresenti in
tutte le applicazioni gestionali, ovvero la possibilità di effettuare delle
ricerche in modo semplice e veloce. In realtà, la capacità che il modulo di
gestione dei database in dotazione a Visual Basic ha di effettuare ricerche va
ben oltre quanto illustrato fino ad ora. Per la localizzazione di elementi fra
quelli posti in archivio, infatti, prevede degli appositi metodi dell’oggetto
Recordset, denominati FindFirst, FindLast, FindPrevious,
FindNext. Com’è facile intuire dai nomi, FindFirst e FindLast
hanno lo scopo di ricercare rispettivamente il primo e l’ultimo elemento in
grado di soddisfare il criterio specificato. FindNext e
FindPrevious permettono invece rispettivamente la localizzazione del
record successivo e del precedente fra quelli in grado di soddisfare una data
condizione. La sintassi è pressoché identica per tutti e 4 i metodi e prevede un
parametro costituito dall’espressione che descrive il criterio di ricerca,
secondo lo schema:
<oggetto_data>.Recordset.<comando>
"<espressione>" dove <espressione> ha la
forma <confronto> [AND | OR <confronto>]. . .
[AND | OR <confronto>] e ogni confronto è del
tipo <nome_campo> = | <> | > |
< | >= | <= LIKE
<valore>
in cui <nome_campo> rappresenta il nome del campo da usare come
oggetto di ricerca e <valore> indica il valore da ricercare. Si
noti che ai comuni operatori di confronto (=, >, <, <>, >=,
<=) ne è stato aggiunto un altro, denominato LIKE. Quest’ultimo
permette di confrontare due stringhe in modo meno rigido rispetto al normale
operatore di uguaglianza. In sostanza, fa sì che la verifica abbia esito
positivo non solo se due stringhe sono identiche, bensì anche quando una
costituisce solo l’inizio dell’altra. Per comprendere questo concetto, si
consideri l’esempio che segue. Si supponga di disporre di un database in cui
sono presenti dei record aventi fra gli altri un campo denominato Nome.
Si supponga che esista un elemento contenente nel campo Nome la stringa
"Dev, Developing software solutions". Se l’archivio è accessibile per mezzo
dell’oggetto Data1, l’esecuzione del metodo
Data1.FindFirst "Nome = ’Dev’"
fornisce un esito negativo, in quanto nessun campo denominato Nome
contiene esattamente la stringa "Dev". Invece, la riga
Data1.FindFirst "Nome LIKE ’Dev’"
ha esito positivo perché esiste un elemento che contiene una stringa che
inizia con la sequenza "Dev". Si noti che per delimitare le stringhe negli
esempi si è fatto uso dell’apice (‘). L’uso di questo carattere può talvolta
essere causa di problemi, in quanto è spesso utilizzato all’interno delle frasi
scritte in lingua italiana. Ad esempio, si osservi la riga
Data1.FindFirst "Nome = ’Viva l’informatica’"
La presenza dell’apostrofo fa sì che la stringa da cercare sia interpretata
in modo errato; il motore di gestione del database riconosce infatti la sequenza
"Viva l". I caratteri mancanti, non essendo riconosciuti come parte della
stringa, causano la generazione di un messaggio di errore. Per ovviare a questo
inconveniente, è opportuno l’utilizzo delle virgolette (") per delimitare le
stringhe da cercare. Per consentire che siano interpretate da Visual Basic
correttamente, essendo esse utilizzate anche per racchiudere la stringa che
costituisce l’intero criterio di ricerca, occorre raddoppiarle. La
riga
Data1.FindFirst "Nome = ""Viva l’informatica"""
ottiene l’effetto desiderato.
La proprietà NoMatch
L’oggetto Recordset è dotato della proprietà NoMatch, che
assume un valore booleano che indica se l’ultima ricerca effettuata è andata a
buon fine. Dopo aver eseguito una ricerca per mezzo di uno fra i metodi
FindFirst, FindLast, FindNext e FindPrevious, è
necessario verificare il valore assunto dalla proprietà NoMatch per
sapere se il record desiderato è stato trovato oppure se la ricerca non ha avuto
successo. In quest’ultimo caso, la proprietà assume il valore logico
True.Si supponga di voler modificare la procedura, descritta in
precedenza, associata al pulsante Cerca in modo da far uso del metodo
FindFirst. Il codice è il seguente:
Private Sub btnCerca_Click() Dim DaCercare As String Dim Criterio As
String DaCercare = InputBox("Stringa da cercare:") Criterio = "Nome = """
& DaCercare & """" dbArchivio.Recordset.FindFirst If
dbArchivio.Recordset.NoMatch Then MsgBox "Stringa non trovata" End
If End Sub
Com’è possibile notare, il numero delle righe di codice è diminuito. La nuova
procedura si limita a chiedere la stringa da cercare ed a comporre il parametro
da passare al metodo FindFirst. Una successiva verifica della proprietà
NoMatch permette di stabilire l’opportunità della visualizzazione di un
messaggio di errore indicante che la ricerca non ha avuto esito positivo.
Un esempio
Quanto appreso in questa lezione può essere applicato all’esercizio descritto
all’inizio dell’articolo. È possibile dotare la rubrica telefonica di un
completo insieme di opzioni di ricerca dei dati. Il codice che costituisce
l’applicazione modificata è visibile nel listato 1 ed è disponibile su
Internet per il download all’indirizzo http://www.infomedia.it. Com’è possibile
osservare in figura, sono stati aggiunti al form una casella di testo,
denominata txtCerca, un pulsante (btnCerca) e 4 option
button. La textbox ha lo scopo di contenere la stringa da cercare nel campo
Cognome. Si noti che, per esigenze di semplicità, la ricerca è stata
limitata a un solo campo. La modalità di ricerca è richiesta all’utente per
mezzo degli option button. Si tratta di pulsanti caratterizzati dall’annullarsi
a vicenda. Questo tipo di componenti prende spesso il nome radio button,
in quanto ricorda i comandi che permettono di cambiare la banda di frequenze
nelle radio. Per mezzo degli option button, l’utente può selezionare una sola
modalità di ricerca fra quelle disponibili. Esse corrispondono ai metodi
FindFirst, FindLast, FindNext, FindPrevious. La ricerca è avviata dalla
pressione del pulsante btnCerca. Il codice ad esso associato si limita a
comporre la stringa che costituisce il criterio di selezione dei record ed a
passarla al corretto metodo in funzione dell’option button selezionato,
riconosciuto controllando il valori della proprietà value. Se la ricerca
non va a buon fine, ovvero se la proprietà NoMatch dell’oggetto
Recordset assume il valore logico true, è visualizzato un
messaggio di errore.
Conclusioni
La potenza e la flessibilità dei database permettono di gestire grandi
quantità di informazioni. Grazie ai metodi di ricerca, diventa estremamente
semplice provvedere alla localizzazione all’interno degli archivi delle
informazioni desiderate. In attesa di approfondire ulteriormente l’argomento
database nel prossimo numero, si provi ad aggiungere alla rubrica telefonica
descritta in precedenza la capacità di richiedere una stringa e di contare i
record presenti in archivio che contengono nel campo Cognome la sequenza
alfanumerica indicata. (Parte 14) Spesso si sente parlare dei database
come di strutture che devono essere "interrogate", ovvero di oggetti pressoché
attivi in grado di restituire delle informazioni a comando. In realtà ciò che è
attivo non è il database vero e proprio, bensì il motore che lo gestisce. In
questa lezione saranno descritte le modalità con cui è possibile richiedere al
modulo responsabile della gestione degli archivi di effettuare delle ricerche e
di fornire dei dati organizzati secondo una struttura differente da quella con
cui essi sono memorizzati sul disco.
La soluzione dell'esercizio proposto
nella scorsa lezione
Come sempre, prima di introdurre i
nuovi argomenti, sarà presentata la soluzione dell'esercizio proposto nella
scorsa lezione. Nello numero precedente, è stato descritto un programma in grado
di gestire una semplice rubrica telefonica. L'esercizio ne prevede la modifica
con l'aggiunta di un pulsante in grado di contare gli elementi dell'archivio
contenenti nel campo Cognome una sequenza di caratteri indicata
dall'utente. Supponendo di assegnare al nuovo tasto il nome btnConta, il
codice che deve essere associato alla sua pressione è il seguente:
Private Sub btnConta_Click() Dim
Criterio As String Dim Contatore As Integer
Contatore = 0 Criterio = "Cognome
LIKE ""*" & txtCerca.Text & "*""" dbArchivio.Recordset.FindFirst
(Criterio) Do While Not dbArchivio.Recordset.NoMatch Contatore = Contatore
+ 1 dbArchivio.Recordset.FindNext (Criterio) Loop MsgBox "Sono stati
trovati " & Contatore & " record" End Sub
Per leggere la stringa da cercare si fa
uso della casella di testo txtCerca, già presente nell'applicazione in
quanto usata dal codice associato alla pressione del tasto di ricerca
(btnCerca). Si noti il criterio di selezione, basato sull'operatore
LIKE. Per fare in modo che siano trovati tutti i record che contengano la
scritta inserita dall'utente in una qualsiasi posizione, occorre introdurre
prima e dopo la sequenza da cercare il carattere jolly *. La stringa che
costituisce il criterio di ricerca, supponendo di aver digitato nella casella
txtCerca la sequenza "Dev", è la seguente:
Cognome LIKE "*Dev*"
L'asterisco è in grado di sostituire un
numero variabile di caratteri. Ciò significa che sono considerate rispondenti al
criterio tutte le stringhe costituite da una sequenza qualsiasi, eventualmente
nulla, seguita dalla parola "Dev" e da un'altra sequenza qualsiasi, anch'essa
eventualmente nulla. Per mezzo del metodo FindFirst dell'oggetto
Recordset associato all'archivio, è ricercato il primo record che
soddisfa il criterio. Un ciclo, che si interrompe solo quando la proprietà
NoMatch diventa positiva, ovvero quando non sono più trovati elementi
corrispondenti ai parametri di ricerca indicati, fa sì che sia più volte
invocato il metodo FindNext, che provoca la lettura del successivo
elemento soddisfacente i criteri. Il codice dell'applicazione completa è
riportato nel listato 1.
Cosa significa "interrogare un
database"
Un'applicazione che fa uso di un
database percepisce tale struttura come attiva, cioè in grado di rispondere a
delle richieste. Il programmatore può infatti creare diverse viste
dell'archivio, scegliendo solo i campi desiderati e ordinando i record secondo
le proprie preferenze. La creazione di una vista non cambia la conformazione
fisica del database, bensì comporta la generazione di una struttura logica
personalizzata che permette all'applicazione che accede all'archivio di avere di
esso una visione adeguata alle proprie esigenze. Una struttura logica di questo
tipo prende il nome di dynaset (abbreviazione di dynamic set,
ovvero insieme dinamico) e l'operazione che porta alla sua creazione è in
genere detta interrogazione del database.
e query
Un termine che si usa spesso quando si
parla di database è costituito dalla parola query. Una query non è altro
che un'interrogazione del database, ovvero l'estrazione da esso dell'insieme
costituito dai dati in grado di soddisfare delle condizioni specificate. Una
query quindi rappresenta un'operazione effettuata sull'archivio secondo delle
modalità ben precise, che sono descritte tipicamente per mezzo di una stringa
contenente un testo composto da nomi di elementi del database correlati per
mezzo di parole chiave appartenenti ad un linguaggio particolare (esso è detto
in genere linguaggio di interrogazione o, dagli amanti della lingua
inglese, query language). Sulla scena esistono molti linguaggi di questo
tipo. In passato, infatti, ogni motore di database tendeva a possedere un
proprio linguaggio di interrogazione proprietario. Col passare del tempo,
tuttavia, l'esigenza di creare uno standard che permettesse di operare con
prodotti diversi riducendo al minimo i costosi corsi di formazione si è sempre
più fatta sentire. I moderni strumenti, quindi, sono andati via via adeguandosi
alle richieste dell'utenza e oggi, sebbene sopravvivano ancora molti linguaggi
proprietari, si può affermare che lo standard mondiale, almeno per alcune
categorie di prodotti, sia costituito dallo Structured Query Language
(linguaggio strutturato di interrogazione), il cui nome è noto ai più
sotto forma di sigla (SQL). I vantaggi principali offerti da questo linguaggio
sono rappresentati dalla semplicità, dalla notevole potenza e dalla portabilità
da uno strumento di gestione dei database a un altro. Quest'ultima
caratteristica non è tuttavia valida al 100%, in quanto spesso si possono
osservare delle piccole variazioni fra i linguaggi previsti dai vari
prodotti.
Il linguaggio SQL
Il linguaggio SQL rappresenta uno
standard per tutti i recenti prodotti Microsoft in grado di accedere a dei
database. Anche il motore di gestione degli archivi integrato in Visual Basic
non si sottrae questa regola. Per mezzo di una stringa SQL è quindi possibile
definire la struttura logica, i requisiti, nonché l'ordinamento dei record che
devono essere utilizzati da un controllo di tipo data. L'istruzione
SQL di più frequente utilizzo è denominata SELECT. La sua sintassi è la
seguente:
SELECT
<elenco_campi>; FROM <tabella> dove
<elenco_campi> ha la
sintassi: <campo_1>[,<campo_2>,…,
<campo_n>]
Per mezzo dell'istruzione SELECT
è possibile specificare un elenco di campi che costituisce la struttura logica
da attribuire ai record da prelevare dal database. La tabella in cui essi sono
contenuti è specificata dopo la clausola FROM. Ad esempio, si supponga
di disporre di un database denominato VeicoliAziendali, in cui ciascun
record contiene tutte le informazioni riguardati un veicolo aziendale. All'atto
dell'acquisto di una nuova automobile, in archivio sono inserite tutte le
informazioni atte a identificarla in modo preciso. Il database deve pertanto
prevedere una tabella, che si supporrà denominata Automobili, in cui sono
presenti dei campi in grado di ospitare la marca, il modello, la versione, il
numero di telaio, la targa, il numero del contratto di assicurazione, la data di
immatricolazione, ecc. Si supponga di voler realizzare un'applicazione che
consenta al responsabile della gestione del parco veicoli di verificare quali
autovetture hanno superato i 5 anni di età e necessitano quindi di essere
sostituite. È evidente che un'applicazione di questo tipo necessita per ogni
veicolo delle informazioni relative all'anno di immatricolazione, al numero
identificativo attribuito dall'azienda e al nominativo della persona a cui è
stato affidato. Sicuramente non risulta utile conoscere il colore del l'auto, né
il numero di telaio o la scadenza del contratto di assicurazione. Per fare in
modo che sia caricata in memoria una minore quantità di informazioni, rendendo
meno gravoso il compito del sistema e agevolando le operazioni di debug al
programmatore, è possibile fare in modo che il controllo di tipo data
preposto alla lettura delle informazioni abbia una percezione dell'archivio
diversa da quella reale, ovvero sia in grado di leggere dei record composti dai
soli campi necessari. Ciò è possibile provvedendo a fornire all'oggetto la
stringa SQL:
SELECT Anno, Numero, Utente;
FROM Automobili
Tale sequenza fa sì che il modulo di
accesso ai dati provveda a creare un recordset composto da record caratterizzati
dalla presenza dei soli 3 campi Anno, Numero, Utente. I dati sono
prelevati dalla tabella Automobili. L'applicazione può eseguire qualsiasi
operazione sugli elementi del dynaset, ivi compresa la modifica dei contenuti o
la loro cancellazione. Naturalmente, le informazioni poste nella tabella di
provenienza sono costantemente mantenute sincronizzate con quelle presenti nel
dynaset, per cui ogni operazione effettuata sulla struttura logica influenza
l'archivio memorizzato fisicamente sul disco. Per poter rendere ancora più
veloce l'applicazione e per semplificare ulteriormente il compito del
programmatore, sarebbe senz'altro utile disporre di un archivio costituito
esclusivamente dai record che descrivono i veicoli che hanno almeno 5 anni. In
tal modo, non sarebbero necessari altri controlli sui dati e il programma
dovrebbe semplicemente limitarsi a visualizzare sequenzialmente tutti gli
elementi forniti dal motore di gestione del database. Per mezzo della clausola
WHERE, da utilizzare in combinazione con l'istruzione SELECT, è
possibile fare in modo che il programma veda i dati relativi ai veicoli più
anziani come contenuti all'interno di una tabella ad hoc, sebbene in realtà
siano posti nella stessa struttura che contiene le informazioni relative alle
altre vetture. La sintassi è la seguente:
SELECT
<elenco_campi>; FROM <tabella>; WHERE
<condizione>
dove <condizione>
rappresenta un'espressione di confronto valida, definita secondo lo
schema
<confronto> [And|Or
<confronto>… And|Or <confonto>]
<confronto> è definito
come
<campo>
=|<>|<|>|<=|>=|Like
[<valore>|<campo>]
oppure
<campo> Between
<valore> And <valore>
L'uso della clausola WHERE
implica la presenza di un criterio di scelta, che è descritto da una o più
condizioni combinate per mezzo dei canonici operatori logici. Si noti che oltre
ai tipici operatori di confronto è possibile utilizzare la parola Between
per verificare l'appartenenza del contenuto di un campo a un preciso intervallo
di valori. Ad esempio, volendo estrarre dall'archivio delle vetture aziendali
tutti i veicoli immatricolati prima del 1995, è necessario effettuare una query
descritta dalla stringa
SELECT Anno, Numero, Utente; FROM
Automobili; WHERE Anno<1995
I campi indicati nei criteri di
selezione non devono necessariamente essere menzionati anche dopo il comando
SELECT. Nel caso della query
SELECT Anno, Numero, Utente; FROM
Automobili; WHERE (Anno<1995) And (Tipo='Station Wagon')
non ha alcun senso prelevare il
campo Tipo dall'archivio, in quanto la condizione indicata fa sì che
possa solo essere costituito dalla stringa "Station Wagon".Osservando la frase
SQL appena descritta, si può notare che il criterio di scelta è costituito da
due condizioni racchiuse fra parentesi e combinate per mezzo dell'operatore
logico And. L'uso delle parentesi, sebbene spesso non sia obbligatorio,
risulta consigliabile in presenza delle parole chiave And e Or al
fine di favorire la leggibilità de codice. Inoltre, la stringa alfanumerica da
confrontare con il contenuto del campo Tipo è racchiusa fra apici. In
alcuni casi, ad esempio in presenza di frasi contenenti degli apostrofi, può
rivelarsi necessario l'utilizzo di un diverso delimitatore per le stringhe.
L'alternativa è costituita dall'uso delle virgolette (").
La selezione di tutti i
campi
Talvolta si rivela necessario
utilizzare una query per leggere tutti i record che soddisfano una condizione
senza che sia necessario variare la loro struttura logica, ovvero rendendo
disponibili all'applicazione tutti i campi che li compongono. In questi casi,
può risultare scomodo dover fornire l'elenco completo dei campi dopo la parola
SELECT, soprattutto se il loro numero è elevato. È allora possibile utilizzare
il carattere *, che significa tutti i campi.
La stringa SELECT *; FROM
Automobili; WHERE Anno<1995
crea pertanto una query che provvede a
prelevare dal database tutti i campi di tutti i record presenti nella tabella
Automobili che sono caratterizzati dal contenere un valore inferiore a
1995 nel campo Anno.
Un esempio…
Si supponga di voler realizzare
un'applicazione in grado di estrarre da un archivio di auto aziendali, contenuto
in una tabella denominata Automobili, l'elenco dei veicoli immatricolati
nell'anno 1998. Si supponga che nell'archivio esistano almeno i campi
Anno, Targa, Modello, Sede, contenenti rispettivamente le
informazioni riguardanti l'anno di immatricolazione, la targa, il modello e la
sede alla quale il veicolo è stato assegnato. Come sempre, il primo passo da
compiere riguarda la creazione di un form, in cui è necessario inserire un
oggetto di tipo data, utilizzato per leggere le informazioni contenute
nel database e quattro etichette, destinate ad accogliere i dati contenuti nei
campi. Come per tutte le altre applicazioni facenti uso dei database, occorre
collegare l'oggetto di tipo data, a cui sarà dato il nome Query,
all'archivio. Il passo successivo consiste nel collegare all'elemento
Query le etichette testuali usate per visualizzare il contenuto dei campi
Anno, Targa, Modello e Sede. Si agisce quindi come se si
volesse realizzare un'applicazione in grado di leggere sequenzialmente tutti i
record dell'archivio. La differenza rispetto a un programma di questo tipo
consiste esclusivamente nel valore assunto dalle proprietà RecordsetType
e RecordSource dell'oggetto di tipo Data. La prima deve essere
impostata al valore Dynaset in luogo di Table (tabella), mentre la
seconda, anziché contenere il nome della tabella da cui devono essere prelevati
i dati, deve ospitare la stringa SQL che descrive la query da effettuare,
ovvero:
SELECT Anno, Targa, Modello, Sede;
FROM Automobili; WHERE Anno=1998
Conclusioni
La possibilità di eseguire su un
archivio locale o remoto delle query descritte da stringhe in linguaggio SQL fa
di Visual Basic uno strumento particolarmente indicato per gestire i database.
Non è quindi un caso che il prodotto Microsoft si stia sempre più affermando
come uno degli strumenti standard per la realizzazione di software gestionale in
ambiente Windows. Data l'importanza dell'argomento, anche la prossima lezione di
questo corso sarà dedicata alla realizzazione e alla gestione delle query. Per
saperne di più, al lettore non resta che attendere il prossimo
numero…
Le query SQL (parte
XV)
di Maurizio Crespi
Continua la trattazione della gestione
dei database in Visual Basic. Questo mese sono ancora protagoniste la query e in
particolare alcune utili clausole previste dal comando SQL SELECT. Non
mancherà, inoltre, un’occasione per rivedere i concetti esposti nelle ultime 10
lezioni di questo corso.
L’ordinamento dei dati risultanti
dall’esecuzione di una query
Spesso si rivela opportuno fare in modo
che i dati risultanti dall’esecuzione di una query siano presentati secondo un
particolare ordine. Ad esempio, si supponga di disporre di un database
contenente i dati anagrafici di tutti gli abitanti di un piccolo comune e di
voler creare un’applicazione che permetta di estrarre l’elenco degli abitanti
che nel corso del 1999 hanno compiuto o compieranno il diciottesimo anno di età.
Si supponga che l’archivio preveda una tabella denominata Anagrafica,
composta da record contenenti i campi Nome, Cognome, Indirizzo,
AnnoNascita. Per estrarre dall’archivio tutti i nati nell’anno 1981 è
necessaria una query descritta dalla seguente stringa SQL:
SELECT Nome, Cognome,
Indirizzo FROM Anagrafica WHERE AnnoNascita=1981
Generalmente, quando si gestiscono
delle anagrafiche, si tende ad agevolare l’utente presentando i nominativi in
ordine alfabetico. La query descritta dalla stringa sopra menzionata non
garantisce che ciò avvenga, in quanto non impone alcuna condizione in merito
all’ordinamento dei dati. Per fare in modo che essi siano presentati secondo
un preciso ordine, è necessario introdurre una clausola aggiuntiva. La sintassi
del comando SELECT diventa pertanto la seguente:
SELECT
<elenco_campi> FROM <nome_tabella> [WHERE
<condizione>] [ORDER BY <ordinamento>]
dove <ordinamento> è
descritto da
<ordinamento> = <campo
1> [, <campo 2>, ..., <campo n>]
Il primo campo indicato dopo la
clausola ORDER BY è quello secondo il quale è effettuato il primo
ordinamento dei dati. Qualora alcuni record prevedano valori analoghi
all’interno dell’elemento specificato, essi sono ordinati fra loro in base al
valore contenuto nel campo indicato dopo la virgola. In caso di ulteriore
analogia fra alcuni record, essi sono ordinati fra loro in base al valore
dell’eventuale terzo campo, ecc. Si noti che è obbligatorio indicare un nome di
un campo dopo la clausola ORDER BY, mentre i successivi elementi sono
facoltativi. Per chiarire il concetto, si osservino gli esempi riportati di
seguito. Si supponga di applicare la query descritta dalla stringa
SELECT Nome, Cognome FROM
Anagrafica WHERE AnnoNascita=1981
al database contenente tutti gli
abitanti di un piccolo comune.
Un possibile risultato è il
seguente:
Rossi Marco Bianchi
Lucia Rossi Alessandro Verdi Marco Rossi Davide Gialli
Claudia
Come si può notare, i dati non sono
ordinati in base al contenuto dei campi.Si supponga ora di applicare allo stesso
archivio la query:
SELECT Nome, Cognome FROM
Anagrafica WHERE AnnoNascita=1981 ORDER BY Cognome
In questo caso, il risultato è il
seguente:
Bianchi Lucia Gialli
Claudia Rossi Davide Rossi Marco Rossi Alessandro Verdi
Marco
Ora i nominativi sono ordinati per
cognome. Si noti però che è assente l’ordinamento per nome nel caso di individui
caratterizzati dallo stesso cognome. Per sopperire a ciò è possibile applicare
la seguente query:
SELECT Nome, Cognome FROM
Anagrafica WHERE AnnoNascita=1981 ORDER BY Cognome,
Nome
Questa volta, i dati sono presentati
nel modo corretto:
Bianchi Lucia Gialli
Claudia Rossi Alessandro Rossi Davide Rossi Marco Verdi
Marco
Ordinamento decrescente.
In mancanza di indicazioni contrarie,
l’ordinamento avviene in modo crescente in base ai valori assunti dai campi
indicati dopo la clausola ORDER BY. Si supponga ora di voler effettuare
una query analoga alla precedente ma tale da fare in modo che l’ordinamento
rispetto al campo Cognome sia decrescente. In questo caso, al nome del
campo deve essere fatta seguire la parola DESC. Ad esempio, la
stringa
SELECT Nome, Cognome FROM
Anagrafica WHERE AnnoNascita=1981 ORDER BY Cognome DESC,
Nome
fornisce il seguente
risultato:
Verdi Marco Rossi
Alessandro Rossi Davide Rossi Marco Gialli Claudia Bianchi
Lucia
Come si può notare, l’ordinamento è
decrescente solo rispetto al campo dopo il cui nome è stata specificata la
parola DESC, abbreviazione di descending. Per fare in modo che
l’ordinamento sia decrescente anche nei confronti del campo Nome, è
necessario quindi scrivere la seguente stringa SQL:
SELECT Nome, Cognome FROM
Anagrafica WHERE AnnoNascita=1981 ORDER BY Cognome DESC, Nome
DESC
Il risultato è costituito dalla
sequenza:
Verdi Marco Rossi Marco
Rossi Davide Rossi Alessandro Gialli Claudia Bianchi
Lucia
Query senza duplicati
Si supponga che per scopi statistici si
debba produrre un elenco dei nomi di persona utilizzati nell’anno 1981 per
iscrivere i nuovi nati all’anagrafe.
Ciò è possibile eseguendo sull’archivio
la query descritta dalla stringa
SELECT Nome FROM
Anagrafe WHERE AnnoNascita=1981 ORDER BY Nome
Il risultato è costituito dai seguenti
valori:
Alessandro Claudia Davide Lucia Marco Marco
Si noti che il nome "Marco" è
duplicato. Infatti, come si è visto in precedenza, vi sono due individui
omonimi. Nel caso dell’esempio, l’esistenza di un voce duplicata nel risultato
rappresenta un fatto indesiderato. Per evitare che ciò avvenga, è possibile far
uso della clausola DISTINCT. La sintassi del comando SQL SELECT
diventa:
SELECT [DISTINCT]
<elenco_campi> FROM <nome_tabella> [WHERE
<condizione>] [ORDER BY <ordinamento>]
La query da effettuare è quindi
descritta dalla stringa:
SELECT DISTINCT Nome FROM
Anagrafe WHERE AnnoNascita=1981 ORDER BY Nome
e il risultato è il
seguente:
Alessandro Claudia Davide Lucia Marco
Un esempio
Il Listato 1 costituisce il
codice sorgente di una semplice applicazione in grado di comporre ed eseguire
una query SQL sul database descritto negli esempi precedenti. I file sono
scaricabili da Internet all’indirizzo
ftp://ftp.infomedia.it/pub/DEV./Listati. Il programma permette di
selezionare, per mezzo di checkbox, i campi da visualizzare. Con
l’ausilio di alcuni option button, inoltre, consente di stabilire
i criteri di ricerca e di ordinamento. I risultati sono visualizzati in una
listbox. Si noti che l’oggetto di tipo Data è utilizzato solo
per l’interfacciamento al database, non per la navigazione; lo stesso dicasi per
le 3 label ad esso collegate. Per questo motivo, questi elementi sono stati resi
invisibili. Per realizzare questa applicazione è necessario disegnare un form
dotato di tutti gli elementi necessari. Si provvede poi a collegare l’oggetto di
tipo Data all’archivio e a legare ad esso le etichette testuali. A tal
fine occorre inizializzare l’elemento di gestione del database utilizzando come
proprietà RecordSource il nome della tabella da cui devono essere
estratti i dati. Successivamente, dopo avere composto la query, il programma può
provvedere a sostituire il contenuto della proprietà con la stringa in
linguaggio SQL. Si noti che, dopo aver eseguito tale operazione, è necessario
invocare il metodo Refresh per aggiornare l’oggetto di tipo Data e
quindi per accedere al risultato dell’interrogazione. Si osservi altresì che,
per verificare l’eventuale assenza di dati soddisfacenti le condizioni imposte,
si fa uso della proprietà RecordCount, che indica il numero degli
elementi presenti nel recordset (insieme di record)
corrente.
Rinfreschiamoci la
memoria…
Il corso dedicato a Visual Basic
prosegue ormai da molto tempo e gli argomenti già trattati sono numerosi. Lo
scopo di quest’ultima parte della lezione è di invitare il lettore a rivedere i
temi discussi negli ultimi 10 numeri, sia per valutare il proprio livello di
comprensione, sia per evitare che dei concetti importanti siano dimenticati in
seguito all’acquisizione di nuove informazioni. Di seguito saranno quindi
formulate alcune domande, a cui il lettore è invitato a rispondere. Le soluzioni
corrette sono indicate di seguito in corsivo.
- A cosa serve l’istruzione
ReDim?
L’istruzione ReDim permette di ridefinire un vettore
per aumentare o ridurre la sua dimensione.
- A quale fine a volte si specifica la
clausola Preserve dopo l’istruzione ReDim?
L’uso della
clausola Preserve permette di aumentare la dimensione di un vettore senza
perdere i valori in esso già contenuti.
- L’indice iniziale di un vettore deve
essere sempre 0?
No, può essere qualsiasi valore intero.
- Quali sono i principali vantaggi
derivanti dall’uso delle subroutine?
Le subroutine (o procedure)
permettono di assegnare un nome a delle porzioni di codice usate in più punti di
un programma. Esse possono poi essere richiamate semplicemente indicandone
l’identificatore. Ciò rende più leggibile il codice dell’applicazione ed evita
inutili duplicati al suo interno.
- A cosa serve la parola chiave
ByVal?
La parola chiave ByVal permette di passare a una
procedura un parametro per valore.
- Quanti parametri possono essere
passati a una procedura?
Non esiste un limite ai parametri passabili a una
procedura, sebbene non sia opportuno esagerare, onde evitare di produrre del
codice scarsamente leggibile.
- Che cosa differenzia un parametro
passato per valore da uno passato per riferimento?
Se si richiama una
procedura passando come parametro una variabile, se esso è definito per
riferimento si rende la procedura in grado di modificare il valore della
variabile. Se invece il parametro è definito per valore, la procedura
acquisisce il valore della variabile ma non la facoltà di
modificarla.
- Qual è la differenza che intercorre
fra una funzione e una procedura?
Una funzione può restituire dei valori.
Una procedura invece no, a meno di ricorrere a dei parametri passati per
riferimento.
- Può una funzione restituire più di un
valore?
No, a meno che non si faccia uso di parametri passati per
riferimento.
- Quali rischi comporta un uso errato
della ricorsione?
Un uso errato della ricorsione, ad esempio quando non è
specificata una condizione di uscita, può provocare il superamento della
capacità massima dello stack di sistema (stack overflow).
- Cos’è la procedura
Main?
La procedura Main, se presente, è la prima procedura eseguita
all’avvio di un programma. Essa deve risiedere in un modulo (file di estensione
BAS).
- Qual è la differenza principale fra i
file sequenziali e i file ad accesso casuale?
I file ad accesso casuale
permettono la ricerca delle informazioni senza comportare la lettura di tutto il
proprio contenuto.
- A cosa serve l’istruzione
Open?
Per mezzo dell’istruzione Open è possibile fare in modo che
il programma assuma il controllo di un file per eseguire su di esso delle
operazioni di lettura o scrittura.
- A cosa serve la funzione Line
Input?
L’istruzione Line Input ha lo scopo di effettuare la
lettura di una riga in un file di testo.
- Cos’è un record?
Un
record è un agglomerato di informazioni di tipo non necessariamente omogeneo.
Costituisce l’elemento fondamentale di cui sono composti i file ad acceso
casuale.
- Quando si apre un file ad accesso
casuale, è opportuno specificare il parametro Len?
E’ obbligatorio,
al fine di consentire al sistema la distinzione dei record all’interno del
file.
- Qual è l’istruzione da utilizzare per
leggere un record in un file ad accesso casuale?
La lettura di un record
in un file ad accesso casuale avviene per mezzo dell’istruzione Get.
- Qual è l’istruzione che permette di
scrivere un record in un file ad accesso casuale?
La scrittura di un
record in un file ad accesso casuale avviene per mezzo dell’istruzione
Put.
- Che vantaggi offre l’uso di un
database in luogo di un file ad accesso casuale?
L’uso di un database
permette di gestire grandi quantità di dati in modo semplice, grazie all’uso di
un "motore" in grado di eseguire autonomamente la maggior parte delle operazioni
di ricerca e selezione.
- Quali formati di database possono
essere gestiti da Visual Basic?
Visual Basic è in grado di gestire
pressoché tutti i formati di database per i quali sia previsto un driver
ODBC.
- A cosa serve il metodo
MoveFirst dell’oggetto Recordset?
Il metodo MoveFirst
permette di accedere al primo record presente in archivio.
- Quando la proprietà EOF
dell’oggetto Recordset assume il valore logico True?
La
proprietà EOF (End Of File) assume il valore True quando il
programma giunge all’ultimo recrod presente in archivio.
- Qual è la proprietà dell’oggetto
Recordset che indica che la ricerca effettuata non è andata a buon
fine?
La proprietà che assume il valore logico True se
un’operazione di ricerca non va a buon fine è denominata NoMatch.
- Che vantaggi offre l’uso delle
query?
Le query permettono di fare in modo che il programma abbia una
visione personalizzata dell’archivio, ovvero che acceda solo delle informazioni
utili. Ciò semplifica il compito del programmatore, che non deve occuparsi dei
filtrare i dati non necessari.
- Qual è il linguaggio di definizione
delle query supportato da Visual Basic?
In Visual Basic le query sono
descritte per mezzo del linguaggio SQL.
- A cosa serve la clausola WHERE
in una query SQL?
La clausola WHERE permette di specificare la
condizione che deve essere soddisfatta dai record che devono essere estratti
dall’archivio.
- Qual è la clausola da utilizzare per
fare in modo che i dati siano ordinati in base al valore assunto da un
campo?
Per fare in modo che i dati siano ordinati in base al valore
assunto da un campo è necessario far uso della clausola ORDER BY.
- Com’è possibile far sì che
l’ordinamento in funzione del valore assunto da un campo avvenga in modo
decrescente?
L’ordinamento avviene in modo decrescente se nella clausola
ORDER BY accanto al nome del campo appare la parola DESC.
- Com’è possibile fare in modo che
l’esecuzione di una query su un archivio non produca record duplicati?
Per
evitare la generazione di record duplicati, è sufficiente far seguire la parola
SELECT dalla clausola DISTINCT.
La gestione della stampante (Parte 16)
Ogni sistema, di qualunque tipo o
dimensione, se usato per uso professionale, è corredato da una stampante. Questa
periferica, infatti, è ormai diventata una componente imprescindibile di ogni
ufficio. Per questo motivo, tutti i programmatori, almeno una volta nella vita,
si trovano a dover scrivere del software in grado di gestirla. In particolare,
coloro che sviluppano applicazioni gestionali non possono in genere esimersi dal
prevedere la rappresentazione su carta delle informazioni elaborate o
archiviate. Visual Basic nel tempo si è conquistato una posizione da leader nel
campo degli strumenti di sviluppo per applicazioni gestionali. Ciò è dovuto
anche alla semplicità e alla flessibilità con cui permette di produrre dei
tabulati.
L’oggetto Printer
In Visual Basic la stampante è
rappresentata da un oggetto, denominato Printer. Tutte le operazioni
devono pertanto essere eseguite su di esso. Lo scopo della lezione è di
consentire al lettore di prendere confidenza con le proprietà e i metodi
fondamentali che l’oggetto prevede.
Il metodo Print
Le stampanti sono nate con lo scopo di
permettere la trasposizione su carta dei testi prodotti dal calcolatore.
L’operazione elementare per questo tipo di periferiche è quindi rappresentata
dalla scrittura di testo. L’oggetto Printer a tal fine mette a
disposizione il metodo denominato Print. Il suo utilizzo ricorda quello
della parola chiave omonima del linguaggio Basic. Per scrivere una frase è
quindi sufficiente invocare il metodo passandogli la stringa da stampare. Ad
esempio, la riga
Printer.Print "Io leggo
Dev"
provoca la scrittura della frase "Io
leggo Dev" sulla stampante. Analogamente, le righe
Printer.Print "Io leggo
Dev" Printer.Print "perché mi insegna a programmare"
provocano la scrittura delle frasi "Io
leggo Dev" e "perché mi insegna a programmare". Esse sono poste su due righe
differenti. Infatti, analogamente a ciò che avviene nel linguaggio Basic per il
comando Print, alla fine della frase è generata automaticamente una
sequenza costituita dai caratteri CR (Carriage Return, ovvero ritorno del
carrello di stampa nella posizione di inizio riga) e LF (Line Feed,
ovvero avanzamento di una riga). Ciò, tuttavia, a volte può risultare
fastidioso, soprattutto quando si desidera comporre delle frasi a partire da
informazioni calcolate dal programma in punti diversi.
Per evitare che sia richiesto alla
stampante di andare a capo, è necessario far seguire l’invocazione del metodo
Print dal punto e virgola. Le righe
Printer.Print "Io leggo
Dev"; Printer.Print "perché mi insegna a programmare"
provocano pertanto la scrittura della
frase
Io leggo Devperché mi insegna a
programmare
Si noti che in questo caso non sono
stati inseriti automaticamente degli spazi fra le parole "Dev" e "perché". È
infatti cura del programmatore l’introduzione al termine di una stringa degli
spazi tali da impedire che sia unita alla successiva quando ciò non è
desiderato. Quindi, per scrivere la frase correttamente, è necessario aggiungere
uno spazio al termine della prima stringa, come nelle righe che
seguono:
Printer.Print "Io leggo Dev
"; Printer.Print "perché mi insegna a programmare"
In alcuni casi, invece, può verificarsi
la necessità di inserire delle righe vuote. Per fare ciò, è sufficiente
richiamare il metodo Print senza parametri. Ad esempio, la
riga
Printer.Print
fa sì che la testina della stampante si
posizioni sulla riga successiva a quella corrente.
L’incolonnamento dei
dati
Spesso, quando si stampano dei dati, si
rivela necessario il loro incolonnamento. È il caso, ad esempio, dei documenti
per uso fiscale. In questi casi, l’uso delle tabulazioni può rivelarsi molto
utile. Esse permettono di suddividere il foglio in colonne, ognuna larga 14
caratteri. Visual Basic permette di gestire le tabulazioni per mezzo
dell’opzione Tab del comando Print. Ad esempio, le
righe
Printer.Print
Tab(2); Printer.Print "Dev"
provocano la stampa della stringa "Dev"
nella seconda colonna del foglio. Si noti che il parametro numerico descrive
sempre una posizione assoluta. Quindi, il numero della colonna in cui la stampa
avviene è indipendente dalla posizione assunta dalla testina di stampa prima
dell’esecuzione dell’istruzione. Per mezzo dell’opzione Tab non è
possibile definire uno spostamento relativo, bensì solo uno assoluto. L’unica
eccezione è costituita dal caso in cui il parametro numerico è omesso. La
scrittura avviene allora nella colonna successiva a quella corrente o, se la
testina si trova alla fine del foglio, nella riga seguente.
Pertanto, le
righe Printer.Print Tab; Printer.Print "Dev"
fanno sì che nella colonna successiva a
quella corrente sia scritta la parola "Dev". Si noti che le colonne iniziano
ogni 14 caratteri. La loro lunghezza risulta allora dipendente dal tipo di
carattere utilizzato e non dalle dimensioni del foglio.
L’inserimento di spazi
Spesso, soprattutto quando si devono
indicare degli importi in valuta, è opportuno allineare i testi a destra.
L’incolonnamento per mezzo delle tabulazioni può in questi casi non rivelarsi
una scelta corretta, dal momento che permette di allineare i testi a sinistra ma
non agevola un granché l’allineamento a destra. Può allora rivelarsi utile l’uso
dell’opzione Spc del metodo Print, che permette di stampare una
sequenza di spazi in quantità pari al valore indicato come parametro. Calcolando
opportunamente tale dato, è quindi possibile ottenere il corretto allineamento a
destra del testo. Si osservi la procedura AllineaDestra di cui è
riportato di seguito il codice sorgente:
Private Sub AllineaDestra(ByVal
Stringa As String, ByVal LarghCol As Integer) Dim Lungh As Integer Dim
Spazi As Integer Stringa = Trim(Stringa) Lungh = Len(Stringa) Spazi =
LarghCol - Lungh Printer.Print Spc(Spazi); Printer.Print Stringa; End
Sub
Essa provvede a stampare la stringa
fornitale come parametro dopo averla fatta seguire da spazi la cui quantità è
espressa dalla variabile Spazi. Il suo valore è calcolato come differenza
fra il numero della colonna in cui deve avvenire l’allineamento, fornito come
parametro (LarghCol) e la lunghezza della stringa da stampare. Si noti
che tale procedura funziona correttamente solo se il carattere utilizzato è di
tipo a spaziatura non proporzionale. In caso contrario, infatti, le larghezze
dei caratteri, compresi gli spazi, risultano diverse fra loro e rendono molto
più complesso l’allineamento. Al fine di ridurre il rischio di errore derivante
da un cattivo allineamento delle informazioni e di rendere più semplice e veloce
lo sviluppo delle applicazioni, è pertanto consigliabile l’uso di un carattere a
larghezza non proporzionale (ad esempio il "Courier New") nei programmi
che prevedono la stampa di tabelle o altri dati su modulistica di tipo
fiscale.
L’impostazione del carattere da
utilizzare
Per selezionare un carattere diverso da
quello predefinito, occorre intervenire sulla proprietà FontName
dell’oggetto Printer. Ad esempio, la riga
Printer.FontName = "Courier
New"
fa sì che la stampa sia eseguita con il
carattere "Courier New".
È inoltre possibile impostare la
dimensione del font da utilizzare agendo sul valore della proprietà
FontSize. L’insieme dei valori che essa può assumere è rappresentato
dalle misure in punti che sono accettate dal sistema per il carattere
selezionato. Per i font di tipo raster, i valori possibili sono pochi, mentre
per i caratteri true type, che costituiscono la quasi totalità della
dotazione di Windows, le dimensioni possono variare in un intervallo
estremamente vasto. Pertanto, per impostare quello che i tipografi chiamano
corpo del carattere, ovvero la sua dimensione, al valore corrispondente a
10 punti, occorre scrivere
Printer.FontSize =
10
Anche lo stile del carattere può essere
variato. Ciò avviene agendo su alcune proprietà in grado di assumere dei valori
booleani. Ad esempio, la riga
Printer.FontBold =
True
fa sì che il testo sia scritto in
grassetto.
La riga
Printer.FontItalic =
True
seleziona invece lo stile
corsivo.
La sottolineatura è invece legata alla
proprietà FontUnderline. Pertanto, può essere attivata mediante la
riga
Printer.FontUnderline =
True
Un’altra proprietà, in genere poco
usata, è quella che permette di generare delle stampe in cui il carattere
risulta barrato. Essa è denominata FontStrikethru. Le proprietà sopra
elencate sono combinabili fra loro. Si osservi il seguente codice:
Printer.FontName =
"Arial" Printer.FontSize = 20 Printer.FontBold =
True Printer.FontItalic = True Printer.FontStrikethru =
False Printer.Print = "Io leggo Dev"
provocano la stampa della frase "Io
leggo Dev" in Arial grassetto corsivo di corpo 20.
L’utilizzo di più tipi di caratteri in
un documento
Le impostazioni relative al carattere
sono attive per tutti i testi stampati dopo l’assegnazione dei valori alle
proprietà, fino a quando esse non subiscono variazioni. In altre parole, le
impostazioni riguardanti il font non interessano l’intero documento, a meno che
non siano state definite prima della sua stampa. Inoltre, possono essere variate
a piacere al suo interno. È quindi possibile usare un numero teoricamente
infinito di caratteri diversi in un’unica stampa.
Si osservi l’esempio che
segue:
Printer.FontName =
"Arial" Printer.FontSize = 20 Printer.FontBold =
True Printer.FontItalic = False Printer.FontStrikethru =
False Printer.Print = "Io leggo Dev" Printer.FontSize =
15 Printer.Print = "perché mi insegna" Printer.FontSize =
10 Printer.FontItalic = True Printer.Print = "a
programmare!"
Il testo prodotto è composto da 3 righe
aventi dimensioni diverse. L’ultima, inoltre, si differenza per lo stile,
essendo scritta in corsivo.
Il passaggio a una nuova
pagina
Molto spesso, le stampe che un
programma deve produrre superano la capienza di una pagina. In questi casi,
occorre inserire uno o più salti di pagina. Ciò è possibile invocando il metodo
NewPage, senza argomenti, come illustrato di seguito:
Printer.NewPage
L’utilizzo di tale metodo provoca
inoltre l’incremento di un’unità del valore della proprietà Page che,
come è facile intuire, contiene il numero della pagina corrente. Si osservi la
seguente procedura
Private Sub
PaginaSucc Printer.NewPage Printer.Print "Pagina "; Printer.Print
Printer.Page End Sub
Essa ha lo scopo di provocare
l’avanzamento alla pagina successiva e di inserire nella parte superiore del
nuovo foglio il numero di pagina.
L’avvio della stampa
Coloro che leggendo questo articolo
hanno già provato a scrivere del codice volto a produrre una stampa, potrebbero
aver subìto una delusione. Infatti, con molta probabilità, la stampante non ha
prodotto alcun risultato. Ciò è dovuto al fatto che i documenti sono prima
composti in memoria e solo in seguito inviati alla stampante. Ciò permette la
massima flessibilità, nonché la possibilità di annullare una stampa già
iniziata. L’invio di un documento alla stampante avviene quindi solo dopo la sua
completa definizione, ovvero quando si provvede a dichiararne il completamento
per mezzo del metodo EndDoc, da richiamare senza l’ausilio di parametri,
come indicato di seguito:
Printer.EndDoc
L’annullamento di una
stampa
Come si è già accennato, è possibile
annullare una stampa in corso di definizione. Per fare ciò, basta ricorrere al
metodo KillDoc.
Ad esempio, la riga
Printer.KillDoc provvede
alla cancellazione della stampa in corso.
Un semplice esempio…
La lezione termina con un piccolo
esempio. Si tratta di un semplice programma in grado di stampare una tavola
pitagorica. Il suo codice, riportato nel listato 1, è tutto contenuto
nella procedura Main, in quanto non necessita di un’interfaccia grafica.
Esso si compone di due cicli nidificati. Uno è utilizzato per gestire le righe,
mentre l’altro mantiene traccia delle colonne. Il prodotto dei valori assunti
dalle variabili di controllo dei cicli costituisce il dato da visualizzare nella
tabella. Si noti che è calcolata la lunghezza del valore da scrivere, per
consentire l’anteposizione di un numero di spazi tale da ottenere l’allineamento
a destra dei dati.
La produzione di stampe di elevata qualità (parte
XVII)
di Maurizio Crespi
Nel corso degli anni, gli
strumenti che permettono di stampare le informazioni su carta hanno subìto una
notevole evoluzione. Dalle prime stampanti a margherita si è passati a quelle ad
aghi, in grado di produrre anche della grafica e di operare con diversi tipi di
carattere, fino poi ad arrivare alle moderne stampanti a tecnologia laser o a
getto di inchiostro, che permettono di ottenere dei risultati di qualità un
tempo di esclusiva competenza dei migliori tipografi. L’evolversi delle
periferiche ha portato ad un aumento delle esigenze dei programmatori. Dalla
semplice rappresentazione di testi si è passati a un uso sempre più massiccio
delle grafica, anche per le applicazioni di tutti i giorni. Per questo motivo,
lo sviluppatore moderno necessita di uno strumento che gli permetta di gestire
le stampanti anche per effettuare le operazioni più complesse. Ancora una volta
Visual Basic non delude le aspettative, in quanto mette a disposizione una
nutrita serie di metodi tali da rendere l’oggetto Printer, di cui si è
già ampiamente parlato nella scorsa lezione, in grado di accontentare anche
l’utenza più esigente senza richiedere uno sforzo elevato al
programmatore.
La proprietà
ScaleMode
Per effettuare stampe di
alta qualità, occorre dapprima essere in grado di posizionare i testi con
estrema precisione in qualsiasi punto del foglio. Il concetto di "colonna",
visto nella precedente lezione a proposito dell’opzione Tab del metodo
Print, non consente un controllo preciso della posizione in cui avviene
la stampa, in quanto risulta legata alla dimensione del carattere. Per un
posizionamento preciso all’interno del foglio è pertanto necessario disporre di
uno strumento che permetta di esprimere delle coordinate in unità di misura
semplici da usare e indipendenti dal carattere selezionato, quali sono ad
esempio i millimetri. È ciò che la proprietà ScaleMode permette di fare.
I valori più utilizzati per questa proprietà sono elencati nel riquadro
1. Per poter definire le coordinate degli elementi da posizionare sul foglio
in centimetri, è quindi sufficiente inserire nel codice la riga
Printer.ScaleMode
= 7
Nella trattazione che
segue si supporrà di aver selezionato come unità di misura i
centimetri.
La verifica delle
dimensioni del foglio
Per ottenere le
dimensioni dell’area di stampa espresse nella scala impostata, è possibile
ricorrere alle proprietà ScaleWidth e ScaleHeight. La prima
restituisce la larghezza del foglio, mentre la seconda, ovviamente, l’altezza.
Esse si basano sulle impostazioni inserite nella finestra di configurazione
della stampante nel Pannello di Controllo di Windows.Le righe che seguono
stampano un messaggio indicante le dimensioni del foglio in uso.
Printer.ScaleMode
= 7 Altezza=Printer.ScaleHeight Larghezza=Printer.ScaleWidth
Printer.Print "Il foglio è largo cm"; Printer.Print
Larghezza; Printer.Print "e alto cm"; Printer.Print
Altezza Printer.EndDoc
Le proprietà
CurrentX e CurrentY
Dopo aver definito una
scala, è possibile stabilire la posizione in cui deve essere visualizzata la
successiva riga di testo sul foglio. A tal fine, si fa uso delle proprietà
CurrentX e CurrentY, che permettono di intervenire rispettivamente
sulla coordinata orizzontale e su quella verticale. Ad esempio, si supponga di
voler scrivere sul foglio a 4 centimetri dal bordo superiore e a 2,5 centimetri
da quello laterale sinistro, la frase "Io leggo Dev". Il codice necessario per
fare ciò è il seguente
Printer.ScaleMode
= 7 Printer.CurrentX = 2.5 Printer.CurrentY = 4 Printer.Print "Io leggo
Dev" Printer.EndDoc
Le proprietà
CurrentX e CurrentY si incrementano automaticamente. I loro valori
riflettono pertanto sempre la posizione che è occupata dalla testina di
stampa.Per mezzo delle suddette proprietà è anche possibile definire degli
spostamenti relativi. Ad esempio, le righe seguenti spostano la testina di
stampa a destra di 3 centimetri e in basso di 2 millimetri rispetto alla
posizione corrente.
Printer.CurrentX
= Printer.CurrentX + 3 Printer.CurrentY = Printer.CurrentY +
.2
L’allineamento di un
testo a destra
Si supponga di voler
scrivere la frase "Io leggo Dev" a 2 centimetri dal bordo destro del foglio. Per
fare ciò occorre calcolare la posizione iniziale in cui deve essere effettuata
la scrittura. Essa è pari alla differenza fra la larghezza del foglio, a cui
sono sottratti i 2 centimetri di margine, meno la lunghezza del testo. È quindi
necessario disporre di uno strumento in grado di calcolare l’ingombro di una
frase in base al carattere e allo stile selezionati ed esprimerlo nell’unità di
misura in uso. Il metodo TextWidth ha questo scopo. La sua sintassi
è:
<lunghezza> =
Printer.TextWidth (<stringa>)
Per scrivere la frase
nella posizione desiderata occorre quindi digitare il seguente codice
Testo = "Io leggo
Dev" LarghFoglio = Printer.ScaleWidth LarghTesto =
Printer.TextWidth(Testo) Printer.CurrentX = LarghFoglio – LarghTesto - 2
Printer.Print Testo
La centratura di un
testo
Analogamente, è possibile
centrare un testo nella pagina. A tal fine si fa ancora uso del metodo
TextWidth, nonché delle proprietà ScaleWidth e CurrentX.
Questa volta è necessario calcolare lo spazio che rimane libero nella riga,
ovvero la differenza fra la larghezza del foglio e quella del testo da scrivere.
Il valore ottenuto deve essere diviso per 2. È ciò che fa la procedura
CentraTesto.
Private Sub
CentraTesto(ByVal Testo As String) LarghFoglio =
Printer.ScaleWidth LarghTesto = Printer.TextWidth(Testo) Printer.CurrentX
= (LarghFoglio – LarghTesto) / 2 Printer.Print Testo End
Sub
La scrittura di note a
fine pagina
In modo perfettamente
analogo è possibile stabile l’allineamento di un testo rispetto ai margini
superiore e inferiore del foglio. In questo caso, è necessario tenere conto
dell’altezza della stringa da stampare. Essa è calcolata dal metodo
TextHeight, la cui sintassi è la seguente
<altezza> =
Printer.TextHeight (<stringa>)
Si supponga di voler
scrivere una procedura in grado di visualizzare delle note di fondo pagina in un
carattere di corpo 8 a 2 centimetri dal margine inferiore del foglio. Il codice
che la descrive è il seguente
Private Sub
NoteFondo(ByVal Testo As String) Printer.FontSize = 8 AltFoglio =
Printer.ScaleHeight AltTesto = Printer.TextHeight(Testo) Printer.CurrentY
= AltFoglio – AltTesto – 2 Printer.Print Testo End
Sub
La stampa in orizzontale
o verticale
Ormai tutte le stampanti
moderne permettono di impostare l’orientamento della stampa, ovvero di scegliere
se utilizzare il foglio in modalità portrait (in verticale) o
landscape (in orizzontale). Visual Basic offre il supporto per sfruttare
questa caratteristica per mezzo della proprietà Orientation dell’oggetto
Printer. I valori che essa può assumere sono due: il primo corrisponde
alla costante vbPRORPortrait, che ha valore 1 e imposta la stampa in
verticale, mentre il secondo, corrispondente alla costante
vbPRORLandscape, permette di attivare la stampa in
orizzontale.
Il tracciamento di
linee
Sovente, al fine di
delimitare delle tabelle o di dare un look più professionale ai propri
documenti, si tracciano delle linee sul foglio. Visual Basic offre questa
possibilità per mezzo del metodo Line, la cui sintassi è la
seguente:
Printer.Line
<coord_inizio> - <coord_fine>
dove le coordinate sono
espresse da coppie di tipo (x,y), in cui x rappresenta la
posizione orizzontale espressa nella scala in uso (indicata dalla proprietà
ScaleMode) e y si riferisce alla posizione verticale.
Pertanto, considerando
sempre il valore della proprietà ScaleMode pari a 7, la riga
Printer.Line
(2,3) – (19,3)
provoca il tracciamento
di una linea a 3 centimetri dal bordo superiore del documento che, in un comune
foglio in formato A4 largo 21 centimetri, occupa l’intera larghezza dell’area
stampabile mantenendo un margine di 2 centimetri dai bordi. La prima delle
coppie indicanti le coordinate può essere omessa. In questo caso è assunta come
punto di partenza la posizione corrente della testina di stampa, ovvero la
coppia (Printer.CurrentX, Printer.CurrentY)
Ad esempio, la
riga
Printer.Line –
(19,3)
provoca il tracciamento
di una linea a partire dalla posizione corrente del cursore fino alla posizione
distante 19 unità (centimetri, se la proprietà ScaleMode è impostata al
valore 7) dal bordo sinistro del foglio e 3 unità da quello
superiore.
Lo spessore della linea
dipende dal valore assegnato alla proprietà DrawWidth. Essa può assumere
un valore interno fra 1 e 32767, che esprime il numero di pixel (punti)
corrispondente allo spessore desiderato.
Ad esempio, le
righe
Printer.DrawWidth =
4
Printer.Line (2,3) –
(19,3)
fanno sì che sia
disegnata una linea di spessore pari a 4 pixel.
Un’altra proprietà,
denominata DrawStyle, descrive il tipo di tratto della linea. I valori
che essa può assumere sono elencati nel riquadro 2. Quelli compresi fra 1
e 4 hanno effetto solo se la proprietà DrawWidth è impostata a 1.
Altrimenti, producono una linea continua, esattamente come quella determinata
dall’uso dell’impostazione predefinita, ovvero quella corrispondente alla
costante vbSolid, che ha valore nullo.
Si osservi il seguente
codice:
Printer.ScaleMode =
7
Printer.DrawWidth =
1
Printer.Drawstyle =
1
Printer.Line (2,3) –
(19,3)
Esso provoca il
tracciamento di una linea orizzontale tratteggiata a partire dalla posizione
posta a 2 centimetri dal bordo sinistro del foglio fino alla posizione distante
da esso 19 centimetri.
La stampa di
immagini
Tutte le stampanti
presenti sul mercato sono ormai in grado di stampare anche delle immagini,
spesso con una risoluzione grafica elevata. Naturalmente, Visual Basic mette a
disposizione dei programmatori uno strumento che permette di sfruttare questa
capacità. Si tratta del metodo PaintPicture, la cui sintassi è la
seguente:
Printer.PaintPicture
<immagine>,
<x1>,
<y1>,
[<larghezza>],
[<altezza>],
dove
<immagine> rappresenta la sorgente dei dati da visualizzare, ovvero
è la proprietà Picture di una PictureBox (oggetto atto a contenere
immagini). I valori x1 e y1 rappresentano invece le coordinate del
punto in cui deve essere posizionato l’angolo superiore sinistro
dell’immagine.
Ad esempio, si supponga
di disporre di una PictureBox, denominata pbImg, in cui è
contenuta un’immagine. Per riportare quest’ultima su carta a partire dal punto
di coordinate (2,3) è necessario scrivere la riga
Printer.PaintPicture
pbImg.Picture, 2, 3
L’immagine è così
disegnata in modo che la sua dimensione non sia alterata. Molto spesso,
tuttavia, si desidera effettuare un ridimensionamento in fase di stampa. Ciò ha
in genere il fine di sfruttare l’intera area disponibile sul foglio,
indipendentemente dalle dimensioni di quest’ultimo. Ad esempio, si potrebbe
voler stampare l’immagine in modo da occupare tutto il foglio mantenendo un
margine di 2 centimetri dai bordi. Ciò è possibile digitando il seguente
codice:
Printer.ScaleMode
= 7 Larghezza = Printer.ScaleWidth - 4 Altezza = Printer.ScaleHeight –
4 Printer.PaintPicture pbImg.Picture, 2, 2, Larghezza,
Altezza
Si noti che sono stati
utilizzati due ulteriori parametri che descrivono, rispettivamente, la larghezza
e l’altezza dell’immagine da stampare. Si osservi altresì che ad essi sono stati
assegnati dei valori pari alle dimensioni del foglio diminuite di 4 centimetri,
al fine di generare i margini.
La scelta fra la stampa a
colori o in bianco e nero
Le stampanti a colori,
dato il continuo ridursi dei prezzi, si stanno sempre più diffondendo. La stampa
a colori comporta tuttavia un costo per pagina più elevato rispetto a quella
monocromatica a toni di grigio. Per questo motivo, spesso si rivela utile fare
in modo che le applicazioni siano in grado di operare una scelta fra le due
modalità di stampa. Ciò è possibile per mezzo della proprietà ColorMode,
che può assumere i valori 1 (corrispondente alla costante predefinita
vbPRCMMonochrome) e 2. L’assegnamento di quest’ultimo valore, a
cui si può far riferimento anche attraverso la costante vbPRCMColor, fa
sì che la stampa avvenga a colori. In caso contrario è monocromatica. Tale
proprietà è ignorata nel caso in cui la stampante in uso sia in grado di
produrre solo dei tabulati monocromatici.
Conclusioni
La capacità di produrre
un risultato di qualità non solo sul video ma anche sulla stampante è sempre più
richiesta dall’utenza moderna, che sembra accettare sempre di meno i tabulati
grezzi e spesso poco leggibili che un tempo costituivano lo standard per le
applicazioni gestionali. Oggi le esigenze sono cambiate e con esse le
periferiche in grado di stampare le immagini. Oggi un programmatore deve essere
in grado di creare delle applicazioni che visualizzino nel migliore dei modi i
risultati delle proprie elaborazioni, ricorrendo a quanto di meglio la
tecnologia mette a disposizione. Un moderno strumento di sviluppo deve quindi
tenere conto di queste esigenze. Visual Basic fin dalla nascita si è affermato
come uno dei prodotti più innovativi. Anche per quanto riguarda la gestione
delle stampe è all’altezza della propria fama, rivelandosi in grado di
soddisfare le richieste dell’utenza più esigente, pur mantenendo la consueta
semplicità d’uso che rappresenta il suo fiore all’occhiello.
La visualizzazione di immagini grafiche (Parte 18)
Il Visual Basic è un ambiente di
sviluppo che offre almeno una soluzione ad ogni tipico problema della
programmazione quotidiana. Perciò se avete ricevuto una commessa per lo sviluppo
di un semplice programma orientato alla gestione di magazzino, potete accettare
perché siete in grado di interagire con una base dati e di eseguire delle stampe
di qualità. Quanto vi manca è forse quel "qual cosa in più" rispetto a ciò che i
vostri concorrenti sono disposti a promettere. Per esempio la visualizzazione
della foto che riguarda il generico articolo gestito dal vostro programma,
oppure la visualizzazione di un catalogo elettronico.Ciò che vi occorre è quindi
uno strumento adatto per visualizzare immagini. A tale scopo, e’ possibile
utilizzare:
- un Form
- un controllo chiamato Picture
- un secondo controllo (simile al
precedente) chiamato Image.
Vediamo ora come vare.
Immagini contenute in un
Form Per visualizzare
un’immagine in un Form create un nuovo progetto partendo da zero. Posizionatevi
quindi sulla finestra principale del progetto (normalmente chiamata
Form1), e cercate nella relativa palette delle proprietà la voce
Picture. Accanto ad essa trovate un pulsante: premetelo, e vi si aprirà
una finestra che vi permette di scegliere il file da visualizzare. Esso deve
ovviamente essere di tipo grafico, ossia deve avere un’estensione appartenente
al seguente elenco:
BMP, DIB, GIF, JPG, WMF, EMF, ICO,
CUR.
Fra gli esempi allegati a questo
articolo troverete anche alcune immagini in formato Bitmap, che potete usare per
eseguire i vostri esperimenti. Perciò selezionatene una a caso, e confermate la
finestra di scelta file su cui vi trovate. Potete inserire un’immagine anche
importandola dalla ClipBoard di Windows, a patto di avercela precedentemente
inserita con un editor di disegni. Se così non è allora lanciate, per esempio,
il programma Paint e scarabocchiate qualche cosa al suo interno. Selezionato
tale immagine ed inviatela in ClipBoard. Poi tornate in VB, e premete il tasto
destro del mouse sul Form. Si apre un menù di contesto: scegliete la voce Paste
(o Incolla) per copiare in automatico l’immagine dalla ClipBoard… al Form. La
cancellazione di un’immagine dal Form è un’operazione altrettanto semplice:
ritornate sulla palette delle proprietà del Form e riposizionatevi sulla voce
Picture. Premete poi il tasto "Canc" assicurandovi che la posizione del
cursore sia sul primo carattere della stringa che compare in
Picture. Bene, adesso che sapete come inserire e cancellare
un’immagine a design time (cioè in fase di creazione), preoccupiamoci di
come fare le stesse cose a run time (durante l’esecuzione del
programma).
Inserire immagini a Run
time Per visualizzare
un’immagine a run time, dobbiamo usare un funzione che fa parte della libreria
standard di Visual Basic. Il suo nome è piuttosto facile da ricordare, poiché si
chiama LoadPicture. Il suo uso è altrettanto immediato, perché basta
eseguire un codice come quello che segue.
Form1.Picture =
LoadPicture("ciliegia.bmp")
Per cancellare l’immagine dal Form è
sufficiente richiamare la LoadPicture con una stringa vuota:
Form1.Picture =
LoadPicture("")
Oppure eseguire un assegnamento a
Nothing:
Form1.Picture =
Nothing
Un’altra funzione di fondamentale
importanza è la SavePicture, tipicamente utile per salvare un’immagine su
disco. Il suo uso, almeno nella forma, è del tutto simile a quello approntato
per la LoadPicture.
Ma attenzione a non perdere i vostri
dati: la SavePicture è distruttiva nei confronti del file su cui la
farete operare, perciò per evitare spiacevoli sovrascritture è bene accertarsi
che l'utente sia d'accordo. Un esempio di codice utile per chiedere conferme è
il seguente:
Dim bSi As Boolean
bSi = True
If Dir("pippo.bmp") <> ""
Then
bSi = MsgBox(_
"Sei sicuro?", _
vbYesNo)
End If
If bSi = True Then
SavePicture _
Form1.Picture, _
"pippo.bmp"
End If
Ricordo che la funzione Dir
usata, serve proprio per verificare l'esistenza di un file su
disco.
Prima di richiamare la
SavePicture dovete inoltre essere certi che all’interno del Form vi sia
un’immagine da salvare, e questo lo ottenete con il seguente codice:
If Form1.Picture <> 0
Then
. . .
End If
Se non farete così, otterrete dal VB un
noioso e poco professionale messaggio di errore.
Per leggere un’immagine da ClipBoard,
dovete usare il metodo GetData dell’oggetto Clipboard usando il
seguente codice:
Form1.Picture =
Clipboard.GetData(vbCFBitmap)
Come avete visto la GetData è
molto semplice da usare, perché è sufficiente passare come suo parametro il
formato con il quale vogliamo importare l’immagine. I formati disponibili sono
pochi, ma sufficienti per coprire la maggior parte delle esigenze:
vbCFBitmap, vbCFMetafile e vbCFDIB.
Ovviamente potrebbe non esserci nulla
di interessante in ClipBoard, o per lo meno nulla che assomigli ad un formato
grafico (potrebbe ad esempio esserci del semplice testo proveniente da un
editor). Perciò prima di richiamare la GetData, potete usare il metodo
GetFormat per scoprire se ci sono immagini di pronto utilizzo. Per
esempio con il seguente costrutto If, è possibile verificare se in
ClipBoard esiste un immagine di tipo Bitmap:
If Clipboard. GetFormat( vbCFBitmap)
Then Form1.Picture = Clipboard.GetData( vbCFBitmap) End If
Per salvare un’immagine anche in
ClipBoard dovete usare il metodo SetData dell’oggetto Clipoard.
Ecco come fare:
Clipboard.SetData Form1.Picture,
vbCFBitmap
Tutto ciò che sino ad ora avete appreso
in via del tutto teorica, lo potete sperimentare dal vivo usando FORM.VBP
(reperibile sul sito ftp di Infomedia), che ho preparato utilizzando la versione
6 del Visual Basic.Ma adesso è tempo di occuparci di nuove cose, ossia del
controllo Image che ci permette di perimetrare un’immagine…
Il controllo Image A volte visualizzare un’immagine in un Form
è sconveniente, perché non si può controllare né la sua posizione video, né la
sua dimensione. Se queste esigenze sono (o saranno) anche le vostre, allora
potete usare il controllo Image, appositamente progettato per
visualizzare immagini.Image è presente nella palette dei controlli tramite
l’icona visibile in Figura 1.Il suo uso è piuttosto semplice:
selezionatelo dalla palette, ed inseritelo all’interno di un Form così come fate
di solito per ogni altro classico componente Visual Basic.Dopodiché, con esso
potrete fare le stesse cose previste per i Form, riciclando quindi lo
stesso codice VB. Ovviamente là dove compariva la scritta Form1, ora deve
apparire la scritta Image1. È la stessa cosa che ho fatto io per
l’esempio IMAGE.VBP (reperibile presso il sito ftp di Infomedia) che è
un’opportuna copia del precedente progetto FORM.VBP.La proprietà fondamentale di
Image si chiama Stretch. Se essa è impostata al valore True,
allora il controllo Image stirerà in lungo e in largo l’immagine, sino a quando
essa non coprirà l’intera superficie video perimetrata dal controllo stesso.
Perciò la prima immediata conseguenza visuale dell’operazione di Strech,
è la trasformazione dei cerchi in ellissi e dei quadrati in rettangoli. Se
invece tale proprietà è impostata al valore False, allora l’immagine si
estenderà oltre il perimetro del controllo Image, a partire però dal suo angolo
superiore sinistro.
Il controllo Picture Un’apparente ridondanza del controllo
Image è il controllo Picture. Sembrano simili, ma non lo sono
affatto.Picture è un controllo abbastanza duttile nelle sue funzionalità, perché
dotato di alcuni strumenti necessari per agire sull’immagine in esso
visualizzata. Attenzione però: il controllo Picture non è un elaboratore di
immagini come ad esempio lo è Paint Shop Pro, ma un semplice editor grafico non
interativo che permette di tracciare figure geometriche fondamentali. Quindi
segmenti, rettangoli ed archi di curva.La sua icona è visibile in Figura
2, mentre il suo utilizzo lo dettaglierò nel prossimo numero di questo corso
di programmazione Visual Basic.Dunque arrivederci al prossima puntata, e nel
frattempo (a mo’ di esercizio), provate ad usare da soli il controllo Picture
"aiutandovi" con il manuale in linea del VB. Potrete così confrontare i vostri
risultati, con le informazioni che leggerete sul prossimo numero di
DEV. |