Visual Basic
Home 
My Life 
Musica 
Corso di C 
DestAscripT 
Hacking 
Visual Basic 
Assembly 
Linux 
Unix 
CGI 
Links 
Contact me 

 

[My Life][Musica][Corso di C][DestAscripT][Hacking][Visual Basic][Assembly][Linux][Unix][CGI][Links][Contact me]

 

Corso di Visual Basic

 

 

(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.

(Parte 6)

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.

 

Le soluzioni degli esercizi della lezione precedente

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.

 

Perché servono i vettori

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.

 

I 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.

 

L'ordinamento per selezione diretta

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.

 

Due esercizi

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.

 

Rinfreschiamoci la memoria...

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.

 

Le soluzioni degli esercizi proposti nella precedente lezione

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.

 

Vettori con indice minimo diverso da 0 o 1

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.

 

I vettori a dimensione variabile

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.

 

La clausola Preserve

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.

 

Vettori a due indici

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.

I vettori di controlli

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.

 

Conclusioni

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

L’oggetto dell’ottava 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 un’applicazione.

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.  L’occasione  è  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,  l’applicazione  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 un’etichetta 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 all’elemento 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  all’avvio  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 all’algoritmo di ordinamento

per selezione diretta visto nella scorsa lezione, in cui l’ordinamento

è eseguito sull’array 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 l’incremento di un conta-

tore numerico di un’entità pari all’indicazione 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 dell’evento

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  un’applicazione  in  grado  di

calcolare la somma di due valori numerici inseriti dall’utente 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

L’acquisizione  dei  valori  avviene  per  mezzo  di  due  cicli.  Il  loro

scopo consiste nel richiedere la digitazione di una stringa all’utente

e nell’effettuare 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 all’utente. 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 un’istruzione in grado di

richiedere un dato e di verificarne l’appartenenza 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  all’interno  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  dell’alfabeto.  Si

noti  che  non  è  la  prima  volta  che  si  usa  la  parola  chiave  Sub

all’interno di questo corso. Infatti, le porzioni di codice da associa-

re  come  risposta  agli  eventi  sono  anch’esse  delle  procedure.

Quando  un  controllo  genera  un  evento,  l’interprete  provvede

automaticamente a verificare l’esistenza di una procedura avente

un  nome  composto  dall’identificatore  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

dall’utente 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

all’interno  di  un  form,  risultano  locali  a  quest’ultimo,  quindi

L’inter faccia utente

della  soluzione  del

secondo  esercizio

proposto  nella

scorsa  lezione

FIGURA 1

Mediante l’uso 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.

L’uso 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

all’interno  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  dell’applicazione  de-

scritta  nell’esempio  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 dell’uscita 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  all’interno  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 all’interno del form.

  Nel  secondo,  possono  essere  utilizzate  addirittura  da  tutte  le

routine contenute nell’applicazione. Con l’introduzione della pro-

cedura LeggiValore, il codice da associare all’evento 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 l’uso 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 un’altra 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  l’uso  delle  istruzioni  di

assegnamento.

I PARAMETRI

Il metodo più semplice ed efficace per comunicare delle informa-

zioni a una procedura consiste nell’uso 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

all’interno della routine equivale a una variabile. Da essa si differen-

zia per il fatto che la sua inizializzazione non avviene all’interno

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.

Un’ulteriore 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

L’applicazione 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

Nell’esempio  precedente,  la  variabile  n  è  stata  passata  alla

procedura  LeggiValore  non  per  fornire  ad  essa  un’informazio-

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 l’indirizzo 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  dall’interprete  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 l’informazione è 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, l’esecuzione 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.

[Home][My Life][Musica][Corso di C][DestAscripT][Hacking][Visual Basic][Assembly][Linux][Unix][CGI][Links][Contact me]

Copyright(c) 2001 My Company. All rights reserved.