Validazione centralizzata degli inputs. (Riccardo Bartolini)
Listati
Download Esempio



Chi si accinge alla stesura di programmi in Visual Basic, specie di tipo gestionale, si accorge ben presto che il semplice gesto di trascinare una TextBox sopra un Form, non e` seguito da un'altrettanta facilita` di validazione del testo inserito. Tutto e` semplice finche` si usano CheckBox, OptionButton ecc., ma quando siamo in presenza di campi a testo libero per acquisire nomi, indirizzi e cosi` via, le cose sono piu` complicate. Vediamo alcune possibili soluzioni.

La prima idea che viene in mente a chi affronta il problema per la prima volta, e` quella di porre il codice di validazione nell'evento LostFocus, cosi` da controllare se il testo o il valore inserito risponde ai criteri voluti nel momento in cui l'utente abbandona la TextBox (o altro Control similare).
Qualora l'utente digiti qualcosa che non va bene, riportiamo il cursore sul testo errato tramite Text1.Setfocus.
Chi tenta questa strada pero`, s'imbattera` presto in un serio problema: supponiamo di avere una Text1 ed una Text2. Nella Text1 e` stato inserito un valore errato. In conseguenza dell'evento LostFocus di Text1, il codice di validazione riscontra il problema e cerca di riportare il fuoco su Text1.
A questo punto, pero`, per tornare alla Text1 necessita soddisfare la regola imposta nel LostFocus di Text2 e magari proprio questa TextBox non accetta valori Null !!! Succede cosi` che prima di tornare alla Text1 dove abbiamo digitato un testo o un valore non corretto, dobbiamo per forza inserire qualcosa di valido nella Text2; solo allora saremmo proiettati nuovamente nella Text1 dal nostro precedente SetFocus. Immaginate cosa succederebbe se di TextBox ce ne fossero una decina e nessuno volesse valori Null, a zero o comunque non quelli di default! Il fuoco diventerebbe una patata bollente che nessuno vuole e si entrerebbe in loop con il cursore che corre veloce da una TextBox all'altra! Qualcuno potrebbe anche pensare di ovviare al problema usando DoEvents qua e la` ma il risultato varierebbe di poco, forse invece di avere un loop infinito se ne avrebbe uno che termina con ôOut of stack spaceö! Vediamo allora cos'altro si potrebbe inventare.
Altre soluzioni potrebbero essere l'uso dell'evento Keypress o KeyDown per riconoscere un tasto particolare, ad esempio il mitico Enter e solo allora controllare il testo inserito, ma che succede se uno si sposta col mouse? E cosa succede se viene usato il taglia/incolla?
No, proviamo un'altra strada. Si potrebbe usare l'evento Change e controllare la stringa man mano che si forma, ovvero mentre l'utente digita il testo.
Questa potrebbe essere una valida soluzione, pero` e` applicabile solo in particolari casi. Uno di questi potrebbe essere la ricerca di un nome in una piccola rubrica, con l'artistico effetto di visione immediata di tutti i nomi che corrispondono al testo digitato fino a quel momento.
In un sistema di rete e/o su un archivio di grandi dimensioni e` pero` una cosa impensabile.
Figuriamoci che i professionisti consigliano di usare meno cursori
di database possibili, figuriamoci a proporre una cosa del genere!
Che dire poi se il controllo dovesse essere fatto solo quando l'utente ha terminato di digitare e non prima, per evitare di fare controlli inutili o addirittura visualizzare errori che non esistono?
Come si capisce che l'utente ha terminato l'input?
Se si usa il LostFocus siamo daccapo; se si usa il KeyPress siamo al punto precedente.
Un'altra soluzione potrebbe essere validare tutti i campi solo alla fine della compilazione del form, quando l'utente preme un pulsante di OK.
Questo e` un sistema consigliato anche nel Mastering Visual Basic 5 for experienced developers della Microsoft ed usato anche dalla casa medesima in molti suoi prodotti.
Ma anche qui si pone un dilemma: mettiamo di avere un Form con il quale si acquisiscono i dati di un'anagrafica clienti e, com'e` d'obbligo, vogliamo evitare all'utente di dover digitare CAP, Citta`, numero di telefono ecc. qualora da nome e cognome si stabilisca tramite una query su database che il cliente e` gia` esistente. Come impostiamo il form? Mettiamo un pulsante da premere dopo l'inserimento di nome e cognome e se non risulta nessun cliente lasciamo che l'utente continui l'inserimento?

Non ci piace! Inoltre al termine serve comunque un altro pulsante per fare la verifica di tutti i dati inseriti e resta quindi un secondo problema: il nervosismo degli utenti che si trovano a premere OK convinti di aver finito di compilare la lunga lista di input e si ritrovano invece con una sfilza di MessageBox che informano di questo e quel campo da dover ridigitare.

E` meglio evitare. Si potrebbe anche controllare a tempo, tramite un Timer, tutti i campi del Form cosi` da accorgersi prima della pressione del pulsante OK che qualche campo contiene valori non validi e porvi di nuovo il fuoco. Se il Timer fa un controllo ogni 300mS si ha poco consumo di tempo macchina e per l'utente il controllo sembra immediato, senza che abbia il tempo di digitare qualcosa in una nuova TextBox prima della validazione della precedente. Non e` male. Pero` questo sistema asincrono puo` complicare un po' la vita. Se facciamo un controllo cosi` nudo e crudo possiamo essere avvertiti di errori inesistenti, solo perche` non abbiamo ancora finito di completare l'input. Ad esempio, se usiamo una TextBox per accettare codici di articoli che non devono essere puramente numerici, potremmo essere avvisati di errore quando scriviamo 123, perche` il meccanismo di validazione non puo` sapere che stavamo per scrivere 123ab. Dobbiamo quindi inserire un filtro per effettuare il controllo solo sui campi senza fuoco. Non e` un problema grazie alla collection Controls. Resta pero` ancora qualche problemino. Se riprendiamo l'esempio precedente dove tramite nome e cognome vogliamo controllare in un database l'esistenza di un cliente e compilare il resto delle TextBox in automatico, vediamo che mettendo nell'evento LostFocus del cognome la query al database, rischiamo di effettuarla anche con valori errati perche` quando il timer si accorge del problema, per veloce che sia, e` ormai gia` avvenuto il LostFocus e quindi la query. Mmmh non siamo certo sulla via della semplicita`. L'unica cosa positiva di questo sistema potrebbe essere la centralizzazione di tutto il codice di validazione dentro il timer o in una Sub.

