La ricerca incrementale, soprattutto se veloce, di solito piace molto agli utenti di applicativi gestionali e non solo. L'articolo ne spiega le basi teoriche e ne illustra una realizzazione in Visual FoxPro.

Ricerca incrementale in Visual FoxPro

di Andrea Nicchi

L'articolo spiega perché la ricerca incrementale è così veloce, quali accorgimenti prendere per migliorarla ulteriormente e ne presenta un esempio illuminante in Visual FoxPro.

Richiede familiarità con: xBase

Visual FoxPro, come è noto, appartiene alla famiglia degli ambienti di sviluppo xBase, di cui probabilmente è il rappresentante di spicco per l’evoluzione che ha avuto in questi ultimi anni, da quando in pratica la Microsoft lo ha acquistato dalla Fox Software. Il linguaggio è stato esteso in modo da permettere una vera programmazione ad oggetti nel rispetto di tutti i requisiti che tale paradigma impone e la parte relativa alla gestione delle tabelle è stata enormemente potenziata, soprattutto grazie all’introduzione del Database Container. La velocità ha sempre contraddistinto questo DBMS e la funzionalità, oggetto di questo articolo, ne è la manifestazione più evidente ed immediata.

Cos’è la ricerca incrementale veloce

Il modo classico per recuperare informazioni da un database in un programma è quello di specificare in una dialog box i dati oggetto della ricerca, corredati eventualmente da parametri jolly, e poi fare una query. Così facendo si procede in due battute perdendo l’immediatezza e la rapidità della ricerca. Un metodo di gran lunga migliore ed è quello della ricerca incrementale veloce.

 

 

Per Fast Incremental Searching si intende la possibilità di selezionare un’informazione specificando i dati minimi (vedi Figura 1), centrando l’obiettivo per approssimazione man mano che vengono arricchiti gli stessi dati utilizzati per la ricerca. In pratica man mano che si digita sulla tastiera ciò che si sta cercando si vede l’indicatore della riga corrente come in Figura 1 spostarsi e posizionarsi sulla riga con il miglior matching possibile.

In Visual FoxPro alcune classi base come ComboBox e il ListBox incorporano direttamente questo tipo di ricerca. L’attivazione è semplicissima basta impostare la proprietà IncrementalSearch dei controlli a .T. (true).

Questi due componenti soffrono comunque di alcune limitazioni. Per esempio, quando si hanno colonne multiple e la proprietà RowSourceType è impostata a 2-Alias o 5-Array la ricerca incrementale viene applicata alla prima colonna della lista, senza considerare quella specifica utilizzata per l’ordinamento. Ogni ostacolo, però, viene superato utilizzando la classe di oggetti Grid con cui la ricerca incrementale esprime il massimo delle sue potenzialità. Nelle precedenti versioni di Fox per realizzare questa modalità di ricerca occorreva utilizzare una libreria esterna JKEY.FLL scritta in linguaggio C. Essa occupava appena 10K di Ram e insieme al comando Browse su una tabella indicizzata, riusciva ad ottenere elevate performance e tutta a una serie di plus, come ad esempio la possibilità di

Con la versione 5.0 di Fox non è più necessario utilizzare librerie esterne ma basta far uso dei meccanismi forniti dal linguaggio.

La classe base Grid

Con l’introduzione della classe Grid si è inteso superare alcune limitazioni del comando Browse come

L’oggetto Grid è uno strumento molto utile al programmatore e nello stesso tempo potente, che dà la possibilità di manipolare velocemente e con facilità righe di informazioni. È un oggetto container ed è costituito da colonne che a sua volta sono formate da un header e da un controllo che può essere una textbox, un checkbox oppure uno spin box.

Il Grid è particolarmente predisposto alla gestione di relazioni uno-a-molti e permette di riempire direttamente una tabella impostando a True la proprietà AllowAddNew, che aggiunge un nuovo record alla tabella quando si è sull’ultima riga della lista e si preme il tasto freccia in giù. La Grid può essere anche modificata dinamicamente a run-time, cosa questa di non poco conto, mediante le proprietà i cui nomi cominciano con Dynamic. Per esempio:

Se nel controllo Grid si imposta la proprietà ColumnCount al valore di -1 le colonne vengono automaticamente dimensionate secondo i campi del record ad esse collegati e i nomi dei campi diventano i titoli delle colonne. Creando e configurando in modo oculato le tabelle del Database Container si possono quindi evitare ulteriori lavori di formattazione della grid sulla form velocizzando così i tempi di sviluppo.

