Le
opzioni dell'istruzione Option Compare, viste nella lezione
precedente, non influenzano solo il comportamento delle funzioni
InStr e InStrRev, ma di qualunque confronto tra stringhe;
solitamente, quando si vogliono confrontare due stringhe a
prescindere dal carattere minuscolo o maiuscolo, si usava
lo stratagemma di convertirle entrambe nella stessa forma
(minuscola o maiuscola) usando le funzioni Lcase$()
e Ucase$() (rispettivamente Lower e Upper Case); in
questo modo l'utente avrebbe potuto evitare di scrivere correttamente
la stringa da cercare, ad es. un cognome in un elenco telefonico:
Do
Until
Ucase$(sCognome) = Ucase$(sElenco(lCount))
LCount = lCount+1
Loop |
Fare
ricerche più complesse, ad es. utilizzando i caratteri
jolly (* e ?), richiede un uso articolato delle funzioni viste
fin qui, ovvero Left$, Right$, Mid$; ad es., per cercare un
cognome del tipo Abr?i* si potrebbe implementare un ciclo
di questo tipo:
Do
Until
(Left$(sCognome, 3) = Left$(sElenco(lCount),3) And
Mid$(sCognome, 5,1) = Mid$(sElenco(lCount),5,1))
lCount = lCount+1
Loop |
Esiste
però un metodo più semplice, che consiste nell'uso
dell'operatore Like: questo operatore, in base all'impostazione
definita con l'istruzione Option Compare (l'impostazione
predefinita è sempre Binary), restituisce un valore
booleano che indica la corrispondenza tra due stringhe, permettendo
anche l'uso dei caratteri jolly; ad es.:
"Abraini"
Like "Abr?i*" restituisce
True
"Abraini" Like
"abr?i*" restituisce True (se si è specificato
Option Compare Text) |
I
caratteri jolly possono comparire solo nella stringa di destra
dell'operatore like, ovvero nel "criterio" con cui
si confronta la stringa:
Ovviamente
caratteri come asterisco e punto di domanda possono comparire
anche nell'espressione a sinistra, ma non vengono interpretati
come caratteri jolly; per chi non lo sapesse, il preciso significato
di questi particolari caratteri è il seguente:
Carattere
|
Significato
|
* |
zero
o più caratteri ("" Like "*"
restituisce True, come anche "abc" Like
"*c") |
? |
un
qualunque carattere (uno e uno solo però:
"abc" Like "?abc" restituisce False) |
Vediamo
qualche altro esempio:
Espressione
|
Risultato
|
"abc"
Like "*abc" |
True |
"abc*"
Like "*abc" |
False
(l'asterisco a sinistra di Like è un carattere
come gli altri) |
"abc*"
Like "*abc?" |
True |
"*"
Like "?" |
True |
""
Like "?" |
False |
Esistono
poi un altro carattere "jolly" utilizzabile con l'operatore
Like (l'asterisco e il punto di domanda hanno validità
universale, questo no):
Carattere
|
Significato
|
# |
una
e una sola cifra da 0 a 9 |
Ad
esempio "abc0k" Like "*#?" restituisce True,
come anche "abc0k" Like "*??" oppure anche
"abc0k" Like "*": una cifra è pur
sempre un carattere, pertanto è riconosciuto dai caratteri
jolly * e ?, ma all'occorrenza è possibile identificarlo
come cifra usando #.
Infine, è possibile specificare un determinato intervallo
di caratteri tra parentesi quadre; ad es., al posto di usare
# per le cifre è possibile usare l'intervallo [0-9]:
Pertanto, le funzioni Left() e Left$() svolgono esattamente
lo stesso compito, differenziandosi unicamente per il tipo di
dati restituito e per il tipo di argomenti: Variant la prima,
String la seconda.
Ciò implica che la funzione Left$() sia più efficiente
della corrispondente Left() perché evita l'implicita
conversione delle variabili coinvolte da Variant a String: questo
è il motivo per cui solitamente si consiglia di usare
la versione "specializzata" della funzione al posto
della versione più generica.
La funzione Left$(), dunque, restituisce i primi n caratteri
di una determinata stringa, che viene passata alla funzione
come argomento insieme al numero n di caratteri:
restituisce
true.
C'è tutta una sintassi particolare, e anche un po' complessa,
per usare compiutamente le opportunità offerte dall'operatore
Like, ma anche un uso semplice con i tre caratteri jolly più
utilizzati (*, ?, #) è già molto potente. Ad es.,
per verificare che un codice alfanumerico corrisponda (almeno
nella forma) a un codice fiscale, basta scrivere:
sCodice
Like "[A-Z][A-Z][A-Z][A-Z][A-Z][A-Z]##[A-Z]##[A-Z]###[A-Z]"
|
Effettuare la stessa verifica usando Left(), Mid(), e If sarebbe
stato molto più complesso.
Per
mettere in pratica le nozioni imparate in queste due lezioni,
riprendiamo in mano il progetto sul nostro blocco note interrotto
nell'undicesima lezione, e aggiungiamo un menù "Cerca"
del tutto simile a quello del blocco note di windows, anzi
con qualcosa in più: la ricerca con caratteri jolly.
I nomi e le caption delle voci di menù sono naturalmente
a discrezione del programmatore; io seguirò questo
standard:
Caption
|
Name
|
Cerca |
mnuCerca |
Trova... |
mnuTrova |
Trova
successivo |
mnuTrovaAncora |
mnuCerca
è quello che compare sulla barra dei menù, gli
altri sono sottomenù.
Per permettere all'utente di indicare la parola da cercare
basterebbe un banale inputbox, ma vale la pena costruire una
finestra un po' più sofisticata, con un textbox (txtTrova)
per scrivere la stringa da cercare, un pulsante per avviare
la ricerca (cmdTrova), uno per chiudere la finestra (cmdChiudi),
tre optionbutton (optSu, optGiu, optTutto) per scegliere la
direzione di ricerca, un checkbox per abilitare la ricerca
case sensitive (chkCaseSens). Il form si può chiamare
frmTrova.
Il codice di ricerca della stringa sarà tutto racchiuso
nel nuovo form appena creato, cosicché il codice del
menù "Cerca" sarà molto semplice:
la voce mnuTrova dovrà soltanto richiamare la finestra
frmTrova:
Private
Sub
mnuTrova_Click()
frmTrova.Show vbModal
End Sub |
La
costante vbModal indica che il form frmTrova è modale
(si dice anche "a scelta obbligatoria") rispetto al
form che lo richiama (frmNotePad), cioè non è
possibile tornare al form originale prima di aver compiuto qualche
scelta (anche la sola chiusura) con la finestra in primo piano.
La voce mnuTrovaAncora dovrà invece richiamare la routine
del pulsante cmdTrova, che scriveremo in seguito:
Private
Sub
mnuTrovaAncora_Click()
frmTrova.cmdTrova_Click
End Sub
|
Per
fare ciò però è necessario che la routine
cmdTrova_Click sia pubblica e quindi visibile anche da frmNotePad:
perciò occorre sostituire "Private" con "Public"
nella dichiarazione dell'evento Click:
Public
Sub
cmdTrova_Click()
'...
'...
End Sub
|
Non
è questa un'operazione molto raccomandabile, perché
va a modificare delle dichiarazioni generate direttamente dall'IDE
di Visual Basic; in realtà non ha molto senso che la
routine di un evento sia "pubblica", perché
la generazione dell'evento avviene privatamente rispetto al
form che contiene il controllo a cui l'evento si riferisce.
Richiamare la routine non corrisponde propriamente alla generazione
dell'evento, anche se in buona sostanza le due cose sono uguali.
In alternativa, è possibile non modificare la dichiarazione
dell'evento Click e impostare a True il valore del pulsante
cmdTrova:
Private
Sub
mnuTrovaAncora_Click()
frmTrova.cmdTrova.Value = True
End Sub |
Possiamo ora dedicarci al codice del form frmTrova, cominciando
dalla cosa più semplice: la chiusura del form:
Private
Sub
cmdChiudi_Click()
Me.Hide
End Sub |
Il
form viene solo nascosto, e non completamente scaricato, perché
è sempre possibile continuare la ricerca della stringa
specificata usando il menù mnuTrovaAncora, che deve
poter accedere alla proprietà Text di txtTrova: se
il form venisse scaricato, il contenuto di txtTrova andrebbe
perso. In alternativa, si può memorizzare il contenuto
del textbox in una variabile pubblica di frmNotePad, il che
permetterebbe comodamente di scaricare frmTrova.
All'apertura del form, sarebbe bene che il pulsante cmdTrova
sia disabilitato, perché il txtTrova è vuoto:
se fosse abilitato e l'utente lo premesse subito, bisognerebbe
cercare una stringa nulla, o visualizzare un messaggio di
errore che avverta di indicare la stringa da cercare; è
possibile evitare tutto ciò semplicemente disabilitando
per default (ovvero in fase di progettazione) il pulsante,
e abilitarlo quando il txtTrova contiene qualche carattere,
sfruttando l'evento Change:
Private
Sub
txtTrova_Change()
If Len(txtTrova.Text) Then
cmdTrova.Enabled = True
Else
cmdTrova.Enabled = False
End If
End Sub |
Prima
di scrivere la routine di ricerca, è opportuno specificare
l'istruzione Option Compare Binary nella sezione delle dichiarazioni
del form: questo perché è semplice impedire un
confronto case sensitive usando le funzioni Lcase o Ucase, ma
è complicato tornare a un confronto case sensitive con
l'operatore Like se l'impostazione di default è Option
Compare Text.
Veniamo ora alla routine cmdTrova_Click: volendo scrivere una
routine abilitata alla ricerca con caratteri jolly (per semplicità
gli stessi usati dall'operatore like: *, ?, #), è opportuno
procedere in questo modo: data l'impossibilità di cercare
direttamente una stringa che contenga caratteri jolly, occorre
suddividere il testo cercato in più parti, isolando il
testo "puro" dai caratteri jolly. Ad es., se l'utente
vuole cercare "abc*", non bisognerà trovare
esattamente "abc*", bensì "abc":
qualunque stringa cominci con "abc" soddisferà
i requisiti indicati dall'utente.
Più complesso è il caso di una stringa del tipo
"##a*123??": in questo caso bisognerà cercare
la lettera "a", oppure i numeri "123", e
successivamente verificare che i caratteri circostanti corrispondano
al criterio indicato. È ovvio che specificare soltanto
"*" o "?" come testo da cercare non ha molto
senso
Come prima cosa, occorre eliminare gli asterischi iniziali e
finali: cercare "*pippo*" è del tutto equivalente
a cercare "pippo", ma il secondo caso è per
noi molto più facile da trattare:
Public
Sub
cmdTrova_Click()
Dim sTestoPuro(1)
As String
Dim lCount As
Long
Dim lInizio As
Long
Dim lFine As
Long
Do
lInizio = lInizio + 1
Loop While Mid$(txtTrova.Text,
lInizio, 1) = "*"
lFine
= Len(txtTrova.Text)
Do While Mid$(txtTrova.Text,
lFine, 1) = "*"
lFine = lFine - 1
Loop
txtTrova.Text = Mid$(txtTrova.Text, lInizio, lFine -
lInizio + 1)
End Sub
|
Prima
si cerca il primo carattere non-asterisco, poi si cerca l'ultimo
carattere non-asterisco e infine si estrae il testo compreso
tra i primi e gli ultimi asterischi (asterischi esclusi): questo
sarà il vero testo da cercare.
A questo punto occorre isolare il testo "puro" dai
caratteri jolly:
Public
Sub
cmdTrova_Click()
Dim sTestoPuro(1) As
String
Dim lCount As
Long
Dim lInizio As
Long
Dim lFine As
Long
Do
lInizio = lInizio + 1
Loop While Mid$(txtTrova.Text,
lInizio, 1) = "*"
lFine
= Len(txtTrova.Text)
Do While Mid$(txtTrova.Text,
lFine, 1) = "*"
lFine = lFine - 1
Loop
txtTrova.Text = Mid$(txtTrova.Text, lInizio, lFine -
lInizio + 1)
'primo
testo "puro"
lInizio = 0
Do
lInizio = lInizio + 1
Loop While Mid$(txtTrova.Text,
lInizio, 1) Like "[?#]"
lFine = lInizio
Do
lFine = lFine + 1
Loop Until Mid$(txtTrova.Text,
lFine, 1) Like "[*?#]"
Or lFine >= Len(txtTrova.Text)
sTestoPuro(0) = Mid$(txtTrova.Text, lInizio, lFine -
lInizio)
'ultimo
testo "puro"
lInizio = lFine
Do
lInizio = lInizio + 1
Loop While Mid$(txtTrova.Text,
lInizio, 1) Like "[*?#]"
lFine = lInizio
Do
lFine = lFine + 1
Loop Until Mid$(txtTrova.Text,
lFine, 1) Like "[*?#]" Or
lFine >= Len(txtTrova.Text)
sTestoPuro(1) = Mid$(txtTrova.Text, lInizio, lFine -
lInizio - 1)
End Sub
|
Per
cercare una sequenza di testo "puro", si cerca il
primo carattere non-jolly, poi si va avanti fino a trovare un
altro carattere jolly o ad arrivare alla fine della stringa;
infine si estrae il testo trovato.
Le sequenze di testo "puro" da cercare sono due: la
prima e l'ultima.
Quello che sta in mezzo in fondo non ci interessa, perché
una volta trovati gli estremi ci basterà confrontare
il testo compreso tra questi estremi con il criterio indicato
dall'utente: l'operatore Like ci renderà molto semplice
questo confronto.
Se ad es. l'utente cerca "pippo*abc*def*carlotta",
a noi basta cercare "pippo" e "carlotta",
dopodiché confronteremo l'intero testo compreso tra "pippo"
e "carlotta" con la stringa "pippo*abc*def*carlotta"
ricercata dall'utente. Se la sequenza di testo "puro"
è solo una, la ricerca potrebbe complicarsi un po' nel
caso in cui siano presenti degli asterischi (ad es. "#pippo*?#").
Complicazioni possono sorgere anche nel caso in cui non esiste
alcun testo "puro" nella stringa specificata dall'utente:
la stringa da cercare infatti conterrebbe solo caratteri jolly;
in tali condizioni bisogna distinguere il caso in cui occorre
cercare cifre dagli altri casi. Infatti, se può avere
un senso cercare ad es. "#*#", non ha molto senso
cercare "*?*", che si ridurrebbe banalmente a "*",
ovvero tutto il testo del file e qualunque suo sottoinsieme!
Nella prossima lezione vedremo meglio queste eccezioni.
|