Se invece lasciassimo andare il fuoco dove vuole e avvertissimo solamente l'utente di quale campo non e` valido? Si potrebbe far cambiare il colore della TextBox incriminata o robe simili e lasciare che sia l'utente a tornare sul luogo del misfatto. Se non lo fa non si attiva il pulsante OK. Non mi sembra male. Teniamola in considerazione.

Un altro sistema potrebbe essere creare degli ActiveX su misura. Non e` una cosa tanto banale, ma si puo` fare. In questo caso pero` e` bene mettere in preventivo un bel consumo di risorse di sistema. Se ne puo` riparlare.

A questo punto viene spontanea una domanda: ma la Microsoft come fa? Pensavo di carpire qualche idea analizzando il comportamento di alcuni suoi programmi famosi, ma mi sono reso conto che anche mamma Microsoft non e` poi cosi` brillante sotto questo aspetto. Provate a dare un'occhiata al funzionamento della rubrica di Exchange o di Outlook: vi siete mai accorti che non c'e` nessun controllo e possono essere inserite anche lettere nel campo telefono o numeri come nome e cognome? Anche in programmi piu` sostanziosi come Access non e` meglio: i controlli vengono effettuati tutti assieme al momento del salvataggio del record (eccetto alcuni casi) e non e` raro ritrovarsi con una sfilza di messaggi di errore uno dietro l'altro, spesso senza neanche l'indicazione di quale campo genera il problema.
Anche nella Mastering Microsoft Series gli unici consigli proposti sono:
(1) restringere il numero di opzioni
(2) usare il piu` possibile Controls che offrano solo scelte obbligate
(3) usare MaxLenght per limitare la lunghezza del testo inserito
(4) usare KeyPress, KeyDown e KeyUp per avvertire immediatamente
se si digita un numero in un campo solo testo o viceversa, oppure per convertire da maiuscolo a minuscolo ecc.
(5) validare tutti i campi tramite un tasto OK che si attiva solo quando tutti i campi obbligatori sono pieni.
Ringrazio di tutti questi ottimi consigli offerti, ma restiamo sempre al punto di partenza. Se certi metodi di programmazione sono comodi a chi sviluppa software, certo non portano vantaggi a chi dovra` usarlo come utente finale. Vediamo quindi un metodo per fare contente entrambi le parti e casomai integrare a questo sistema di validazione anche i consigli Microsoft.

Controllo centralizzato
Il metodo che sto per illustrare, non e` niente di strabiliante, forse e` l'uovo di Colombo, ma e` abbastanza comodo da realizzare e mantenere dato che il codice di validazione dei campi e` raggruppato tutto insieme e non c'e` bisogno di scorrere il programma su e giu` alla ricerca dei nostri controlli di validazione. Consuma pochissimo tempo macchina e risorse. Tengo a precisare che e` solo un esempio di una tecnica e prescinde dall'uso specifico che se ne vuole fare: chi usa il Jet Database Engine puo` voler usare il piu` possibile le validation rules imposte ai fields ed evitare l'imposizione di regole nell'interfaccia utente. Cio` non toglie che, nei casi in cui non e` possibile applicare solo tale tecnica (e sono moltissimi), non possa essere usato il controllo centralizzato in esame. Magari qualcuno potrebbe fare anche un ibrido per un doppio controllo. Morale: fateci quello che vi pare! Ma veniamo al sodo; il concetto di funzionamento e` questo: ogni campo di acquisizione, che nel programma chiamo UIF (User Input Field), quando acquista il fuoco, quindi nell'evento GotFocus, setta una variabile pubblica di nome UIFActive di tipo Control come ActiveControl (set UIFActive=Me.ActiveControl). Questa variabile viene usata in un Select Case nella sub UIFValidation per sapere quale Control validare. Non puo` infatti essere usata direttamente la proprieta` ActiveControl perche` prima di arrivare al Select Case il Control attivo e` gia` un altro, ovvero quello che ha causato il LostFocus del campo da validare. Al momento del LostFocus si richiama la Sub UIFValidation la quale impone validazioni diverse per ogni Control. Il listato 1 mostra l'implementazione base del concetto appena illustrato applicando la validazione a due TextBox.

Passiamo alla pratica
Il programma allegato UIFValidation.vbp e` composto da cinque TextBox a cui vengono applicate validazioni diverse, come riportato sopra il campo stesso. Avviando il programma, Text1 prende il fuoco e si ha l'evento GotFocus che setta UIFActive per contenere Text1 come oggetto. Inseriamo in Text1 un valore non corretto e proviamo a spostarci su un altro campo con il mouse o con il tasto Tab. Qui si verifica una cosa che inizialmente lascia un po' perplessi. Abbiamo dapprima l'evento LostFocus di Text1 che richiama UIFValidation. Entrati in questa Sub, L'activeControl e` gia` Text2, nonostante debba ancora verificarsi il suo evento GotFocus. La Sub, tramite UIFActive puo` comunque effettuare la validazione sul Control corretto e si accorge che in Text1 sono stati inseriti valori errati. Impone quindi Text1.SetFocus e ci avviamo ad uscire dalla Sub. Il bello pero` arriva adesso: ad Exit Sub ci aspetta come un'avvoltoio il GotFocus della Text2 che carica in UIFActive l'oggetto TextBox che ha il fuoco e indovinate chi e`? Ci si aspetterebbe fosse Text2 dato che siamo nel suo GotFocus. Se fosse cosi` pero`, sarebbe la fine perche` al momento in cui LostFocus richiama inevitabilmente UIFValidation per controllare Text2 saremmo nella condizione identica all'uso diretto della validazione nel LostFocus di cui abbiamo parlato ad inizio articolo. Invece nonostante sia in corso di esecuzione l'evento GotFocus di Text2, l'ActiveControl e` gia` su Text1! Questo avviene grazie ad UIFActive.SetFocus eseguito dalla Sub UIFValidation invocata dal LostFocus di Text1. Ma non e` tutto; se nella UIFValidation inseriamo delle MessageBox per notificare all'utente la ragione della non validazione, non si ha per niente l'esecuzione del codice nell'evento GotFocus della Text2, quindi UIFActive resta Text1. Per di piu`, nonostante non si verifichi il GotFocus di Text2, abbiamo invece il suo LostFocus e questo richiama nuovamente UIFValidation. Grazie alla linea If Me.ActiveControl Is UIFActive Then Exit Sub si impedisce la visualizzazione della stessa MessageBox di errore sul campo Text1 due volte consecutive. Provate a toglierla per constatare di persona il problema. La sub UIFValidation include anche un piccolo controllo sulle validazione richieste dai campi, ma non implementate per dimenticanza. Il nome del Form e del campo incriminato vengono in questo caso mostrate all'utente. E` tutto. Non e` semplice come sistema? Forse e` piu` complicata la spiegazione di quanto non lo sia nella realta`! Analizzando l'esempio dovrebbe risultare tutto chiaro. Provate a mettere dei Debug.Print in ogni GotFocus e LostFocus delle TextBox o usare dei Watch in modo da vedere in diretta il succedersi degli eventi e verificare quanto sopra.