Se la tabella visualizzata dispone di indici allora è possibile cambiare l'ordinamento dei record nelle varie colonne, come avviene per i file che mostra Explorer. Questa funzionalità deve essere aggiunta intercettando i clic del mouse sulle intestazioni delle colonne interessate. La procedura di gestione dell’evento è la seguente:

 

PROCEDURE Riordina
LPARAMETER M.xiTagName
LOCAL M.xRecNo, M.xAlias
IF NOT EMPTY(M.xiTagName)
   M.xAlias = THIS.RECORDSOURCE
   SELECT (M.xAlias)
   M.xRecNo = RECNO()
   SET ORDER TO (M.xiTagNAme)
   GO M.xRecNo
   THIS.REFRESH
ENDIF

Il parametro locale M.xiTagName indica rispetto a quale tag ordinare la tabella specificata nella proprietà RecordSource della grid. Nella variabile locale M.xRecNo viene memorizzato l’indirizzo fisico del record corrente ottenuto mediante la funzione recno(). Ciò è necessario perché pur cambiando l'ordine dei record e la vista della tabella (set order to M.xiTagName), per l'utente non dovrà cambiare nulla e il record selezionato non deve mutare. Ecco quindi la necessità di riposizionamento diretto ottenuto con go M.xRecNo. L’ultima istruzione this.refresh rende finalmente visibili i cambiamenti.

Personalizzando in questo modo una grid è possibile effettuare ricerche incrementali su differenti campi in modo immediato e con la massima libertà.

Un esempio pratico

Supponiamo di avere nel Database Container un tabella CONTATTI con un indice sul Cognome e Nome il cui tag è ALFA. Gli oggetti di cui si ha bisogno per realizzare la ricerca incrementale sono una textbox e una grid contenuti in una form. La politica di base che verrà usata è questa: man mano che vengono inseriti dei caratteri nella textbox viene eseguita una "soft-seek" sulla tabella. I passi sono:

  1. Creare un nuova form.
  2. Aggiungere sulla form una Grid e una textbox.
  3. Aggiungere una Custom Property.
  4. Personalizzare il metodo KeyPress della textbox.

Nel primo step quando si crea la nuova form occorre aggiungere nel suo Data Environment la tabella CONTATTI. Questa operazione è molto semplice: basta spostare con il drag-and-drop la tabella dal Database Container alla finestra del Data Environment. Nel Load Event della Form, poi, inseriamo il comando set order to ALFA per abilitare come master index quello relativo al tag ALFA. Infine aggiungiamo alla form una custom property chiamandola ad esempio HoldValue. Il suo valore di default sarà blank.

Per eseguire il secondo passo sempre via drag-and-drop spostare la tabella CONTATTI dal Data Environment della form sulla form stessa. Fox pensa al resto perché capisce che deve creare un oggetto Grid relativo alla tabella. Dopo aver aggiunto una textbox senza specificare il ControlSource nella scheda Data del Properties Sheet occorre personalizzare il suo metodo KeyPress.

È questo il metodo che in pratica pilota tutta la ricerca incrementale. Uno schema possibile è quello riportato nel Listato 1. Analizziamone il codice. L’istruzione nodefault disabilita l’elaborazione di default della classe base della textbox. La condizione dell’IF, che è praticamente un filtro, serve a verificare se il carattere digitato dalla tastiera è ammissibile, in tal caso si procede ad una ricerca incrementale, altrimenti si controlla se è un tasto speciale per un’eventuale gestione. Si noti che la variabile nKeyCode fornita di default da Fox contiene il codice numerico ANSI di quanto inserito da tastiera.

 

Listato 1

* Inibizione Comportamento di DefaultNODEFAULT
* Filtraggio di quanto viene digitato
* nKeyCode è il codice numerico ANSI
* di quanto digitato dal tastiera

 

IF (nKeyCode>=49 AND nKeyCode<=122);
  OR nKeyCode=32
 

  THIS.VALUE = ALLTRIM(THIS.VALUE)+;
              CHR(nKeyCode)

  SELECT CONTATTI
  GO TOP
  SET EXACT OFF

  * Posizionamento mediante
  * "the closest matching"  

  SET NEAR ON

  SEEK UPPER(ALLTRIM(THIS.VALUE))
  IF FOUND()
    THISFORM.HOLDVALUE = THIS.VALUE
    THISFORM.GRID1.SETFOCUS
    THISFORM.GRID1.REFRESH
    THIS.VALUE = THISFORM.HOLDVALUE
    THISFORM.TEXT1.SETFOCUS
   THIS.SelStart=;
         LEN(ALLTRIM(THIS.VALUE))
  ENDIF

