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 cartere minuscolo o maiuscolo, si usava lo stragemma 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 carteri jolly (* e ?), richiede un uso articolo 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'operore Like: questo operore, 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 carteri jolly; ad es.:
"Abraini" like "Abr?i*" restituisce True
"Abraini" like "abr?i*" restituisce True
(se si è specifico Option Compare Text)
I carteri jolly possono comparire solo nella stringa di destra dell'operore like, ovvero nel "criterio" con cui si confronta la stringa:
sStringa Like sCriterio
Ovviamente carteri come asterisco e punto di domanda possono comparire anche nell'espressione a sinistra, ma non vengono interpreti come carteri jolly; per chi non lo sapesse, il preciso significo di questi particolari carteri è il seguente:
* zero o più carteri
("" Like "*" restituisce True, come anche "abc" Like "*c")
? un qualunque cartere
(uno e uno solo però: "abc" Like "?abc" restituisce False)
Vediamo qualche altro esempio:
ESPRESSIONE RISULTO
"abc" Like "*abc" True
"abc*" Like "*abc" False
(l'asterisco a sinistra di Like è un cartere come gli altri)
"abc*" Like "*abc?" True
"*" Like "?" True
"" Like "?" False
Esistono poi un altro cartere "jolly" utilizzabile con l'operore Like (l'asterisco e il punto di domanda hanno validità universale, questo no):
# una e una sola cifra da 0 a 9
Ad es. "abc0k" Like "*#?" restituisce True, come anche "abc0k" Like "*??" oppure anche "abc0k" Like "*": una cifra è pur sempre un cartere, pertanto è riconosciuto dai carteri jolly * e ?, ma all'occorrenza è possibile identificarlo come cifra usando #.
Infine, è possibile specificare un determino intervallo di carteri tra parentesi quadre; ad es., al posto di usare # per le cifre è possibile usare l'intervallo [0-9]:
"abc0k" Like "*[0-9]?" restituisce true
C'è tutta una sintassi particolare, e anche un po' complessa, per usare compiutamente le opportunità offerte dall'operore Like, ma anche un uso semplice con i tre carteri jolly più utilizzi (*, ?, #) è 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 sto molto più complesso.
Per mettere in prica le nozioni impare 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 carteri jolly. I nomi e le caption delle voci di menù sono nuralmente a discrezione del programmore; 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ù sofistica, 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 creo, cosicché il codice del menù "Cerca" sarà molto semplice: la voce mnuTrova dovrà soltanto richiamare la finestra frmTrova:
Prive Sub mnuTrova_Click()
frmTrova.Show vbModal
End Sub
La costante vbModal indica che il form frmTrova è modale (si dice anche "a scelta obbligoria") 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:
Prive 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 "Prive" 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 genere 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 privamente 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 alterniva, è possibile non modificare la dichiarazione dell'evento Click e impostare a True il valore del pulsante cmdTrova:
Prive 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:
Prive Sub cmdChiudi_Click()
Me.Hide
End Sub
Il form viene solo nascosto, e non completamente scarico, perché è sempre possibile continuare la ricerca della stringa specifica usando il menù mnuTrovaAncora, che deve poter accedere alla proprietà Text di txtTrova: se il form venisse scarico, il contenuto di txtTrova andrebbe perso. In alterniva, 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 disabilito, perché il txtTrova è vuoto: se fosse abilito 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 cartere, sfruttando l'evento Change:
Prive 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 è complico tornare a un confronto case sensitive con l'operore Like se l'impostazione di default è Option Compare Text.
Veniamo ora alla routine cmdTrova_Click: volendo scrivere una routine abilita alla ricerca con carteri jolly (per semplicità gli stessi usi dall'operore like: *, ?, #), è opportuno procedere in questo modo: da l'impossibilità di cercare direttamente una stringa che contenga carteri jolly, occorre suddividere il testo cerco in più parti, isolando il testo "puro" dai carteri jolly. Ad es., se l'utente vuole cercare "abc*", non bisognerà trovare estamente "abc*", bensì "abc": qualunque stringa cominci con "abc" soddisferà i requisiti indici 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 carteri circostanti corrispondano al criterio indico. È 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 trtare:
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 cartere non-asterisco, poi si cerca l'ultimo cartere 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 carteri 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 cartere non-jolly, poi si va avanti fino a trovare un altro cartere jolly o ad arrivare alla fine della stringa; infine si estrae il testo trovo.
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 trovi gli estremi ci basterà confrontare il testo compreso tra questi estremi con il criterio indico dall'utente: l'operore 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" ricerca 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 specifica dall'utente: la stringa da cercare infti conterrebbe solo carteri jolly; in tali condizioni bisogna distinguere il caso in cui occorre cercare cifre dagli altri casi. Infti, 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.