Forse ci siamo
Abbiamo cosi` realizzato un sistema per avere inputs secondo un certo criterio semplice e funzionale. A questo punto possiamo includere anche altri controlli come quelle indicati dalla Microsoft, ad esempio nell'evento Keypress. Se pensate che potrebbe valere la pena disabilitare per esempio la digitazione dei numeri in un campo Cognome invece di attendere che l'utente termini la compilazione per mostrare il messaggio di errore, basta un istruzione:

If IsNumeric(Chr$(KeyAscii)) then KeyAscii = 0.

Ricordate solo di mettere questi controlli come optional e di includere comunque sempre la verifica in UIFValidation altrimenti avreste sicuramente problemi quando un testo viene introdotto col taglia/incolla. Questi mini controlli portano a decentralizzare un po' la validazione, ma e` pur sempre tutto abbastanza compatto e quel che conta di piu`, ottimo per l'utente. E` altresi` valida l'idea di disabilitare il pulsante OK finche` non sono stati compilati i campi obbligatori, anzi lo ritengo proprio indispensabile per l'effettuazione del prossimo passo: la gestione dei campi obbligatori.

Gestione dei campi obbligatori
Come si nota dal programma di esempio, possiamo imporre che certi campi non accettino valori Null, quindi il problema di rendere obbligatori certi campi sembrerebbe risolto. E` bene pero` riservare tale caratteristica per casi particolari. I campi che non accettano Null possono infatti essere un po' noiosi perche` una volta che hanno il fuoco non e` piu` possibile andarsene senza inserire qualcosa di valido (vedi Text2 e Text4 nel file allegato). Cosi`, se mi posiziono sopra un ipotetico campo Partita I.V.A., poi mi accorgo che non la conosco e per adesso voglio inserire solo nome e cognome non posso piu` spostarmi prima di aver inserito una partita I.V.A. valida. Conviene quindi adottare una gestione dei campi obbligatori vera e propria. Per realizzarla possiamo scegliere almeno tre strade:

1) Lasciare abilitato il pulsante OK ed inserire del codice
nell'evento Click che controlli se qualche campo obbligatorio
e` rimasto vuoto. Quando l'utente preme OK riceve un messaggio
e viene spostato sul campo da compilare.
Premendo di nuovo OK, se ve ne sono altri succede la stessa
cosa fino al completamento.

2) Porre il codice di test dei campi obbligatori dentro
la UIFValidation e farlo eseguire ad ogni sua invocazione.

3) Creare una apposita Sub da richiamare dall'evento
Change di ogni TextBox.

La prima soluzione non mi aggrada molto, e` ancora troppo irritante per l'utente, meno funzionale e piu` difficile da implementare delle altre due. La seconda strada e` ottima. Ha un consumo di tempo di CPU ridotto all'osso: solo quando si conferma l'inserimento spostandoci su un altro campo si ha la validazione del campo attuale piu` il controllo dei campi obbligatori. Unico neo risiede nell'abilitazione del pulsante OK:

supponendo di aver appena terminato di riempire l'ultima delle TextBox obbligatorie, il suddetto pulsante non si attivera` finche` non ci spostiamo su un altro Control a scelta, perche` solo allora si verifica il LostFocus e l'invocazione di UIFValidation. Non e` un grave problema, tuttavia se puo` sembrare brutto c'e` sempre il terzo metodo, che guarda caso e` quello che ho usato nel file di esempio UIFVAlidation.vbp e riportato nel Listato 2.