ELSE

* Gestione dei Tasti Speciali
** Traps for the ENTER key
*** Generalmente si conferma
*** il successo della ricerca
...

** Traps for the ESC key
*** Generalmente si annulla
*** l'operazione di ricerca

...

** Traps for the UP ARROW key
*** Spostamento sulla riga
*** Precedente

...

** Traps for the DOWN ARROW key
*** Spostamento sulla riga
*** Precedente

...

** Traps for the BACKSPACE key
*** Eliminazione di un carattere
*** erroneamente digitato

...

**** Ripetizione della ricerca
**** con la stringa accorciata

...

**** Spostamento del cursore
**** alla fine della stringa
... ...

ENDIF

 

All’interno dell’IF il comando di setting set near on prima dell’istruzione seek modifica il comportamento dell’istruzione di ricerca dicendogli di posizionarsi sul record con il miglior matching possibile nel caso in cui non ci sia alcun matching esatto con le chiavi presenti sull’indice della tabella. Questo fatto è fondamentale per la ricerca incrementale. This.value che è il contenuto attuale del campo di testo viene passato come parametro alla seek e salvato in HoldValue. Poi si effettua un refresh della grid e ci si riposiziona sulla textbox dopo aver reimpostato il valore con quello parcheggiato in HoldValue.

Quando si esegue la form si vedrà il puntatore della riga dell’oggetto grid, che è in pratica quel triangolino nero che appare alla sinistra, posizionarsi istantaneamente, in modo quasi indipendente dalle dimensioni della tabella, sul record che effettua il miglior matching con quanto digitato.

In genere questo tipo di ricerca piace molto ai clienti, forse perché fa sentire l’informazione veramente a portata di mano con un clic del mouse e pochi caratteri digitati dalla tastiera. In molti casi, infatti, essi preferiscono tenere costantemente aperta la finestra di ricerca incrementale per spostarsi istantaneamente dove vogliono.

Dall’esempio pratico appena esposto si può generalizzare creando un vero e proprio "componente complesso" riusabile in tutte le situazioni in cui esso e necessario. Si sta parlando della creazione di una classe partendo dalle classi base dell’ambiente di sviluppo:

 

Questa classe avrà opportuni proprietà e metodi che daranno la possibilità di inizializzare il componente in modo da farlo lavorare a seconda delle nostre specifiche esigenze. In particolare occorre dire al componente:

 

Da quanto detto sopra si evince che il motore della ricerca incrementale è l’istruzione seek, che quindi merita un approfondimento particolare.

Il ruolo chiave dell’istruzione seek

In Visual FoxPro ci sono due forme sintattiche: comando e funzione. La seconda forma è più comoda, perché in un colpo solo si riesce ad avere informazioni circa l’esito della ricerca senza scomodare la funzione found() come nel Listato 1.

Con questo secondo formato, la parte relativa all’istruzione seek diventerebbe:

SET EXACT OFF
SET NEAR ON
IF SEEK(UPPER(ALLTRIM(THIS.VALUE)))
   THISFORM.HOLDVALUE=THIS.VALUE
   THISFORM.GRID1.SETFOCUS
   THISFORM.GRID1.REFRESH
   THIS.VALUE=THISFORM.HOLDVALUE
   THISFORM.TEXT1.SETFOCUS
   THIS.SelStart = ;
   LEN(ALLTRIM(THIS.VALUE))

ELSE

...

ENDIF

L’istruzione seek opera in due fasi. Nella prima esegue una ricerca binaria o logaritmica dell’espressione sull’indice attivo della tabella. Questa fase è velocissima, grazie soprattutto alla tecnologia Rushmore nata con FoxPro, che comprime gli indici al massimo permettendo di averli quasi interamente in RAM evitando page fault e quindi continui accessi all’hard disk. Grazie a ciò le prestazioni si mantengono interessanti anche su macchine dotate di poca RAM. Nella seconda fase ci si posiziona direttamente sul record fisico utilizzando il puntatore allo stesso presente sull’indice e associato alla chiave che soddisfa il matching.

Nella seconda fase il puntatore al record all'interno della tabella viene spostato su quello che soddisfa il matching mediante il numero ad esso associato come chiave primaria vale a dire il numero restituito dalla funzione recno().

L’istruzione seek basandosi sulla ricerca binaria ha necessariamente bisogno di operare su una tabella con indice, in quanto l’ipotesi di partenza per il funzionamento di tale ricerca è che la lista sia ordinata su qualche chiave.

Nel caso di una tabella T con n elementi la complessità computazionale concreta di questo tipo di ricerca è dell'ordine del logaritmo di n. Con opportune strategie, comunque, si riescono ad avere anche valori medi considerevolmente più bassi. Questi dati spiegano i posizionamenti quasi istantanei nella ricerca incrementale.

Costruire l’indice nel modo giusto

L’indice che si usa per la ricerca incrementale deve essere costruito con oculatezza nel senso che occorre una corrispondenza biunivoca tra la chiave di ricerca da digitare e quella dell’indice. Per essere più chiari se si costruisce l’indice concatenando le stringhe del cognome e nome così:

UPPER(ALLTRIM(COGNOME))+;
UPPER(ALLTRIM(NOME))

quando si ricerca il nominativo occorre digitare il cognome e nome di seguito senza alcuno spazio, però il nominativo che viene mostrato all’utente ha sicuramente il cognome e il nome separati da uno spazio. Ne consegue che il rischio di finire sul record sbagliato è elevato.
L’espressione giusta per l’indice è la seguente:

UPPER(ALLTRIM(COGNOME))+’ ‘+;
UPPER(ALLTRIM(NOME))

Si ha così una vera corrispondenza biunivoca tra la chiave di ricerca e quanto visualizzato sulla Grid. La performance di seek può essere ulteriormente migliorata utilizzando degli accorgimenti nella costruzione dell’indice. Prendiamo in considerazione il comando set collate che serve per eseguire corretti ordinamenti ed indicizzazioni in base alla lingua per cui il programma è scritto. Ad esempio se si considera

set collate to "spanish"

"ch" è considerato una singola lettera e viene ordinato tra la "c" e la "d" mentre "ll" è considerato tra la "l" e la "m". Tutte queste localizzazioni dell’ordinamento appesantiscono l’indice rendendolo meno maneggevole e trattabile attraverso la tecnologia Rushmore.

La machine collate sequence è più veloce ed è preferibile per join e seek, mentre altri tipi di collate sequence sono perfetti per ordinamenti corretti relativi ad una determinata lingua. Ad esempio si potrebbero avere addirittura due tipi di indice sui campi di ricerca e ordinamento nel seguente modo:

SET COLLATE To "GENERAL"
SELECT CONTATTI
INDEX ;
  ON UPPER(ALLTRIM(COGNOME))+’ ‘+;
  UPPER(ALLTRIM(NOME));
TAG _ALFA
SET COLLATE TO "MACHINE"
INDEX ;
  ON UPPER(ALLTRIM(COGNOME))+’ ‘+;
  UPPER(ALLTRIM(NOME));
TAG ALFA

Sintonizzando per bene la macchina FoxPro si riesce, quindi, ad ottimizzare sempre più la ricerca.

Conclusioni

Nel caso di ricerche puntuali la rapidità con cui viene ripescata una data informazione è l’aspetto che contraddistingue e qualifica un buona procedura applicativa. Spesso non si ricorda il nome di una persona o di ciò che si vuole cercare per intero, per cui procedendo in modo incrementale si rende tutto più semplice e veloce e si ha qualche probabilità in più di trovare ciò che si sta cercando.

 

Bibliografia

[1] Visual FoxPro Developer’s Guide, Microsoft Corporation
[2] How to Simulate an Incremental Search from a Text Box, MSDN, 1997
[3] How to Create Special Effects in a Grid, MSDN, 1997
[4] Dan LeClair - "Amaze Your Friends with Grid-O-Matic", FoxPro Advisor, May 1997

Andrea Nicchi è laureato in Scienze dell’Informazione e si occupa da anni di Programmazione Windows con particolare riferimento alle problematiche mission-critical, fault tolerant e decision support system in particolar modo im ambiente Multiuser con architetture di rete pee-to-peer e Client/Server.

Copyright dell'articolo:Andrea Nicchi

© FoxPro e Visual FoxPro sono un marchi registrati da Microsoft Corporation



Data: 7/09/1999
webmaster@foxitaly.com

dal 22 Giugno 1999