Il consumo di tempo di CPU e` maggiore, ma non di molto. A differenza della seconda soluzione vengono controllati al volo tutti i campi che non devono restare vuoti, ogni volta che si digita un carattere. Dando un'occhiata alla Sub di nome UIFEmpty si puo` notare comunque come sia scheletrica. Pensando a quali mattoni di programmi girano sulle povere macchine d'oggi si puo` certo chiudere un occhio! Il funzionamento e` semplicissimo: alla pressione di un tasto o comunque quando si verifica l'evento Change di una TextBox, viene richiamata UIFEmpty. Dentro la sub vengono controllati uno dopo l'altro tutti i campi obbligatori. Appena ne viene incontrato uno vuoto la sub termina. Se invece vengono verificati positivamente tutti i campi desiderati, si raggiunge l'ultima linea della sub che abilita il pulsante OK. Piu` semplice di cosi` non saprei cosa inventare. Dobbiamo solo ricordare di richiamare UIFEmpty dall'evento Change di ogni campo che definiamo come obbligatorio. Abbiamo cosi` risposto alla richiesta degli utenti, abbiamo una gestione semplice delle validazioni ed abbiamo fatto contento il processore. Questo sdoppiamento fra validazione di input e campo obbligatorio dona piena liberta` di movimento in fase di inserimento dati.

Abbiamo ora dei campi di acquisizione che accettano valori Null o meno (ricordarsi di usare Trim$ se si desidera considerare Null anche un campo riempito a soli spazi), gestione dei campi obbligatori, notifica di errore immediato e al contrario di altri metodi, funziona anche il taglia/incolla e spostamenti con tastiera o mouse. A questo punto lascio a voi integrare altri controlli di validita` piu` o meno interattivi ed abbellire il tutto. Tanto per imboccare qualcosa, si potrebbe iniziare indicando con un asterisco o con un diverso colore i campi obbligatori. Una volta imparato il meccanismo l'utente sara` spinto automaticamente a controllare i campi contrassegnati senza che neanche gli sfiori il pensiero di provare a premere OK in anticipo. Si potrebbe anche selezionare in automatico tutto il testo della TextBox tramite l'evento GotFocus inserendo qualcosa di simile:

ActiveControl.SelSart=0

ActiveControl.SelLenght=Len(ActiveControl)

Volendo si potrebbe anche centralizzare tutto dentro un Class Module ed usare RaiseEvent per sollevare un evento di errore della validazione che ritorni anche il testo da mostrare all'utente magari in un prossimo articolo.