Access - Manuale Access 2010 - ITA - Pag. 817

February 18, 2017 | Author: Ninny | Category: N/A
Share Embed Donate


Short Description

Manuale Access 2010 italiano...

Description

Costruire applicazioni con Access 2010

Guida completa

Nella collana Guida completa 3ds Max per l’architettura (III ed.), di Fabio D’Agnano Access 2010, di Piriou e Tripolini Android: guida per lo sviluppatore, di Massimo Carli AutoCAD 2010, di Santapaga e Trasi Autodesk Revit Architecture 2011, di Minato e Nale CSS (II ed.), di Gianluca Troiani Cloud Computing per applicazioni web, di Jeff Barr Computer Forensics (II ed.), di Ghirardini e Faggioli Cubase 4, di Calderan e Djivas ePub: per autori, redattori, grafici, di Brivio e Trezzi Excel 2010, di Mike Davis Flash CS5, di Feo e Rotondo Fotoelaborazione: creatività e tecnica, di Eismann e Duggan Fotografia digitale (II ed.), di Paolo Poli Fotografia RAW con Photoshop (II ed.), di Volker Gilbert Grafica 3D con Blender, di Francesco Siddi Hacker 6.0, di McClure, Scambray, Kurtz HTML5 e CSS3, di Gabriele Gigliotti Il manuale del Guild Leader, di Scott F. Andrews JavaScript, di Yank e Adams jQuery, di Castledine e Sharkie L’arte dell’hacking (II ed.), di Jon Erickson L’arte della fotografia digitale in bianconero, di Marco Fodde L’hacker della porta accanto, di Johnny Long Legge 2.0, di Elvira Berlingieri Linux Server per l’amministratore di rete (IV ed.), di Silvio Umberto Zanzi Linux Ubuntu (III ed.), di Hill, Bacon, Krstić, Murphy, Jesse, Savage, Burger Mac OS X Snow Leopard, di Accomazzi e Bragagnolo Manuale di grafica e stampa, di Mariuccia Teroni Manuale di redazione, di Mariuccia Teroni Manuale per giovani band, di Pier Calderan MySQL 5, di Michael Kofler Photoshop CS5, di Edimatica Rhinoceros per professionisti, di Daniele Nale S.E.O., ottimizzazione web per motori di ricerca, di Davide Vasta SQL: quello che i libri non dicono, di Bill Karwin Sviluppare applicazioni con iPhone SDK, di Dudney e Adamson Sviluppare applicazioni con Objective-C e Cocoa, di Tim Isted Sviluppare applicazioni per iPad, di Steinberg e Freeman Sviluppare applicazioni web con Django, di Marco Beri Sviluppare applicazioni web con PHP e MySQL, di Mark Wandschneider Sviluppare siti con gli standard web, di Zeldman e Marcotte Tecniche di registrazione (II ed.), di B. Bartlett e J. Bartlett Windows 7, di Riccardo Meggiato Web Analytics, di Davide Vasta

Mike Davis

Costruire applicazioni con Access 2010

Costruire applicazioni con Access 2010 Titolo dell’edizione originale inglese: Developing Professional Applications with Microsoft Access 2010 © 2011 Mike Davis. Autore: Mike Davis Copyright © 2011 – APOGEO s.r.l. Socio Unico Giangiacomo Feltrinelli Editore s.r.l. Via Natale Battaglia 12 – 20127 Milano (Italy) Telefono: 02 289981 – Fax: 02 26116334 Email: [email protected] Sito web: www.apogeonline.com ISBN 978-88-503-1249-8 Tutti i diritti sono riservati a norma di legge e a norma delle convenzioni internazionali. Nessuna parte di questo libro può essere riprodotta con sistemi elettronici, meccanici o altro senza l’autorizzazione scritta dell’Editore. Nomi e marchi citati nel testo sono generalmente depositati o registrati dalle rispettive case produttrici. Le riproduzioni a uso differente da quello personale potranno avvenire, per un numero di pagine non superiore al 15% del presente volume, solo a seguito di specifica autorizzazione rilasciata da AIDRO, c.so di Porta Romana, n.108, 20122 Milano, telefono 02 89280804, telefax 02 822864, e-mail [email protected].

Realizzazione editoriale: PMT sas - Monza Editor: Fabio Brivio Copertina e progetto grafico: Enrico Marcandalli Redazione: Federica Dardi

Indice generale

Prefazione..................................................................... xi

.

Parte I

Le basi del sistema.......................................................... 1

Capitolo 1

Il concetto di applicazione............................................................. 3 Database e applicazioni................................................................... 4 L’esigenza applicativa....................................................................... 5 Le applicazioni Access..................................................................... 7 Il progetto delle applicazioni database.............................................. 7 Realizzare applicazioni database con Access....................................20 Le versioni di Access......................................................................21

Capitolo 2

Gli strumenti interattivi dell’interfaccia grafica..............................27 Creazione di un database vuoto......................................................28 La finestra dell’applicazione............................................................30 Creazione delle tabelle...................................................................41 La finestra Struttura di una tabella e le proprietà dei campi.............47 I tipi di dati dei campi....................................................................49 Rispunta l’esigenza applicativa.......................................................57 Modificare l’ordine dei record........................................................75 Modificare i criteri di visualizzazione.............................................78 Le azioni Macro.............................................................................81 I limiti degli strumenti interattivi...................................................85

Capitolo 3

Gli strumenti di programmazione.................................................87 Perché le routine............................................................................88 Quel che occorre sapere..............................................................102

vi  Costruire applicazioni con Access 2010

Parte II

Il ruolo delle macro..................................................... 105

Capitolo 4

Semilavorati per programmare...................................................107 Nomenclatura delle macro...........................................................108 Che cosa si può fare.....................................................................119 I limiti storici delle macro............................................................120 Le macro di Access 2010..............................................................124 Le macro di dati...........................................................................131 Quando le macro sono indispensabili...........................................143 Convertire le macro.....................................................................148 L’erede delle azioni macro: l’oggetto DoCmd...............................152

Parte III

Gli strumenti di sviluppo............................................. 153

Capitolo 5

Gli oggetti.................................................................................155 Che cos’è un oggetto...................................................................156 Oggetti Access.............................................................................159 Oggetti DAO...............................................................................161 Oggetti ADO..............................................................................164 Oggetti Visual Basic.....................................................................165 Come si usano gli oggetti.............................................................166

Capitolo 6

L’ambiente di sviluppo................................................................177 L’Editor di Visual Basic.................................................................179 Gli strumenti per il debug............................................................196 Gli errori di run-time..................................................................210

Capitolo 7

Lavorare con VBA........................................................................211 VBA: un BASIC per gli oggetti....................................................211 Gli schemi sintattici.....................................................................213 Gli elementi degli enunciati.........................................................214 Le categorie degli enunciati.........................................................221

Capitolo 8

Gestire maschere e controlli con VBA...........................................251 Le proprietà Evento.....................................................................252 Le routine evento........................................................................256

Capitolo 9

Lavorare con i DAO......................................................................285 Primi esperimenti........................................................................286 Un po’ di teoria...........................................................................290 Gli oggetti Recordset...................................................................309 Gli oggetti DAO nelle applicazioni...............................................319

Indice generale  vii

Capitolo 10

Lavorare con gli ADO ..................................................................347 L’universo dei dati e i dati universali.............................................348 Utilizzare gli ADO.......................................................................351 Come ottenere gli stessi risultati con DAO e con ADO................360 Gli ADO e il futuro di Access.......................................................390

Capitolo 11

La gestione degli errori...............................................................393 Gli errori di run-time..................................................................395 Gli strumenti per la gestione degli errori......................................396 I limiti della gestione degli errori.................................................415

Capitolo 12

Altri strumenti...........................................................................417 Campi calcolati a livello di record e di tabella...............................417 Le funzioni di aggregazione sui domini........................................419 Query da programma...................................................................425 La funzione SysCmd....................................................................426 I controlli ActiveX.......................................................................438 Personalizzare l’interfaccia utente.................................................442 Semilavorati utili..........................................................................464

Parte IV

L’accesso ai dati........................................................... 469

Capitolo 13

Il linguaggio SQL........................................................................471 La lingua franca dei database........................................................473 I concetti base di SQL.................................................................474 L’utilizzo di SQL in Access...........................................................514

Capitolo 14

I dati esterni..............................................................................525 Si fa presto a dire dati...................................................................526 Nel database o fuori?...................................................................526 Il ruolo del Microsoft Access Database Engine..............................527 Come si accede a dati esterni.......................................................529 L’accesso mediante ODBC...........................................................555 Query di tipo pass-through..........................................................564 Come inviare una tabella a un server............................................567 Provider invece di driver..............................................................567

Capitolo 15

Sicurezza e conflitti....................................................................571 Perdita di dati..............................................................................572 Uso illecito di dati e applicazioni.................................................576 Altre protezioni...........................................................................584 Tutti insieme, separatamente.........................................................594

viii  Costruire applicazioni con Access 2010

Parte V

Access e il resto del mondo........................................... 611

Capitolo 16

Access e il Web............................................................................613 I collegamenti ipertestuali............................................................614 WWW e HTML.........................................................................622 Access come browser web............................................................624 I dati universali degli altri: XML...................................................629 Importare ed esportare con VBA..................................................634 I database web.............................................................................637

Capitolo 17

Lavorare con altre applicazioni Office..........................................647 Accedere ad altre applicazioni dall’interfaccia grafica....................648 Il ruolo dell’automazione.............................................................649 Eseguire applicazioni Office da Access..........................................654

Capitolo 18

L’API di Windows e Access............................................................699 API e DLL..................................................................................700 Qualche semplice esperimento.....................................................704 Usare direttamente le finestre di Windows....................................723 Applicazioni senza Access.............................................................725 Per concludere.............................................................................727

Parte VI

Appendici................................................................... 729

Appendice A

Le azioni macro e l’oggetto DoCmd..............................................731

Appendice B

La messa a punto di un’applicazione............................................775 Visualizzazione Layout: vantaggi e inconvenienti..........................775 Piazzare i controlli nelle maschere................................................778 Prima di consegnare.....................................................................781 Impostazioni per l’avvio...............................................................784



Indice analitico..........................................................................785

For Laura, Marco, Federico, Martina and their lovely mothers Gramps Mike

Prefazione

La quinta edizione della versione italiana di Developing Professional Applications with Microsoft Access, uscita nel 2008, è andata esaurita nel 2010, quasi in coincidenza con l’uscita sul mercato di Access 2010, l’ultima versione di questo ottimo strumento concepito per creare applicazioni fondate su database relazionali. Mike Davis non ha potuto fare altro che rimboccarsi le maniche e lavorare all’aggiornamento del suo libro forse più fortunato.Ancora una volta sono stato chiamato a curarne la versione italiana, che mi ha impegnato negli ultimi mesi del 2010. Con soddisfazione di Mike, dell’Editore e mia, a meno di un anno dal rilascio di Access 2010, l’edizione italiana di Developing Professional Applications with Microsoft Access 2010 sta così par andare in stampa. In questa sesta edizione, Mike Davis approfondisce le caratteristiche e le funzionalità specifiche di Access 2010, la cui interfaccia utente è notevolmente migliorata rispetto a quella della versione precedente, che aveva rivoluzionato tutto il sistema dei comandi e dei menu dei prodotti Office, Access compreso. Le molte novità di Access 2010 non sono tali, però, da renderlo incompatibile con le versioni 2000 e 2003, quindi questo libro si rivolge anche a chi lavora con versioni di Access uscite prima delle versioni 2007/2010, dandogli tra l’altro la possibilità di decidere in modo informato sull’opportunità o meno di continuare con la versione che sta usando oppure passare ad Access 2010. Il libro mantiene la sua articolazione originale, in cinque parti che si sviluppano in diciotto capitoli, più due appendici. La Parte I, “Le basi del sistema”, passa in rassegna i principali aspetti, teorici e pratici, dell’attività di sviluppo delle applicazioni professionali. Come tutti gli strumenti concepiti per essere utilizzati nell’ambiente Windows, Access è dotato di una potente interfaccia grafica, che consente di costruire in modo agevole ed efficace prototipi di applicazioni, ovvero database dotati di meccanismi di input e di output chiari e intuitivi, basati sulla metafora delle finestre che caratterizza le applicazioni Windows. Dopo aver illustrato, con un esempio puntuale e articolato passo per passo, con quanta rapidità si possa arrivare – con l’uso della sola interfaccia grafica di Access – ad avere un “database attrezzato”, cioè qualcosa che può somigliare a un’applicazione, l’autore presenta i molti argomenti che giocano a favore del ricorso agli strumenti di sviluppo, che in Access sono numerosi e molto potenti, per ottenere qualcosa di più solido e di autenticamente professionale.

xii  Prefazione

La Parte II, “Il ruolo delle macro”, passa in rassegna il più semplice e intuitivo degli strumenti di sviluppo disponibili in Access, le macro, che in Access 2010 si sono arricchite di una nuova tipologia, chiamata macro di dati. Con le macro si possono ottenere risultati decisamente più potenti e più flessibili di quelli realizzabili con la sola interfaccia grafica. Numerosi esempi e una rassegna delle azioni macro disponibili in Access 2010 formano il nucleo essenziale di questa sezione, che si conclude con una analisi dei limiti delle macro, superabili soltanto con il ricorso al linguaggio di programmazione Visual Basic for Applications o VBA, al quale è dedicato quasi tutto il resto dell’opera. La Parte III, “Gli strumenti di sviluppo”, è il nucleo centrale del libro, dove si passano in rassegna il linguaggio Visual Basic for Applications, gli oggetti con i quali si accede ai dati, quindi i Data Access Objects (DAO) e gli ActiveX Data Objects (ADO), e numerosi altri strumenti (in particolare i controlli ActiveX) per lavorare con le tabelle. In considerazione del fatto che DAO e ADO sono strumenti concepiti per svolgere sostanzialmente le stesse funzioni, vengono analizzati sempre in parallelo, dimostrando con moltissimi esempi come ottenere gli stessi risultati, a quali condizioni, con DAO e con ADO, mostrando caso per caso vantaggi e limiti di ciascuno strumento. La descrizione analitica delle funzionalità e dei modi di utilizzo di VBA, DAO, ADO e controlli ActiveX si accompagna a una panoramica accurata sulle tecniche di stesura e controllo delle routine in cui si articola il codice di programmazione che trasforma un database attrezzato in un’applicazione professionale. Un intero capitolo è dedicato alla gestione degli errori, un tema essenziale per chiunque intenda lavorare in modo concreto e non approssimativo. Nella Parte IV, “L’accesso ai dati”, si affrontano, in tre lunghi capitoli, fitti di esempi puntuali, tre argomenti diversi, ma che confluiscono su uno stesso tema: come e con quali strumenti si accede materialmente ai dati disponibili in un database Access o in applicazioni diverse. Si descrive quindi che cos’è e come si può usare al meglio lo Structured Query Language o SQL, la lingua franca che consente di accedere a quasi tutti i database esistenti, e che in Access sta alla base delle query e non solo. Quasi sempre, i dati raccolti in un database sono un bene prezioso, un vero e proprio cespite patrimoniale, da mettere al riparo da rischi di danneggiamento o di uso improprio. Viene quindi dedicato un ampio spazio alla sicurezza dei dati, agli strumenti che in Access sono disponibili per difenderli da rischi di perdita accidentale o di utilizzo arbitrario. Diverso, ma affine al tema della sicurezza, è quello della gestione degli accessi multipli quando un’applicazione Access viene utilizzata, come è nella sua vocazione, da più utenti in rete, che lavorano contemporaneamente sugli stessi dati. La descrizione delle tecniche di gestione degli accessi in multiutenza si accompagna con una panoramica delle varie architetture di rete entro le quali è possibile creare e utilizzare applicazioni Access. La Parte V, intitolata “Access e il resto del mondo”, riprende e amplia alcuni concetti esaminati nella parte precedente e allarga l’orizzonte ai vari modi in cui è possibile utilizzare Access in contesti più ampi: nei siti web di Internet o di intranet aziendali. Un ampio spazio è dedicato all’ultima novità di Access (e di Office), cioè all’apertura verso le nuove modalità XML per rappresentare e utilizzare dati strutturati che possono provenire dalle fonti più diverse. Pur essendo radicalmente diverso dagli altri prodotti che appartengono alla famiglia Microsoft Office, Access è però in grado di accedere a strumenti quali Word, Excel o Outlook, inviando a queste applicazioni flussi di dati o ricevendone. È il tema al quale è

Prefazione  xiii

dedicato uno dei capitoli della ParteV, che si conclude mostrando – in un capitolo dedicato alla Application Programming Interface, API, di Windows – come si può accedere, senza uscire da un’applicazione Access, a funzionalità disponibili soltanto nel sistema operativo. Le due appendici sono infine dedicate rispettivamente a “Le azioni macro e l’oggetto DoCmd” e “La messa a punto di un’applicazione”. Questa nuova fatica di Mike Davis dimostra così di essere un lavoro importante e sostanzioso, che nelle sue circa 800 pagine non mancherà di soddisfare le necessità dei professionisti che in Access cercano le soluzioni alle proprie esigenze. Marco Ferrero [email protected] Milano, aprile 2011

Parte I

Le basi del sistema

In questa parte •

Capitolo 1 Il concetto di applicazione



Capitolo 2 Gli strumenti interattivi dell’interfaccia grafica



Capitolo 3 Gli strumenti di programmazione

Capitolo 1

Il concetto di applicazione Nei poco più di cinquant’anni trascorsi da quando i computer sono diventati una realtà industriale e commerciale la tecnologia ha fatto passi da gigante. Se analoghi aumenti di potenza e di capacità si fossero realizzati anche nel settore dell’automobile, oggi potremmo fare il giro del mondo in dieci minuti con una vettura capace di ospitare cento persone e consumando mezzo litro di benzina. Il progresso tecnico si è manifestato quasi per intero nella componente hardware, cioè in quella fisica, dei computer. Non altrettanto vertiginoso è stato lo sviluppo delle funzionalità e delle capacità del software, cioè la componente logica: per ottenere qualcosa da un computer bisogna sempre e comunque fargli arrivare comandi sotto forma di istruzioni di un qualche linguaggio di programmazione. I computer si usano nelle aziende per eseguire operazioni complesse e ripetitive: elaborare i conteggi necessari per stampare i listini degli stipendi; acquisire ordini ed emettere fatture e così via. Per ottenere queste prestazioni da un computer bisogna preparare un programma applicativo ovvero un’applicazione. Un’applicazione è molto più di un programma, cioè di una serie di istruzioni scritte in un linguaggio di programmazione. Se andiamo a consultare il Computer Dictionary edito dalla Microsoft Press, troviamo questa definizione di applicazione: Programma per computer, progettato dall’utente per eseguire un certo tipo di lavoro.

In questo capitolo •

Database e applicazioni



L’esigenza applicativa



Le applicazioni Access



Il progetto delle applicazioni database



Realizzare applicazioni database con Access



Le versioni di Access

4  Capitolo 1

La definizione prosegue, precisando: Le applicazioni si distinguono così dai sistemi operativi (che fanno funzionare i computer), dai programmi di utilità (che svolgono compiti di manutenzione o generici) e dai linguaggi (con cui vengono creati i programmi per computer).A seconda della funzione per cui è stata progettata, un’applicazione può manipolare testi, numeri, immagini o una combinazione di questi elementi.

Non si potrebbe dire meglio. Contraddicendo, però, lo spirito di questa definizione, Microsoft chiama poi “applicazioni” i suoi prodotti software quali Word, Excel e Access, per citare i più noti, che sono compresi nella confezione Microsoft Office. In realtà, Access, per restare sul nostro argomento centrale, è uno strumento per creare applicazioni, cioè programmi che fanno qualcosa di utile per l’utente che li ha creati. Non si tratta di una questione di lana caprina, ma di un aspetto sostanziale, che è utile chiarire perché dà un senso a tutto questo libro.

Database e applicazioni Si utilizza un prodotto software come Word per scrivere documenti: lettere commerciali, biglietti di auguri, saggi, tesi di laurea, romanzi, racconti e quant’altro. Con Excel si producono fogli di calcolo, cioè tabelle con numeri e formule, utili in tutte le situazioni della vita privata o del lavoro nelle quali si devono mettere insieme un po’ di conti, semplici o complessi. Il risultato ottenuto con Word o con Excel di norma esce dal computer per andare al destinatario della lettera o all’editore del romanzo, oppure per entrare in un rendiconto economico o in una relazione di bilancio. Ciò che si produce con Access, invece, rimane nel computer, resta strettamente integrato con lo stesso Access, ed è un database, nel quale risiedono dati sotto forma di tabelle, che si gestiscono con query, report e maschere. L’interfaccia grafica di Access è talmente ricca di possibilità che consente di creare automaticamente un intero database, completo di tutti gli strumenti per gestirlo. Basta selezionare Nuovo nel pannello di sinistra della schermata iniziale di Access 2010 per far comparire nel pannello centrale un elenco di modelli preconfezionati di database fra i quali scegliere per partire alla grande con un database già provvisto di tutto (Figura 1.1). E se questi non bastassero, se ne trovano molti altri nel sito Office.com. Ciò che si può costruire usando l’interfaccia grafica di Access, ricorrendo oppure no a un modello predefinito di database, non è ancora un’applicazione nel senso pieno del termine, ma un database attrezzato. Dove sta la differenza? Sostanzialmente nel fatto che un database Access costruito usando soltanto l’interfaccia grafica è estremamente vulnerabile: l’utente ha sempre a disposizione la barra multifunzione, dalla quale potrebbe attivare qualunque comando e mettere, per esempio, una maschera in visualizzazione Struttura e modificarla. Inoltre, se non si predispongono adeguate salvaguardie, un dato improprio immesso in una maschera (per esempio una data che si colloca fuori da un intervallo temporale predefinito) può far uscire incomprensibili e allarmanti messaggi di errore o può addirittura bloccare l’intero sistema. Un’applicazione professionale, invece: •• non consente modifiche alla sua struttura da parte dell’utente finale; •• non emette segnalazioni di errore incomprensibili; •• non si blocca se l’utente immette un valore improprio o esegue una manovra sbagliata.

Il concetto di applicazione   5

Figura 1.1  Dal pannello Nuovo si può avviare la creazione automatica di un nuovo database Access completo di tutto.

Per ottenere con Access applicazioni professionali e non semplici database attrezzati bisogna ricorrere ai linguaggi di programmazione intrinseci ad Access, perché le innumerevoli operazioni che si possono fare con la sola interfaccia grafica non sono sufficienti per creare applicazioni.

L’esigenza applicativa Un’applicazione nasce da un’esigenza applicativa, cioè da una necessità obiettiva di affrontare un problema gestionale ricorrente programmando un computer in modo che lo risolva. La vera, grande difficoltà concettuale sta nell’individuare con chiarezza l’esistenza e le caratteristiche di un’esigenza applicativa. Alcune sono immediatamente riconoscibili: elaborare gli stipendi a mano, con brogliacci di carta e calcolatrici, non ha senso quando i dipendenti superano un certo numero. Lo stesso vale per la gestione delle scorte, per l’acquisizione degli ordini o per la fatturazione, quando le operazioni da eseguire sono centinaia o migliaia al giorno. Molto più difficile è riconoscere con chiarezza un’esigenza applicativa in attività aziendali meno consolidate e predefinite. È il caso del marketing, per esempio, dove gli addetti ai lavori sentono quasi quotidianamente l’esigenza di elaborare in modo nuovo dati attinti al patrimonio informativo aziendale (venduto per quantità e valore, per area geografica e per periodo) per ricavarne qualche indicazione di tendenza o di potenzialità, applicando ogni volta ai dati (volumi di vendita effettivi o previsti) nuovi algoritmi statistici

6  Capitolo 1

di varia complessità. In casi di questo genere, più che un’applicazione, che risolverebbe un solo problema, serve uno strumento generalizzato, che permetta di definire rapidamente alcune formule e applicarle altrettanto rapidamente a flussi di dati già disponibili. È questo il tipico contesto nel quale l’esigenza applicativa viene soddisfatta meglio con Excel o con prodotti analoghi, capaci ormai, per potenza e sofisticazione, di soddisfare qualunque necessità. Con Access non è ragionevole affrontare esigenze applicative occasionali e limitate nel tempo, come sono quelle che tipicamente si affrontano e si risolvono con Excel. Non bisogna dimenticare, tra l’altro, che Excel dispone di molte funzionalità per la gestione di tabelle simili a quelle che formano i database, per cui può essere più conveniente eseguire con Excel, invece che con Access, determinate analisi su tabelle di qualche decina di record. Le esigenze applicative che vengono meglio soddisfatte con Access sono tutte quelle per le quali occorre: •• gestire volumi medio-grandi di dati; •• mettere in relazione fra loro famiglie di dati diversi; •• utilizzare per l’input meccanismi automatici o risorse umane poco professionalizzate; •• generare prospetti o tabelle attingendo selettivamente ai dati disponibili; •• consentire a più persone di accedere contemporaneamente agli stessi dati, per consultarli o modificarli, da più computer collegati fra loro in una rete locale. Non è un caso che la gestione degli ordini sia l’esempio ricorrente utilizzato per spiegare come sono fatti in generale i database e in particolare quelli relazionali che si possono creare con Access. Le esigenze applicative poste dall’amministrazione degli ordini di una qualsiasi azienda industriale richiedono la creazione e la gestione di diverse tabelle correlate, il controllo rigoroso della loro integrità e un sistema di input a prova di bomba, che intercetti qualunque dato potenzialmente erroneo e che potrebbe inquinare la base dei dati se venisse inserito nelle tabelle. Per soddisfare questo tipo di esigenze occorrono applicazioni database che si possono realizzare egregiamente con Access e non con Excel, tanto per dire. Naturalmente è possibile crearle anche con Visual Basic, con C# o con Ada, se proprio uno insiste, o magari con PHP, se si vuole essere alla moda, ma Access ha dalla sua il fatto che è già predisposto per creare e gestire applicazioni database, mentre con i linguaggi di programmazione che ho elencato bisognerebbe partire da zero, con un immenso consumo di tempo (e, quindi, di denaro). Quando l’esigenza applicativa deve soddisfare un’utenza diffusa, che opera su reti locali o geografiche o magari su Internet, il ricorso ad Access è quasi obbligatorio, trattandosi di uno strumento che è strutturalmente predisposto per operare in rete e può mettere i dati a disposizione di più utenti contemporaneamente, attivando opportuni meccanismi di blocco per disciplinare l’accesso alle tabelle in modo da evitare conflitti quando due o più utenti intervengono per modificare gli stessi dati. Infine, se l’applicazione database che si intende realizzare ha bisogno di qualche forma di protezione e di sicurezza per inibire selettivamente l’accesso ai dati, ancora una volta è Access lo strumento da utilizzare, per via dei potenti meccanismi di protezione che offre a chi sviluppa applicazioni.

Il concetto di applicazione   7

Capire le esigenze applicative e ricavarne gli indirizzi guida per realizzare applicazioni database capaci di soddisfarle non è un’arte né una scienza. È, piuttosto, una forma elevata di artigianato, dominata da pochissimi professionisti dell’informatica. In questo libro non ci sogniamo neppure di insegnare come si fa, ma diamo per scontato che il progetto dell’applicazione sia ben definito e che si voglia utilizzare al meglio tutta la potenza di Access per realizzarlo.

Le applicazioni Access Quando si costruisce un’applicazione database con un linguaggio di programmazione di tipo generale, per esempio con Visual Basic o con C#, si è liberi di fare tutto quello che si vuole. Il prezzo di questa libertà si paga in termini di fatica: bisogna costruire da zero tutto ciò che serve. Costruendo un’applicazione database con Access si è meno liberi, ma in cambio si può attingere a un repertorio gigantesco di elementi prefabbricati, con i quali si arriva molto prima al risultato che si vuole ottenere, avendo in più la sicurezza che ciascuno degli elementi utilizzati funziona bene sia per conto proprio, sia a contatto con gli altri. Questi “elementi prefabbricati” si chiamano tecnicamente oggetti. Tutto quello che si vede sullo schermo quando si esamina un database Access è un oggetto: sono oggetti le tabelle, le query, le maschere, i report, le macro e i moduli; sono oggetti i controlli sulle maschere e sui report così come sono oggetti la barra multifunzione e il pulsante File. Associati a maschere e report troviamo gli eventi, un altro elemento qualificante di Access. Quando si fa clic su un controllo o quando si apre una maschera e ci si porta su una casella di testo con il tasto di tabulazione si genera un evento, per il quale Access consente di definire una risposta predefinita. Assemblando opportunamente oggetti tabella, maschera e report si ottiene un database Access, che è un oggetto a sua volta. Per fare di un database Access un’applicazione Access, bisogna: 1. impostare le proprietà degli oggetti e 2. definire le risposte agli eventi. Tutto qui.

Il progetto delle applicazioni database Per fissare una base comune di termini e concetti, percorreremo ora i passi principali in cui si articola il progetto di un’applicazione database. I lettori esperti possono tranquillamente saltare le prossime pagine e passare al Capitolo 2.

Il disegno complessivo del sistema Le applicazioni non si creano per ragioni estetiche o per divertimento, ma per soddisfare esigenze applicative. Creare un’applicazione richiede tempo e quindi, per definizione, costa denaro. Anche chi lavora per conto proprio e sviluppa un’applicazione da solo sostiene un costo, perché il tempo che dedica al progetto e alla realizzazione è sottratto al riposo o al lavoro produttivo, e quindi è un costo, anche se non c’è esborso materiale

8  Capitolo 1

di denaro. Per questa elementare ragione, non ha senso costruire applicazioni a vanvera, mettendo giù una tabella o due tanto per vedere dove si andrà a finire, ma occorre un approccio strutturato, cioè si deve costruire in via preliminare un disegno complessivo del sistema. In termini pratici, bisogna elencare tutte le attività che si vogliono automatizzare, descrivendole sinteticamente e cercando di cogliere eventuali correlazioni. Tanto per fare un esempio elementare, supponiamo di avere individuato le seguenti attività per una società che vende elettrodomestici. •• Acquisire informazioni sui clienti: nome, indirizzo, sconti abituali. •• Acquisire ordini: numero e data fattura e bolla di spedizione, informazioni sul cliente, articoli ordinati, quantità e codici degli articoli. •• Gestire una situazione del magazzino: acquisire codici degli articoli, loro descrizioni, prezzo unitario di vendita, costo unitario di acquisto. •• Generare elenchi selettivi di clienti/ordini/articoli, su domanda dell’utente. •• Generare prospetti sul fatturato mensile, classificato per articoli, fasce di prezzo, tipo di cliente. •• Generare situazioni di magazzino: giacenze per quantità e valore, frequenze di movimentazione, margini commerciali. La stesura di elenchi di attività come questo è un processo iterativo: stabilito un primo elenco di partenza, lo si sottopone all’utente e si ragiona con lui a più riprese, tornando eventualmente sui punti già definiti per arricchirli di nuovi elementi che emergono dal confronto fra le diverse attività. Per esempio, nell’elenco precedente non sono presenti le attività di acquisizione dei prodotti commercializzati (gestione dei fornitori e dei prodotti che si acquistano per la vendita), che sono il naturale complemento delle attività di vendita. Si può, ovviamente, decidere di sviluppare un’applicazione gestionale completa – vendite e acquisti o vendite e produzione – in due o più fasi successive, concentrando gli sforzi in un primo tempo sulla parte vendite e in un secondo tempo sulla parte acquisti o viceversa, dando la precedenza al ramo di attività che conviene meccanizzare per primo perché è il più complesso o, magari invece perché è il più semplice e quindi consente di ottenere più in fretta un risultato interessante.

I risultati, prima di tutto L’elenco delle attività, una volta stabilizzato, sia pure provvisoriamente, serve in primo luogo per produrre un elenco di risultati attesi dall’applicazione, ovvero di output. Che cosa deve produrre l’applicazione? Elenchi di clienti ordinati per ragione sociale? Liste di fatture emesse e non ancora incassate? Fatture e bolle di accompagnamento per merce venduta? Come si devono presentare questi output? Per ottenere una risposta professionale a queste domande c’è un solo modo: produrre un prototipo di ciascun output. Con carta e matita o, meglio, visto che si ha a disposizione un computer, con Word o con un qualunque altro strumento per elaborare testi, si scrivono i report distinguendo le parti testuali e descrittive da quelle che si ricaveranno dai dati o che si otterranno per calcolo. Qualcosa di simile allo schema riprodotto nella Figura 1.2.

Il concetto di applicazione   9

Figura 1.2  Una bozza di report preparata a mano.

Lo schema di report della Figura 1.2 permette di individuare agevolmente le informazioni che dovranno essere gestite con l’applicazione, classificandole inoltre per famiglia. Per esempio: •• NumFattura, DataFattura,TipoSpedizione e NumOrdine sono dati specifici di ogni singola fattura; •• Società, IndirizzoStradaleSped, CAPSped, CittàSped, ProvinciaSped sono dati che caratterizzano un cliente; •• CodiceProdotto, Descrizione, Quantità, Prezzo e PrezzoCalcolato definiscono le singole righe dell’ordine, aggregate complessivamente dal dato NumOrdine; •• il dato PrezzoCalcolato è, ovviamente, il risultato del prodotto di Quantità per Prezzo; •• TotaleImponibile è la sommatoria dei vari PrezzoCalcolato; •• IVACalcolata risulta dall’esecuzione di una moltiplicazione sul valore di TotaleImponibile; •• TotaleFattura non è altro che la somma di TotaleImponibile e IVACalcolata; •• il dato CondizioniPagamento può essere, a seconda dei casi, considerato come elemento della famiglia Ordini, o Clienti o Fatture. Escludendo i dati PrezzoCalcolato, TotaleImponibile, IVACalcolata e TotaleFattura, che sono ottenuti con un calcolo di volta in volta, gli altri dati – NumFattura, DataFattura, TipoSpedizione, NumOrdine, Società, IndirizzoStradaleSped, CAPSped, CittàSped,

10  Capitolo 1

ProvinciaSped, CodiceProdotto, Descrizione, Quantità, Prezzo e CondizioniPagamento – sono tutti potenziali campi di tabelle Ordini, Clienti, Fatture o Prodotti. L’esercizio di disegnare la struttura dei report per definire le prestazioni richieste dall’applicazione va eseguito per tutti i report che l’utente potrebbe desiderare. E non solo per i report, ma anche per le possibili schermate di inserimento e consultazione dei dati, che saranno poi realizzate con maschere. Questo esercizio preliminare crea le basi per la terza fase del processo di sviluppo di un’applicazione: la progettazione dei dati.

Quali dati servono? La seconda fase ha consentito di individuare i dati elementari che servono per l’applicazione. Nella terza fase si tratta di separarli per famiglia di appartenenza, individuando così le tabelle che costituiranno il database sul quale si baserà l’applicazione. In questa fase può essere utile sistemare in un foglio di lavoro Excel i nomi dei dati presenti nei vari report e schermate, cercando di capire: •• quali dati sono omogenei e vanno in una specifica tabella; •• quali dati vengono ripetuti nei report/schermate. È essenziale controllare che eventuali dati che hanno lo stesso nome in più report siano davvero gli stessi, in modo da evitare ridondanze nelle tabelle che si creeranno per gestire i dati. La progettazione dei dati comporta anche la previsione del modo e del contesto in cui verranno acquisiti, utilizzando opportune maschere per l’immissione dei dati. Per esempio, le informazioni che definiscono un cliente (Società, IndirizzoStradaleSped e così via), anche se vengono utilizzate nelle fatture, andranno acquisite con una maschera che aggiorna una tabella Clienti, non con la maschera che si creerà per generare le fatture. Analogamente, i dati CodiceProdotto, Descrizione e Prezzo saranno da gestire in una tabella Prodotti. Utilizzando la tecnica di spostamento col mouse delle celle nei fogli di lavoro Excel, è molto agevole costruire bozze di tabelle come quelle illustrate nella Figura 1.3.

L’integrità dei dati: le chiavi Le tabelle sono l’essenza di un database: tutto vi confluisce e tutto promana da esse. È per questa ragione che la definizione di quali e quante tabelle si utilizzeranno in un’applicazione è così importante. In questa fase, oltre a definire i dati, cioè i campi dei record, che formeranno le singole tabelle, è essenziale scegliere per ciascuna tabella una chiave primaria, cioè un elemento che distingua ciascun record dagli altri (i record di una tabella, come sappiamo, sono formati da tutti i campi definiti per quella tabella). La presenza di una chiave primaria in una tabella serve per distinguere ogni singolo record in modo da renderlo univoco, quale che sia il contenuto degli altri campi; questa univocità dei record è indispensabile per garantire l’integrità dei dati

Il concetto di applicazione    11

Nell’elenco ricavato dallo schema di un report. . .

. . . si isolano i dati che sono specifici di una tabella. . .

. . . e si individuano elementi che si ripetono.

Figura 1.3  Il processo di progettazione dei dati.

12  Capitolo 1

La chiave primaria si basa su un campo, che può essere uno di quelli che sono già stati definiti, purché si sia certi che non se ne presenteranno mai due con lo stesso contenuto. La cosa migliore da fare, però, è aggiungere a ciascuna tabella un campo specifico destinato a contenere la chiave primaria. Nel caso, per esempio, della tabella Clienti, sarà opportuno creare un campo CodiceCliente, così come per la tabella Prodotti sarà bene avere un campo CodiceProdotto. Una chiave primaria può essere formata da caratteri di testo (cioè il campo può avere il tipo di dato Testo), nel qual caso sarà responsabilità dell’utente digitare una stringa diversa per ogni nuovo record che andrà ad aggiungere. Una scelta di questo genere è sconsigliabile per quasi tutte le applicazioni gestionali, perché affida all’utente una responsabilità (stabilire il contenuto della chiave primaria) che è meglio assegnare al sistema. Se il valore della chiave primaria dovesse essere scelto dall’utente a ogni immissione di un nuovo record, ciascuna operazione di questo genere potrebbe diventare molto complessa, soprattutto quando nella tabella si aggiungono ogni volta parecchie decine di record. Il processo di immissione dei dati può invece essere notevolmente semplificato lasciando che sia Access a determinare automaticamente il contenuto del campo chiave primaria di ciascun nuovo record. Per ottenere questo risultato si attribuisce a un campo un nome qualsiasi, per esempio CodiceProdotto, lo si definisce come chiave primaria e gli si assegna il tipo di dato Numerazione automatica (AutoNumber, in inglese).Va ricordato che il contenuto dei campi di tipo Numerazione automatica non è modificabile da chi fa sviluppo e tanto meno dall’utente: nasce automaticamente una chiave primaria diversa per ogni nuovo record che si inserisce e non resta altro da fare che prenderne atto e utilizzarla così come è quando se ne ha bisogno. In una situazione aziendale nella quale sono già in uso codici che si chiamano CodiceProdotto o CodiceCliente, sarà meglio che il campo destinato a contenere la chiave primaria di tipo Numerazione automatica abbia un nome che lo distingua dai codici esistenti, per esempio IDCliente o IDProdotto.

L’univocità dei dati: la normalizzazione Quando si individuano i dati che verranno memorizzati nelle tabelle, bisogna evitare ogni forma di ripetizione o di ridondanza. In altri termini, ogni elemento informativo specifico e ben definito, un indirizzo stradale, per esempio, una partita IVA, un codice prodotto, deve comparire una e una sola volta nell’intero database. Inoltre, i singoli elementi – che sono poi i campi dei record – devono essere aggregati in modo da rappresentare compiutamente una realtà o un’attività. Per esempio, un record della tabella Prodotti potrà contenere campi come CodiceProdotto, CodiceFornitore, UnitàDiMisura, Colore, Peso, DescrizioneProdotto e altri ancora, ma non è corretto aggiungere campi tipo QuantitàDisponibile, PrezzoAcquisto o PartitaIVAFornitore, che troveranno la loro legittima sede rispettivamente nei record delle tabelle Magazzino, OrdiniAcquisto e AnagraficoFornitore. Il lavoro di analisi concettuale che si richiede in questa fase prende il nome di normalizzazione e comporta, oltre all’eliminazione delle ridondanze e all’aggregazione in famiglie omogenee dei dati elementari, anche la decisione di quali dati conservare come record e quali lasciare fuori dal database. Per questa decisione, la regola da seguire è piuttosto elementare: nelle tabelle non devono esistere campi calcolati, vale a dire campi il cui contenuto risulta da un calcolo. Il caso

Il concetto di applicazione    13

più semplice è l’importo fatturato espresso in una fattura. Questo valore è il risultato di una o più operazioni aritmetiche: la moltiplicazione di un prezzo unitario per la quantità venduta (imponibile), completata dal conteggio dell’importo dell’IVA (un’altra moltiplicazione) e dalla somma di imponibile e di IVA. I record di una eventuale tabella Fatture dovranno contenere i campi PrezzoUnitario, QuantitàVenduta e AliquotaIVA, ma non campi per l’imponibile, l’IVA e l’importo fatturato, che sono campi calcolati a partire dagli altri tre. La normalizzazione ha due finalità distinte, ma ugualmente importanti: 1. contenere le dimensioni dei record (e quindi delle tabelle); 2. prevenire l’immissione di dati errati o contraddittori. L’esclusione dalle tabelle dei dati calcolati aiuta a raggiungere la prima finalità: una tabella che ha meno dati ha dimensioni più contenute di una che ne contiene di più. La considerazione può apparire (ed è) lapalissiana, ma non è banale. L’idea è che le informazioni che si ottengono dai dati calcolati si possono ottenere in qualunque momento rieseguendo il calcolo, quindi non è il caso di consumare spazio sui dischi per memorizzare campi calcolati.Vanno memorizzate, invece, tutte le informazioni necessarie e che non si possono derivare da altre già disponibili. Per quanto riguarda la seconda finalità, la prevenzione degli errori, un semplice esempio dimostra l’utilità della normalizzazione. Immaginiamo che un record della tabella Ordini preveda un campo SocietàCliente e un campo IndirizzoSpedizione destinati a contenere la ragione sociale e l’indirizzo del cliente che ha emesso l’ordine, oltre a campi che definiscono il prodotto e le quantità ordinate. Se un cliente invia dieci ordini, si devono creare dieci record, uno per ogni ordine, ripetendo per ogni record l’immissione della ragione sociale e dell’indirizzo del cliente. La probabilità che una volta si scriva IBM e un’altra I. B. M. e un’altra ancora Ibm nel campo SocietàCliente sono molto elevate e ogni volta il campo conterrebbe un dato diverso, anche se la società cliente è sempre la stessa. Il rischio di errore aumenta ulteriormente se si deve ripetere per ogni ordine il dato dell’indirizzo per la spedizione della merce. La logica della normalizzazione suggerisce di utilizzare invece un codice compatto, diverso per ogni cliente, come campo dei record della tabella Ordini per identificare il cliente. Questo campo, che possiamo chiamare genericamente CodiceCliente, fa riferimento al record anagrafico della tabella Clienti, nella quale si possono gestire comodamente, registrandoli una sola volta per ogni nuovo cliente, tutti i dati descrittivi che possono interessare: RagioneSociale, IndirizzoSpedizione, IndirizzoFatturazione e quant’altro. Qualcuno potrebbe osservare che in questo modo si ha una certa ridondanza, perché in due tabelle del database esiste un campo uguale. È così: normalizzazione, infatti, non significa eliminare tutte le ridondanze, ma soltanto quelle che non servono o che possono creare difficoltà di gestione. La presenza di un campo uguale in due o più tabelle non soltanto è legittima, ma è determinante per un utilizzo efficace ed efficiente dei dati organizzati in tabelle.

Le relazioni fra le tabelle e l’integrità referenziale Tutte le applicazioni si basano su archivi di dati organizzati in forma tabellare.Tali tabelle nel loro insieme formano il cosiddetto database, che nel caso di Access si caratterizza

14  Capitolo 1

come database relazionale. In un database di questo tipo, ogni attività o realtà gestita viene rappresentata con una tabella separata, che contiene tutti i dati specifici di quella realtà o attività e soltanto quelli. Abbiamo quindi una tabella Clienti, che contiene tutte e sole le informazioni che identificano compiutamente un cliente, chiamate in gergo “informazioni anagrafiche”; una tabella Ordini, che svolge la stessa funzione di raccoglitore di informazioni descrittive degli ordini; una tabella Prodotti e via enumerando. Le tabelle si possono mettere in relazione fra loro predisponendo un campo comune, che “aggancia”, per così dire, un record di una tabella con uno o più record di un’altra. Le relazioni fra tabelle sono l’essenza stessa dei database relazionali.Vediamo con qualche esempio di che cosa si tratta.

Quando un database non è relazionale L’organizzazione dei dati sotto forma di tabelle è una tecnica che nasce con i primi grandi computer progettati per la gestione aziendale, all’inizio degli anni Sessanta del secolo scorso . Gli strumenti software allora disponibili non consentivano interventi agevoli sulla struttura delle tabelle e le limitazioni di potenza elaborativa e di capacità di memoria impedivano di avere più tabelle aperte contemporaneamente. Per molto tempo, quindi, i database erano formati da poche tabelle, non collegate fra loro. Database di quel tipo vengono chiamati oggi database piatti. Con l’aumento di potenza dei computer mainframe (anni Settanta) si trovò il modo di creare correlazioni rigide fra i record, che formavano database gerarchici. In database di questo tipo l’associazione fra record si otteneva usando farraginose tecniche di puntamento a posizioni prestabilite nelle tabelle correlate: le righe di ogni tabella corrispondevano a registrazioni fisiche su disco e venivano individuate in base al loro indirizzo hardware. La rigidità del meccanismo rendeva molto ardua qualunque modifica alla struttura degli archivi. Se, a seguito di cambiamenti nella logica gestionale, si rendeva necessario un intervento sulla struttura di un database gerarchico, quasi sempre bisognava mettere nel conto anche il rifacimento dell’intera applicazione che forniva dati al database gerarchico o li ricavava da esso. Nei database relazionali la modifica della struttura e delle relazioni fra tabelle non è più così impegnativa: le righe delle tabelle non sono rigidamente associate a specifiche aree del disco rigido, l’ordine in cui le righe si susseguono nelle tabelle è irrilevante e altrettanto irrilevante è la sequenza delle colonne in una tabella. Alle tabelle, alle loro righe e alle colonne entro le righe si accede tramite il loro nome (che deve essere per forza di cose univoco) e non mediante riferimenti alla loro posizione fisica sul disco.

Relazione uno a molti Supponiamo di voler creare un database di indirizzi. Il database potrebbe essere costituito da un’unica tabella, con un record per ogni persona. I campi di questo record potrebbero essere Cognome, Nome, Indirizzo, Città, Provincia, CAP e Telefono. Per gestire i casi di omonimia completa, quando due o più persone si chiamano Mario Rossi e magari abitano nella stessa città, prevediamo anche un campo IDPersona, che funge da chiave primaria e contiene un codice diverso per ogni record, generato automaticamente da Access (tipo di dato Numerazione automatica o Autonumber che dir si voglia). Fin qui tutto bene. Il problema nasce sul campo Telefono. Per molte persone occorre memorizzare più di un numero di telefono: casa e ufficio, per esempio. O magari Casa, Ufficio

Il concetto di applicazione    15

e Fax. E ormai tutti hanno un telefono cellulare, quindi Casa, Ufficio, Fax e Cellulare. Ma alcuni hanno anche una seconda casa, dove amano passare i fine settimana e qualche breve periodo di vacanza. E allora i campi per i numeri di telefono diventano cinque. Una struttura di record di questo genere va contro i principi della normalizzazione, perché non tutte le persone hanno cinque numeri di telefono diversi, quindi la tabella sarebbe ridondante. E che cosa succede quando nasce la necessità di memorizzare un sesto numero di telefono per qualche caso particolare? Bisognerà aggiungere un campo al record, aumentando ulteriormente l’ingombro della tabella. E il sesto campo resterebbe vuoto per gran parte dei record. La soluzione corretta di questo problema di ridondanza consiste nel creare due tabelle invece di una. Nella prima tabella, che chiameremo Madre, i record contengono i campi IDPersona, Cognome, Nome e via via tutti gli altri, meno il campo Telefono. La seconda tabella, che chiameremo Figlia, è formata da record composti da tre campi: IDPersona, NumeroTel e Descrizione. Nella tabella Figlia si potranno creare liberamente tanti record riferiti alla stessa persona quanti sono i suoi numeri di telefono: cinque, uno, nessuno o centomila. Per ciascun record, il campo Descrizione può contenere un breve testo che spiega se si tratta del numero di telefono di casa, di ufficio, del centralino della società, della casa al mare, del cellulare, della casa della mamma o di quella dell’amante. L’aggancio fra la tabella Madre e la tabella Figlia è dato dal campo IDPersona che hanno in comune. Questo tipo di relazione fra tabelle si chiama relazione uno a molti ed è quella che si utilizza più spesso nelle applicazioni gestionali. In una relazione uno a molti la tabella che sta dalla parte “uno” è detta Madre o Genitore (quando si vuole essere politicamente corretti) o Master e quella che sta dalla parte “molti” è detta Figlia o Correlata.

Altre relazioni Il gioco delle combinazioni consente di definire altri due tipi di relazioni fra tabelle: la relazione uno a uno e la relazione molti a molti (una eventuale relazione molti a uno non è altro che la relazione uno a molti vista dall’altra parte). In una relazione uno a uno a ciascun record di una tabella ne corrisponde esattamente uno in un’altra tabella. Questa relazione si usa raramente, quasi solo nei casi in cui si vuole gestire separatamente una tabella che contiene un sottoinsieme di record di un’altra, mantenendo però in sincronia fra loro le due tabelle. L’esempio più tipico è quello di una tabella Impiegati che contiene informazioni di tipo anagrafico (Nome, Cognome, DataNascita, Residenza eccetera) e una tabella Retribuzioni, i cui record contengono campi quali PagaBase, SovraMinimo eccetera. La tabella Impiegati può essere consultata da chiunque usi l’applicazione, mentre l’accesso alla tabella Retribuzioni è consentito soltanto agli impiegati dell’amministrazione del personale. Nel caso di una relazione molti a molti le cose si complicano. L’esempio più tipico si presenta quando si devono definire le tabelle di un database bibliografico o editoriale. Molti manuali universitari e libri scientifici in genere sono opera di più autori e gli stessi autori scrivono anche libri da soli o insieme con altri. Concettualmente simile è il caso degli studenti e dei corsi: ogni studente universitario segue più corsi, ma non tutti i corsi sono seguiti dagli stessi studenti. Per gestire correttamente tabelle che contengono dati di questo tipo – titoli e autori, autori e titoli, oppure studenti e corsi, corsi e studenti – che sono correlati con relazioni molti a molti, è necessario scomporre queste relazioni con l’aiuto di tabelle intermedie,

16  Capitolo 1

che fungano da raccordo, in modo da poter rientrare nella casistica delle relazioni uno a molti.Vediamo un esempio nella Figura 1.4. In un database utilizzato per gestire una biblioteca abbiamo tre tabelle, Titoli, Autori ed Editori. La tabella Titoli contiene informazioni sui libri e utilizza come chiave primaria il codice ISBN, cioè l’International Standard Book Number, che identifica in modo univoco ciascun libro in circolazione. Questa tabella sta dal lato molti di una relazione uno a molti con la tabella Editori, dove lo stesso codice ISBN è la chiave esterna. La tabella Autori contiene due sole colonne: ID_Au, la chiave primaria che identifica univocamente ciascun autore, e Autore, che contiene il nome di ciascun autore. Dato che fra Autori e Titoli esiste una relazione molti a molti, è stata creata una tabella di raccordo Titolo_Autore, che fa da ponte fra Autori e Titoli, avendo un campo ISBN e un campo ID_Au per ogni coppia autore-libro.

Figura 1.4  Strutture e relazioni delle tabelle Autori, Titolo_Autore, Titoli ed Editori.

Come si può vedere dalla figura, in Access le relazioni fra tabelle si rappresentano graficamente come una linea che congiunge la chiave primaria con la chiave esterna, affiancando il simbolo dell’infinito al nome della chiave esterna (lato molti) e il numero 1 al nome della chiave primaria (lato uno).

L’integrità referenziale Con questo termine, dal suono un po’ minaccioso e vagamente medico-legale, ci si riferisce a un obiettivo che è essenziale raggiungere quando si crea un database: garantire che i riferimenti da un record a un altro in una tabella diversa non vengano mai persi quando si eliminano record nelle tabelle master o in quelle correlate. Torniamo all’esempio canonico di un’applicazione per gestire ordini di vendita. Se si elimina il record del cliente “PessimiPagatori Srl” dalla tabella Clienti mentre nella tabella Ordini esistono record di ordini di questa società, tali record restano per così dire “orfani”, perché non hanno più un record genitore nella tabella Clienti. Mettendosi da una prospettiva diversa, non avrebbe senso inserire record in una tabella Ordini se non esiste nella tabella Clienti un record al quale associare gli ordini (cioè, non può esistere un ordine senza un cliente che lo abbia emesso).

Il concetto di applicazione    17

In sintesi, la presenza di una relazione uno a molti fra due tabelle comporta anche l’imposizione di un vincolo di integrità referenziale, ovvero la catena dei riferimenti da cliente a ordini e da ordini a cliente non può essere interrotta. Quindi, se si vuole eliminare il record di un cliente dalla tabella Clienti, occorre eliminare prima tutti i record presenti nella tabella Ordini associati al cliente da eliminare. Nello stesso spirito, prima di poter aggiungere record nella tabella Ordini bisogna accertarsi che esista nella tabella Clienti un cliente al quale associarli e, se non esiste, è necessario crearlo. In un’applicazione che utilizza un database relazionale l’integrità referenziale va gestita con molta cura: l’esistenza di relazioni uno a molti prepara il terreno per questa gestione, ma di per sé non la garantisce. I meccanismi interni di Access consentono di gestire automaticamente il vincolo dell’integrità referenziale quando è stata creata una relazione uno a molti fra tabelle.

Come si acquisiscono i dati Dopo aver definito il disegno complessivo del sistema, individuato i dati che servono e stabilito il modo in cui tali dati vanno aggregati in tabelle, la quarta e ultima fase del progetto di un’applicazione è quella nella quale si definiscono gli input. Come si devono acquisire i dati? Come verificare che i dati immessi siano corretti e che si possano inserire nelle tabelle senza creare ambiguità o incongruenze? I dati possono arrivare ai campi dei record nelle tabelle di un database mediante meccanismi molto diversi: possono essere digitati a mano, campo per campo, da operatori umani o essere letti in un colpo solo da un file creato a questo scopo da un’altra applicazione. Inoltre, per soddisfare determinate esigenze applicative, a volte non occorre immettere nuovi dati, ma si devono selezionare in modo opportuno dati contenuti in campi di record già esistenti ed eseguire su tali dati calcoli matematici, ordinamenti o selezioni logiche. Quali che siano la fonte dei dati e lo strumento che si utilizza per acquisirli, è necessario predisporre in questa quarta fase una serie di meccanismi di controllo della correttezza degli input, detti genericamente regole di convalida.

Regole di convalida La prima e fondamentale regola di convalida nei database Access viene stabilita all’atto della creazione delle tabelle. Ogni tabella può contenere un numero praticamente illimitato di record (fino a due gigabyte in Access 2007/2010) tutti con lo stesso numero di campi. Ciascun campo può contenere dati di vari tipi: lettere, numeri, informazioni su data e ora, valori logici (sì/no; vero/falso). Se un campo è stato definito per contenere una data, per esempio, non accetterà dati in input che non rappresentino date corrette, come per esempio “32/12/99” o “1221-1997”. La salvaguardia che si ottiene col meccanismo della definizione dei tipi di dati può intercettare gli errori di input più grossolani, ma da sola non basta. Se siamo nel mese di gennaio 2011 e immettiamo la data “10/10/2011” come valore in un campo DataOperazione, probabilmente commettiamo un errore, perché attribuiamo all’operazione una data futura. Il valore “10/10/2011”, in questo caso, è formalmente corretto se immesso in un campo che ha come tipo dati Data/ora, ma è funzionalmente sbagliato. Per prevenire errori di questo genere, si hanno a disposizione in Access numerosi meccanismi, che

18  Capitolo 1

consentono di fissare intervalli di validità per valori numerici e per informazioni di calendario o di orologio. Altri meccanismi, più raffinati, consentono di limitare l’input selezionandolo da un elenco predefinito, che può risiedere in una tabella di servizio. Per esempio, se si deve immettere il nome di una filiale o di un prodotto, si possono prevenire errori materiali di battitura predisponendo un elenco chiuso di nomi di filiali o di prodotti, fra i quali l’operatore può scegliere direttamente, usando il mouse o i tasti freccia, senza digitare nulla. Regole di convalida ancora più raffinate possono stabilire che un determinato input è obbligatorio o facoltativo e, quando è obbligatorio, verificare che sia coerente con altri valori immessi in precedenza. Infine, le regole per la salvaguardia dell’integrità referenziale possono garantire che non vengano creati nelle tabelle correlate record privi di riferimento e che non vengano eliminati record nelle tabelle master quando esistono ancora record associati in tabelle correlate. Un etto di prevenzione rende molto più di un chilo di cure e le regole di convalida sono la miglior forma di difesa contro il rischio di inquinamento e deterioramento delle informazioni contenute in un database.

L’interfaccia utente I meccanismi di acquisizione dei dati concorrono a determinare il modo in cui l’applicazione si presenta ai suoi utilizzatori, cioè l’interfaccia utente. Le applicazioni costruite per lavorare sotto il sistema operativo Windows presentano le loro funzionalità mediante finestre, barre degli strumenti e menu, per sfruttare la familiarità che gli utenti già hanno con questi veicoli di presentazione delle opzioni e dei comandi disponibili. Le tipiche “finestre” che nell’ambiente Windows permettono di accedere a documenti esistenti o di crearne di nuovi, in Access sono oggetti software chiamati maschere. Con le maschere si possono visualizzare in molti modi diversi i contenuti delle tabelle che formano il database, consentendo all’utente di esaminare dati e di aggiungere, eliminare o modificare record (rispettando le regole di convalida). Maschere particolari permettono di attivare processi automatici, quali la fusione di record da più tabelle in un’unica tabella, la stampa di prospetti (chiamati nel gergo di Access report), l’accesso a risorse esterne all’applicazione (sul computer dell’utente, su un altro computer in rete locale, in una intranet o nella Internet), il richiamo di informazioni da altre applicazioni (da documenti Word o da fogli di lavoro Excel) e altro ancora. Le funzionalità delle maschere sono determinate fondamentalmente dalle funzionalità degli oggetti che le compongono, i controlli, con i quali si presentano testi descrittivi, si consente l’accesso a specifici campi dei record e si rendono disponibili comandi e opzioni. Le maschere con i loro controlli si aprono nel contesto della finestra Database, l’ambito nel quale si svolgono tutte le operazioni previste dall’applicazione. In tale contesto, l’applicazione dovrà presentare menu e barre degli strumenti specificamente orientati alle sue funzionalità, avendo cura di rispettare per quanto è possibile le convenzioni adottate comunemente dai menu e dalle barre degli strumenti di tutte le applicazioni Windows, in modo da non disorientare l’utente e, anzi, rendergli più agevole l’utilizzo degli strumenti offerti da quella specifica applicazione.

Il concetto di applicazione    19

Un’applicazione con un’interfaccia utente sgangherata non farà mai molta strada, anche se fosse ben progettata e avesse ottime funzionalità. Utilizzando in modo coerente e sistematico gli oggetti tipici di Windows (menu, barre degli strumenti, caselle di riepilogo, caselle combinate, pulsanti di comando, pulsanti di opzione e via enumerando), una buona interfaccia utente deve: •• far capire in ogni momento all’utente dove si trova e che cosa può fare; •• segnalare gli errori e consentire sempre una via d’uscita. Nello stesso spirito, una buona interfaccia utente non deve mai consentire all’utente la modifica dell’applicazione o di suoi oggetti specifici: come vedremo nei prossimi capitoli, l’inibizione delle modifiche (casuali o deliberate) a un’applicazione è una delle ragioni principali per ricorrere agli strumenti di programmazione di Access. Le quattro fasi principali in cui si articola il progetto di un’applicazione database sono sintetizzate graficamente nella Figura 1.5.

Figura 1.5  Le quattro fasi del progetto di un’applicazione database.

20  Capitolo 1

Realizzare applicazioni database con Access L’ambiente di lavoro Access offre almeno uno strumento (e in molti casi più di uno) per realizzare ogni singola attività in cui si articola il progetto di un’applicazione database. A condizione di aver ben definito l’esigenza applicativa, le sue finalità, le risorse disponibili e gli utenti da servire, tutto quello che è creazione di record e di tabelle, messa a punto di report, di maschere e di controlli si può realizzare agevolmente ricorrendo alle creazioni guidate disponibili in Access. L’espressione italiana “creazione guidata” traduce sobriamente l’enfatico termine originale americano Wizard, cioè Mago. In effetti, di magico non c’è nulla: le creazioni guidate sono programmi interni al sistema Access, che utilizzano schemi predefiniti, con opzioni aperte, che vengono presentate all’utente per fargli scegliere i risultati che intende ottenere. Esistono creazioni guidate per quasi tutte le fasi di sviluppo di un’applicazione database: dalla creazione semiautomatica di un intero database, alla generazione di tabelle, query, maschere, controlli e report. E ancora, vi sono creazioni guidate per importare o esportare flussi di dati, per impostare le regole di sicurezza per un database e per altre operazioni complesse. Questa abbondanza di strumenti ha lo scopo di rendere semplice la creazione di un database. E l’obiettivo viene senz’altro raggiunto. C’è però un problema: tutto quello che si ottiene è, appunto, un database attrezzato di tutto punto con maschere per esaminare le tabelle, modificare dati esistenti o aggiungere nuovi record, stampare report standard e così via. Ma non è possibile ottenere automaticamente un’applicazione database, perché gran parte delle funzionalità essenziali che distinguono un’applicazione da un database attrezzato non sono previste dalle creazioni guidate, e neppure potrebbero esserlo: se si vuole ottenerle, bisogna impegnarsi seriamente con gli strumenti di programmazione disponibili all’interno di Access. Beninteso, le creazioni guidate non sono affatto inutili, anzi: sfruttandole nel modo più opportuno si può creare in un paio di giorni il prototipo completo di un’applicazione, da presentare all’utente finale per verificare fino a che punto le finalità dell’applicazione che ha richiesto sono state individuate e raggiunte. Il feedback che è possibile ottenere in questo modo è estremamente importante, perché contribuisce a spazzare via equivoci e malintesi (sempre in agguato, quando si tratta di capire che cosa uno vuole ottenere da un’applicazione), aiuta a individuare nuove opportunità e a scartare soluzioni che si credevano praticabili e che si dimostrano, già a livello di prototipo, incongrue o inadeguate. Anche quando committente dell’applicazione e realizzatore sono la stessa persona è opportuno creare in via preliminare un prototipo servendosi delle creazioni guidate. Per quanto potente sia l’immaginazione di una persona, difficilmente può farsi un’idea completa di come si presenterà un’applicazione se non prepara almeno una bozza e il prototipo che può ottenere con una creazione guidata è un’ottima bozza preliminare. Chi ha già fatto un po’ di esperienza realizzando applicazioni database, può trovare conveniente definire a mano campi, record, tabelle e relazioni fra tabelle e sfruttare le creazioni guidate per creare bozze preliminari di maschere e report, da affinare e mettere a punto in seguito, dopo aver definito le funzionalità complessive che vuole ottenere. Nel prossimo capitolo vedremo come si realizzano prototipi e applicazioni funzionanti ricorrendo all’interfaccia grafica e alle creazioni guidate di Access.

Il concetto di applicazione    21

Le versioni di Access Nel 1992 venne presentata sul mercato la prima versione di Access, chiamata 1.0, che lavorava sotto il sistema operativo Windows 3.0. Nei dieci anni precedenti, che erano stati anche i primi dieci anni di vita dei personal computer di tipo IBM (con processore Intel e sistema operativo Microsoft-DOS), si era affermato sul mercato un ottimo prodotto per lo sviluppo di sistemi di database relazionali chiamato dBASE, che si era diffuso notevolmente nel mondo applicativo, era dotato di funzionalità di tutto rispetto e aveva un’ampia base di utenti professionali. Rispetto a dBASE, Access 1.0 aveva due punti di forza potenziali: 1. lavorava sotto Windows e non sotto DOS, quindi era più facile e intuitivo da utilizzare; 2. tutte le tabelle di un database, i loro indici e le routine di programmazione che si creavano per agire sulle tabelle risiedevano in un unico file, mentre i database creati con dBASE si articolavano in tanti file DOS separati, uno per ogni tabella, indice e routine di programmazione. Questi punti di forza, uniti a una poderosa campagna promozionale di Microsoft e al prestigio dell’immagine della casa produttrice consentirono ad Access 1.0 di diffondersi molto rapidamente, conquistando nuovi utenti al mondo dei database relazionali ed erodendo anche una quota non piccola dell’utenza storica di dBASE. Una grave limitazione di Access 1.0 derivava dal fatto che quell’unico file nel quale risiedevano tabelle, indici e routine non poteva superare i 128MB. Questa limitazione fu eliminata con la versione 1.1, che venne diffusa pochi mesi dopo la 1.0: il limite superiore di un file Access venne portato a 1GB, cosa che per quell’epoca (quando un disco rigido di 100MB era una rarità) rappresentava quasi un limite utopistico. Poco più di un anno dopo, in concomitanza con l’uscita di Windows 3.1, Microsoft mise in distribuzione Access 2.0, un notevole passo avanti rispetto alla versione 1.1, soprattutto per l’aggiunta di funzionalità specifiche per la programmazione, in particolare una versione di Visual Basic chiamata Access Basic, dotata di un suo specifico ambiente di sviluppo chiamato Integrated Development Environment (IDE). Nella seconda metà del 1995, dopo l’uscita trionfale di Windows 95, il rifacimento a 32 bit del vecchio Windows 3.x a 16 bit, venne presentato Access 95, le cui strutture portanti (il motore per database Jet e il sistema degli oggetti DAO) erano state anch’esse riprogrammate per lavorare su 32 bit invece che su 16. Al posto di Access Basic era subentratoVisual Basic for Applications, dotato di un IDE migliorato. Con Access 95 veniva anche introdotta una funzionalità che consentiva di creare repliche di un database, che si potevano aggiornare in contesti separati facendo poi convergere le varie modifiche sulla versione d’origine. Due anni dopo uscì Access 97, che migliorava alcune prestazioni e funzionalità di Access 95, fra le quali: •• un nuovo tipo di dati Collegamento ipertestuale; •• l’Autocomposizione Pubblicazione sul Web, che agevolava il processo per rendere disponibili dati Access in forma statica o dinamica nella rete Internet o in una intranet aziendale; •• possibilità di esportare dati da tabelle Access in formato HTML;

22  Capitolo 1

••

possibilità di definire maschere e report leggeri, senza modulo in codice VBA, che si caricano più rapidamente; •• un nuovo controllo Struttura a schede per creare maschere con la struttura delle finestre di dialogo a schede; •• possibilità di agire da programma su menu e barre degli strumenti per modificarli/ crearli, tramite l’insieme CommandBars e l’oggetto CommandBar; •• nuove Proprietà delle query (TipoRecordset, FailOnError e MaxRecords); •• supporto dei moduli di classe; •• potenziamento dell’ambiente di sviluppo per VBA, con nuove funzionalità per elencare automaticamente oggetti, proprietà e metodi fra i quali scegliere mentre si scrive una routine in VBA; •• supporto di repliche parziali e di repliche su Internet; •• supporto di un nuovo tipo di connessione client-server chiamato ODBCDirect; •• un nuovo tipo di formato (MDE) per i file database, che rendeva invisibile tutto il codice sorgente VBA, al fine di impedire copie e modifiche non autorizzate del codice stesso. L’uscita di Access 2000 si colloca fra la fine del 1999 e l’inizio del 2000. Rispetto ad Access 97 le novità sono notevoli e comprendono fra l’altro: •• nuovo aspetto della finestra del database, basato su uno schema grafico omogeneo con quello di altri strumenti Office della stessa versione; •• versione 6.0 di VBA e un editor condiviso con le altre applicazioni Office; •• nuove funzioni e parole chiave VBA: StrRv, MonthName, Split, Join, Replace, AddressOf, Debug.Assert e Implements; •• regole di formattazione condizionale per i dati visualizzati in caselle di testo o in caselle combinate; •• possibilità di associare maschere a Recordset; •• visualizzazione automatica, tramite un Foglio dati secondario, dei dati collegati con una tabella quando questa è aperta in visualizzazione Foglio dati; •• un intero nuovo componente di interfaccia, la Pagina di accesso ai dati, che si aggiunge alle maschere e ai report per creare rappresentazioni di dati Access che vengono create anche in file esterni a quello del database e sono visualizzabili mediante un browser web; •• progetti Access (Access Data Project: ADP): file creati in Access (con tutte le funzionalità di accesso ai database tipiche dell’interfaccia grafica) che si correlano con tabelle database residenti in un database Microsoft SQL Server, un sistema per database relazionali concepito per operare su macchine server di dimensioni medio-grandi; •• una Creazione guidata per la gestione delle tabelle collegate; •• nuovo modello a oggetti ADO, per la gestione degli accessi ai dati, utilizzabile in alternativa al modello DAO, che continua a essere supportato; •• aumento a 2GB della dimensione massima di un database; •• snellimento delle procedure per la definizione della sicurezza a livello di utente;

Il concetto di applicazione    23

••

possibilità di compattare un database mentre è aperto o automaticamente al momento della chiusura. La versione successiva, Access 2002, messa in circolazione alla fine del 2001, aggiunge funzionalità ad Access 2000, con molte innovazioni, ma conservando un forte legame con la versione precedente, al punto che, per impostazione predefinita, in Access 2002 un nuovo file database (.mdb) viene fatto nascere nel formato di Access 2000. Le novità più significative di Access 2002 sono le seguenti: •• le Pagine di accesso ai dati sono diventate uno strumento comodo da utilizzare e più ricco di funzionalità di quante ne avessero nella versione precedente, dove erano difficili da gestire e piuttosto rigide e limitate nelle prestazioni; •• maschere e report si possono ora salvare come pagine di accesso ai dati; •• le relazioni fra Access e SQL Server sono ancora più agevoli grazie alla presenza di una versione per macchine indipendenti del motore di SQL Server, il SQL Server 2000 Desktop Engine; •• nuova versione dello strumento per la gestione guidata delle tabelle collegate, basata sui servizi di collegamento di SQL Server; •• due nuove opzioni di visualizzazione – Tabella pivot e Grafico pivot – per tabelle e maschere si aggiungono alle due visualizzazioni tradizionali (Tabella/Maschera e Struttura); •• versione 6.3 del linguaggio VBA; •• possibilità di esportare e importare dati nei nuovi formati standard XML; •• possibilità di esportare anche maschere, report e pagine di accesso ai dati in file XML/XSL; •• numerose nuove proprietà ed eventi per i report e per alcuni controlli. Per quanto riguarda Access 2003, messo sul mercato alla fine del 2002, tutte le funzionalità di fondo sono rimaste identiche a quelle di Access 2000/2002, con l’aggiunta di alcuni arricchimenti che rendono più comodo il lavoro di sviluppo dei database e delle applicazioni che li utilizzano. In particolare: •• un nuovo comando Dipendenze oggetti del menu Visualizza apre una scheda nel Riquadro Attività che presenta in forma grafica l’elenco degli oggetti (tabelle, query, maschere e report) che dipendono da un oggetto selezionato e quelli da cui lo stesso oggetto dipende. Nella costruzione e nella successiva manutenzione di un database di grandi dimensioni, questa funzionalità può essere molto comoda per individuare le possibili conseguenze dell’eliminazione di un oggetto esistente; •• numerosi errori di struttura che possono insinuarsi in maschere e report durante la loro realizzazione vengono segnalati automaticamente; •• quando si modifica le proprietà di un campo di una tabella in visualizzazione Struttura, la modifica può essere propagata automaticamente ad alcuni o a tutti i controlli che utilizzano quel campo in maschere e report; •• è possibile aggiungere uno smart tag a qualsiasi campo incluso in una tabella, in una query, in una maschera, in un report o in una pagina di accesso ai dati di un database; •• un nuovo comando, Backup database, consente di creare direttamente una copia di sicurezza del database attivo, salvandola in una posizione predefinita o nella cartella corrente;

24  Capitolo 1

••

le Creazioni guidate Casella di riepilogo e Casella combinata, disponibili per maschere e report, sono state arricchite di ulteriori funzionalità per l’ordinamento; •• si possono creare direttamente copie della sola struttura o della struttura e dei dati di tabelle collegate, trasformandole quindi da collegate a locali; •• la funzionalità di Guida che si può attivare selezionando una parola chiave entro una routineVBA e premendo il tasto F1 è ora disponibile anche per le parole chiave degli enunciati SQL quando sono presentati nella finestra Visualizzazione SQL; •• quando si esportano o si importano dati in XML è possibile specificare un file di trasformazione. Nel 2007 Microsoft fa uscire una versione radicalmente modificata di Office e di tutti i prodotti che appartengono a quella famiglia, intervenendo con determinazione soprattutto sull’interfaccia utente, cioè sugli strumenti grafici che contengono i comandi e li rappresentano: tutti gli strumenti di Office 2007, compreso Access, non hanno più le tradizionali barre dei menu e degli strumenti, che vengono sostituite da un unico blocco grafico, chiamato in inglese “Ribbon”. Il nome inglese caratterizza molto bene il nuovo strumento, una specie di nastro multicolore che domina la parte superiore della finestra dell’applicazione, articolandosi in varie schede dove sono raggruppati tutti i comandi necessari per lavorare. Per la versione italiana di Office 2007 è stato scelto di rendere “Ribbon” con il più austero (e burocratico) termine “barra multifunzione”. Per quanto riguarda le funzionalità, le novità più rilevanti di Access 2007 rispetto alle versioni precedenti sono: •• un nuovo tipo di dati, chiamato Allegato, col quale si possono associare più file di qualunque tipo a un campo di un record;

••

la possibilità di formattare in modalità Rich Text Format (RTF) il testo contenuto in un campo di tipo Memo;

•• ••

la possibilità di predisporre più valori in uno stesso campo di un record;

•• ••

nuovi tipi di maschere, composte con più elementi;

••

nuovo Riquadro di spostamento, che consente di presentare in molti elenchi strutturati diversamente gli oggetti contenuti nel file Access, per semplificarne la gestione quando sono molto numerosi;

•• ••

sistema di protezione completamente rinnovato;

una nuova visualizzazione per tabelle, maschere e report, chiamata Layout, che si aggiunge alle visualizzazioni tradizionali Struttura e Foglio dati/Maschera/Anteprima di stampa; possibilità di ottenere – nella visualizzazione Foglio dati delle tabelle – una riga di valori calcolati per le colonne che contengono dati di tipo numerico, selezionando il tipo di risultato che si vuole ottenere: conteggio, somma, media, massimo, minimo; per le colonne con tipo dati non numerico si può ottenere soltanto il conteggio;

modifiche strutturali e arricchimento delle macro.

Il concetto di applicazione    25

Alcune funzionalità sono state eliminate, per cui non sono più disponibili la Replica, le Pagine di accesso ai dati e gli Access Data Project. È cambiato anche il suffisso che identifica i file Access, i cui nomi adesso usano l’estensione .accdb invece di quella storica .mdb utilizzata in tutte le versioni precedenti. I file creati con Access 2000/2002/2003 vengono regolarmente aperti con Access 2007, mantenendo tutte le loro funzionalità. Nella seconda metà del 2010, Microsoft lancia sul mercato Office 2010, che comprende anche Access 2010, le cui funzionalità sono sostanzialmente identiche a quelle di Access 2007, con l’aggiunta di alcune interessanti novità, in particolare: •• interfaccia utente fortemente snellita e alleggerita; •• al posto del Pulsante Office, una linguetta specializzata della Barra multifunzione, identificata dalla dicitura File su sfondo rosso, dà accesso alle funzionalità di gestione del database attivo e di creazione di nuovi database; •• la Barra multifunzione o Ribbon può essere personalizzata molto più agevolmente; •• un nuovo tipo di dato, detto Calcolato, per i campi delle tabelle; •• un’interfaccia utente radicalmente rinnovata e migliorata per la creazione e la gestione delle macro; •• nuove azioni macro da associare a eventi delle tabelle con le quali creare nuovi tipi di macro, dette macro di dati: •• nuovi tipi di file database predisposti per essere salvati in un sito web, dove sono fruibili anche mediante un browser, oltre che con Access. I nomi commerciali delle versioni di Access inizialmente sono stati attribuiti con la stessa convenzione che Microsoft utilizzava per le versioni del sistema operativo MS-DOS: un primo numero per indicare la versione principale, seguito da un punto e da un secondo numero che indica una variante non radicale della versione principale (se il secondo numero è 0 la versione è quella indicata dal primo numero, senza variazioni). Con il rifacimento di Windows, messo sul mercato nel 1995, Microsoft abbandonò la convenzione precedente sia per Windows sia per i suoi prodotti applicativi, adottando il numero dell’anno di uscita (95, 98, 2000, 2002, 2003). La nuova convenzione, però, non è stata sempre rispettata, probabilmente per ragioni di marketing: la versione di Windows che nel 2000 prese il posto di Windows 98 venne commercializzata col nome Windows ME (da Millennium Edition); alla versione successiva, uscita nel 2002, si assegnò il nome di Windows XP, dove XP stava per eXPerience. Una versione di Windows, profondamente rinnovata, viene messa sul mercato nel 2007 col nome commerciale di Windows Vista, sostituita dopo un paio d’anni da una nuova versione, più leggera e funzionale, chiamata Windows 7. Le versioni di Access vennero identificate agli inizi con il tradizionale codice numerico a due cifre: Access 1.0 (uscito nel 1992 e seguito quasi subito dopo da Access 1.1, che rimediava ad alcune gravi lacune), poi Access 2.0, che operava sotto Windows 3.1 (la versione a 16 bit di quel sistema operativo).Vennero poi Access 95, uscito contestualmente con Windows 95, e poi altre cinque versioni, sempre identificate con l’anno di uscita, con la stessa cadenza delle corrispondenti versioni di Microsoft Office.

26  Capitolo 1

Quando si lavora con gli strumenti interni di Access (motori per database, linguaggi di programmazione, librerie dei tipi e oggetti in generale), la distinzione fra le versioni è data da un semplice numero, secondo il prospetto seguente: Access 97 Access 2000 Access 2002 Access 2003 Access 2007 Access 2010

8 9 10 11 12 14

In questo libro ci occupiamo della creazione di applicazioni con Access, tenendo in considerazione la versione 2010 di questo strumento, che a prima vista appare molto diversa dalle precedenti. In realtà, esiste un nucleo duro, comune a tutte le versioni dello strumento, che ci permette di parlare genericamente di Access nel trattare tutte le funzionalità principali; quando presenteremo caratteristiche peculiari di Access 2010 avremo cura di indicarle.

Capitolo 2

Gli strumenti interattivi dell’interfaccia grafica I sistemi operativi Microsoft Windows si chiamano così perché utilizzano finestre per presentare informazioni e ricevere comandi. Coerentemente con l’ambiente in cui operano, anche i programmi applicativi si servono di finestre per interagire con gli utenti. La finestra di un programma applicativo si chiama finestra dell’applicazione e tale finestra presenta un insieme di dati, detto convenzionalmente documento, per cui il contenuto della finestra di un’applicazione si chiama finestra del documento. In alcune applicazioni, quali per esempio Word ed Excel, è possibile aprire più documenti nella finestra dell’applicazione e lavorare a turno su ciascuno di essi, eventualmente trasferendo dati direttamente da un documento a un altro. Nel caso di Access, la finestra dell’applicazione può presentare un solo documento per volta, il database, e la finestra del documento prende il nome di finestra Database.

Ambiguità Nella Guida di Access 2010 il termine “finestra Database” viene usato anche per riferirsi alla sezione della schermata di lavoro che prende il nome di “Riquadro di spostamento”, chiamando “finestra del documento” l’area nella quale compaiono gli oggetti Access – tabelle, maschere, query e così via – quando vengono aperti usando una delle loro modalità di visualizzazione specifiche. Nel libro useremo sempre il termine “finestra Database” per riferirci all’area lasciata libera dal Riquadro di spostamento.

In questo capitolo •

Creazione di un database vuoto



La finestra dell’applicazione



Creazione delle tabelle



La finestra Struttura di una tabella e le proprietà dei campi



I tipi di dati dei campi



Rispunta l’esigenza applicativa



Modificare l’ordine dei record



Modificare i criteri di visualizzazione



Le azioni Macro



I limiti degli strumenti interattivi

28  Capitolo 2

Per lavorare con Access occorre mettere qualcosa nella finestra Database, cioè aprire un file Access esistente o crearne uno nuovo. Se si opta per la creazione di un nuovo file, si può scegliere fra creare un database vuoto o generarne uno predefinito, selezionandolo fra i modelli elencati nella finestra di avvio di Access 2010.

Database web Che si decida di creare un database vuoto o di crearne uno utilizzando un modello esistente, si può (anzi, si deve) optare fra la creazione di un database da utilizzare nel computer dove lo si crea, eventualmente in collegamento con una rete LAN, e la creazione di un database web: di che cosa si tratta? Una importante novità di Access 2010 è la possibilità di creare database destinati a essere salvati in un sito web particolare, dove si possono aprire e consultare con un qualunque browser Internet invece che con Access e per questo sono chiamati database web. Un database web si crea localmente come un database normale, lo si completa con dati e funzionalità opportune e successivamente lo si trasferisce in un server web: esamineremo in modo approfondito queste operazioni nel Capitolo 16.

Un clic sull’icona Nuovo database vuoto al centro della schermata, fa aprire una finestra di dialogo nell’angolo inferiore destro: vediamo i due eventi nella Figura 2.1. Questo capitolo è dedicato a una rassegna degli strumenti che si hanno a disposizione per creare con tecniche grafiche e interattive un’applicazione database. Per dare concretezza alla rassegna, creeremo una semplice applicazione, montandola gradualmente, un oggetto per volta, senza però ricorrere a un modello predefinito, perché questo strumento non fa vedere i singoli passaggi. L’esigenza applicativa che intendiamo soddisfare è molto semplice, al limite del banale: gestire in modo uniforme gli indirizzi di tutte le persone e le istituzioni con le quali abbiamo un rapporto di conoscenza, di lavoro o di amicizia. In breve, vogliamo creare una rubrica di indirizzi. Abbiamo scelto questo esempio perché, per realizzare una rubrica di indirizzi, non occorre un impegno rilevante di analisi e valutazione preliminare degli output per risalire da questi alla definizione e alla normalizzazione delle tabelle necessarie. Quel che vogliamo ottenere con Access è l’equivalente – computerizzato – del libriccino che tutti portiamo in tasca, nel quale annotiamo man mano gli indirizzi e i numeri di telefono delle nuove conoscenze e aggiorniamo le stesse informazioni per le persone che già conosciamo, quando qualcuna cambia casa o si dota di un telefono cellulare o di una linea dedicata per il fax.

Creazione di un database vuoto Su questa semplice traccia, possiamo avviare la nostra esplorazione e nella finestra di dialogo che si è aperta nell’angolo inferiore destro della schermata iniziale di Access 2010 (si veda sopra, la Figura 2.1), facciamo clic sull’icona della cartella aperta, ottenendo l’apertura della finestra di dialogo Salva nuovo database, riportata nella Figura 2.2.

Gli strumenti interattivi dell’interfaccia grafica   29

Figura 2.1  La scelta di creare un nuovo database Access 2010.

Figura 2.2  La finestra di dialogo Salva nuovo database.

30  Capitolo 2

È una variante della finestra di dialogo standard Salva con nome di Windows, nella quale si può definire un percorso, eventualmente creare al momento una cartella di destinazione e assegnare un nome al file. Digitiamo Rubrica01 come nome del nuovo database nella casella di riepilogo Nome file e lo salviamo in una nuova cartella che creiamo sul disco C: chiamandola Esempi. Concludiamo l’operazione con un clic sul pulsante OK. Access esegue le operazioni richieste e crea un file Rubrica01.accdb: l’estensione .accdb caratterizza i file creati con Access 2007 o 2010; l’elenco a discesa della casella combinata Tipo file consente di selezionare anche due formati alternativi, che saranno identificati dall’estensione .mdb, allo scopo di ottenere un nuovo database nel formato di Access 2000 o 2002/2003. Anche se Access 2010 non supporta più gli Access Data Project, fra l’elenco dei tipi di file che si possono creare c’è anche questo, che usa l’estensione .adp. Subito dopo la creazione del nuovo file Access, questo viene aperto mostrando l’imponente finestra dell’applicazione che vediamo nella Figura 2.3.

La finestra dell’applicazione La finestra dell’applicazione è dominata da due elementi grafici collocati in alto: a sinistra della barra del titolo troviamo la Barra di accesso rapido, subito sotto la Barra multifunzione. Nella parte centrale della schermata si trova la finestra Database, sul cui lato sinistro si colloca il Riquadro di spostamento. Il bordo che chiude la schermata è la Barra di stato. Prima di procedere con la creazione del nostro database passiamo brevemente in rassegna gli elementi grafici che sono riportati nella Figura 2.3; questi sono la novità più evidente di Access 2010 rispetto alle versioni precedenti. Comando File   Barra di accesso rapido   Barra multifunzione       Riquadro di spostamento   Finestra Database    Barra di stato

Figura 2.3  La finestra dell’applicazione di Access 2010.

Gli strumenti interattivi dell’interfaccia grafica   31

Il pulsante File e la finestra Backstage Un clic sul pulsante di comando rosso con la dicitura File apre l’elenco di comandi che vediamo nella Figura 2.4 e che prende il nome di finestra Backstage. Come il nome lascia capire, da questa finestra si accede a comandi e funzioni che operano “dietro le quinte” del file Access che in questo momento è aperto. La finestra Backstage è articolata in fasce verticali, la prima a sinistra è suddivisa in tre segmenti, che contengono altrettanti gruppi di comandi. Il primo gruppo è formato da comandi che si possono eseguire in riferimento al database aperto (Salva database con nome, Chiudi database) oppure all’oggetto selezionato nel momento in cui è stata aperta la finestra Backstage (Salva, Salva oggetto con nome). Il comando Apri chiude il database corrente e apre una finestra Windows di esplorazione dei file dove si può cercare e selezionare un file database da aprire al posto di quello che è stato chiuso (come abbiamo indicato, in tutte le versioni di Access e la 2010 non fa eccezione, è possibile aprire – e quindi lavorare su – un solo file database per volta). Il secondo gruppo è un elenco dei nomi degli ultimi quattro file database che sono stati aperti: un clic su uno di questi nomi fa chiudere il file attivo e aprire quello selezionato. Il terzo gruppo di comandi della finestra Backstage è composto dai comandi Informazioni, Recente, Nuovo, Stampa, Salva e pubblica, Guida e Opzioni: quando si apre questa finestra, viene eseguito per impostazione predefinita il comando Informazioni, come vediamo nella Figura 2.4.

Figura 2.4  Il comando Informazioni nella finestra Backstage.

Il comando Informazioni presenta in una fascia centrale le informazioni complete (nome e percorso) sul file database aperto in questo momento e due sottocomandi per la gestione

32  Capitolo 2

di questo file: Compatta e ripristina e Crittografa tramite password; nella fascia verticale di destra è visualizzata una icona della finestra Database, con un comando Visualizza e modifica le proprietà del database: un clic su quel comando apre la finestra di dialogo a schede Proprietà, presente anche in altre applicazioni Office, in particolare Word ed Excel, nella quale si possono digitare informazioni complementari sul file database attivo. Il comando Recente presenta un elenco degli ultimi file database che sono stati aperti; l’impostazione predefinita (che si può modificare a piacere) elenca 17 file, ciascuno individuato dal suo nome e percorso completi: l’elenco è dinamico, nel senso che i nomi dei file aperti più di recente vanno a sostituire quelli dei file più vecchi, mantenendo costante il numero dei file elencati. Se si desidera che nell’elenco compaia sempre un determinato file, un clic sull’icona dello spillone che è affiancata al nome di quel file modifica l’aspetto dell’icona e rende permanente il nome del file nell’elenco.

Per sbloccare il file si fa clic sull’icona dello spillone. Il comando Nuovo apre le finestre che abbiamo visto nella Figura 2.1. Col comando Stampa si rendono disponibili tre sottocomandi: Stampa immediata, Stampa e Anteprima di stampa, a condizione che sia stato selezionato un oggetto database nel riquadro di spostamento o nella finestra Database (questi sottocomandi agiscono soltanto su una tabella, una maschera, una query o un report, mai sull’intero database). Il sottocomando Stampa è diverso dal sottocomando Stampa immediata, in quanto consente di selezionare una stampante, mentre con Stampa immediata l’output viene inviato direttamente alla stampante predefinita. Il sottocomando Anteprima di stampa presenta, come dice il suo nome, un’immagine del risultato che si otterrebbe eseguendo il sottocomando Stampa o Stampa immediata. Con il comando Salva e pubblica si accede ad alcune funzionalità introdotte con Access 2007 e ulteriormente potenziate nella versione 2010. Questo comando apre due fasce verticali a destra della prima nella finestra Backstage. Nella fascia centrale sono disponibili i comandi Salva database con nome e Salva oggetto con nome, che abbiamo già visto in precedenza. Subito sotto abbiamo i comandi Pubblica in Access Services (sotto l’intestazione Pubblica) e Pacchetto (sotto l’intestazione Crea pacchetto e distribuisci): approfondiremo questi due comandi rispettivamente nei Capitoli 16 e 18. Un clic sul comando Salva database con nome fa aprire nella fascia verticale più a destra un elenco di opzioni alternative per il salvataggio del database corrente, vale a dire: •• salvare il database con un nome diverso, ma nel formato predefinito (che è quello di Access 2007 anche per Access 2010) identificato dall’estensione .accdb; •• salvare il database nel formato di Access 2002-2003, quindi in un file con estensione .mdb; •• salvare il database nel formato di Access 2000, anche in questo caso in un file con estensione .mdb;

Gli strumenti interattivi dell’interfaccia grafica   33

••

salvare il database come modello per creare altri database in formato 2007, quindi in un file con l’estensione .accdt. A queste opzioni di salvataggio si aggiunge la possibilità di creare: •• una versione del file database in un formato (ACCDE) che non consente di esaminare/modificare il contenuto delle macro e dei moduli VBA; •• una versione compilata (pacchetto) del database associata a una firma digitale; •• un backup del file database così come è al momento, con un nome convenzionale; Infine, è presente un’opzione detta SharePoint, con la quale si può salvare il database su un server Document Manager, dove può essere condiviso fra quanti hanno accesso a tale server (questa funzionalità è, ovviamente, utilizzabile se si dispone di un accesso ai servizi SharePoint, dei quali parliamo nel Capitolo 16). Per rendere operativa una qualunque delle opzioni elencate qui sopra e presentate nella terza fascia verticale del comando Salva e pubblica, la si seleziona e successivamente si fa clic sul pulsante di comando con l’icona del dischetto e la scritta “Salva con nome” che sta in fondo all’elenco delle opzioni. Se si seleziona l’opzione Salva oggetto con nome nella fascia centrale della pagina di Salva e pubblica, l’elenco delle opzioni che si apre nella terza fascia della pagina, intitolata Salvataggio oggetto di database corrente, si riduce a due soltanto: •• Salva oggetto con nome, per ottenere una copia con un nome diverso dell’oggetto selezionato (tabella, maschera o altro); •• PDF o XPS, con il quale si può scegliere di esportare l’oggetto database selezionato in un file nel noto formato PDF (Portable Document Format) promosso da Adobe o nel meno noto formato XPS (XML Paper Specification) promosso da Microsoft. Anche in questo caso, per rendere operativa una delle due opzioni, la si seleziona e quindi si fa clic sul pulsante di comando con l’icona del dischetto e la scritta “Salva con nome” che sta in fondo all’elenco delle opzioni. Dopo il comando Salva e pubblica, un comando Guida rende disponibili le funzionalità di guida in linea di Access 2010, che si articolano in una guida residente nel computer locale e in una più ricca di materiali, fra i quali interessanti video, residente nel sito Office.com, accessibile quando il comando Guida viene eseguito mentre il computer è collegato col Web. L’ultimo comando presente nella fascia verticale di sinistra della finestra Backstage si chiama Opzioni e fa aprire una schermata intitolata Opzioni di Access, molto ricca e articolata in undici pagine, accessibili con un clic sul loro titolo che compare in un riquadro a sinistra della schermata. Possiamo per il momento lasciare le impostazioni delle opzioni come sono definite automaticamente, salvo poche, ma importanti, eccezioni. Selezionando Generale nell’elenco di sinistra si apre la pagina Opzioni generali per l’utilizzo di Access, nella quale può essere utile modificare la Cartella database predefinita, se si lavora abitualmente con i dati salvati in un percorso diverso da quello predefinito, che corrisponde al percorso della cartella Documenti di Windows. La pagina Opzioni del database corrente, che si apre con un clic sul comando Database corrente, contiene tutte le opzioni disponibili per il database sul quale si sta lavorando. Qui, può essere utile impostare una delle due opzioni in alternativa per la finestra del documento: si può scegliere fra Finestre sovrapposte e Documenti a schede.

34  Capitolo 2

Figura 2.5  Il comando Salva e pubblica della finestra Backstage.

Nel primo caso, ogni oggetto Access che si apre nella sezione della finestra Database a destra del Riquadro di spostamento compare nella forma di una finestra ridimensionabile, sovrapponibile sulle altre: è una visualizzazione molto simile a quella delle versioni precedenti di Access; scegliendo, invece, l’opzione Documenti a schede, quella predefinita, gli oggetti che vengono aperti si presentano sovrapposti e con una linguetta che li distingue uno dall’altro. Nella stessa pagina è opportuno selezionare la casella di controllo Compatta alla chiusura: quando si fanno molte modifiche ai contenuti delle tabelle di un database il file che le contiene tende ad aumentare di volume, perché gli spazi lasciati vuoti dai record eliminati non vengono recuperati; se l’opzione Compatta alla chiusura è attivata, gli spazi vuoti vengono eliminati automaticamente quando si esce da Access. Le opzioni modificate in questa pagina diventano effettive dopo aver chiuso e riaperto il database corrente. Non è il caso di intervenire sulle opzioni predefinite impostate nelle pagine che si aprono con i comandi Foglio dati, Progettazione oggetti, Strumenti di correzione, Lingua, Impostazioni client e Componenti aggiuntivi. Le pagine che si aprono con i comandi Personalizzazione barra multifunzione e Barra di accesso rapido danno la possibilità di aggiungere o togliere comandi nella Barra multifunzione o nella Barra di accesso rapido: se non si hanno esigenze particolari, conviene lasciare le cose come stanno. Molto importante è la pagina che si apre con un clic sulla voce Centro protezione: la pagina è suddivisa in fasce orizzontali, l’ultima delle quali si intitola Centro protezione di Microsoft Office Access e contiene un pulsante di comando con la dicitura Impostazioni Centro protezione. Un clic su questo pulsante apre una pagina subalterna delle Opzioni di

Gli strumenti interattivi dell’interfaccia grafica   35

Access, intitolata Centro protezione, che vediamo nella Figura 2.6. Un clic sulla voce Percorsi attendibili presenta l’elenco dei percorsi che Access deve considerare come attendibili, cioè al riparo da virus e atti a immagazzinare file Access che contengono macro e/o moduli. Possiamo aggiungere nuovi percorsi attendibili a quello predefinito da Access, facendo clic sul pulsante di comando Aggiungi nuovo percorso. Nella figura vediamo che è già stato aggiunto un percorso da considerare come attendibile.

Figura 2.6  Dalla pagina Centro protezione si possono impostare percorsi attendibili per i file Access.

Se si apre un database da un percorso non impostato come attendibile, si riceve da Access la segnalazione che vediamo qui di seguito:

Un clic sulla dicitura apre la finestra Backstage sulla sezione Informazioni, nella quale si spiega che cosa ha portato Access a disattivare in parte il contenuto del file database e dove è possibile scegliere fra abilitare indiscriminatamente tutto il contenuto oppure fare una scelta più informata: in questo secondo caso si ottiene l’apertura della finestra di dialogo della Figura 2.7, dove possiamo abilitare (oppure no) il contenuto considerato a rischio. Se intendiamo lavorare con continuità su un determinato file Access, faremo bene a impostare il suo percorso fra quelli attendibili per risparmiarci il piccolo fastidio di dover abilitarne il contenuto ogni volta che lo si apre.

36  Capitolo 2

Figura 2.7  L’avviso di protezione quando si apre un database che contiene macro e non risiede in un percorso attendibile.

La barra multifunzione A destra del pulsante di comando File si sviluppa la barra multifunzione, che occupa tutta la larghezza della finestra dell’applicazione. Questo elemento grafico aggrega in un’unica struttura tutti i comandi disponibili per creare e utilizzare gli oggetti Access e si articola in un certo numero di schede, ciascuna delle quali contiene un gruppo di comandi. Come si vede dalla Figura 2.8, le schede comandi sono Home, Crea, Dati esterni e Strumenti database.

Figura 2.8  La barra multifunzione e le sue schede comandi.

Nella parte superiore della figura si vede l’aspetto della barra multifunzione quando la finestra dell’applicazione è ingrandita fino a occupare l’intero schermo; nella parte inferiore si vede come si presenta quando la finestra dell’applicazione è stata rimpicciolita: le quattro schede sono sempre visibili, con gli stessi gruppi di comandi, che però sono di dimensioni ridotte e presentano alla base una freccia a discesa: un clic sulla freccia

Gli strumenti interattivi dell’interfaccia grafica   37

presenta l’elenco dei comandi disponibili in ciascun gruppo. La barra multifunzione contiene altre schede oltre alle quattro che vediamo nella Figura 2.8: queste schede vengono visualizzate e i loro comandi sono attivabili in circostanze particolari. Quando si apre una tabella, alla barra multifunzione si aggiunge una scheda intitolata Strumenti tabella, che si articola in due schede subalterne, visualizzabili in alternativa: Campi e Tabella; compare nella Figura 2.3 e possiamo vederla meglio nella Figura 2.9. I gruppi di comandi disponibili nella scheda Campi servono per intervenire sulla struttura della tabella aperta nella finestra Database: aggiungere nuovi campi, stabilirne il tipo di dati, eliminare campi esistenti, impostare criteri di convalida e altro ancora. Nella scheda Tabella si possono selezionare e impostare macro da eseguire in corrispondenza di vari eventi che possono interessare la tabella: apertura, chiusura, aggiornamento, modifica, eliminazione e così via. Tali macro sono dette “macro di dati” e sono una novità di Access 2010. Quando è aperta la scheda Strumenti tabella/Campi, un clic sul pulsante con l’icona della squadra, contenuto nel primo gruppo di comandi intitolato Visualizzazioni, chiude la scheda Strumenti tabella/Campi e apre la scheda Strumenti tabella/Progettazione, che raggruppa tutti i comandi che possono servire per intervenire sulla struttura della tabella, vale a dire inserire nuovi campi o eliminare campi esistenti, modificare il tipo di dati dei campi e così via.

Figura 2.9  La scheda Strumenti tabella inserisce due schede diverse nella barra multifunzione.

Sovrabbondanza L’interfaccia grafica di Access 2010 è più snella e più ricca di quella di Access 2007, arrivando a essere persino più sovrabbondante, dato che consente di ottenere gli stessi risultati in almeno due (e a volte anche quattro o addirittura sei) modi diversi. Per evitare noiose lungaggini e ripetizioni, in questo capitolo descriviamo soltanto i principali meccanismi dell’interfaccia grafica, senza pretendere di darne una descrizione esauriente.

Si possono aprire gli oggetti Access con due scopi distinti: per usarli o per modificarli; nell’interfaccia utente si distingue fra visualizzazione e apertura vera e propria, come si vede dal menu contestuale che esce quando si fa clic col pulsante destro del mouse sul

38  Capitolo 2

nome di un oggetto selezionato nel Riquadro di spostamento (Figura 2.10). Ciò che viene visualizzato può essere la struttura (per tutti gli oggetti Access) o il layout (solo per maschere e report: un tipo di visualizzazione introdotto con Access 2007).

Figura 2.10  Dal menu contestuale degli oggetti Access si può scegliere se aprirli o visualizzarli.

Quando una tabella è aperta in modo da mostrarne i dati, si dice che è in Visualizzazione Foglio dati mentre di una maschera o di un report aperti per lo stesso scopo, cioè per esaminarne i dati, si dice che sono in Visualizzazione Maschera o in Visualizzazione Report. La Visualizzazione Struttura è disponibile anche per query e macro, che, come sappiamo, non si “aprono”, ma si eseguono. Quando si attiva la Visualizzazione Struttura per maschere, report, query e macro, anche per questi oggetti nella barra multifunzione si apre una specifica scheda Strumenti, analoga a quella che abbiamo visto per le tabelle.

La barra di accesso rapido Posizionata subito sopra la barra multifunzione (oppure subito sotto) si trova una barra di accesso rapido, che contiene alcuni comandi essenziali (Salva, Annulla, Ripeti) e un pulsante che apre un elenco di comandi che si possono aggiungere a questa barra per personalizzarla (Figura 2.11). Altri comandi si possono aggiungere tramite la pagina Personalizzazione della barra di accesso rapido delle Opzioni. Un clic col pulsante destro del mouse sulla barra di accesso rapido fa aprire un menu contestuale, l’ultimo dei suoi comandi è Riduci a icona barra multifunzione. È opportuno eseguire questo comando, perché, quando la barra multifunzione è ridotta a icona, restano visibili soltanto le linguette delle sue schede, liberando così parecchio spazio sullo schermo per esaminare e utilizzare gli oggetti Access; in qualunque momento si può accedere ai comandi contenuti in una scheda della barra multifunzione facendo semplicemente

Gli strumenti interattivi dell’interfaccia grafica   39

clic sulla sua linguetta: questo modo di lavorare è senz’altro il più vantaggioso, quindi lo adotteremo nel resto del libro.

Figura 2.11  La barra di accesso rapido e le sue opzioni di personalizzazione.

In Access 2010 la barra multifunzione presenta un piccolo pulsante di comando all’estrema destra, individuato dall’icona di una freccia rivolta verso l’alto. Un clic su questo pulsante riduce a icona la barra multifunzione e l’icona dello stesso pulsante diventa quella di una freccia rivolta verso il basso, per significare che un successivo clic ripristinerà la barra multifunzione: qui di seguito vediamo i due aspetti dello stesso pulsante:

Il Riquadro di spostamento Il Riquadro di spostamento, che compare sulla sinistra della finestra Database, si chiamava Barra oggetti nelle versioni precedenti di Access dove serviva da contenitore e da piattaforma di lancio degli oggetti database. In Access 2010 a queste funzioni sono state aggiunte molte altre, che possono far comodo quando gli oggetti contenuti nel file sono parecchi.Vediamo come si presenta nella Figura 2.12, quando è aperto un file database molto ricco. Il pannello del Riquadro di spostamento è suddiviso in fasce orizzontali, ognuna delle quali contiene i nomi degli oggetti Access: tabelle, query, maschere eccetera. Queste fasce si possono aprire con un clic sull’icona della doppia freccia che punta in basso a destra del nome di ciascuna fascia; quando la fascia è aperta, un clic sull’icona, che ha cambiato aspetto assumendo quello di una doppia freccia che punta verso l’alto, fa chiudere la fascia.

40  Capitolo 2

Figura 2.12  Il Riquadro di spostamento elenca gli oggetti Access presenti nel file database.

Un clic destro sul titolo del Riquadro di spostamento fa uscire un menu contestuale, dal quale si possono scegliere varie funzionalità: nella Figura 2.13 vediamo l’effetto che si ottiene selezionando l’opzione Tabelle e viste correlate del comando Categoria: gli oggetti Access vengono presentati raggruppati in base alle correlazioni che li collegano fra loro; nella figura vediamo che la voce Categorie raggruppa la tabella Categorie, cinque query che agiscono su quella tabella e cinque report che attingono dati dalla stessa fonte (le “viste” di cui si parla nel nome dell’opzione selezionata sono sinonimo di query). Il Riquadro di spostamento è utile ma ingombrante: fortunatamente, un clic sul pulsante che si trova alla estremità destra del titolo di questo pannello lo riduce a una stretta striscia verticale, lasciando spazio nella finestra Database vera e propria.

La barra di stato La fascia che chiude la finestra dell’applicazione in Access 2010 mantiene le stesse funzionalità che aveva nelle versioni precedenti, la più significativa delle quali è mostrare la descrizione (se è stata predisposta) di un campo selezionato in una tabella o di un controllo attivato in una maschera. A queste se ne aggiunge una, che vediamo nella Figura 2.14: quando è aperta una tabella, una maschera o un report, all’estremità destra della barra di stato sono presenti dei pulsanti che consentono di passare con un solo clic dalla visualizzazione corrente a un’altra fra quelle disponibili per l’oggetto aperto (Struttura, Foglio dati, Anteprima di stampa eccetera).

Gli strumenti interattivi dell’interfaccia grafica   41

Figura 2.13  Gli oggetti Access sono adesso raggruppati in base alle loro correlazioni.

Figura 2.14  La barra di stato con i pulsanti per cambiare visualizzazione.

Nella Figura 2.14 notiamo un’altra caratteristica della nuova grafica di Access 2010: quando si piazza il puntatore del mouse, senza fare clic, su un elemento attivabile (pulsante, icona di un comando e simili), compare subito sotto un cartiglio azzurro con una breve descrizione di quello che si otterrà attivando quell’elemento: è un’innovazione molto utile, considerando la quantità industriale di nuovi oggetti grafici attivabili presenti in Access 2010 in più rispetto a quelli disponibili nelle versioni precedenti.

Creazione delle tabelle Quando si crea un nuovo database, come prima cosa è bene impostare almeno una tabella destinata a contenere i dati. Non è un obbligo: con Access si possono realizzare applicazioni database che non contengono tabelle nel file .accdb, ma fanno riferimento, per i dati da elaborare, a tabelle esterne al file dell’applicazione, come spieghiamo nella Parte IV di questo libro. Potremmo sfruttare questa possibilità anche adesso, ma non è il caso di complicare le cose in questo momento, per cui passiamo all’operatività creando una nuova tabella nel database Rubrica01. Come si vede nella Figura 2.15, all’apertura di un database nuovo,Access 2010 predispone nella finestra Database lo schema di una nuova tabella, col nome provvisorio di Tabella1,

42  Capitolo 2

che contiene già un campo chiamato ID, seguito dallo schema di un campo vuoto con la dicitura Fare clic per aggiungere e un pulsante con una freccia che punta verso il basso: un clic su quel pulsante fa comparire l’elenco dei tipi di dati possibili per l’eventuale nuovo campo. La barra multifunzione è aperta sulla scheda Campi, che è una delle due sotto-schede della scheda Strumenti tabella.

Figura 2.15  Lo schema vuoto predefinito per una nuova tabella.

Nella figura vediamo che il campo predefinito chiamato ID ha come tipo di dati Numerazione automatica. La scheda Campi nella barra multifunzione contiene i gruppi di comandi Aggiungi ed elimina, Proprietà, Formattazione e Convalida campo: in questo contesto possiamo costruire la tabella che ci serve, aggiungendo un campo per volta e scegliendo per quel campo il tipo di dato nell’elenco Aggiungi ed elimina, impostandone le caratteristiche fra quelle disponibili nei gruppi di comandi Proprietà e Formattazione ed eventualmente stabilire regole di convalida selezionandole dal gruppo Convalida campo. Questo modo di operare è stato introdotto con Access 2007 e perfezionato in Access 2010, ma obbliga a un continuo spostamento dalla struttura della tabella in costruzione, visualizzata nella finestra Database, ai comandi della scheda Campi nella barra multifunzione, un procedimento piuttosto macchinoso, che possiamo risparmiarci passando a una modalità diversa. Chiudiamo quindi lo schema della nuova tabella presentato per impostazione predefinita facendo un clic destro sulla linguetta del titolo e scegliendo l’opzione Chiudi dal menu contestuale. Apriamo poi la finestra Opzioni dalla finestra Backstage, scegliamo nelle Opzioni la pagina Database corrente e nel pannello di destra, intitolato Opzioni del database corrente, individuiamo il gruppo di opzioni intitolato Opzioni finestra del documento e selezioniamo con un clic l’opzione Finestre sovrapposte, che si attiva mentre contestualmente si disattiva l’opzione predefinita Documenti a schede. Scorrendo più avanti nello stesso pannello, togliamo il segno di spunta dalla casella di controllo che sta a sinistra della dicitura Attiva modifiche alla struttura delle tabella in visualizzazione Foglio dati: in questo modo si disattiva la funzionalità che consente di aggiungere un nuovo campo alla struttura di una tabella quando questa è aperta in visualizzazione Foglio dati. Un clic su OK per confermare le modifiche fa uscire una finestra di messaggio dove si avverte che “Per rendere effettiva l’opzione specificata è necessario chiudere e riaprire il

Gli strumenti interattivi dell’interfaccia grafica   43

database corrente”. Chiudiamo la finestra di messaggio, la pagina delle Opzioni e il database e torniamo ad aprirlo. Una volta aperto, diamo il comando Crea/Tabelle/Struttura tabella.

Comandi Da qui in avanti, per non essere troppo prolissi, ci riferiremo ai comandi eseguiti dalla barra multifunzione con la seguente successione di termini: Scheda/Gruppo/Comando quindi l’espressione Crea/Tabelle/Struttura tabella sta a indicare, concisamente, “il comando Struttura tabella nel gruppo di comandi Tabelle della scheda Crea della barra multifunzione”. Nello stesso spirito, diremo “clic destro” invece di “clic col pulsante destro del mouse”.

Compare una finestra articolata nel modo che si vede nella Figura 2.16.

Figura 2.16  La finestra Struttura per creare una tabella.

La barra multifunzione contiene ora una nuova scheda, chiamata Strumenti tabella/Progettazione, composta da tre gruppi: Visualizzazioni, Strumenti e Mostra/Nascondi, Eventi per campi, record e tabelle, Relazioni. Il gruppo Mostra/Nascondi, come il suo nome lascia capire, aggrega comandi a commutatore, che, eseguiti una prima volta, mostrano qualcosa (nel caso specifico, la finestra delle proprietà della tabella o quella per comporre gli indici) ed eseguiti in seguito nascondono ciò che è stato aperto. Il gruppo Visualizzazioni contiene

44  Capitolo 2

una finestra a discesa che, aperta, consente di scegliere fra le quattro visualizzazioni disponibili: quella attuale (Struttura), Foglio dati, Grafico pivot e Tabella pivot. In questo libro non ci occuperemo di queste due ultime visualizzazioni, perché sono strumenti tipici di Excel, fatti migrare in Access a partire dalla versione 2002 e del tutto irrilevanti ai fini delle applicazioni gestionali che ci interessano. Il gruppo Strumenti contiene sei comandi, ne vedremo all’opera alcuni qui di seguito. Creare una tabella vuol dire, in realtà, definire la struttura delle sue righe, cioè dei record che vi saranno contenuti. Una tabella di un database Access può contenere record per un totale massimo di due miliardi di caratteri, ma in ogni tabella è ammesso un solo tipo di record. Un record è formato da caratteri, aggregati in campi distinti. In una ideale rappresentazione grafica di una tabella, i record sono le righe e i campi le colonne. In un record di Access 2007/2010 si possono memorizzare al massimo 4000 caratteri (2000 nelle versioni precedenti), raggruppabili in non più di 255 campi diversi. Ogni campo può contenere dati di un solo tipo: caratteri, per esempio, se il campo è stato definito come testo, o numeri, se è stato definito come numerico. La finestra Struttura è divisa in due pannelli. In quello superiore si definiscono i singoli campi, uno per volta, scrivendo un nome nella casella Nome campo e selezionando un tipo di dato nella casella di riepilogo della colonna Tipo dati. È facoltativo digitare un testo esplicativo per ciascun campo, usando la casella corrispondente nella colonna Descrizione. Il pannello inferiore permette di definire le proprietà di ciascun campo selezionato nel pannello superiore. Le proprietà definibili variano in funzione del tipo di dato assegnato a ciascun campo. I tipi possibili sono riepilogati nella Tabella 2.1. Tabella 2.1  I tipi di dati dei campi. Tipo di dato

Caratteristiche

Testo Memo Numerico Data/ora

Una stringa di non più di 255 caratteri alfanumerici. Un testo libero lungo al massimo 65.535 caratteri. Un numero intero o decimale, con o senza segno. Un valore che può essere formattato in modo da rappresentare una data, un’ora o entrambi. Un numero decimale con un formato particolare per rappresentare importi di denaro. Un valore numerico di tipo particolare, usato per individuare in modo univoco i record di una tabella. Un valore logico che può essere o vero o falso. Un elemento software (file immagine, file audio o di altro tipo), associato al campo, ma residente in un file separato. Stringa di caratteri particolari, che individuano un elemento di informazione collegato al record.Tale elemento può essere interno o esterno al file del database. Uno o più file (documenti, immagini, suoni, filmati) integrati nel file database come allegati. Campo il cui contenuto è il risultato di un’operazione aritmetica su valori di altri campi nello stesso record: funzionalità da utilizzare esclusivamente con i database web.

Valuta Numerazione automatica Sì/No Oggetto OLE Collegamento ipertestuale Allegato Calcolato

Gli strumenti interattivi dell’interfaccia grafica  45

Campi con più valori e valori calcolati Una novità introdotta con Access 2007 e disponibile anche nella versione successiva è la possibilità di avere più valori in un campo di un record. Secondo noi, si tratta di un’innovazione dissennata, perché va contro il principio fondamentale della normalizzazione, in base al quale ogni campo in un record deve rappresentare una sola entità, quindi avere un unico valore. Non utilizzeremo i campi multi valore in questo libro e sconsigliamo i nostri lettori dal farlo. In Access 2010 troviamo un tipo di dato nuovo chiamato Calcolato, il cui contenuto è il risultato dell’esecuzione di un calcolo fra campi dello stesso record o tra un campo e una costante. Dal punto di vista della teoria dei database relazionali, è pura eresia e la disponibilità di questo “tipo di dato” si giustifica esclusivamente nelle tabelle di un database web, come vedremo nel Capitolo 16.

Nei 4000 caratteri che possono essere contenuti in un record non si tiene conto delle dimensioni dei campi di tipo Memo, Oggetto OLE e Allegato. In ogni casella combinata Tipo dati è presente anche una voce Ricerca guidata. Come il nome lascia immaginare, non è un tipo di dato, ma una funzionalità che può essere attivata selezionando quella voce. Possiamo trascurarla, per il momento, ce ne occuperemo più avanti. Per una elementare rubrica di indirizzi, possono bastare i seguenti otto campi, tutti di tipo Testo: Nome Cognome IndirizzoStrada CAP Città Provincia Telefono

Digitiamo quindi questi nomi in altrettante righe del pannello superiore, nelle caselle della colonna Nome campo, accettando l’automatica impostazione su Testo della corrispondente casella Tipo dati nella colonna a fianco. Nel dare i nomi ai campi valgono le regole per l’assegnazione dei nomi agli oggetti in Access: si possono impiegare fino a 64 caratteri, a eccezione del punto (.), del punto esclamativo (!), dell’apice inverso (`) e delle parentesi quadre ([ ]). I nomi possono contenere spazi, ma è meglio evitarli, perché la loro presenza complica un po’ i riferimenti agli oggetti nelle espressioni e nella programmazione. È bene anche tenersi molto al di sotto del limite massimo di 64 caratteri, diciamo non più di 10/15, perché i nomi troppo lunghi sono difficili da ricordare, complicati da inserire nei riferimenti e favoriscono gli errori di battitura. È bene, inoltre, scegliere nomi che indicano con chiarezza il significato del contenuto del campo, adottando, quando serve, la scrittura detta “a dorso di cammello”, cioè combinare assieme due o più parole, ciascuna con l’iniziale maiuscola, come nel nome IndirizzoStrada: i nomi che abbiamo indicato sopra sono brevi e chiari quanto basta. Terminata l’impostazione dei campi, non resta che salvare la nuova tabella per poterla utilizzare. Possiamo eseguire l’operazione facendo clic sul comando Salva nella barra di accesso rapido (è il comando individuato dal pulsante con l’icona del dischetto). Ricevuto

46  Capitolo 2

il comando, Access apre una finestra Salva con nome, per chiedere un nome per la tabella, proponendo come nome predefinito Tabella1. Digitiamo tblIndirizzi e facciamo clic su OK. Subito dopo compare una finestra di messaggio come quella riprodotta nella Figura 2.17. La proposta di creare una chiave primaria ci sta bene, quindi facciamo clic su Sì. Subito dopo, la struttura della nostra tabella tblIndirizzi viene modificata: compare un nuovo campo, che viene infilato nella prima casella, facendo scorrere tutti gli altri di una riga verso il basso. Il Tipo dati del nuovo campo, creato automaticamente col nome ID, è Numerazione automatica e sono state impostate queste proprietà nel pannello Proprietà campo: Dimensione campo: Nuovi valori: Indicizzato:

Intero lungo Incremento Sì (Duplicati non ammessi)

Per capire il senso delle operazioni che sono state fatte a mano e dell’ultima, avvenuta automaticamente, dobbiamo sospendere un momento il processo di montaggio della nostra piccola applicazione ed esaminare il funzionamento della finestra Struttura.

Figura 2.17  All’atto del salvataggio di una nuova tabella, Access propone di creare una chiave primaria.

Convenzione per i nomi in Access Tutti gli oggetti che si creano e utilizzano in Access devono avere un nome; siccome può essere comodo usare lo stesso nome per una tabella e per una maschera che visualizza i dati di quella tabella, è opportuno aggiungere ai nomi un prefisso di tre lettere per distinguere la natura degli oggetti, quindi daremo il nome frmIndirizzi alla maschera che visualizza i dati di una tabella che avremo chiamato tblIndirizzi. Utilizzeremo queste sigle: Oggetto Sigla Tabella tbl Query qry Maschera frm Report rpt Macro mcr Modulo mdl Ricorreremo alla stessa convenzione, con sigle diverse, per i nomi dei controlli nelle maschere.

Gli strumenti interattivi dell’interfaccia grafica  47

La finestra Struttura di una tabella e le proprietà dei campi Al punto in cui siamo arrivati, la finestra Struttura si presenta nel modo che vediamo nella Figura 2.18.

Figura 2.18  La struttura della nuova tabella tblIndirizzi.

Il pannello Proprietà del campo contiene due schede, Generale e Ricerca. La scheda Generale elenca le proprietà definibili per ciascun campo selezionato nel pannello superiore. Tali proprietà variano a seconda del tipo di dati predisposto per ciascun campo. Per i campi di tipo Testo si possono impostare le seguenti proprietà, intervenendo se necessario sui valori predefiniti, impostati automaticamente: Dimensione campo  Digitando un valore compreso fra 1 e 255 è possibile fissare la dimensione massima della stringa di testo che è memorizzabile nel campo. Il valore predefinito è 255. Con questa proprietà si fissa un limite superiore: assegnando a un campo una dimensione di 50 caratteri è possibile inserire stringhe più corte, ma non più lunghe di 50 caratteri. Formato  Inserendo in questa casella codici opportuni, si può vincolare il formato della stringa che viene immessa. Per il tipo dati Testo la proprietà Formato è scarsamente rilevante, mentre è più significativa per i tipi dati Numerico, Valuta e Data/ora. Maschera di input  Per stringhe fortemente strutturate, come per esempio un Codice fiscale, l’immissione in questo campo di un opportuno schema di caratteri favorisce la digitazione di informazioni organizzate secondo uno schema fisso. Un clic su questa casella fa comparire un pulsante chiamato Genera, che come icona ha tre puntini di sospensione. Premendo questo pulsante Genera si apre una finestra di dialogo Maschera di input, dove si può selezionare uno degli schemi predefiniti da applicare all’input per questo campo oppure crearne uno nuovo.

48  Capitolo 2

Etichetta  Un testo immesso in questa casella viene utilizzato al posto del nome del campo quando viene presentato il contenuto della tabella in visualizzazione Foglio dati. Valore predefinito  Il contenuto immesso in questa casella viene presentato come valore predefinito del campo quando si apre la tabella per l’input. Il valore predefinito può essere sostituito liberamente con uno scelto dall’operatore. Valido se  In questa casella si immette un’espressione che funge da regola di convalida, limitando i valori che possono essere immessi nel campo.Viene accettato in input soltanto un contenuto che rispetti il vincolo di validità indicato dall’espressione. Messaggio errore  Qualora sia stata immessa una regola di convalida nella casella Valido se, qui si può inserire il testo di un messaggio per segnalare l’errore quando la regola di convalida non viene rispettata. Richiesto  Nella casella si può immettere soltanto un valore logico Sì o No, per indicare se il campo deve obbligatoriamente avere un contenuto oppure può essere vuoto. Consenti lunghezza zero  Casella che accetta soltanto un valore logico Sì o No per indicare se è consentita o meno l’immissione di una stringa di lunghezza zero. Indicizzato  La casella ammette soltanto tre valori: No, Sì (Duplicati ammessi), Sì (Duplicati non ammessi). Scegliendo uno di tali valori si definisce se il campo è da utilizzare come base per la creazione di un indice e, in caso affermativo, se si consente l’immissione di più campi con lo stesso contenuto o se non sono ammessi duplicati. Specificando Sì (Duplicati non ammessi), si impone un vincolo di univocità per il contenuto del campo, che dovrà essere diverso in ogni record della tabella. Seguono altre cinque proprietà: possiamo trascurare le prime quattro Compressione Unicode, Modalità IME, Modalità frase IME e Smart tag – lasciando impostati i valori predefiniti, mentre per la quinta, Allineamento testo, il valore predefinito Standard indica che il testo verrà allineato a sinistra, numeri e date allineati a destra. La casella contiene altre quattro opzioni per l’allineamento – A sinistra, Centrato, A destra, Ripartito – che possiamo ignorare, perché l’allineamento dei dati di tipo Testo è irrilevante nella struttura delle tabelle, mentre può essere importante nei controlli delle maschere, come vedremo a suo tempo. Le proprietà che si possono definire per i campi possibili sono quelle elencate nella Tabella 2.2, dove si riepilogano le proprietà e i tipi di dati ai quali si possono attribuire. Tabella 2.2  Le proprietà dei campi. Data/ Sì/ Oggetto Collegamento Testo Numerico ora Memo Valuta Contatore No OLE ipertestuale Allegato Dimensione campo

*

*

Formato Posizioni decimali Nuovi valori

*

*

*

Etichetta Maschera di input Messaggio errore

*

*

*

*

*

*

*

*

*

*

*

*

*

*

*

*

* * *

*

*

*

*

*

* *

*

*

*

*

Gli strumenti interattivi dell’interfaccia grafica  49

Indicizzato Formato testo Consenti lunghezza zero Allineamento testo Solo accodamento Visualizza selezione data

*

*

*

*

*

*

*

* * *

* *

*

*

* *

*

*

*

* *

*

I tipi di dati dei campi Testo Un campo Testo può contenere fino a 255 caratteri alfabetici e numerici.Anche se contenesse soltanto cifre, queste verrebbero comunque considerate come un testo, cioè non sono consentite operazioni aritmetiche sui numeri contenuti nei campi di testo. La proprietà Consenti lunghezza zero, prevista anche per i campi di tipo Memo e Collegamento ipertestuale, serve per gestire eventuali stringhe senza caratteri, che si ottengono digitando due virgolette doppie di seguito senza alcun carattere in mezzo, in questo modo: “”. Una stringa di lunghezza zero segnala che il contenuto del campo non è assente, ma è espressamente senza un valore. Numerico  Un campo Numerico può contenere numeri di vari tipi, che vengono determinati scegliendo un tipo da un elenco associato con la casella della proprietà Dimensione campo. Ciascun tipo numerico consente di rappresentare valori numerici diversi, che sono riepilogati nella Tabella 2.3. Tabella 2.3  I tipi di dati numerici. Impostazione Byte Intero Intero lungo Precisione singola Precisione doppia ID replica Decimale

Descrizione Un numero intero e positivo compreso tra zero e 255. Un numero intero compreso tra -32.768 e 32.767. Un numero intero compreso tra -2.147.483.648 e 2.147.483.647. Un numero reale espresso con virgola mobile compreso fra -3.402823E38 e -1.401298E-45 per valori negativi e fra 1.401298E-45 e 3.402823E38 per valori positivi. Un numero reale espresso con virgola mobile compreso fra – 1.79769313486231E308 e – 4.94065645841247E-324 per valori negativi e fra 1.79769313486231E308 e 4.94065645841247E-324 per valori positivi. Codice del Globally Unique Identifier (GUID). Un numero composto da 29 cifre, che può avere da 0 a 28 decimali. Il valore maggiore possibile è +/-79.228.162.514.264.337.593.543.950.335. Con 28 decimali, il valore maggiore è +/-7,9228162514264337593543950335 e il valore minore, diverso da zero, è +/-0,0000000000000000000000000001. Questo tipo di dato è specifico di Access 2007/2010.

50  Capitolo 2

Il tipo Intero lungo è quello che viene assegnato per impostazione predefinita, se non si opta per un tipo diverso quando si assegna il tipo Numerico a un campo. Se si prevede di usare numeri reali, quelli che hanno una parte decimale, il tipo da scegliere deve essere Precisione singola o Precisione doppia, che utilizza la notazione a virgola mobile.

Virgola mobile Per rappresentare valori numerici estremamente elevati o molto piccoli, i linguaggi di programmazione utilizzano una tecnica detta di rappresentazione con virgola mobile (in inglese floating-point), che ha il vantaggio di ridurre e tenere costante l’ingombro in memoria del numero rappresentato, quale che sia il suo valore. La tecnica è basata su una rappresentazione di tipo logaritmico, che definisce il numero mediante due componenti: una mantissa e un esponente. La mantissa specifica la parte significativa del numero (le cifre diverse da zero), mentre l’esponente indica l’ordine di grandezza del numero, cioè quanti zeri seguono la parte significativa, se si tratta di un numero intero, o per quante cifre il numero si estende alla destra della virgola decimale, se si tratta di un numero reale. Il meccanismo utilizza una regola delle potenze, che si studia nelle scuole medie e che riportiamo per chi l’avesse dimenticata. Elevando un numero a una potenza negativa si ottiene questo risultato: x-y = 1/xy per cui 10-5 = 1/105 = 0,00001 Sfruttando questa regola, i numeri con virgola mobile rappresentano grandezze moltiplicate per potenze di 10: se l’esponente è positivo, il numero è formato dalla mantissa seguita da un numero di zeri pari all’esponente; se l’esponente è negativo, il numero viene composto con uno zero, seguito da una virgola decimale e dalla mantissa, preceduta da tanti zeri quanti ne occorrono per ottenere il numero di posizioni decimali indicato dal valore assoluto dell’esponente. Per esempio, i numeri 527.800.000 e 0,0000321 si rappresentano rispettivamente con 5278E5 e 321E-7, occupando in questo modo soltanto sei posizioni di memoria. Il valore 5278E5 significa 5278*105, mentre 321E-7 significa 321*10-7. Le tecniche di gestione dei numeri con virgola mobile utilizzano 4 oppure 8 byte per rappresentare un numero, dando luogo a due diversi formati, detti precisione singola (quando il numero è rappresentato con 4 byte) e precisione doppia (quando si usano 8 byte). La rappresentazione con virgola mobile è detta anche notazione scientifica, perché usata prevalentemente nei programmi che eseguono calcoli matematici complessi.

Data/ora  La proprietà Formato per un campo Data/ora offre sette formati diversi per rappresentare valori temporali: quattro per le date e tre per le ore. Le caratteristiche dei diversi formati sono esemplificate con chiarezza nella casella combinata associata alla casella Formato, per cui non ci dilunghiamo oltre. Memo  In tutto simile a un campo Testo, un campo Memo ha le stesse proprietà, con l’eccezione di Dimensione campo, che non compare nell’elenco Proprietà campo. Nelle versioni precedenti ad Access 2007 non era possibile usare un campo di questo tipo come base per un indice, mentre adesso è disponibile la proprietà Indicizzato. Nuove in Access 2007/2010 sono anche due proprietà Solo accodamenti e Formato testo: quando

Gli strumenti interattivi dell’interfaccia grafica   51

è abilitata Solo accodamenti, si possono aggiungere dati al campo Memo, ma non è possibile modificare o rimuovere i dati esistenti; la proprietà Formato testo ha due possibili impostazioni: Testo normale e RTF; scegliendo RTF si può intervenire sul contenuto di un campo Memo impostando formati e stili come in un documento Word tramite i comandi Home/Carattere.

Y2K Esaltato oltre ogni ragionevolezza dalle chiacchiere approssimative del giornalismo-spettacolo, il cosiddetto problema del baco dell’anno 2000, lo Year 2000 Bug, in sigla Y2K, ormai è stato risolto per tutti i software, da quelli applicativi a quelli di sistema, in tutti i computer del mondo. Per quanto riguarda Access, la gestione del passaggio del secolo e del cambio del millennio per le date venne affrontata alla buona, con una trovata che si potrebbe definire salomonica: quando si usa il formato Data in cifre. Access suppone che le date tra il 1/1/00 e 31/12/29 facciano riferimento al ventunesimo secolo, vale a dire agli anni compresi tra il 2000 e il 2029. Le date comprese tra il 1/1/30 e 31/12/99 fanno, invece, riferimento al ventesimo secolo, vale a dire agli anni compresi tra il 1930 e il 1999. Questa soluzione salomonica, però, può creare problemi quando con un database Access si devono gestire dati anagrafici riferiti a persone anziane: un signore nato il 24 maggio 1915, la cui data di nascita fosse registrata nel 2002 come 24/05/15, risulterebbe ancora da nascere, perché l’anno sarebbe il 2015. Per non correre rischi, la cosa migliore è impostare sempre il formato dell’anno su quattro cifre.

Un campo Memo può contenere fino a 65.535 caratteri, che non risiedono però nel record, ma in un’area separata del file database. Valuta  Un campo di tipo Valuta è un campo Numerico particolare. Il valore che vi viene memorizzato è un numero reale, che può contenere fino a quindici cifre nella parte intera (quella a sinistra della virgola decimale) e fino a quattro cifre decimali. Questo tipo di dato è per certi versi simile al tipo Precisione doppia, ma non è rappresentato con virgola mobile. Le operazioni aritmetiche fatte su numeri di tipo Valuta sono precise fino alla quarta posizione decimale e non sono soggette ad arrotondamenti, il che rende questo tipo di numeri particolarmente adatti a rappresentare importi di denaro. Oggetto OLE  Il contenuto di questo campo è un riferimento a un file binario, che può contenere un’immagine, un brano audio o altro ancora. Per questa sua natura, le proprietà di un campo Oggetto OLE sono soltanto Etichetta, Richiesto e Allineamento (quest’ultima soltanto in Access 2007/2010). Sì/No  Un campo di questo tipo serve per rappresentare un valore logico, che come tale può essere uno solo fra due. La proprietà Formato permette di scegliere la rappresentazione del valore logico impostato, che può essere soltanto Sì o No (da cui il nome del tipo), Vero o Falso, On oppure Off. Collegamento ipertestuale  Questo tipo di dato può essere utilizzato per indicare la posizione esatta di un documento,che può trovarsi ovunque:nello stesso file database,in un altro file sullo stesso computer o su un altro computer, in rete locale o nel World Wide Web di Internet. Diversamente dal riferimento che si trova in un campo Oggetto OLE o Memo, che viene

52  Capitolo 2

attivato automaticamente quando si apre la tabella, facendo apparire nel campo l’Oggetto OLE o il contenuto del Memo, il contenuto di un campo Collegamento ipertestuale è una stringa con un formato particolare, che viene visualizzata come una normale stringa quando si apre la tabella. Se si fa doppio clic su un collegamento ipertestuale, questo si attiva aprendo il documento al quale il collegamento si riferisce. Numerazione automatica  Il tipo di dato Numerazione automatica è radicalmente diverso dagli altri tipi di dati ed è una caratteristica specifica di Access. Il valore di un campo di tipo Numerazione automatica non è determinato dall’utente, ma viene generato automaticamente da Access per ogni nuovo record che si immette nella tabella ed è sempre diverso da tutti i valori contenuti nello stesso campo di tutti i record già esistenti nella tabella. La proprietà Nuovi valori, che è specifica di questo tipo di dato, permette soltanto di scegliere se si desidera che il nuovo valore generato quando si immette un nuovo record sia incrementale rispetto all’ultimo valore utilizzato o sia un numero casuale, senza alcun rapporto con i valori esistenti. Anche i valori di tipo incrementale sono univoci. Se in una tabella vuota si immettono 20 record con un campo Numerazione automatica la cui proprietà Nuovi valori è impostata su Incremento, i record conterranno i numeri da 1 a 20 in quel campo. Se si inserisce un nuovo record, il suo campo Numerazione automatica conterrà 21. Se si elimina un record e successivamente se ne aggiunge uno, questo avrà il valore 22 nel campo Numerazione automatica, indipendentemente dal fatto che i record siano in tutto 21. Si assegna normalmente il tipo di dato Numerazione automatica a un campo che deve svolgere il ruolo di chiave primaria della tabella, per sfruttare la circostanza che i valori contenuti in quel campo saranno sempre univoci.

Acquisizione dei dati Riprendiamo ora il percorso principale di questa esplorazione di Access e immettiamo qualche dato nella tabella tblIndirizzi che abbiamo appena creata. Questa operazione si può eseguire accedendo direttamente alla tabella oppure con l’ausilio dell’oggetto Access specializzato per questo scopo, che si chiama maschera. Proviamo prima col metodo diretto. Nella finestra Database, si apre la tabella tblIndirizzi facendo doppio clic sul suo nome nel Riquadro di spostamento. La tabella si apre presentando uno schema vuoto, come quello che si può vedere nella parte superiore della Figura 2.19. Una riga di intestazione visualizza i nomi dei campi, quelli attribuiti in fase di creazione della struttura e quello del campo ID (generato automaticamente quando si è accettata la proposta di creare una chiave primaria). Sotto l’intestazione, compare un record con i campi vuoti, eccetto il primo, il campo ID, che contiene la dicitura (Nuovo). Si passa all’azione inserendo il puntatore del mouse nel primo campo, dove cambia forma, assumendo quell’aspetto di una lettera I maiuscola, sottile e allungata, che caratterizza il puntatore del mouse quando si trova in un’area dove è possibile scrivere un testo. Scriviamo un nome nel campo Nome e passiamo al campo successivo (col mouse o con la tastiera, premendo Tab). Accadono due cose: nel campo ID compare un valore (il numero 1, in questo caso) e nella casella gialla che funge da intestazione della riga del record compare l’icona di una matita. L’icona segnala che è in corso la modifica o la creazione del record, mentre il valore 1 è l’identificativo del record, che è stato generato automaticamente.

Gli strumenti interattivi dell’interfaccia grafica   53

Figura 2.19  L’immissione dei dati direttamente in una tabella.

Fino a quando l’icona della matita è visibile, si può annullare l’intera operazione di creazione di un nuovo record premendo semplicemente il tasto Esc. Proseguendo con l’immissione di dati, quando si esce dall’ultimo campo l’icona della matita scompare e il record è stabilizzato, mentre sotto di esso ne compare uno nuovo, vuoto, pronto a ricevere dati. Possiamo proseguire con l’immissione di nuovi dati fino a ottenere una tabella con almeno una ventina di record. Questa attività dovrebbe produrre una tabella simile a quella mostrata nella Figura 2.20.

Figura 2.20  La tabella tblIndirizzi completa di nomi.

54  Capitolo 2

Tabelle Access ed Excel La somiglianza fisica fra le tabelle di Access e i fogli di lavoro di Excel ha indotto Microsoft ad attribuire alle tabelle numerose funzionalità e caratteristiche che sono tipiche di Excel, al punto che si potrebbe parlare – con un impronunciabile neologismo – di “excellizzazione” di Access, iniziata con la versione 2000, proseguita con forza nelle versioni 2003 e 2007 e spinta ancora più avanti da Access 2010. Per esempio, quando una tabella Access 2010 è aperta in visualizzazione Foglio dati dando il comando Home/Record/Totali compare in fondo alla tabella stessa una riga intitolata Totale, dove si possono selezionare vari tipi di conteggi o di calcoli riferiti a ciascuna colonna, come si vede qui di seguito:

Con la stessa logica, dando il comando Home/Ordina e filtra/Filtro, si ottiene l’uscita di un elenco a discesa di opzioni di ordinamento e di filtraggio che si possono applicare al campo selezionato nel momento in cui è stato dato il comando:

Sono inoltre un chiaro sintomo del progressivo fenomeno di “excellizzazione” di Access le visualizzazioni Tabella pivot e Grafico pivot che sono disponibili in Strumenti tabella/Progettazione/ Visualizzazioni. In questo libro non ci occupiamo di tabelle e grafici pivot: i lettori che fossero interessati possono consultare con profitto il nostro libro “Microsoft Excel 2010. Guida completa”, pubblicato da Apogeo.

Gli strumenti interattivi dell’interfaccia grafica  55

Come si può notare, la tabella si presenta con l’aspetto di un foglio di lavoro Excel: ogni riga corrisponde a un record, le colonne sono i campi e portano come intestazione i nomi dei campi definiti nella fase di creazione della tabella (visualizzazione Struttura). Questo modo di presentare una tabella si chiama visualizzazione Foglio dati. Si può passare da una visualizzazione all’altra agendo sul pulsante Visualizza nella scheda Home della barra multifunzione e scegliendo la visualizzazione che interessa dall’elenco a discesa.

Salvare i dati in Access Chi è abituato a lavorare con strumenti tipo Word o Excel sa bene, spesso in conseguenza di amare esperienze, che è opportuno salvare di frequente il documento sul quale sta lavorando. Questa prassi, sacrosanta in altre applicazioni, non solo non è richiesta quando si lavora con Access, ma in molti casi è impossibile, a causa di una differenza sostanziale che esiste fra Access e gli altri strumenti applicativi di Microsoft Office. L’utente di Access, infatti, può essere un realizzatore o un operatore. È responsabilità dell’utente/realizzatore, cioè di chi crea o modifica un’applicazione con Access, salvare e assegnare un nome agli oggetti che crea di volta in volta: per esempio, la struttura di una tabella. Deve, inoltre, provvedere a salvare di nuovo gli oggetti esistenti quando ne ha modificato la struttura. Chi agisce come utente/operatore usa il database senza intervenire sulla sua struttura: inserisce nuovi record in tabelle esistenti, modifica il contenuto di campi di record già aggregati in tabelle e può eliminare record. Non appena vengono eseguite, tutte queste operazioni aggiornano automaticamente il file del database. Non solo, ma quando l’utente/operatore chiude il database col comando Chiudi database dalla finestra Backstage, vengono salvate tutte le modifiche fatte ai dati, senza alcuna richiesta di conferma o di autorizzazione, come accade invece quando si chiude un documento Word che è stato modificato.

Per chiudere la tabella si può fare clic sul pulsante Chiudi, in alto a destra della sua barra del titolo, secondo la convenzione valida per qualunque finestra Windows. Acquisire dati inserendoli man mano in un record nuovo, mentre la tabella è aperta in visualizzazione Foglio dati, non è scomodo, ma neppure particolarmente agevole. Se, infatti, il record contenesse una quindicina di campi, invece degli otto della nostra tabella di prova, dopo un po’ i campi già riempiti sfilerebbero fuori dall’area visibile della finestra, rendendo più arduo il controllo dell’immissione. Per immettere dati in una tabella si può ricorrere a un comodo strumento, chiamato maschera, che è uno degli oggetti più ricchi e flessibili fra quelli disponibili in Access. Una maschera è un oggetto software che ha l’aspetto di una finestra Windows, dove è possibile presentare contemporaneamente e disposti nell’ordine che si preferisce tutti i campi di un record oppure soltanto alcuni. Le maschere si creano normalmente a mano, utilizzando opportuni strumenti dell’interfaccia grafica, selezionando e disponendo oggetti di vario tipo mediante mouse e tastiera. Non è un’operazione difficile e le maggiori difficoltà sono più di natura grafica che informatica: una composizione corretta rende una maschera agevole da usare, mentre una composizione inadeguata e confusa può rendere difficoltoso il lavoro di immissione dei dati, invece di agevolarlo.

56  Capitolo 2

A meno di avere già una notevole dimestichezza con lo strumento, piuttosto di creare ex novo una maschera mediante gli strumenti di composizione grafica è opportuno realizzare una prima bozza funzionante ricorrendo alla Creazione guidata Maschera: la maschera così ottenuta potrà agevolmente essere modificata in un secondo tempo, se i risultati della procedura guidata non fossero del tutto soddisfacenti.

Generazione di una maschera predefinita Nel Riquadro di spostamento selezioniamo con un clic la tabella tblIndirizzi, senza però aprirla e apriamo la scheda Crea della barra multifunzione. Questa scheda contiene tutti i comandi disponibili in Access 2010 per creare oggetti. Nel gruppo Maschere si trovano sei comandi per creare maschere in vari modi. Il più semplice e immediato è il primo, per cui eseguiamo Crea/Maschere/Maschera e otteniamo immediatamente quello che ci serve, che viene presentato in visualizzazione Layout, un nuovo tipo di visualizzazione nato in Access 2007 e disponibile anche in Access 2010; cambiamo questa visualizzazione nella visualizzazione Maschera con un clic sul pulsante omonimo nella barra di stato. Nella barra del titolo della maschera compare, per impostazione predefinita, il titolo della tabella alla quale la maschera si riferisce. La maschera, però, è un oggetto nuovo e diverso e come tale va salvato con un suo nome. Chiudendola, infatti, con un clic sul pulsante di chiusura, si riceve un messaggio di avvertimento, che chiede se si vuole davvero rinunciare a utilizzare l’oggetto chiamato provvisoriamente tblIndirizzi. Modifichiamo il nome in quello, più coerente, di frmIndirizzi e autorizziamo il salvataggio. La maschera standard così ottenuta è riprodotta nella Figura 2.21.

Selettore del record     Pulsanti di spostamento Figura 2.21  La maschera frmIndirizzi per gestire i record della tabella tblIndirizzi.

Come si può vedere, la maschera è strutturalmente simile a una normale finestra Windows, in quanto ha una barra del titolo, nella quale sono presenti i tre caratteristici pulsanti di riduzione a icona, ingrandimento e chiusura ed è circondata da un bordo, che può essere trascinato col mouse per ridimensionare a piacere la finestra. Ciò che caratterizza una maschera distinguendola da una generica finestra Windows sono gli altri elementi evidenziati nella figura: il selettore del record e i pulsanti di spostamento. Il selettore del record è un pulsante sul quale si può fare clic. Questa azione seleziona l’intero record, che può quindi essere copiato, tagliato o eliminato per intero.

Gli strumenti interattivi dell’interfaccia grafica  57

I pulsanti di spostamento, disposti in batteria in fondo alla maschera, servono per navigare nell’insieme dei record della tabella sulla quale si basa la maschera, passando agevolmente con un solo clic da un record all’altro, andando direttamente sul primo o sull’ultimo o su un record determinato, quando se ne conosce il numero d’ordine, digitando direttamente il numero del record nella casella situata fra i pulsanti di spostamento veri e propri. Nella stessa struttura di pulsanti, l’ultimo a destra non serve per navigare, ma predispone la maschera per la creazione di un nuovo record. A destra dei pulsanti di spostamento c’è un pulsante di comando con la dicitura Nessun filtro, scritta con caratteri offuscati per significare che il pulsante non è attivo. La scritta verrebbe modificata in Filtro e il pulsante diventerebbe attivo se applicassimo alla maschera un filtro come si può fare con le tabelle (si veda sopra, il riquadro “Tabelle Access ed Excel”) dando anche in presenza di una maschera il comando Home/Ordina e filtra/Filtro. Subito a destra del pulsante per il filtro, una piccola casella di testo contiene la dicitura Cerca. Un clic sulla dicitura svuota la casella e abilita la scrittura di un testo qualunque, che viene immediatamente acquisito e cercato all’interno di tutti i controlli della maschera: se c’è una corrispondenza, anche parziale, la maschera si sposta sul record che contiene il campo trovato, che appare selezionato ed evidenziato. La pressione del tasto Esc cancella la stringa di caratteri immessa per la ricerca. All’interno della maschera troviamo una serie di oggetti chiamati casella di testo ed etichetta. Questi oggetti sono la vera sostanza di una maschera e nel gergo di Access si chiamano controlli. Un controllo serve per visualizzare e gestire un elemento di informazione, per lo più un campo di un record, ma non solo. Il controllo Etichetta è un elemento grafico che contiene una dicitura fissa, non modificabile dall’utente/operatore, mentre il controllo Casella di testo presenta un contenuto sul quale l’operatore può intervenire. Nella maschera che stiamo esaminando, le diciture dei controlli Etichetta sono state ricavate automaticamente dal nome dei campi del record tblIndirizzi, mentre il contenuto dei controlli Casella di testo corrisponde al contenuto dei campi di ciascun record. Adesso che abbiamo a disposizione la maschera frmIndirizzi, le operazioni di visualizzazione, modifica e inserimento di nuovi record risultano più immediate e più agevoli rispetto a quando le abbiamo fatte agendo direttamente sulla tabella tblIndirizzi in visualizzazione Foglio dati. Arrivati a questo punto, il file database Rubrica01.accdb è quasi un’applicazione gestionale, ancorché ridotta all’osso: con la maschera frmIndirizzi possiamo consultare la nostra rubrica, inserire nuovi indirizzi, modificare quelli esistenti ed eliminare quelli inutili, tutto questo senza agire direttamente sulla tabella tblIndirizzi, nella quale peraltro si riflettono puntualmente le modifiche eseguite tramite la maschera.

Rispunta l’esigenza applicativa Provando a utilizzare la mini-applicazione Rubrica01, ci accorgiamo ben presto che si tratta di una povera cosa: •• possiamo registrare un solo numero di telefono per ogni persona; •• i record ci vengono presentati secondo l’ordine di immissione e non, come sarebbe più comodo, ordinati alfabeticamente per cognome;

58  Capitolo 2

••

dobbiamo scorrere tutti i record uno per uno per trovare l’indirizzo di una persona, mentre invece ci farebbe comodo circoscrivere la ricerca agli indirizzi di una determinata provincia; •• non abbiamo uno spazio per qualche annotazione libera, per esempio un commento che ci ricordi qualcosa di specifico su una persona. E l’elenco delle lacune potrebbe continuare. Quello che stiamo verificando è la dimostrazione oggettiva dell’importanza di definire l’esigenza applicativa prima di mettere mano alla creazione di un’applicazione database. Abbiamo fatto male, allora, a creare Rubrica01 senza aver prima stabilito con puntuale chiarezza l’esigenza applicativa da soddisfare? Non proprio. In molti casi, l’esigenza applicativa si chiarisce per differenza rispetto a quello che si ha a disposizione. Adesso che possediamo una semplice rubrica di indirizzi computerizzata, usandola possiamo scoprire – più facilmente che con un’analisi astratta – quel che vogliamo avere in realtà, quali servizi o quali comodità vorremmo ottenere. Considerata la fatica pressoché nulla che abbiamo dovuto sostenere per creare Rubrica01, ora che le idee sono più chiare non sarà difficile modificare o addirittura rifare l’applicazione in modo che sia davvero comoda. Questo modo di procedere, creando prima un prototipo con poche funzionalità essenziali e poi modificandole in base all’esperienza che se ne ricava nell’uso concreto, è favorito dall’estrema semplicità con la quale si possono mettere insieme applicazioni con Access, componendo vari oggetti con gli strumenti interattivi dell’interfaccia grafica. Partiamo, quindi, dal nostro modesto prototipo per ottenere un’applicazione più articolata, che non sia una banale trascrizione informatica di un elenco di indirizzi su carta. Supponiamo che ci servano queste funzionalità aggiuntive: 1. associare più numeri di telefono a un solo nominativo; 2. annotare informazioni non strutturate, commenti o altro, su ciascun record; 3. presentare i record in ordine alfabetico; 4. selezionare i record per provincia. Per ottenerle, bisogna: a. creare una tabella complementare per i soli numeri di telefono; b. modificare la struttura del record di tblIndirizzi; c. modificare la struttura della maschera frmIndirizzi. È necessario, inoltre, stabilire una relazione uno a molti fra la tabella principale con gli indirizzi e quella con i numeri di telefono. Eseguendo tutte queste modifiche scopriremo concretamente alcuni aspetti importanti dei rapporti fra tabelle e maschere e impareremo altre utili tecniche di composizione mediante gli strumenti interattivi dell’interfaccia grafica. Possiamo eseguire le modifiche sullo stesso file database Rubrica01, ma cogliamo l’occasione per far pratica di importazione di dati fra file database. Chiudiamo quindi Rubrica01 col comando Chiudi database dalla finestra Backstage.

Importazione di dati in un nuovo database Dopo aver chiuso Rubrica01, ricompare la rutilante schermata di avvio di Access 2010, nella quale torniamo a eseguire i comandi per creare un nuovo file database, che chiamiamo Rubrica02, collocandolo nella stessa cartella del precedente (C:\Esempi).

Gli strumenti interattivi dell’interfaccia grafica  59

Si apre il nuovo file database presentando lo schema vuoto di una nuova tabella; la chiudiamo e apriamo la scheda Dati esterni della barra multifunzione, che nel gruppo Importa e collega aggrega otto comandi; scegliamo il comando Dati esterni/Importa e collega/Access. Compare ora una finestra di dialogo intitolata Carica dati esterni – Database di Access, che presenta una casella di testo Nome file dove possiamo scrivere direttamente il nome del file Access dal quale vogliamo importare dati. Un pulsante Sfoglia ci consente di navigare agevolmente nel disco rigido per trovare il file che ci interessa, che nel nostro caso è C:\ Esempi\Rubrica01.accdb. Trovato il file, possiamo scegliere fra importare dati da quel file database oppure collegare il nostro nuovo file database con quello: la differenza è importante e la approfondiremo nel Capitolo 13; scegliamo di importare i dati, facciamo clic su OK, la finestra di dialogo si chiude e se ne apre un’altra, intitolata Importa oggetti, che elenca in una struttura a schede tutti gli oggetti contenuti nel file Access selezionato. In Importa oggetti selezioniamo con un clic la tabella tblIndirizzi e quindi premiamo il pulsante OK. Qualche secondo dopo l’intera tabella tblIndirizzi di Rubrica01 è stata importata nel nuovo database Rubrica02, risparmiandoci la fatica di crearla di nuovo. Subito dopo torna a presentarsi la finestra di dialogo Carica dati esterni – Database di Access, che propone di Salvare i passaggi dell’operazione di importazione. Al momento non ci interessa, quindi chiudiamo questa finestra di dialogo senza alcun salvataggio. Si possono importare in un database Access dati in forma tabellare provenienti non soltanto da altri file database Access (come in questo caso), ma anche da molte altre applicazioni diverse: fogli di calcolo Excel, tabelle Word, file di testo generati da qualunque applicazione sotto forma di sequenze strutturate di caratteri. L’importazione di dati esterni è una funzionalità di enorme importanza in Access, dal momento che consente di recuperare dati già esistenti, che possono diventare agevolmente parte integrante di una nuova applicazione Access. Questo specifico argomento è approfondito nel Capitolo 13, “I dati esterni”, quindi qui ci limitiamo a constatare con quale semplicità è possibile reperire dati esistenti per utilizzarli in un contesto diverso.

Creazione di una tabella subordinata Per poter associare più numeri di telefono a una persona sarebbe concettualmente sbagliato aggiungere più campi Telefono al record tblIndirizzi. Infatti, non possiamo stabilire in anticipo quanti numeri di telefono una persona potrebbe avere: casa e ufficio? Casa in città, ufficio, casa al mare? Casa in città, ufficio, casa al mare, telefono cellulare? Predisponendo quattro campi, per esempio, per avere un maggior numero di possibilità, si lasciano inutilizzati due o tre campi per le persone che hanno uno o al massimo due numeri di telefono, ma poi salterebbe sempre fuori il caso di chi ne ha cinque: casa in città, ufficio, fax, casa al mare e cellulare. In breve, questo è un tipico caso di relazione uno a molti: a un nome possono corrispondere molti numeri di telefono. E Access è fatto apposta per gestire relazioni di questo genere, per le quali occorrono almeno due tabelle: quella con le informazioni uno e quella con le informazioni molti. Il primo passo da compiere, quindi, consiste nel creare una tabella tblTelefoni, formata da record di due campi: uno per il riferimento alla persona il cui indirizzo sta nella tabella tblIndirizzi e uno per il numero di telefono vero e proprio. In questo modo, a ciascun record di tblIndirizzi si potranno associare tutti i numeri di telefono che una persona potrebbe avere, non soltanto nel momento in cui i dati della persona vengono registrati nel suo

60  Capitolo 2

record di tblIndirizzi, ma anche in tempi successivi, quando qualche amico ci fa sapere che ha un nuovo numero di telefono da aggiungere a quelli che già abbiamo registrato. Per distinguere i vari tipi di numeri di telefono (casa, ufficio, seconda casa, cellulare, fax eccetera) aggiungeremo anche un campo TipoTelefono a ciascun numero della tabella tblTelefoni. Il legame fra ciascun record tblTelefoni e il record della persona nella tabella tblIndirizzi si farà con un campo che chiameremo Raccordo. Riepilogando, il record tblTelefoni avrà questa struttura: Nome campo

Tipo di dato

Raccordo TipoTelefono Numero

Numerico/Intero lungo Testo Testo

Perché il campo Raccordo deve essere di tipo Numerico/Intero lungo, mentre tutti gli altri sono di tipo Testo? L’aggancio fra il record di tblTelefoni e il record di tblIndirizzi si fa in base al campo ID del record tblIndirizzi, che è di tipo Numerazione automatica e svolge la funzione di chiave primaria nella tabella tblIndirizzi. Se assegnassimo il tipo Numerazione automatica al campo Raccordo della tabella tblTelefoni, ogni record avrebbe un valore diverso, univoco in quel campo, che è esattamente quello che non ci serve, visto che tutta l’operazione parte dal concetto che possano esserci più numeri di telefono per una sola persona. Il campo che si usa in una tabella secondaria (detta anche tabella esterna) per associare i suoi record con quelli della tabella principale (detta anche primaria o master) si chiama chiave esterna e deve avere un tipo dati omogeneo col campo col quale si raccorda. Il tipo dati Numerico, con la proprietà Dimensione campo impostata su Intero lungo è omogeneo col tipo dati Numerazione automatica. Al momento del salvataggio della struttura della nuova tabella tblTelefoni compare la proposta di creare una chiave primaria. Anche se in questa tabella non ci serve, accettiamo la soluzione predefinita, che inserisce un campo con tipo dati Numerazione automatica e chiamato ID in testa all’elenco dei campi. Ci fa comodo avere i numeri di telefono ordinati in base al valore del campo Raccordo, per cui indicheremo, in visualizzazione Struttura, che la proprietà Indicizzato per questo campo avrà valore Sì (Duplicati ammessi). Inoltre, dato che senza un valore in questo campo un record della tabella tblTelefoni non avrebbe senso, impostiamo su Sì la proprietà Richiesto. Al termine del nostro lavoro, la struttura della tabella tblTelefoni dovrebbe presentarsi come nella Figura 2.22. Abbiamo ora due tabelle, tblIndirizzi e tblTelefoni, che avranno una relazione uno a molti basata sulla chiave primaria di tblIndirizzi e sulla chiave esterna Raccordo di tblTelefoni. Lo sappiamo noi, ma, se vogliamo che la cosa funzioni, deve saperlo anche Access. A questo scopo, si deve chiudere la tabella tblTelefoni ed eseguire il comando Strumenti database/Relazioni/Relazioni. Così facendo compare una finestra Relazioni vuota, sulla quale è sovrapposta una finestra di dialogo che chiede di selezionare le tabelle da mettere in relazione. Questo lo si fa selezionando le uniche due tabelle disponibili, una dopo l’altra e premendo ogni volta il pulsante Aggiungi. Fatto questo, si chiude la finestra di dialogo Mostra tabella con un clic sul suo pulsante Chiudi (Figura 2.23).

Gli strumenti interattivi dell’interfaccia grafica   61

Figura 2.22  La nuova tabella tblTelefoni in visualizzazione Struttura.

Figura 2.23  La finestra Relazioni.

Per stabilire la relazione fra le due tabelle, si aggancia col mouse il campo ID della tabella tblIndirizzi e lo si trascina fisicamente fino a toccare il campo Raccordo della tabella tblTelefoni. L’apparente spostamento viene evidenziato graficamente e, al rilascio del pulsante del mouse, appare una finestra di dialogo intitolata Modifica relazioni, che indica la natura della relazione che si sta per stabilire. Un clic sulla casella di controllo Applica integrità referenziale e un successivo clic sul pulsante Crea completano l’operazione: la finestra di dialogo

62  Capitolo 2

Modifica relazioni si chiude e nella finestra Relazioni le due tabelle appaiono congiunte da una linea sottile, che presenta il numero 1 sull’estremità che tocca la tabella tblIndirizzi e il simbolo di infinito ∞ sull’estremità che tocca la tabella tblTelefoni. Il grafismo segnala che tra le due tabelle esiste ora una relazione uno a molti, di cui Access è consapevole. L’intera operazione è illustrata nella Figura 2.24. I concetti di chiave primaria, chiave esterna, relazione uno a molti e integrità referenziale sono già stati presentati nel Capitolo 1, quindi non è il caso di riprenderli.

Figura 2.24  È stata creata una relazione uno a molti fra due tabelle, impostando l’integrità referenziale.

Inserimento di dati nella nuova tabella La tabella tblTelefoni è pronta, ma è vuota: occorre popolarla, come si dice nel gergo di Access, cioè inserirvi i numeri di telefono e il codice Raccordo che associa ciascun numero di telefono con il corrispondente campo ID della tabella tblIndirizzi. Trattandosi di pochi dati, si potrebbe eseguire questa operazione a mano, con semplici comandi Copia (nel campo d’origine di ogni record di tblIndirizzi) e Incolla (nel corrispondente campo di destinazione dei record di tblTelefoni). Questo modo di procedere, però, sarebbe concettualmente sbagliato, perché è sempre rischioso eseguire ripetute operazioni manuali che potrebbero generare ogni volta uno o più errori. Bisogna quindi far fare il travaso alla macchina. Per farlo si ha a disposizione un poderoso strumento di Access, che di fatto è il cuore delle sue funzionalità di gestione. Stiamo parlando dell’oggetto Query, che impareremo a conoscere utilizzandolo.

Gli strumenti interattivi dell’interfaccia grafica   63

Creazione di una query Il termine query in inglese vuol dire interrogazione. La sua forma verbale (to query) è identica alla forma sostantivale, per cui query vuol dire sia interrogare, sia il risultato di un’interrogazione. Per questa sua flessibilità semantica il nome dell’oggetto query è rimasto in inglese anche nella versione italiana di Access. Che cosa si interroga con una query? Le tabelle di un database, per ricavarne informazioni, cioè una particolare selezione dei dati contenuti nei campi. Una query è quindi un comando dato ad Access e produce un insieme di risultati, chiamato recordset. Un recordset ha l’aspetto di una tabella, essendo formato da record, come dice il nome (insieme di registrazioni, tradotto letteralmente). Fra una tabella e un recordset esiste una differenza sostanziale: la tabella è un insieme di dati registrato fisicamente nel file database, mentre il recordset è un insieme di dati che viene creato ad hoc ogni volta che si esegue una query. Ciò che viene registrato nel file database è il comando che definisce una query, non il recordset prodotto da tali comandi, che può essere considerato una tabella virtuale. La composizione dei comandi per ottenere una query si può fare in vari modi. Qui utilizzeremo la tecnica visiva chiamata Query By Example o QBE, mediante la quale si crea un esempio grafico di quello che si vuole ottenere: l’esempio – costruito interattivamente col mouse – genera i comandi veri e propri, che vengono poi eseguiti, producendo il recordset che interessa. La scheda Crea della barra multifunzione contiene un gruppo chiamato Query, dove sono disponibili due comandi per creare una query: Creazione guidata query e Struttura query. Trascuriamo la creazione guidata e diamo il comando Crea/Query/Struttura query. Si aprono due finestre, una più grande sullo sfondo e sopra di essa una finestra di dialogo intitolata Mostra tabella, non dissimile da quella che si è vista nel paragrafo precedente, quando si è preparato il contenuto della finestra Relazioni. Questa finestra di dialogo Mostra tabella è articolata in tre schede, intitolate Tabelle, Query ed Entrambe. Il concetto è che si possono eseguire query sia sul contenuto di tabelle, sia su recordset generati da una query già definita, che in questo caso vengono gestiti come se fossero tabelle a tutti gli effetti. Le prime due schede elencano separatamente le tabelle e le query disponibili nel database e la scheda Entrambe mostra l’elenco completo. Selezioniamo la tabella tblIndirizzi e facciamo clic sul pulsante Aggiungi. Un’icona particolare va a collocarsi nella finestra che sta sullo sfondo e la finestra di dialogo Mostra tabella resta aperta per consentire eventuali ulteriori selezioni (le query si possono eseguire su più tabelle o su un mix di tabelle e di recordset). Per il momento ci basta lavorare sulla tabella tblIndirizzi, quindi chiudiamo la finestra di dialogo Mostra tabella con un clic su Chiudi. La finestra che rimane in primo piano è lo strumento per la creazione grafica di una query. Come si può vedere dalla Figura 2.25, è articolata in due pannelli, in quello superiore si trova una icona che rappresenta schematicamente la struttura dei record della tabella che è stata selezionata come origine dei dati, cioè la tabella sulla quale si intende eseguire l’interrogazione. Nel pannello inferiore troviamo una serie di righe e colonne che formano la griglia di struttura: è qui che si compone interattivamente lo schema del risultato che si vuole ottenere. La finestra porta il titolo provvisorio Query1, che modificheremo quando salveremo la query dopo averne definita e collaudata la struttura.

64  Capitolo 2

Figura 2.25  La griglia di struttura per una query di selezione.

La griglia di struttura è formata da un certo numero di colonne vuote, ognuna delle quali può rappresentare un campo estratto dall’origine dei dati. Il significato delle celle che compongono ciascuna colonna è dato dalle intestazioni di riga che si trovano sulla sinistra della griglia di struttura, così come si descrivono qui di seguito: Campo  Il nome del campo da estrarre. Tabella  La tabella che contiene il campo da estrarre. In una query possono essere presenti più tabelle o recordset come origine dei dati. Ordinamento  Il tipo di ordinamento da utilizzare per mostrare il contenuto di quel campo. Le opzioni possibili sono tre: Crescente, Decrescente e (Non ordinato). Mostra  La casella di controllo serve per stabilire se il contenuto del campo da estrarre debba essere mostrato nel risultato (casella di controllo spuntata) oppure no (casella di controllo non spuntata). Criteri  In questa cella si possono definire criteri logici per condizionare l’estrazione di quel campo: maggiore di, minore di o uguale a e così via. Oppure  Se si sono immessi criteri nella cella superiore, questa cella può contenere criteri alternativi o complementari. Il nostro obiettivo è ricavare dalla tabella tblIndirizzi tutti i numeri di telefono e gli ID dei nominativi, senza escluderne alcuno.Trattandosi di una query eseguita su una sola tabella, la cella Tabella di ciascuna colonna può essere trascurata, in quanto si seleziona da sé. Facciamo clic sulla prima cella della prima colonna nella griglia di struttura e in essa si apre una casella di riepilogo, con l’elenco di tutti i campi disponibili in tblIndirizzi. Selezioniamo il campo ID e passiamo alla colonna successiva, subito a destra. Nel frattempo, nella casella di controllo del campo Mostra della prima colonna, compare il segno di spunta: per impostazione predefinita, si suppone che un campo selezionato debba essere visibile. Il nome della tabella d’origine, tblIndirizzi, è comparso nella cella Tabella. Nella seconda colonna selezioniamo Telefono per il Campo. Al termine di queste semplici operazioni, la struttura della query si presenta come nella Figura 2.26. In questa figura si vede anche la scheda della barra multifunzione Strumenti query/Progettazione, che si apre

Gli strumenti interattivi dell’interfaccia grafica  65

automaticamente quando si sta costruendo una query. Nel gruppo Risultati, un pulsante contraddistinto dall’icona di un punto esclamativo in rosso è associato al comando Esegui.

Figura 2.26  La query pronta per l’esecuzione.

Diamo il comando Strumenti query/Progettazione/Risultati/Esegui e un momento dopo compare il recordset formato dai campi ID e Telefono estratti dalla tabella tblIndirizzi (Figura 2.27).

Figura 2.27  Il recordset ottenuto con la query.

66  Capitolo 2

Nel gruppo di comandi Risultati della scheda Strumenti query/Progettazione, un pulsante Visualizza con una freccia a discesa apre un elenco con cinque possibili visualizzazioni della query: 1. Foglio dati 2. Tabella pivot 3. Grafico pivot 4. SQL 5. Struttura La visualizzazione Foglio dati è quella in essere, mentre scegliendo Struttura si torna alla griglia di composizione. Con la visualizzazione SQL si apre la finestra di un editor di testi interno ad Access dove si vede (e si può modificare direttamente) il comando effettivo che è stato generato manipolando la griglia QBE. Il comando è scritto in un linguaggio interno di Access, chiamato SQL, al quale è dedicato l’intero Capitolo 14 nella Parte IV, per cui possiamo per ora trascurare questa opzione. Le opzioni Tabella pivot e Grafico pivot in questo contesto non ci interessano. Il risultato ottenuto è quello che andavamo cercando: una serie di record formati ciascuno con i campi ID e Telefono estratti dai corrispondenti record della tabella tblIndirizzi. Il problema adesso è: come far arrivare questi dati nella nuova tabella tblTelefoni? Prima di procedere, chiudiamo la query e accettiamo la proposta di salvarla invece di chiuderla, digitando il nome qryTestEstrazioneNumTelefono nella finestra di dialogo che invita a immettere un nome. Un’ulteriore operazione preliminare è la creazione di una copia di questa query. Le finestre degli oggetti Access si comportano come normali cartelle aperte in Windows con Esplora risorse. Ogni oggetto al loro interno può essere selezionato, copiato e incollato (con un nome diverso, ovviamente) nella stessa finestra, usando le note tecniche che si usano in Esplora risorse. Selezioniamo quindi la query appena creata facendo un clic destro sul suo nome nel Riquadro di spostamento. Scegliamo poi il comando Copia dal menu contestuale, che subito dopo si chiude. Torniamo ad aprirlo con un altro clic destro e scegliamo il comando Incolla: compare una piccola finestra di dialogo che sollecita l’immissione di un nuovo nome per la copia da incollare. Digitiamo il nome qryAccodamentoNumTelefono e facciamo clic su OK. La copia della query, col nome qryAccodamentoNumTelefono, è ora visibile nella sezione Query del Riquadro di spostamento.

I tipi di query L’utilizzo di una query per popolare di dati la tabella tblTelefoni prelevandoli dalla tabella esistente tblIndirizzi sfrutta la circostanza che esistono vari tipi di query, oltre a quella appena eseguita a titolo sperimentale, che è una query di selezione. Il concetto di base è sempre quello: una query è un’interrogazione con la quale si estraggono dati da una o più tabelle (o anche da recordset di altre query, ma limitiamoci per il momento alle tabelle, per non mettere troppa carne al fuoco contemporaneamente). Oltre alle query di selezione, si possono creare ed eseguire query che non soltanto selezionano campi, ma creano campi il cui contenuto viene generato da calcoli eseguiti su alcuni campi presenti nelle tabelle di origine. Queste query prendono il nome improprio di query con totali, dall’originale inglese Total Query: il nome è improprio, perché con

Gli strumenti interattivi dell’interfaccia grafica  67

queste query non si eseguono soltanto totalizzazioni, ma si ha praticamente a disposizione tutto l’armamentario dell’aritmetica e della statistica. Esistono, poi, alcune query di tipo particolare, dette collettivamente query di comando (nell’originale si chiamano Action Query), la cui esecuzione crea una nuova tabella o modifica il contenuto di una tabella esistente. Le operazioni possibili con una query di comando sono: 1. la creazione di una nuova tabella; 2. l’aggiornamento selettivo di determinati campi di una tabella; 3. l’accodamento di dati a una tabella (che può essere vuota o contenere già record); 4. l’eliminazione selettiva di record da una tabella. L’azione che vogliamo eseguire (popolare la tabella tblTelefoni) è quella di tipo 3, cioè l’accodamento, vale a dire mettere nella tabella tblTelefoni alcuni record formati con campi ricavati dalla tabella tblIndirizzi.

Una query di accodamento Dal momento che l’esecuzione di una query di comando modifica il contenuto della tabella obiettivo, conviene sempre preparare prima una query di selezione per vedere che cosa si ottiene. Se il recordset generato in questo modo soddisfa i criteri richiesti, si può trasformare la query di selezione in una query di comando con poche operazioni fatte col mouse e la tastiera. La trasformazione è resa agevole dal fatto che le query di comando usano una griglia di struttura solo leggermente diversa da quella delle query di selezione. Avviamo l’operazione aprendo la query qryAccodamentoNumTelefono in visualizzazione Struttura. In Strumenti query/Progettazione/Tipo di query scegliamo il comando Tipo di query: accodamento. Sopra la finestra di struttura si apre una finestra di dialogo Accodamento, nella quale si deve digitare il nome della tabella di destinazione dell’accodamento (tblTelefoni, nel nostro caso). Nella stessa finestra si può indicare se la tabella di destinazione sta nel database corrente o in un altro database. Selezioniamo il nome della tabella tblTelefoni nella casella combinata Nome tabella e facciamo clic su OK. I passi di questa prima fase sono riprodotti nella Figura 2.28. Subito dopo, possiamo constatare che nella griglia di progettazione è comparsa una nuova riga, intestata con Accoda a. Nelle celle Accoda a, in corrispondenza delle colonne dei campi della tabella tblIndirizzi, possiamo ora selezionare il nome dei campi di destinazione della tabella tblTelefoni: Origine

Destinazione

(tblIndirizzi) ID Telefono

(tblTelefoni) Raccordo Numero

Questa impostazione fa sì che la query, quando viene eseguita, crei un nuovo record nella tabella tblTelefoni per ciascun record della tabella tblIndirizzi, mettendo nel campo Raccordo il contenuto del campo ID e nel campo Numero il contenuto del campo Telefono.

68  Capitolo 2

Figura 2.28  L’impostazione di una query di accodamento.

Un clic sul pulsante Esegui avvia l’esecuzione della query di accodamento, operazione che viene preceduta da una segnalazione di pericolo, come si può vedere nella Figura 2.29. Il tono ultimativo dell’avvertimento non deve spaventare: le modifiche create con l’esecuzione di questa query si potrebbero benissimo annullare eliminando manualmente i record accodati nella tabella di destinazione, se proprio dovesse essere necessario. Quello che viene segnalato, in modo un po’ troppo criptico, è che – se fosse necessario annullare le modifiche – non basterà fare semplicemente clic sul pulsante Annulla nella barra di accesso rapido.

Figura 2.29  La query di accodamento segnala quanti record verranno accodati.

Più importante è la segnalazione, data nella stessa finestra messaggio, del numero di record che verranno accodati: avendo verificato con la query di selezione di prova che i record possibili da accodare sono 26, è utile ricevere la conferma di questo numero all’atto dell’esecuzione della query di accodamento. Se vi fosse una discrepanza, vorrebbe dire che qualcosa non ha funzionato o che la query di accodamento è stata formulata in modo errato. Un clic sul pulsante Sì abilita l’esecuzione materiale della query. Passando alla sezione Tabelle del Riquadro di spostamento, potremo constatare, aprendo la tabella tblTelefoni, che questa adesso contiene i 26 record previsti.

Gli strumenti interattivi dell’interfaccia grafica  69

Modificare la struttura delle tabelle Adesso che tutti i numeri di telefono sono stati copiati dalla tabella tblIndirizzi nella tabella tblTelefoni, i campi corrispondenti a queste informazioni nella tabella tblIndirizzi non servono più. Per eliminarli definitivamente bisogna agire sulla struttura della tabella, perché diversamente si potrebbe soltanto cancellarne il contenuto, ma i campi resterebbero nei record, vuoti e inutilizzati. È buona norma evitare che nei record si trovino campi vuoti e inutilizzati, perché occupano spazio su disco e assorbono potenza elaborativa per essere gestiti: ovviamente, in un’applicazione database minuscola come questa che stiamo costruendo per fare un esempio il sovraccarico determinato dalla presenza di un paio di campi inutili è irrilevante, ma nelle applicazioni gestionali importanti, formate da decine o centinaia di tabelle, con migliaia o centinaia di migliaia di record, il sovraccarico può incidere in modo significativo sulle prestazioni complessive delle applicazioni stesse. Apriamo quindi la tabella tblIndirizzi in visualizzazione Struttura e selezioniamo la riga corrispondente al campo Telefono e poi eseguiamo il comando Strumenti tabella/Progettazione/Strumenti/Elimina righe, provocando l’uscita di una finestra di messaggio che segnala le conseguenze dell’azione: è proprio quello che vogliamo ottenere, quindi confermiamo la decisione con un clic sul pulsante Sì (Figura 2.30).

Figura 2.30  Eliminazione di un campo dalla struttura di una tabella.

C’è ancora un’operazione di modifica da fare sulla tabella tblIndirizzi. Per poter annotare liberamente informazioni di qualsiasi tipo, occorre un campo in più, con tipo dati Memo. Selezioniamo quindi nel pannello superiore la prima riga libera nella colonna Nome campo e digitiamo Note, specificando nella colonna a fianco che il tipo dati è Memo. Dopo aver salvato la modifica, aprendo la tabella tblIndirizzi in visualizzazione Foglio dati possiamo constatare che il campo Telefono, con il suo contenuto, è sparito, mentre è nato un nuovo campo, Note, che in questa visualizzazione si presenta come una normale casella di testo. Chiudiamo la tabella tblIndirizzi e passiamo a esaminare la tabella tblTelefoni. In questa tabella avevamo predisposto un campo, chiamato TipoTelefono, per registrarvi informazioni sul tipo di numero di telefono: casa, ufficio, fax, cellulare, seconda casa e così via. Nell’ipotesi che i 26 numeri di telefono presenti nella tabella siano tutti telefoni di casa, potremmo completare la tabella digitando Casa in tutti i campi TipoTelefono, dopo aver aperto la tabella tblTelefoni in visualizzazione Foglio dati. Ancora una volta, questo modo di procedere potrebbe andar bene in una situazione sperimentale, ma sarebbe poco professionale. L’approccio giusto, come si sarà indovinato, consiste nel ricorrere a una query di comando, nel caso specifico a una query di aggiornamento.

70  Capitolo 2

Attiviamo dunque il ciclo di creazione di una nuova query in visualizzazione Struttura, inseriamo la tabella tblTelefoni nel pannello superiore della griglia di struttura e in Strumenti query/Progettazione/Tipo di query scegliamo il comando Aggiornamento. Nella griglia di struttura si apre una nuova riga, intestata Aggiorna a. Nella prima colonna della griglia facciamo uscire il campo TipoTelefono nella casella Campo e digitiamo “Casa” nella casella Aggiorna a, badando a digitare anche le doppie virgolette (Figura 2.31). Eseguiamo la query, confermiamo la decisione quando compare la finestra di messaggio che avverte che saranno eseguiti 26 aggiornamenti e chiudiamo la query senza salvarla, visto che intendiamo usarla una sola volta.

Figura 2.31  La query di aggiornamento per immettere la stringa “Casa” nel campo TipoTelefono.

Aprendo la tabella tblTelefoni, possiamo notare che, dopo l’esecuzione della query di aggiornamento, tutti i record contengono nel campo TipoTelefono la parola Casa. Le doppie virgolette che racchiudono la parola Casa nella query stanno a indicare che si vuole utilizzare la stringa formata dalle lettere C, a, s, a e non il nome Casa. Come si può vedere, confrontando le rispettive griglie di struttura, una query di aggiornamento non è molto diversa da una query di accodamento. La differenza sostanziale sta nel fatto che con l’accodamento si creano nuovi record, mentre con l’aggiornamento si modificano record esistenti.

La Ricerca guidata Il campo TipoTelefono serve, come abbiamo convenuto, per contenere informazioni sul tipo di numero di telefono associato a una persona. Sarebbe opportuno che questa

Gli strumenti interattivi dell’interfaccia grafica   71

informazione fosse normalizzata, vale a dire che si usasse sempre lo stesso termine per indicare che si tratta di un numero di telefono di casa o di ufficio oppure di un cellulare. Se si scrivesse qualche volta Casa e qualche volta Numero di casa, per esempio, si andrebbe incontro a qualche problema quando si volessero fare selezioni (o anche un semplice ordinamento) su questo campo. Dal momento che le possibili varianti si possono facilmente prevedere, sarà il caso di metterne i nomi in una tabella apposita, da utilizzare come riferimento per scegliere il nome corretto, scritto sempre nello stesso modo. A questo scopo, creiamo una tabella che chiamiamo tblTipo, con un record formato da un campo ID di tipo Numerazione automatica, che fungerà da chiave primaria, e un campo di tipo Testo, chiamato TipoTelefono. Inseriamo poi un certo numero di valori nel campo TipoTelefono della tabella tblTipo, fino a ottenere un risultato simile a quello mostrato nella Figura 2.32.

Figura 2.32  La tabella tblTipo con il campo TipoTelefono completato.

Tenendo aperte contemporaneamente le tabelle tblTelefoni e tblTipo, si potrebbe ricavare il nome esatto del tipo di telefono ogni volta che si inserisce un nuovo record nella tabella tblTelefoni. Si potrebbe, ma sarebbe un modo di operare primitivo e goffo. Con gli strumenti disponibili in Access si può fare decisamente meglio. Quello che ci serve si trova nell’elenco dei tipi di dati dei campi quando si apre una tabella in visualizzazione Struttura: si tratta della voce Ricerca guidata, l’ultima nell’elenco dei tipi, che – come lascia intuire il suo nome – non è un tipo di dato. Apriamo quindi la tabella tblTelefoni in visualizzazione Struttura, selezioniamo la casella Tipo dati corrispondente al campo TipoTelefono e dall’elenco a discesa scegliamo Ricerca guidata. Si attiva un meccanismo di creazione guidata, che per procedere ha bisogno di alcune informazioni su quel che vogliamo ottenere. Il nostro obiettivo è quello di trasformare il modo in cui si presenta il campo TipoTelefono quando la tabella tblTelefoni è aperta in visualizzazione Foglio dati: vogliamo che in quel campo venga visualizzato – invece di una semplice casella di testo – un controllo Casella combinata, nel quale si possa scegliere uno dei tipi di telefono che abbiamo definito prima creando la tabella tblTipo. Ecco i passaggi della Ricerca guidata.

72  Capitolo 2

1. La prima finestra di dialogo della Ricerca guidata chiarisce che cosa verrà generato e chiede se i dati da visualizzare nella casella combinata si dovranno ricavare da una tabella o da una query oppure andranno reperiti in un elenco scritto direttamente fra le proprietà del campo.Avendo già a disposizione una tabella con i dati che ci servono, optiamo per l’uso di una tabella e facciamo clic su Avanti. 2. La finestra di dialogo successiva chiede quale tabella utilizzare fra quelle presenti nel file database. Selezioniamo tblTipo e proseguiamo. 3. La terza finestra di dialogo presenta due caselle, in quella di sinistra ci sono i campi disponibili nella tabella indicata nel passo precedente, mentre nella casella di destra, vuota, dovremo indicare quali campi vogliamo che vengano usati fra quelli disponibili.Visto che il record tblTipo contiene un solo campo, la scelta non è difficile e si fa con un clic sul pulsante che porta il simbolo > (Figura 2.33).

Figura 2.33  La finestra di dialogo della Ricerca guidata nella quale si selezionano i campi da presentare nella casella combinata.

4. Nel passo successivo si ha la possibilità di scegliere un criterio di ordinamento per l’elenco dei dati che verrà reso disponibile nella casella combinata. Optiamo per Crescente e proseguiamo. 5. Proseguendo, la Ricerca guidata chiede di fissare una larghezza per la colonna dove sarà visualizzato il dato. Ottenuta quest’ultima informazione, la Ricerca guidata è pronta a concludere il suo lavoro. Un clic su Fine fa uscire una richiesta di salvare la tabella tblTipo, perché i passi precedenti hanno creato una relazione fra questa tabella e la tabella tblTelefoni, che può diventare attiva soltanto dopo il salvataggio della tabella. Eseguito il salvataggio, torniamo alla struttura della tabella tblTelefoni, che nella scheda Ricerca del pannello Proprietà campo ora presenta quel che si vede nella Figura 2.34.

Gli strumenti interattivi dell’interfaccia grafica   73

Figura 2.34  Le nuove proprietà per il campo TipoTelefono generate dalla Ricerca guidata.

Tradotte in parlar comune, le varie proprietà indicano che: 1. il campo TipoTelefono verrà visualizzato mediante un controllo Casella combinata; 2. i dati da presentare in tale controllo provengono da una query; 3. le righe visualizzate nella casella combinata si originano con un enunciato in linguaggio SQL; 4. nella casella combinata viene mostrata una sola colonna, senza intestazione, elencando fino a 16 righe; 5. si possono immettere anche valori diversi da quelli presentati nell’elenco della casella combinata. L’elemento chiave di tutto è il contenuto della casella Origine riga, che è il seguente: SELECT [tblTipo].[ID], [tblTipo].[TipoTelefono] FROM tblTipo ORDER BY [TipoTelefono];

Questo enunciato SQL è il comando che estrae i valori del campo TipoTelefono per presentarli nella casella combinata quando si apre la tabella tblTelefoni in visualizzazione Foglio dati. Come si può notare, la casella Origine riga, quando è selezionata, mostra all’estremità destra due pulsanti, contrassegnati il primo dall’icona di una freccia che punta in basso e il secondo da una serie di tre puntini (...). Questo simbolo viene detto ellissi e indica la presenza di un generatore, uno strumento interno di Access che può essere attivato per generare in modo automatico o semi-automatico funzioni particolari. Un clic sul pulsante col simbolo dell’ellissi fa aprire una finestra intitolata tblTelefoni: Generatore di query, che ha le stesse caratteristiche di una finestra di struttura query e rappresenta graficamente l’istruzione SQL che viene utilizzata per estrarre i dati dalla tabella tblTipo. Chiudiamo questa finestra query e salviamo la struttura della tabella tblTelefoni, per aprirla poi in visualizzazione Foglio dati. Se ci portiamo su un qualunque record e selezionia-

74  Capitolo 2

mo il campo TipoTelefono, vedremo che questo ora si presenta nella forma di una casella combinata, presentando l’elenco di descrizioni ricavato dalla tabella tblTipo (Figura 2.35).

Figura 2.35  La modifica della struttura della tabella tblTelefoni ora consente di scegliere la descrizione del tipo di telefono da un elenco presentato con una casella combinata.

Modifica della struttura della maschera frmIndirizzi La maschera predefinita che avevamo creato automaticamente in Rubrica01 per accedere con più comodità ai record della tabella tblIndirizzi adesso non va più bene: la tabella ha un campo in meno e un campo in più. Importando la maschera in Rubrica02 e utilizzandola riceveremo una segnalazione di errore per il campo mancante e non vedremo il nuovo campo Note. Non è un problema: vista l’estrema semplicità con la quale si può creare una maschera standard, possiamo generarne automaticamente una nuova. Selezioniamo quindi il nome della tabella tblIndirizzi nel Riquadro di spostamento e diamo il comando Crea/Maschere/ Maschera: otteniamo in un istante una maschera che salviamo col nome frmIndirizzi e si presenta come nella Figura 2.36. Come possiamo vedere, la maschera ottenuta automaticamente a partire dalla tabella tblIndirizzi visualizza anche il record corrispondente della tabella tblTelefoni, entro una struttura grafica identica a quella di una maschera, ma inserita nella struttura della maschera frmIndirizzi. Questa maschera subalterna si chiama sottomaschera ed è stata creata in base alla relazione uno a molti che abbiamo impostato precedentemente fra le tabelle tblIndirizzi e tblTelefoni.

Gli strumenti interattivi dell’interfaccia grafica  75

Figura 2.36  La nuova maschera predefinita contiene una sottomaschera.

Modificare l’ordine dei record Nel paragrafo “Rispunta l’esigenza applicativa” avevamo annotato quattro esigenze da soddisfare con la maschera Indirizzi: 1. associare più numeri di telefono a un solo nominativo; 2. annotare informazioni non strutturate, commenti o altro, su ciascun record; 3. presentare i record in ordine alfabetico; 4. selezionare i record per provincia. Le modifiche fatte fin qui soddisfano le prime due esigenze. Occupiamoci ora della terza, che ci darà l’occasione di approfondire un importante aspetto delle applicazioni database: l’ordine dei record nelle tabelle e gli indici che lo determinano. Se non si interviene esplicitamente, stabilendo un criterio diverso nella definizione della struttura, i record vengono memorizzati in una tabella secondo l’ordine col quale vi vengono immessi. In presenza di una chiave primaria, l’ordine è dato dal valore della chiave primaria stessa. Nell’esempio col quale abbiamo lavorato finora, i record degli indirizzi si susseguono nell’ordine di incremento del campo ID, quello con tipo dati Numerazione automatica e che forma la chiave primaria. Questo criterio di ordinamento definisce un indice, un oggetto interno all’oggetto tabella, che può essere visualizzato ed eventualmente modificato scegliendo il comando Strumenti tabella/Progettazione/Indici quando la tabella è aperta in visualizzazione Struttura (si veda la Figura 2.37). Che cos’è un indice? Sostanzialmente, un criterio di ordinamento basato su uno o più campi. Come lascia intuire la struttura della finestra di dialogo Indici, è possibile definire più indici per una stessa tabella, dando a ciascun indice un nome diverso. Qualificando un indice come primario, sarà quell’indice a determinare l’ordinamento dei record quando la tabella viene aperta in visualizzazione Foglio dati.

76  Capitolo 2

Figura 2.37  La finestra di dialogo Indici nella tabella tblIndirizzi.

C’è però un vincolo: in una tabella può esistere un solo indice primario, per cui, se creassimo un indice basato sul campo Cognome e lo definissimo come primario, la chiave primaria non sarebbe più il campo ID, ma il campo Cognome. Inoltre, tale modifica non verrebbe consentita in questa situazione, dal momento che la tabella tblIndirizzi è in relazione con la tabella tblTelefoni in base alla chiave primaria ID e alla chiave esterna Raccordo. Per abilitare la modifica della chiave primaria, bisognerebbe prima disattivare le relazioni esistenti basate sulla chiave primaria precedente e poi, se possibile, crearne di nuove sulla scorta della nuova chiave primaria. Insomma, un discreto pasticcio. In realtà, la presenza di uno o più indici non modifica l’ordine col quale vengono visualizzati i record di una tabella: questo ordine è dato dalla chiave primaria. Gli indici vengono utilizzati quando si lavora sulle tabelle mediante codice di programmazione, come avremo modo di vedere in altre parti del libro, mentre per visualizzare il contenuto di una tabella con un criterio personalizzato si hanno a disposizione tecniche interattive molto più semplici ed efficaci. Rinunciamo quindi a creare altri indici per la tabella tblIndirizzi agendo in visualizzazione Struttura e proviamo a disporne i record in ordine alfabetico mentre siamo in visualizzazione Foglio dati. Quando viene aperta, la tabella tblIndirizzi si presenta con i record disposti secondo il valore crescente del campo ID, la chiave primaria. Per vederli in ordine alfabetico per cognome, basta selezionare la colonna Cognome e quindi dare il comando Home/Ordina e filtra/ Crescente. Istantaneamente i record si dispongono nell’ordine alfabetico per cognome. Chiudiamo la tabella e compare una finestra di messaggio che chiede se si vogliono salvare le modifiche alla struttura della tabella. Accettiamo con un clic sul pulsante Sì e proviamo ad aprire di nuovo la tabella in visualizzazione Struttura. La struttura non sembra modificata, nonostante l’avvertimento precedente.Anche visualizzando la finestra di dialogo Indici si nota che esiste soltanto l’indice chiamato PrimaryKey, che viene creato per impostazione predefinita quando si imposta una chiave primaria. Se, però, riapriamo la tabella in visualizzazione Foglio dati, questa si presenta di nuovo con i record ordinati alfabeticamente in base al contenuto del campo Cognome. In realtà, agendo sui pulsanti Crescente o Decrescente del gruppo di comandi Ordina e filtra nella scheda Home non si modifica la struttura della tabella, ma il suo layout, cioè il modo in cui sono presentati i record. Poco male, quel che conta è il risultato.

Gli strumenti interattivi dell’interfaccia grafica  77

Siccome, però, preferiamo usare la nuova maschera frmIndirizzi, completa di sottomaschera frmTelefoni, per consultare ed eventualmente modificare o aggiornare la nostra rubrica di indirizzi, proviamo ad aprire la maschera frmIndirizzi per vedere se il nuovo layout della tabella sottostante si riflette sull’ordine con il quale la maschera presenta i record. Non è cambiato nulla: sono ancora nella successione determinata dal numero ID. Per ottenere il risultato che ci interessa, dobbiamo agire sulla struttura della maschera. Nella Finestra delle proprietà della maschera frmIndirizzi, apriamo la scheda Dati e digitiamo tblIndirizzi.Cognome nella casella corrispondente alla proprietà Ordina per (Figura 2.38). Salviamo la maschera così modificata: ora i record compaiono ordinati alfabeticamente per cognome.

Figura 2.38  Impostazione della proprietà Ordina per sulla maschera frmIndirizzi.

La stringa che abbiamo digitato nella casella Ordina per è formata da due nomi, congiunti con un punto: in questo modo si indica che l’ordinamento deve avvenire in base al campo Cognome contenuto nella tabella tblIndirizzi: digitando semplicemente Cognome, senza specificare la tabella da cui proviene il campo, la proprietà Ordina per in questo caso non si attiva, perché la maschera è basata su due diverse tabelle e il solo nome del campo Cognome non è sufficiente per attivare l’ordinamento. Possiamo ottenere lo stesso risultato agendo direttamente sulla maschera, quando è in visualizzazione Maschera: selezioniamo la casella di testo che contiene il campo Cognome e nel gruppo Ordina e filtra della scheda Home facciamo clic sul pulsante Crescente. In questo modo, non soltanto la maschera presenterà i record ordinati per Cognome (indipendentemente da come sono ordinati nella tabella tblIndirizzi da cui provengono), ma verrà anche inserito automaticamente il valore tblIndirizzi.Cognome nella casella Ordina per della Finestra delle proprietà della maschera.

78  Capitolo 2

Modificare i criteri di visualizzazione L’esigenza applicativa di selezionare i record per provincia può essere ridefinita, in modo più generale, come la possibilità di vedere soltanto i record che hanno un determinato valore in un certo campo: per esempio, soltanto i record corrispondenti agli indirizzi della provincia di Pavia, quindi quelli che hanno la stringa “PV” nel campo Provincia. In pratica, bisognerebbe poter creare un filtro, che rendesse visibili soltanto i record che corrispondono a un determinato criterio di selezione. Questa possibilità è intrinseca nelle visualizzazioni Foglio dati e Maschera e la sua attivazione non richiede alcuna predisposizione particolare. Basta selezionare un campo che contenga il valore da usare come filtro e fare clic sul pulsante Selezione nel gruppo Ordina e filtra della scheda Home. Come si vede nella parte superiore della Figura 2.39, scende un elenco di opzioni per scegliere un criterio di filtraggio; se ne sceglie uno e, istantaneamente, l’insieme dei record visualizzati si riduce a quelli che nel campo selezionato contengono un valore corrispondente al criterio scelto nell’elenco del comando Selezione, come si può vedere nella parte inferiore della Figura 2.39. La segnalazione che l’insieme di record che ora è visualizzato è soltanto una parte dell’intera tabella sottostante alla maschera è data dalla presenza di un pulsante di comando con l’etichetta Filtrato, che compare alla destra dei pulsanti di navigazione. Per disattivare il filtro e tornare a visualizzare tutti i record, si fa clic su questo pulsante.

Comandi personalizzati per filtrare Pur essendo l’applicazione di un filtro una funzionalità semplice e intuitiva, comporta il ricorso alla barra multifunzione, la cui conoscenza non può essere data per scontata nell’ipotetico utente finale dell’applicazione. È meglio, quindi, predisporre nella maschera frmIndirizzi opportuni controlli che consentano all’utente finale di applicare e di rimuovere un filtro sul campo Provincia, in maniera del tutto intuitiva e al riparo da errori di manovra. In termini pratici, avremo bisogno di: 1. un controllo Casella combinata per selezionare la sigla della provincia da utilizzare come filtro; 2. un controllo Pulsante di comando per rimuovere il filtro. L’obiettivo da raggiungere è che l’utente possa selezionare una provincia nella casella combinata e ottenere automaticamente, per il solo fatto che ha selezionato tale provincia, l’applicazione del filtro in base a selezione. Un clic sul pulsante di comando gli deve consentire di rimuovere il filtro, tornando a visualizzare tutti i record della tabella tblIndirizzi. Oltre ai controlli, quindi, servono alcuni elementi nuovi, che attivino le funzionalità richieste quando si agisce su quei controlli. Le azioni che occorrono si possono eseguire con oggetti Access concepiti appositamente per questo scopo e che si chiamano macro. Come prima cosa, prepariamo i nuovi controlli. In un’area libera della maschera frmIndirizzi, aperta in visualizzazione Struttura, tracciamo un controllo Casella combinata e un controllo Pulsante di comando prelevandoli dal gruppo Controlli della scheda Strumenti struttura maschera/Progettazione. Diamo al pulsante di comando il nome cmdTutti e alla casella combinata il nome cboSelProv. Digitiamo la dicitura Mostra tutti per il pulsante di comando cmdTutti e l’etichetta Scegli

Gli strumenti interattivi dell’interfaccia grafica  79

provincia per la casella combinata cboSelProv. Salviamo la maschera così modificata e lasciamola aperta in visualizzazione Struttura.

Figura 2.39  I record tblIndirizzi filtrati in base alla provincia di Pavia.

80  Capitolo 2

Piazzare controlli: un gioco di pazienza Creare e piazzare controlli su una maschera Access può essere un lavoro complesso e laborioso, dal momento che le possibilità di selezione, dimensionamento e collocazione sono numerosissime, vanno eseguite col mouse il cui puntatore può assumere fino a otto diversi aspetti, in funzione di quello che si può o si vuole ottenere per piazzare uno o più controlli. Per non rendere troppo prolisse le descrizioni delle operazioni, da qui in poi si daranno per conosciute le tecniche di piazzamento dei controlli, che sono descritte nel paragrafo “Piazzare i controlli nelle maschere” dell’Appendice B.

Nella casella combinata dovrà comparire un elenco di sigle di province fra cui scegliere quella da usare per filtrare i record. Non avrebbe senso mettere a disposizione nell’elenco province non presenti nei record della tabella tblIndirizzi, per cui andremo a ricavare le sigle dalla tabella tblIndirizzi stessa con una semplice query di selezione basata sul campo Provincia, che generiamo con un clic su Crea/Query/Struttura query. Per fare in modo che la nuova query estragga tutte le sigle delle province ignorando le eventuali ripetizioni, bisogna specificare, nella griglia di struttura, una proprietà per la query, prima di eseguirla.Tale proprietà si imposta nella finestra di dialogo Proprietà query, che si può aprire facendo un clic destro nel pannello superiore della griglia di struttura. Nella finestra Proprietà query, si imposta Sì nella casella Valori univoci. Nel pannello inferiore della griglia di struttura selezioniamo Provincia come Campo e Crescente nella casella Ordinamento. Prima dell’esecuzione, la query si presenterà come nella Figura 2.40.

Figura 2.40  La query per selezionare le sigle delle province ignorando le ripetizioni.

Eseguendo la query per prova, possiamo notare che il recordset presenta le sigle delle province in ordine alfabetico e in un numero inferiore a quello dei record della tabella tblIndirizzi, alcuni dei quali hanno lo stesso valore nel campo Provincia. È quello che volevamo.

Gli strumenti interattivi dell’interfaccia grafica   81

Dal pulsante Visualizza nel gruppo Risultati della scheda Strumenti query/Progettazione selezioniamo Visualizzazione SQL e copiamo l’intero enunciato che compare nella finestra dell’editor SQL. Il testo sarà il seguente: SELECT DISTINCT tblIndirizzi.Provincia FROM tblIndirizzi ORDER BY tblIndirizzi.Provincia;

Chiudiamo la query senza salvarla e torniamo alla struttura della maschera frmIndirizzi. Selezioniamo la casella combinata cboSelProv e facciamo uscire la sua Finestra delle proprietà. Nella scheda Dati selezioniamo la casella Origine riga e vi incolliamo l’istruzione SQL che avevamo copiato nel passaggio precedente. Impostiamo su 1 la casella della proprietà Colonna associata e su Sì la casella della proprietà Solo in elenco. Salviamo la maschera così modificata e apriamola in visualizzazione Maschera. Agendo sulla freccia in giù della casella combinata che porta la dicitura Scegli provincia, possiamo constatare che è possibile selezionare una provincia (come conseguenza della proprietà Origine riga, che manda in esecuzione la query quando si apre la casella combinata). Se proviamo a scrivere una sigla direttamente nella casella combinata, una finestra di messaggio ci avverte che non è consentito immettere valori diversi da quelli contenuti nell’elenco (questa segnalazione è provocata dall’impostazione su Sì della proprietà Solo in elenco). Tutto bene. Ma non è ancora attiva la funzionalità di filtro, perciò la selezione della provincia per ora non ha alcun effetto. Analogamente, non accade nulla quando si fa clic sul pulsante di comando che porta la dicitura Mostra tutti. Perché i due nuovi controlli diano l’effetto desiderato – applicare un filtro e rimuoverlo – bisogna associare un’azione a ciascun controllo.

Le azioni Macro Il termine “macro” si usa da sempre in informatica ed è la contrazione del termine originale “macroistruzione”, col quale si designavano determinati costrutti di programmazione ottenuti combinando assieme più istruzioni elementari e identificando tali costrutti con un nome convenzionale. Con le macroistruzioni si programma più in fretta e con maggior sicurezza, perché non si devono scrivere, per esempio, dieci o venti righe di codice con sigle astruse e difficili da tenere a mente, ma ne basta una sola, magari caratterizzata da un nome facile da ricordare ed evocativo della funzione da eseguire. Dal momento che con i primi linguaggi di programmazione si dovevano utilizzare molto spesso gruppi di istruzioni che si ripetevano sempre uguali, il ricorso alle macroistruzioni snelliva notevolmente il pesante lavoro di scrittura del codice. Nelle applicazioni che fanno parte del pacchetto Office, per esempio in Word e in Excel, è possibile creare e utilizzare macro, che sono in realtà piccoli programmi scritti in Visual Basic for Applications (VBA) e che hanno in comune con le macroistruzioni di un tempo soltanto il nome. Le macro che si hanno a disposizione in Access sono invece più simili strutturalmente alle macroistruzioni: si tratta di oggetti che incorporano un codice di programma col quale si esegue una determinata azione su altri oggetti del database. Le azioni possibili

82  Capitolo 2

sono un numero finito (53, per l’esattezza, in Access 2000, 56 in Access 2002/2003, 70 in Access 2007 e 86 in Access 2010), si attivano richiamando il loro nome e specificando per ogni singola azione opportuni parametri o argomenti, cioè informazioni che identificano l’oggetto (o gli oggetti) sul quale eseguire l’azione e in alcuni casi il modo in cui l’azione deve essere eseguita. Usate largamente nelle prime versioni di Access (1.0, 2.0 e 95), a partire da Access 97 le macro passano in secondo piano rispetto al linguaggio di programmazione VBA, che approfondiremo nella Parte III del libro. Con Access 2010 le macro tornano a essere importanti, al punto che è stata rifatta completamente la struttura dell’interfaccia grafica per crearle e sono state introdotte macro con caratteristiche funzionali diverse da quelle già note e per questo chiamate macro di dati. Chiarito che una macro è, sostanzialmente, un codice di programma pronto all’uso, si tratta di capire in che modo si possa eseguire l’azione che caratterizza una macro. La risposta a questa domanda sta nel concetto di evento, che permea di sé tutto il mondo dei programmi applicativi che operano in ambiente Windows e svolge un ruolo fondamentale in Access. Un evento è un’azione specifica che si verifica su o con un determinato oggetto. Gli eventi sono in genere il risultato di un’azione dell’utente: clic del mouse, battute sulla tastiera, apertura o chiusura di maschere, attivazione di controlli e via enumerando. Tutti gli oggetti Access sono predisposti per accorgersi che si è verificato un evento che li riguarda. Associando una macro a un evento si ottiene l’esecuzione dell’azione della macro quando si verifica quell’evento. Nell’esempio che stiamo sviluppando, si tratta di associare l’applicazione del filtro all’evento “selezione di una provincia” nella casella combinata cboSelProv. Una volta applicato il filtro, un’altra azione, la rimozione del filtro, dovrà essere associata all’evento “clic” sul pulsante cmdTutti. Tenendo a mente questi concetti, procediamo prima a definire le azioni macro che occorrono e poi ad associare tali azioni con gli eventi che ne provocheranno l’esecuzione. Per lavorare sulle macro è necessario crearne almeno una, dando il comando Crea/ Macro e codice/Macro dalla barra multifunzione. Si ottiene così l’apertura della finestra di composizione delle macro, che come abbiamo accennato, in Access 2010 è radicalmente rinnovata rispetto alle versioni precedenti di Access. La esamineremo approfonditamente nel prossimo capitolo, per ora limitiamoci a creare una semplice macro scegliendo dall’elenco Catalogo macro o dall’elenco che si apre agendo sulla freccia a discesa al centro della finestra l’azione ApplicaFiltro, come vediamo nella Figura 2.41. Un clic sul nome ApplicaFiltro e al posto dell’elenco delle azioni compaiono tre caselle per impostare i parametri o argomenti richiesti dall’azione selezionata: Nome filtro, Condizione WHERE e Nome controllo. Il terzo argomento è facoltativo e possiamo ignorarlo; i primi due sono in alternativa: o si indica il nome del filtro da applicare (casella Nome filtro) o si indica esplicitamente la condizione che rappresenta il filtro (casella Condizione WHERE). Un filtro può essere una query esistente, nel qual caso il nome del filtro è il nome della query. Scegliamo la seconda modalità, non avendo a disposizione una query già definita, e nella casella Condizione WHERE digitiamo questa espressione: [Provincia]=[Forms]![frmIndirizzi]![cboSelProv]

che equivale a dire: mostra tutti i record dove (WHERE) il contenuto del campo Provincia è uguale al contenuto selezionato nella casella combinata cboSelProv che si trova nella maschera frmIndirizzi.

Gli strumenti interattivi dell’interfaccia grafica   83

Figura 2.41  La finestra Macro in Visualizzazione Struttura.

I punti esclamativi che separano i nomi dei tre oggetti a destra del segno uguale stanno a indicare che l’oggetto che sta a destra del punto esclamativo appartiene all’oggetto che sta a sinistra. Il primo oggetto della successione si chiama Forms e rappresenta tutte le maschere (forms, in inglese) esistenti nel database aperto. frmIndirizzi è la specifica maschera sulla quale si esegue l’azione e cboSelProv è uno degli oggetti (controlli) contenuti nella maschera frmIndirizzi. La casella Condizione WHERE è un po’ piccola per scrivervi dentro agevolmente l’espressione. Si può ottenere un ambiente di lavoro più comodo facendo clic sul pulsante marcato con i puntini di sospensione che compaiono sulla destra della casella stessa: si apre in questo modo l’articolata finestra di dialogo del Generatore di espressioni (Figura 2.42), nella quale non soltanto si può scrivere più agevolmente ma si potrebbe anche, volendo, comporre la condizione agendo sugli elementi che si trovano nella parte inferiore. Chiudiamo la finestra di struttura salvando la macro col nome mcrFiltroProvincia. Torniamo ora alla maschera frmIndirizzi, apriamola in visualizzazione Struttura, selezioniamo la casella combinata cboSelProv e nella scheda Eventi della Finestra delle proprietà facciamo clic nella casella Su modifica. Un ulteriore clic sul pulsante a freccia fa uscire il nome della macro mcrFiltroProvincia, che selezioniamo in modo che sia associata all’evento Su modifica. Così facendo, si ottiene che venga eseguita la macro mcrFiltroProvincia ogni volta che si verifica l’evento “modifica del contenuto della casella combinata”. Salviamo la maschera così modificata, apriamola in visualizzazione Maschera, selezioniamo una provincia dall’elenco Scegli provincia e istantaneamente i record compaiono filtrati in base alla provincia selezionata. Per disattivare il filtro dobbiamo fare clic sul pulsante Filtrato a destra dei pulsanti di spostamento, perché non è ancora pronta la macro da associare al pulsante che porta l’etichetta Mostra tutti.

84  Capitolo 2

Figura 2.42  Nella finestra Generatore di espressioni si può comporre agevolmente l’espressione per la Condizione WHERE.

Chiudiamo la maschera e attiviamo la creazione di una nuova macro, che sarà formata da due azioni diverse: rimuovere il filtro e far sparire dalla casella combinata cboSelProv la sigla della provincia sulla quale è stato impostato il filtro. Questo accorgimento è dettato da ragioni di chiarezza per l’operatore: la presenza di una sigla di provincia nella casella combinata sotto la dicitura Scegli provincia fa pensare che sia attivo un filtro. La rimozione del filtro non comporta la cancellazione del contenuto della casella combinata, quindi il permanere di una sigla in questa casella potrebbe generare confusione. Selezioniamo l’azione MostraOgniRecord, che non ha argomenti. Questa azione equivale al comando Rimuovi filtro, eseguito dalla barra multifunzione. Nella casella immediatamente successiva selezioniamo l’azione ImpostaValore, che chiede due argomenti: l’elemento da impostare e il valore da attribuirgli. Nella casella Elemento va digitato il nome completo dell’elemento (l’oggetto) sul quale si esegue l’azione: [Forms]![frmIndirizzi]![cboSelProv]

Questa formulazione segue la stessa convenzione sintattica dell’espressione per l’azione della macro mcrFiltroProvincia: letta da destra verso sinistra identifica il controllo cboSelProv, che sta nella maschera frmIndirizzi, che appartiene all’insieme Forms. Nella casella Valore digitiamo il valore che intendiamo impostare per l’elemento.Vogliamo che compaia vuoto, quindi immettiamo una stringa vuota, che si rappresenta con due doppie virgolette separate da uno spazio: “ ”. Salviamo la macro col nome mcrTutti e torniamo di nuovo alla struttura della maschera frmIndirizzi. Qui associamo la macro mcrTutti all’evento Su clic del pulsante di comando

Gli strumenti interattivi dell’interfaccia grafica  85

nel caso precedente, per la scheda Evento della Finestra delle proprietà del pulsante di comando. Salviamo la maschera e creiamo una nuova macro, scegliendo per questa l’azione ChiudiFinestra, che richiede tre parametri: Tipo oggetto, Nome oggetto e Salva. Per ciascuno di questi parametri è disponibile un elenco a discesa, per cui scegliamo Maschera come Tipo oggetto, il nome della maschera frmIndirizzi per il Nome oggetto e No per il parametro Salva. Salviamo la macro col nome mcrChiudi e chiudiamo la finestra di composizione delle macro. Torniamo ad aprire in visualizzazione Struttura la maschera frmIndirizzi e associamo la macro mcrChiudi all’evento Su clic del pulsante di comando cmdChiudi. Chiudiamo la maschera salvando le modifiche appena fatte e torniamo ad aprirla. Se tutte le impostazioni sono state eseguite correttamente, ora è possibile selezionare una provincia per attivare il filtro e rimuovere il filtro facendo clic sul pulsante con l’etichetta Mostra tutti. È inoltre possibile chiudere la maschera con un clic sul pulsante di comando che porta la dicitura Chiudi. La maschera frmIndirizzi con i nuovi controlli associati a macro è riportata nella Figura 2.43. cmdTutti, passando, come

Figura 2.43  La versione finale della maschera frmIndirizzi.

I limiti degli strumenti interattivi Realizzando la semplice applicazione Rubrica01 e quella appena un po’ più complessa che è stata chiamata Rubrica02, si sono utilizzati quattro oggetti principali di Access: tabelle, query, maschere e macro. Con questa esercitazione volevamo passare in rassegna le caratteristiche e le funzionalità più salienti di questi oggetti e mostrare come sia possibile realizzare un’applicazione ragionevolmente completa usando quasi esclusivamente le tecniche interattive disponibili nell’interfaccia grafica di Access. Sono stati deliberatamente trascurati gli oggetti Report, perché strutturalmente simili alle maschere e i Moduli, che sono semplici file di testo contenenti enunciati di programmazione in linguaggio VBA Lavorando col mouse sugli strumenti interattivi – finestre Struttura e creazioni guidate – è facile creare applicazioni database di discreto livello, se sono state individuate

86  Capitolo 2

analiticamente l’esigenza applicativa da soddisfare e tutte le sue conseguenze in termini di interfaccia utente. Questi strumenti, però, vanno presi per quello che sono: comode metafore per far generare codici di programma all’interno del file database Access. La griglia di struttura con la quale si può costruire una query, per esempio, è un comodo strumento grafico per selezionare i campi da estrarre da una tabella, ma ciò che si ottiene, e che esegue materialmente la query, è una stringa di caratteri che forma un enunciato in linguaggio SQL.Anche la Ricerca guidata, alla fine dei passaggi interattivi, genera un enunciato SQL. Quanto alle macro, le azioni che le compongono sono in realtà schemi di codice di programmazione predefiniti, che vanno completati con gli argomenti opportuni per renderli operativi. Quel che si ottiene, con gli strumenti interattivi dell’interfaccia grafica, è un file database formato da tabelle e da una serie di codici di programmazione, che definiscono query, maschere, controlli e quant’altro può servire per rendere agevole il lavoro sui dati, cioè sulle tabelle, il vero nocciolo duro di qualunque applicazione database. Per sfruttare appieno Access e le sue grandi potenzialità occorre capire a fondo quel che c’è dietro l’interfaccia grafica e i suoi strumenti interattivi e impadronirsi delle tecniche di programmazione interne di Access. E questo perché, lavorando soltanto con l’interfaccia grafica, non si possono ottenere da un’applicazione database tutte le funzionalità che sono necessarie per un’utilizzazione realmente professionale. In concreto, un’applicazione professionale: 1. deve presentarsi in modo chiaro all’utente, con un’interfaccia che non richieda particolari sforzi di interpretazione; 2. deve essere a prova di errore umano e di errore di sistema. Molto si può fare con i soli strumenti interattivi per raggiungere il primo obiettivo, ma per avvicinarsi al secondo bisogna ricorrere direttamente alle funzionalità di programmazione disponibili in Access. Occorre inoltre tener presente che Access è al tempo stesso un ambiente di sviluppo e un ambiente nel quale si possono eseguire applicazioni database. Come abbiamo già accennato nelle pagine precedenti (si veda il riquadro “Salvare i dati in Access”), Access è concepito per essere usato – in momenti diversi – da due tipi di utenti: l’utente/realizzatore e l’utente/operatore. Chi usa un’applicazione (l’utente/operatore) non deve preoccuparsi di salvare le modifiche che apporta ai dati, perché queste diventano effettive a mano a mano che le esegue; non può salvare l’applicazione con un nome diverso nella stessa cartella in cui risiede o con lo stesso nome in un’altra cartella, perché non esistono neppure i comandi di menu per poterlo fare. Questo per impostazione predefinita. È invece responsabilità di chi realizza un’applicazione (l’utente/realizzatore) predisporre tutta una serie di impostazioni – alcune delle quali si possono attivare soltanto con codice di programma – per inibire all’utente/operatore l’accesso, casuale o deliberato, agli strumenti interattivi con i quali si possono modificare la struttura di maschere, query, tabelle e quant’altro è stato creato per dar corpo all’applicazione. Il prossimo capitolo, dedicato a una breve rassegna degli strumenti di programmazione disponibili in Access, consentirà fra l’altro di vedere in che modo un utente/realizzatore può preparare un’applicazione in modo che un utente/operatore non rischi di scardinarla attivando inopportunamente qualche modifica di struttura.

Capitolo 3

Gli strumenti di programmazione Tutti i risultati che si ottengono con Access sono generati dall’esecuzione di codici di programma. Nella maggior parte dei casi, tali codici non sono visibili all’utente/realizzatore, che li genera inconsapevolmente operando con gli strumenti interattivi. Questo meccanismo è ben visibile nel caso delle query: si compone interattivamente una query utilizzando metodi grafici nella griglia di struttura e si ottiene un codice di programma scritto in linguaggio SQL, che si può visualizzare ed eventualmente modificare, a condizione di conoscerne la sintassi, senza ricorrere all’interfaccia grafica. La presenza di codici di programma non è visibile in altri oggetti con la stessa immediatezza con cui la si può rilevare nelle query, ma è facile intuirne il ruolo nel caso, per esempio, delle macro. Non è possibile esaminare direttamente il contenuto delle azioni macro, ma è evidente che si tratta di spezzoni di codice di programma, predefiniti e pronti a operare (a eseguire “azioni”, appunto) in base agli argomenti che vengono indicati quando si definisce la loro struttura. Oltre ai codici per così dire “occulti”, che stanno dietro le schermate grafiche di query e macro, esistono due aree specifiche in un file database Access, predisposte per contenere codici di programma “palesi”, scritti deliberatamente da chi fa sviluppo. Queste aree possono contenere codici di programma indipendenti o associati a una maschera o a un report. Quando sono indipendenti si chiamano moduli e quando sono associati a maschere o a report si chiamano moduli di classe. Si chiamano moduli di classe anche codici di programma particolari con i quali si creano nuovi oggetti. I moduli sono oggetti con un loro nome, at-

In questo capitolo •

Perché le routine



Quel che occorre sapere

88  Capitolo 3

tribuito dall’utente/realizzatore, che viene visualizzato nella sezione Moduli del Riquadro di spostamento in Access 2007 e 2010 e nella Barra oggetti delle versioni precedenti. I moduli si presentano materialmente come documenti di testo, che contengono istruzioni scritte in un linguaggio di programmazione. Un gruppo di istruzioni che esegue un’operazione specifica prende il nome di routine. Normalmente le routine che si memorizzano in un modulo di classe sono routine evento, che vengono eseguite quando si verifica un determinato evento su uno degli oggetti nella maschera (o nel report) alla quale è associato il modulo di classe. Nei moduli indipendenti si memorizzano, per lo più, routine non collegate a un oggetto o a un evento specifico, destinate a un utilizzo più generalizzato all’interno dell’applicazione database. Nello stesso contesto si memorizzano i moduli di classe non associati a maschere o a report. Stabilito che cosa sono le routine, resta da chiarire a che cosa servono e quando si utilizzano.

Perché le routine Le routine servono per eseguire operazioni senza richiedere l’intervento dell’operatore. Tali operazioni possono essere semplici, come per esempio chiudere una maschera o applicare un filtro, o complesse, come generare un recordset con una serie di campi calcolati e visualizzarlo a stampa. Si ricorre inoltre alle routine per eseguire automaticamente operazioni di una certa complessità, quali: •• intercettare e bloccare errori dell’operatore; •• eseguire operazioni di calcolo sui campi di un recordset; •• acquisire input in modo controllato; •• generare output in formati diversi da quelli di Access; •• acquisire segnalazioni di errore provenienti dal sistema, attivando vie d’uscita che non blocchino l’applicazione. Integrando con opportune routine il database attrezzato che si ottiene con gli strumenti interattivi lo si può trasformare in un’applicazione database a pieno titolo. Stabilire quali routine creare, quali collocare in oggetti Modulo e quali in moduli di classe associati a maschere o a report, scegliere lo strumento di programmazione più adatto per ciascuna routine, sono tutte decisioni che caratterizzano quella forma elevata di artigianato che è la realizzazione di applicazioni database. Nei prossimi capitoli descriveremo analiticamente i vari strumenti di programmazione disponibili in Access. Qui esamineremo alcuni esempi pratici, per capire perché si usano le routine e come sono fatte. Gli esempi sono tratti per lo più dall’applicazione dimostrativa Northwind, che viene distribuita insieme con Access proprio per questo genere di finalità didattiche.

Gli strumenti di programmazione   89

L’applicazione Northwind in Access 2010 Fin dalla prima versione di Access, la vituperata Access 1.0, e in tutte le successive, è sempre stato distribuito col programma un file database chiamato Northwind.mdb, concepito per dare un esempio concreto di quello che si può ottenere con gli strumenti di Access. Anche tutte le versioni in italiano di Access contengono un file Northwind.mdb con i nomi degli oggetti opportunamente tradotti in italiano. Lo stesso è stato fatto per l’edizione 2010 di Access, dove l’applicazione Northwind è disponibile sotto forma di modello in base al quale creare un file Access 2010 da provare e studiare. Malauguratamente, però, la versione italiana del file Northwind.accdb, diversamente da quella di Northwind.mdb, è molto povera di dati esemplificativi, le tabelle contengono un minor numero di record, come si vede nell’elenco che segue e i contenuti dei campi in molte tabelle sono poco differenziati: per esempio, i dieci fornitori nella tabella Fornitori di Northwind.accdb si chiamano Fornitore A, Fornitore B e così via fino a Fornitore J, mentre i 29 fornitori della stessa tabella di Northwind.mdb hanno nomi più articolati e realistici, tipo Cooperativa de Quesos ‘Las Cabras’ o Heli Süßwaren GmbH & Co. KG. Principali tabelle Northwind

.mdb

.accdb

Clienti

91

29

Dettagli ordini

2155

55

Fornitori

29

10

Impiegati

9

9

Ordini

830

48

Prodotti

77

45

La maggiore ricchezza e diversificazione dei dati nelle tabelle delle versioni precedenti dell’applicazione Northwind rende quindi il file database Northwind.mdb più adatto a essere utilizzato ai fini dimostrativi per i quali è stata creata l’applicazione. Nel CD-ROM allegato al libro abbiamo immagazzinato un file Access 2010 chiamato NorthwindLavoro.accdb, creato importando in un nuovo file in formato Access 2007 tutti gli oggetti del file Northwind.mdb, versione Access 2003.

Gestire gli errori dell’operatore Attiviamo Access e apriamo il file database NorthwindLavoro.accdb. Un clic sul pulsante OK nella finestra di apertura ne provoca la scomparsa, lasciando libero l’accesso alla finestra Database. Nella sezione Maschere del Riquadro di spostamento apriamo la maschera Finestra Vendite per anno e facciamo clic sul suo pulsante OK. Si apre immediatamente una finestra di messaggio, con la quale veniamo cortesemente informati che questa maschera non può essere utilizzata separatamente dal report Vendite per anno al quale è associata (Figura 3.1). Un clic su OK nella finestra di messaggio ne provoca la chiusura. A questo punto non ci resta che chiudere con un clic su Annulla la maschera che abbiamo aperto incautamente e tornare alla finestra Database.

90  Capitolo 3

Figura 3.1  La maschera Finestra Vendite per anno e il messaggio che si riceve quando si fa clic su OK.

Per capire come funziona e come è fatto il meccanismo che ha provocato l’avvertimento, dobbiamo aprire la maschera Finestra Vendite per anno in visualizzazione Struttura, selezionare il controllo pulsante di comando OK ed esaminarne le proprietà. Nella scheda Evento della sua Finestra delle proprietà, osserviamo che la casella Su clic contiene un generico richiamo a una routine evento. Per esaminarla, possiamo fare clic sul pulsante con il simbolo di ellissi alla destra della casella Su clic, oppure scegliere il comando Strumenti struttura maschera/ Struttura/Strumenti/Visualizza codice (Figura 3.2). In entrambi i casi, si apre il modulo di classe associato alla maschera Finestra Vendite per anno, con la differenza che nel primo caso (clic sul pulsante ellissi) ci si trova direttamente posizionati sulla routine associata all’evento Clic, mentre nel secondo caso (clic sul pulsante Visualizza codice) ci si trova in testa al modulo di classe, che contiene più di una routine evento. La finestra che si apre ha un titolo composito, Form_ Finestra Vendite per anno: (codice), formato con la parola inglese Form (che sta per l’italiano “maschera”), il titolo della maschera alla quale si riferisce e l’indicazione (codice), che specifica appunto che si tratta di codice di programmazione VBA. La presenza di parole inglesi è una caratteristica dei linguaggi interni di Access con la quale bisogna fare i conti: se non si hanno un po’ di rudimenti di inglese non è possibile programmare in Access. E se uno l’inglese non lo sa? Che lo impari: quello che serve per programmare è un inglese talmente elementare che si può imparare in un paio d’ore.

Gli strumenti di programmazione    91

Figura 3.2  L’accesso al modulo di classe associato a una maschera.

Il modulo del codice è un file di testo, all’interno del quale ci si muove con i consueti strumenti di Windows. Portiamoci sulla routine evento che ci interessa: si chiama Private Sub OK_Click() e il suo testo è riportato qui di seguito. Private Sub OK_Click() On Error GoTo Err_OK_Click     Dim strMsg As String, strTitolo As String     Dim intStile As Integer     '     '     '     '

Se il report Vendite per anno non è già stato aperto per la stampa o per la visualizzazione in anteprima, genera un errore. (La variabile blnOpening è vera solo se l'evento Apertura associato al report è stato eseguito).

    If Not Reports![Vendite per anno].blnOpening Then Err.Raise 0     ' Nasconde la maschera.     Me.Visible = False Esci_OK_Click:     Exit Sub Err_OK_Click:     strMsg = "Per utilizzare la maschera, è necessario stampare o         ➥visualizzare in anteprima il report Vendite per anno dalla         ➥finestra del database oppure in visualizzazione Struttura."     intStile = vbOKOnly     strTitolo = "Apri dal report"     MsgBox strMsg, intStile, strTitolo     Resume Esci_OK_Click End Sub

92  Capitolo 3 NOTA Gli enunciati di programmazione sono righe intere, senza a capo, che a volte possono essere molto lunghe. Il segno ➥ in testa a una riga stampata indica che nella routine quella riga è la continuazione della precedente.

Nonostante l’aspetto formidabile e le molte parole misteriose che la compongono, la routine è piuttosto semplice e illustra molto bene un meccanismo di protezione in caso di errore dell’operatore, uno degli eventi che ricorrono più spesso quando si usano le applicazioni. Vediamo prima i concetti: i particolari del codice di programma si capiranno più agevolmente in seguito. La maschera Finestra Vendite per anno è associata al report Vendite per anno e si apre contestualmente con l’apertura del report. La maschera, però, è visibile nella sezione Maschere del Riquadro di spostamento, per cui un operatore non informato potrebbe aprirla direttamente e provare a fare clic sul pulsante OK. Non essendo aperto il report Vendite per anno, l’evento clic sul pulsante OK non avrebbe senso. Chi ha realizzato l’applicazione ha ritenuto giusto predisporre un meccanismo di protezione, per avvertire l’operatore dell’errore commesso e consentirgli di chiudere la finestra che ha aperto e attivato incautamente. Il meccanismo funziona in questo modo. 1. Quando viene fatto clic sul pulsante di comando OK, si controlla se è aperto il report Vendite per anno. Se non è aperto, vuol dire che l’operatore ha fatto un errore, aprendo impropriamente la maschera Finestra Vendite per anno e facendo clic su OK. 2. L’errore viene segnalato componendo il testo di una finestra di messaggio e facendo uscire la finestra in modo che si sovrapponga alla maschera. 3. L’operatore legge il testo contenuto nella finestra di messaggio e per chiuderla deve fare clic sul suo pulsante OK. 4. La finestra di messaggio si chiude e all’operatore non resta che fare clic sul pulsante Annulla della maschera Finestra Vendite per anno per chiuderla. I passi salienti della routine che stiamo esaminando sono stati opportunamente documentati dal programmatore con una serie di righe di commento. Tali righe iniziano tutte con un carattere apice (‘), che le caratterizza come commenti: quando si esegue la routine, le righe dei commenti vengono ignorate, quindi è consentito scrivere tutto quello che si vuole, in una routine, a condizione di mettere in testa a ogni riga un carattere apice. Sul monitor le righe dei commenti compaiono in colore verde, per differenziarle dagli enunciati di programma, che invece sono in nero. Per maggiore chiarezza, le parole chiave del linguaggio di programmazione (il VBA, cioè Visual Basic for Applications, in questo caso), sono scritte in blu. Compaiono in nero, invece, i nomi definiti dal programmatore. Un programma VBA è composto da enunciati, espressioni formate con parole chiave che fanno riferimento a variabili. Le variabili sono identificate con nomi convenzionali, scelti dal programmatore e fanno riferimento a dati. I dati possono essere numeri o stringhe di caratteri oppure oggetti. I primi due enunciati della routine: Dim strMsg As String, strTitolo As String Dim intStile As Integer

Gli strumenti di programmazione    93

definiscono tre variabili, due sono caratterizzate come stringhe di caratteri (strMsg e strTitolo) e una come numero intero (intStile). Queste variabili verranno utilizzate più avanti, per costruire la finestra di messaggio. Subito dopo la definizione delle variabili, si esegue il passo (1) della routine, cioè la verifica se è aperto il report Vendite per anno. Questa verifica si esegue con un enunciato If ... Then, che riportiamo qui di seguito: If Not Reports![Vendite per anno].blnOpening Then Err.Raise 0

Con questo enunciato ci si pone la domanda: è aperto il report Vendite per anno? La verifica viene fatta controllando il valore logico della variabile blnOpening, definita nel modulo di classe del report. Questa variabile rappresenta un valore logico, che può essere vero o falso. All’atto dell’apertura del report Vendite per anno il valore logico della variabile blnOpening viene impostato su Vero. L’espressione: Reports![Vendite per anno].blnOpening

si legge da destra a sinistra e rappresenta la variabile logica blnOpening contenuta in [Vendite per anno], che fa parte dell’insieme Reports. La parte iniziale dell’enunciato, If Not, vuol dire: se il valore logico di blnOpening è falso.... Siccome il report Vendite per anno non è aperto, la variabile blnOpening ha valore logico falso. Allora (Then) viene eseguito il comando: Err.Raise 0

Questo comando emette una segnalazione di errore a livello di sistema.Tale segnalazione viene intercettata dall’enunciato: On Error GoTo Err_OK_Click

che è predisposto subito all’inizio della routine. L’enunciato On Error GoTo ordina al programma di sospendere – in caso di errore – l’esecuzione degli enunciati (che vengono normalmente eseguiti uno dopo l’altro, nell’ordine in cui sono scritti) e di passare a eseguire gli enunciati che si trovano subito dopo il segnaposto Err_OK_Click. I segnaposti si definiscono liberamente creando un nome qualunque seguito dal carattere due punti (:). Subito dopo il segnaposto Err_OK_Click: inizia il gruppo di enunciati che costruisce e fa uscire la finestra di messaggio (passo 2). Err_OK_Click: strMsg = "Per utilizzare la maschera, è necessario stampare o " strMsg = strMsg & "visualizzare in anteprima il report Vendite per ➥ anno dalla finestra " strMsg = strMsg & "del database oppure in visualizzazione Struttura." intStile = vbOKOnly strTitolo = "Apri dal report" MsgBox strMsg, intStile, strTitolo

94  Capitolo 3

Viene utilizzata la variabile stringa strMsg per comporre il testo del messaggio, accodando via via pezzi di frase. Viene poi assegnato alla variabile intStile, predisposta per rappresentare un numero, un codice particolare, che si chiama vbOKOnly, e che caratterizza lo stile della finestra di messaggio, facendovi comparire un pulsante di comando con la dicitura OK. La variabile stringa strTitolo riceve invece il testo Apri dal report, che verrà visualizzato nella barra del titolo della finestra di messaggio. I testi da associare a variabili stringa vanno racchiusi fra virgolette, come si è visto sopra per la variabile stringa strMsg. Composti gli elementi della finestra di messaggio (titolo, messaggio e pulsante di comando), la finestra viene visualizzata tramite l’enunciato: MsgBox strMsg, intStile, strTitolo

formato con la parola chiave MsgBox seguita dai nomi delle tre variabili. La routine a questo punto aspetta la risposta dell’operatore, che può essere una sola: un clic sul pulsante di comando OK (passo 3). Ricevuta la risposta, la routine riprende dall’enunciato successivo, che è un rinvio a un altro segnaposto: Resume Esci_OK_Click

In corrispondenza di Esci_OK_Click: si ha un enunciato Exit Sub

che è un comando di uscita dalla routine (passo 4).

Generare output in formati diversi da Access Apriamo la maschera Prodotti e osserviamo che, nella parte superiore, vi sono due pulsanti di comando, caratterizzati dalle diciture Anteprima elenco prodotti e Output prodotti come HTML. Un clic sul secondo pulsante provoca la comparsa di una finestra di avvertimento, che segnala che è in corso la generazione di un report in formato HTML. Dopo qualche istante, viene attivato il browser Internet Explorer (se è installato sul sistema col quale si lavora), nel quale si apre automaticamente il documento appena generato in formato HTML. Contenuto e struttura del documento sono gli stessi del report Elenco alfabetico prodotti (Figura 3.3). Chiudendo il browser si torna alla maschera Access. Il controllo Pulsante di comando sul quale abbiamo fatto clic si chiama OutputSuHTML e la routine evento che è associata al clic su questo pulsante è la seguente: Private Sub OutputSuHTML_Click() On Error GoTo Err_OuputSuHTML_Click ' Effettua l'output dell'elenco alfabetico dei prodotti come documento ' HTML e apre il documento in un Browser Internet. ' I file Nwindtem.htm (modello per Northwind) e NWLogo.gif (logo di ' Northwind) devono trovarsi nella cartella di database predefinita.     DoCmd.OutputTo acOutputReport, "Elenco alfabetico prodotti",             ➥acFormatHTML, "Products.htm", True, "Nwindtem.htm"

Gli strumenti di programmazione   95 Exit_OutputSuHTML_Click:     Exit Sub Err_OuputSuHTML_Click:     ' Non visualizza un messaggio d'errore se l'operazione     ' viene annullata dall'utente.     Const conErrDoCmdCancelled = 2501     If (Err = conErrDoCmdCancelled) Then         Resume Exit_OutputSuHTML_Click     Else         MsgBox Err.Description         Resume Exit_OutputSuHTML_Click     End If End Sub

La routine invia a un file chiamato Products.htm, che crea al momento, il contenuto dell’oggetto report Elenco alfabetico prodotti, utilizzando un file modello chiamato Nwindtem. htm. Il file modello prevede l’uso di un file immagine, chiamato NWLogo.gif. Queste informazioni le ricaviamo dalle righe di commento che si trovano all’inizio della routine evento. La routine comprende alcuni enunciati per la gestione degli errori, che vedremo più avanti.

Figura 3.3  Il report Elenco alfabetico prodotti generato automaticamente nel formato HTML.

96  Capitolo 3

Tutto il lavoro è eseguito col complesso enunciato: DoCmd.OutputTo acOutputReport, "Elenco alfabetico prodotti", _     acFormatHTML, "Products.htm", True, "Nwindtem.htm"

che nel modulo di classe è scritto su una sola riga. Questo enunciato non è un’istruzione VBA, ma è un richiamo all’oggetto Access DoCmd e al suo metodo OutputTo. Approfondiremo nei prossimi capitoli i concetti di oggetto e di metodi di un oggetto, quindi per ora limitiamoci a vedere che cosa succede. Il nome dell’oggetto è una contrazione dell’espressione “do command”, cioè “esegui il comando” e per attivare l’oggetto DoCmd bisogna specificare quale comando gli si vuol fare eseguire. Per farlo si specifica un metodo. I metodi di DoCmd possono avere argomenti, e il metodo OutputTo ne prevede sei, secondo lo schema sintattico che segue: DoCmd.OutputTo tipooggetto[, nomeoggetto][, formatooutput]    [, fileoutput][, avvioautomatico][, filemodello]

La Tabella 3.1 mostra la corrispondenza fra gli argomenti dello schema sintattico e quelli effettivamente usati nell’enunciato DoCmd.OutputTo. Tabella 3.1  Schema sintattico ed enunciato effettivo per DoCmd.OutputTo. Elemento

Schema sintattico

Enunciato effettivo

Oggetto

DoCmd 

DoCmd 

Metodo Argomento Argomento Argomento

.OutputTo 

.OutputTo 

tipooggetto [, nomeoggetto] [, formatooutput]

acOutputReport 

Argomento Argomento

[, fileoutput] [, avvioautomatico]

Argomento

[, filemodello]

, “Elenco alfabetico prodotti” , acFormatHTML  , “Products.htm” , True , “Nwindtem.htm”

Il significato degli argomenti nomeoggetto, fileoutput e filemodello è intuitivo. Quanto agli altri: •• l’argomento tipooggetto acOutputReport specifica che l’oggetto sul quale si esegue il metodo OutputTo è un report; •• l’argomento formatooutput acFormatHTML definisce il formato dell’output (HTML, in questo caso); •• il valore logico True assegnato al quinto argomento – avvioautomatico – indica che deve essere avviato il programma associato al tipo di file generato in output (trattandosi di un file HTML, il programma predefinito è il browser Microsoft Internet Explorer).

Segnalare errori provenienti dal sistema Il report Elenco alfabetico prodotti occupa poco più di quattro pagine e su una macchina potente viene generato in pochi istanti. La finestra di messaggio che viene presentata

Gli strumenti di programmazione   97

durante l’elaborazione di DoCmd, quindi, resta visibile per poco tempo. Se, per una qualunque ragione, il processo di generazione dell’output si prolungasse troppo o si volesse annullarlo mentre è in corso, è disponibile nella finestra di messaggio un pulsante Annulla (Figura 3.4).

Figura 3.4  La finestra di messaggio segnala che è in corso l’elaborazione dell’output e può essere annullata.

Un clic su tale pulsante genera una segnalazione di errore di esecuzione (detto errore di run-time). Tutte le segnalazioni di errore sono gestite tramite un particolare oggetto Access che si chiama Err, il quale le identifica con un codice numerico univoco, associato a una descrizione. Se l’errore segnalato ha il codice 2501, vuol dire che non si tratta di un errore in senso proprio, ma è stata emessa la richiesta (legittima) di annullare il processo. Questa segnalazione viene gestita con il frammento di codice che segue: Err_OuputSuHTML_Click:     ' Non visualizza un messaggio d'errore se l'operazione     ' viene annullata dall'utente.     Const conErrDoCmdCancelled = 2501     If (Err = conErrDoCmdCancelled) Then         Resume Exit_OutputSuHTML_Click     Else         MsgBox Err.Description         Resume Exit_OutputSuHTML_Click     End If

Col primo enunciato si crea una costante chiamata conErrDoCmdCancelled e le si assegna il valore 2501. L’enunciato che segue, If...Then...Else...End If, è un’istruzione VBA che si chiede se il codice dell’oggetto Err è uguale a conErrDoCmdCancelled, cioè 2501. Se così fosse, verrebbe eseguito l’enunciato che viene immediatamente dopo, che fa uscire dalla routine evento OutputSuHTML_Click. In qualunque altro caso (Else...), cioè se Err avesse un codice qualsiasi diverso da 2501, vorrebbe dire che l’errore è di altro tipo, nel qual caso verrebbe eseguita l’istruzione MsgBox Err.Description, che visualizza la descrizione in chiaro dell’errore e successivamente si uscirebbe dalla routine evento (Resume Exit_OutputSuHTML_Click). Che cosa succederebbe se non esistesse questa parte di codice nella routine evento che stiamo esaminando? Un clic sul pulsante Annulla nella finestra di messaggio riprodotta nella Figura 3.4 provocherebbe l’uscita della finestra di messaggio che si può vedere nella Figura 3.5.

98  Capitolo 3

Figura 3.5  La segnalazione predefinita per l’errore 2501.

Questa finestra ha due gravi inconvenienti: •• drammatizza come errore quella che in effetti è una legittima azione dell’operatore, dandone per di più una descrizione gergale e incomprensibile; •• presenta due pulsanti di comando, uno per terminare la routine, e l’altro, etichettato Debug; un clic su questo pulsante provoca l’apertura del modulo del codice e l’evidenziazione dell’enunciato DoCmd, la cui interruzione ha provocato l’errore 2501. In un’applicazione database ben fatta nessuna di queste cose deve poter accadere: le segnalazioni di errore – se proprio devono comparire – vanno formulate in un linguaggio facilmente comprensibile dall’operatore e le opzioni di uscita non devono consentire l’accesso al codice di programma. Correttamente, quindi, chi ha programmato l’applicazione Northwind ha predisposto il codice di gestione dell’errore che abbiamo appena visto.

Eseguire operazioni di calcolo Nell’applicazione Northwind la maschera Ordini trimestrali presenta gli ordini dei clienti, visualizzando nella maschera principale alcune informazioni anagrafiche del cliente e all’interno di una sottomaschera gli ordini raggruppati per trimestre. La Sottomaschera Ordini trimestrali attinge i dati dalla query a campi incrociati Ordini trimestrali per prodotto, che ha la struttura riportata nella Tabella 3.2. Tabella 3.2  Lo schema della query a campi incrociati Ordini trimestrali per prodotto. Prodotto

Cliente

Anno

Alice Mutton

Antonio Moreno Taquería 2006

Alice Mutton

Berglunds snabbköp

2006

Come si può vedere dalla Figura 3.6, la in fondo a ciascun trimestre.

Trim. 1

Trim. 2

Trim. 3

Trim. 4

€ 576,00 € 320,00

Sottomaschera Ordini trimestrali

presenta un totale

Gli strumenti di programmazione   99

Figura 3.6  La Sottomaschera Ordini trimestrali con i totali per trimestre.

Questo totale non è presente nella query che alimenta la sottomaschera, ma è ottenuto con una formula VBA. In questo caso il programmatore non ha utilizzato il modulo di classe, ma ha preferito servirsi della proprietà Origine controllo di ciascuna delle quattro caselle di testo – chiamate TotaleT1, TotaleT2, TotaleT3 e TotaleT4 – che presentano i totali dei trimestri. La formula è scritta nel modo seguente: =NZ(Somma([Trimestre 1]))

e indica che nella casella di testo deve essere visualizzato il risultato della sommatoria dei campi Trimestre (Trimestre 1 per la casella TotaleT1, Trimestre 2 per la casella TotaleT2 e così via). Le lettere NZ rappresentano una funzione che viene eseguita sul risultato della formula. I campi Trimestre che non presentano un valore numerico contengono un valore particolare detto Null. La funzione NZ (che significa, all’incirca, “da Null a Zero”) forza la conversione degli eventuali valori Null in zeri. In questo modo il risultato di Somma() è sempre un numero. Questo è importante ai fini di una seconda operazione aritmetica che viene eseguita nella sottomaschera, con la formula: =[TotaleT1]+[TotaleT2]+[TotaleT3]+[TotaleT4]

che si trova nella proprietà Origine controllo della casella di testo Totale. Questa formula, come è facile capire, esegue la somma dei contenuti delle caselle di testo Totale1, Totale2 e così via. Se qualcuna di queste caselle contenesse Null invece di zero, l’intera sommatoria produrrebbe un Null, anche se gli altri totali parziali fossero numerici, e quindi nella casella Totale non comparirebbe alcun valore (per una spiegazione completa di Null si veda il paragrafo “I valori Empty e Null” del Capitolo 7). La Figura 3.7 mostra le formule per le caselle di testo nella Sottomaschera Ordini trimestrali.

100  Capitolo 3

Figura 3.7  Le formule di calcolo nella proprietà Origine controllo delle caselle di testo Totale e TotaleT1.

Gestire input complessi La maschera Fornitori dell’applicazione Northwind serve per esaminare i dati anagrafici di ogni singolo fornitore ed è basata sulla tabella Fornitori. I realizzatori dell’applicazione hanno immaginato che l’operatore, nel momento in cui esamina la posizione di un fornitore, possa essere interessato a esaminare anche l’elenco dei prodotti che fornisce abitualmente e possa aver bisogno di inserire nuovi prodotti in tale elenco. Per esaminare l’elenco dei prodotti, l’operatore dovrebbe aprire la maschera Elenco prodotti mentre è aperta la maschera Fornitori. L’operazione non è particolarmente ardua: nella finestra Database è possibile tenere aperti più oggetti contemporaneamente. Supponendo, però, che l’operatore non conosca questa funzionalità, sulla maschera Fornitori è presente un pulsante di comando, con l’etichetta Visualizza prodotti, per il quale è stata predisposta una routine evento, associata all’evento clic. Questa routine evento non soltanto apre la maschera Elenco prodotti, ma vi applica anche un filtro, facendo in modo che siano visibili soltanto i prodotti del fornitore visualizzato in quel momento nella maschera Fornitori. Il nucleo essenziale di questa routine evento è fatto in questo modo: 1. sono state definite due variabili stringa, chiamate rispettivamente strNomeDoc e strCriteriCollegamento;

Gli strumenti di programmazione    101

2. la prima variabile rappresenta il nome della maschera da aprire, mediante l’enunciato di assegnazione:     strNomeDoc = "Elenco prodotti"

3. alla seconda variabile si assegna la descrizione del criterio di collegamento fra il record Elenco prodotti e il record Fornitori:     strCriteriCollegamento = "[IDFornitore] = Forms!     ➥[Fornitori]![IDFornitore]"

4. L’enunciato che apre la maschera Elenco prodotti e visualizza i prodotti del fornitore corrente utilizza il metodo OpenForm dell’oggetto DoCmd:     DoCmd.OpenForm strNomeDoc, , , strCriteriCollegamento

Gli argomenti del metodo sintattico che segue:

OpenForm

possono essere sette, come si vede dallo schema

DoCmd.OpenForm nomemaschera[, visualizzazione][, nomefiltro][, condizioneWHERE]     [, modalità dati][, modalità finestra][, apriarg]

Tutti gli argomenti indicati fra parentesi quadre nello schema sintattico sono facoltativi, cioè, se non vengono indicati dopo l’argomento nomemaschera, che è obbligatorio, vengono assunti altrettanti valori predefiniti. Esiste però un vincolo: se si omettono soltanto alcuni argomenti facoltativi, l’omissione va segnalata lasciando una virgola e uno spazio come segnaposto. L’enunciato riportato sopra, al punto 4, utilizza soltanto l’argomento condizioneWHERE, per cui le posizioni non utilizzate per gli argomenti visualizzazione e nomefiltro sono occupate con due segnaposti. Non occorrono segnaposti, invece, per gli argomenti successivi all’ultimo usato. La Tabella 3.3 mostra la corrispondenza fra gli argomenti dello schema sintattico e quelli effettivamente usati nell’enunciato DoCmd.OpenForm. Tabella 3.3  Schema sintattico ed enunciato effettivo per DoCmd.OpenForm. Elemento Oggetto

Schema sintattico

Enunciato effettivo

DoCmd

DoCmd

Metodo Argomento Argomento Argomento Argomento Argomento Argomento Argomento

.OpenForm

.OpenForm

nomemaschera [,visualizzazione] [,nomefiltro] [,condizioneWHERE] [,modalitàdati] [,modalitàfinestra] [,apriarg]

"Elenco prodotti"

, , , "[IDFornitore] = Forms![Fornitori]![IDFornitore]"

     

102  Capitolo 3

Lo stesso metodo OpenForm dell’oggetto DoCmd viene utilizzato per la routine evento associata al clic sul pulsante di comando Aggiungi prodotti. Gli argomenti vengono usati in modo diverso, però. Il nucleo essenziale del codice è formato da due enunciati, uno che assegna alla variabile strNomeDoc la stringa “Prodotti” e un altro che attiva il metodo OpenForm: strNomeDoc = "Prodotti" DoCmd.OpenForm strNomeDoc, , , , acAdd, , Me!IDFornitore

Nel secondo enunciato, si usano soltanto gli argomenti modalitàdati e apriarg del metodo OpenForm, inserendo gli opportuni segnaposti per gli argomenti intermedi non utilizzati. Il valore attribuito all’argomento modalitàdati è la costante intrinseca acAdd, che indica che l’oggetto (la maschera Prodotti, nella fattispecie) deve essere aperto in modalità immissione dati in un nuovo record. NOTA Le costanti intrinseche sono codici numerici che hanno un nome letterale perché si possano usare più agevolmente. Esistono vari tipi di costanti intrinseche, i nomi di quelle specifiche di Access cominciano col prefisso ac, quelle specifiche di Visual Basic usano il prefisso vb e così via.

Per capire il valore assegnato all’argomento apriarg bisogna capire che cosa questo argomento rappresenta. Il suo nome inglese è OpenArgs e serve per contenere un’espressione che verrà utilizzata in fase di apertura (open) della maschera quando questa viene aperta con il metodo OpenForm. Il valore Me!IDFornitore attribuito all’argomento apriarg indica che il contenuto del campo IDFornitore del nuovo record Prodotti sarà uguale al contenuto del campo IDFornitore del record corrente nella maschera Fornitori. La parola chiave Me è un riferimento all’oggetto attivo, in questo caso la maschera Fornitori. La forma sintattica corretta per identificare il controllo casella di testo IDFornitore nella maschera Fornitori sarebbe Forms!Fornitori!IDFornitore: il ricorso alla parola chiave Me fa risparmiare caratteri e previene possibili errori di battitura. L’effetto che si ottiene – usando nel modo appena descritto il metodo OpenForm – è quello di aprire un record Prodotti vuoto, già predisposto per essere associato all’IDFornitore che si stava esaminando.

Quel che occorre sapere Come si è visto nei paragrafi precedenti, l’applicazione Northwind contiene molte più cose di quante ne lasci intendere la sua semplice ed elegante interfaccia grafica. Le spiegazioni e i commenti che abbiamo fornito non possono bastare per capire a fondo il funzionamento dei codici di programma che abbiamo passato in rassegna. Per capire come sono fatte le applicazioni Access e per imparare a costruirle bisogna dominare un articolato insieme di tecniche e di linguaggi, dal momento che in Access si hanno a disposizione svariati strumenti per gestire oggetti ed eventi, non tutti appartenenti alla

Gli strumenti di programmazione    103

stessa famiglia logica. Tutti questi strumenti saranno presentati e spiegati, con numerosi esempi, nei prossimi capitoli. Prima di affrontare i capitoli dedicati agli strumenti di programmazione, ci sembra opportuno mettere sull’avviso i nostri lettori: questi strumenti non formano un tutto omogeneo di elementi in relazione gerarchica fra loro, non si va dal più semplice al più complesso, ma coesistono, raccolti insieme in un’ideale cassetta di attrezzi da lavoro, dalla quale si preleva di volta in volta quello che serve e che meglio si adatta al risultato che si vuole ottenere. C’è di più: lo stesso risultato si può ottenere con due o più strumenti diversi, un’azione macro, per esempio, o una routine evento VBA. O magari attivando un oggetto DAO oppure eseguendo un enunciato SQL. Paradossalmente, pur essendo un prodotto profondamente americano, per Access non vale il principio americano secondo il quale per realizzare qualunque cosa esiste soltanto una one best way: per realizzare applicazioni database in Access si possono utilizzare gli strumenti più diversi, dosandoli come meglio si crede. E qui spunta fuori di nuovo la nostra vecchia conoscenza, l’esigenza applicativa. È da lì, da quello che si vuole e si deve ottenere che vengono le indicazioni e i criteri per scegliere gli strumenti di programmazione più opportuni.

Parte II

Il ruolo delle macro

In questa parte •

Capitolo 4 Semilavorati per programmare

Capitolo 4

Semilavorati per programmare Lo strumento di programmazione più semplice da usare è l’oggetto macro. Il nome viene dalla contrazione di macro-istruzione, termine che risale agli albori dell’informatica, quando si iniziò ad aggregare blocchi di istruzioni elementari in linguaggio macchina formando con queste un’istruzione più grande – una macro istruzione, per l’appunto – caratterizzata da un codice simbolico più semplice da scrivere (e da ricordare) della serie di istruzioni elementari che rappresentava. Il termine macro ricorre anche in altri prodotti della famiglia Microsoft Office, in particolare in Excel e in Word, ma si tratta di strumenti completamente diversi. Infatti, in Word e in Excel una macro è una routine VBA, che può essere scritta direttamente da un programmatore o creata in modo semiautomatico registrando operazioni fatte con tastiera e mouse, mentre le macro di Access sono schemi di routine VBA predefiniti, che si chiamano azioni, da completare fornendo opportuni parametri o argomenti per definire l’oggetto destinatario dell’azione e le modalità in cui l’azione dovrà essere eseguita. Nelle prime due versioni di Access, la 1.1 e la 2.0, le macro fornivano una serie di funzionalità non disponibili per altre vie, in particolare per la personalizzazione dei menu e delle barre degli strumenti. A partire da Access 95 e con maggiore impegno in Access 97, questo ruolo è stato drasticamente ridotto, perché le funzionalità per creare menu e barre degli strumenti erano state snellite e rese accessibili direttamente dall’interfaccia grafica e gli utenti venivano incoraggiati a servirsi del VBA invece di ricorrere alle macro. Questa evoluzione corrispondeva anche

In questo capitolo •

Nomenclatura delle macro



Che cosa si può fare



I limiti storici delle macro



Le macro di Access 2010



Le macro di dati



Quando le macro sono indispensabili



Convertire le macro



L’erede delle azioni macro: l’oggetto DoCmd

108  Capitolo 4

a una politica di prodotto di Microsoft, orientata a fare di Visual Basic la piattaforma di programmazione comune per tutti i suoi applicativi destinati al grande pubblico. Quali che fossero, però, i piani di marketing di Microsoft, le macro hanno continuato ad avere un ruolo in Access, tant’è vero che alle 49 azioni macro disponibili in Access 97 ne sono state aggiunte quattro in Access 2000 e altre tre in Access 2002/2003, arrivando così a 56 in tutto. Access 2007 ne ha aggiunte altre 14, portando il repertorio a 70 azioni. Con Access 2010 l’assortimento delle macro si arricchisce ulteriormente: le azioni da 70 diventano 86, nascono nuovi tipi di macro e viene profondamente rinnovata e arricchita di strumenti e opzioni l’interfaccia grafica per creare le macro. La maggiore enfasi data a questi strumenti si giustifica soprattutto con l’introduzione in Access 2010 dei database web, nei quali non si possono utilizzare moduli con routine VBA: di conseguenza, nelle applicazioni database da fruire sul Web (che vediamo nel Capitolo 16), le funzionalità da associare agli eventi si possono ottenere soltanto con le macro. Le macro, quindi, rappresentano tuttora un comodo strumento per imbastire rapidamente un prototipo di applicazione, sottoporlo al collaudo degli utenti finali per individuare punti di forza e punti di debolezza da enfatizzare o da correggere, per poi rielaborare l’applicazione sostituendo, quando è il caso, le macro con routineVBA in moduli standard o associati a maschere e report. In questo capitolo passiamo in rassegna le caratteristiche delle azioni macro, spiegando con alcuni esempi come si possono utilizzare e con quali risultati. Per iniziare, esaminiamo il contesto in cui si creano le macro.

Nomenclatura delle macro Per creare una nuova macro si esegue il comando Crea/Macro e codice/Macro nella barra multifunzione, ottenendo l’apertura della finestra di composizione che vediamo nella Figura 4.1. La stessa finestra di composizione compare quando si seleziona il nome di una macro esistente nella sezione Macro del Riquadro di spostamento e vi si fa sopra un clic destro, scegliendo poi di aprirla in Visualizzazione Struttura. Nella barra multifunzione si apre una nuova scheda, intitolata Strumenti macro/Progettazione, articolata in tre gruppi di comandi: Strumenti, Comprimi/Espandi, Mostra/Nascondi. In quest’ultimo gruppo sono disponibili due comandi, Catalogo azioni e Mostra tutte le azioni. Nella figura appaiono entrambi attivati, di conseguenza sulla destra della finestra di composizione è aperto un pannello intitolato Catalogo azioni, che può essere spostato, ridimensionato o anche chiuso agendo col mouse sulla sua barra del titolo. Come dice il suo nome, questo pannello elenca tutte le azioni disponibili, raggruppate, come vediamo nella figura, in varie famiglie, rappresentate graficamente con la nota convenzione di Windows, per cui il nome di ciascuna famiglia è affiancato dall’icona di una cartella chiusa contrassegnata da un segno più: un clic su quel segno fa scendere l’elenco delle azioni raggruppate in quella famiglia e contestualmente il segno più a sinistra dell’icona della cartella diventa un segno meno. Il pannello Catalogo azioni presenta otto famiglie di azioni e altri due raggruppamenti con la stessa simbologia (icona di una cartella e segno più o segno meno): uno chiamato Flusso esecuzione programma, che descriveremo fra poco, e un altro con un nome che si spiega da sé: In questo database.

Semilavorati per programmare   109

Figura 4.1  La finestra di composizione delle macro.

Azioni a rischio In Access 2010 il Catalogo azioni presenta per impostazione predefinita soltanto 66 azioni sulle 86 che formano il repertorio completo. Questo è dovuto alle impostazioni per la sicurezza: le 20 azioni non visualizzate sono considerate, per ragioni varie, a rischio, perché la loro esecuzione potrebbe alterare il contenuto di tabelle, campi, controlli o altro. Per avere l’elenco completo delle azioni disponibili, è necessario premere il pulsante Mostra tutte le azioni nel gruppo Mostra/Nascondi della scheda Strumenti macro/Progettazione: quando si seleziona un’azione a rischio, il suo nome compare nel Catalogo azioni contraddistinto da un segnale di pericolo:

110  Capitolo 4

Se abbiamo deciso di creare una macro nuova, dobbiamo scegliere almeno una azione, cosa che possiamo fare in tre modi diversi: 1. un clic sulla freccia a discesa della casella combinata con la dicitura Aggiungi nuova azione collocata nel corpo vuoto della nuova macro fa uscire l’elenco completo in ordine alfabetico delle azioni disponibili, nel quale selezioniamo con un clic l’azione che ci interessa (le prime quattro voci di questo elenco a discesa coincidono con quelle della sezione Flusso esecuzione programma nel Catalogo azioni, che descriviamo più avanti); 2. apriamo uno degli otto raggruppamenti della sezione Azioni nel pannello Catalogo azioni, selezioniamo con un clic l’azione che ci interessa e la trasciniamo col mouse nella struttura della macro (otteniamo lo stesso effetto con un doppio clic); 3. se ricordiamo soltanto una parte del nome di un’azione, possiamo digitare qualche carattere nella casella di testo Cerca che sta in testa al pannello Catalogo azioni: se Access trova qualche corrispondenza la elenca nel Catalogo azioni al posto dei raggruppamenti predefiniti; individuata così l’azione, un doppio clic sul suo nome la inserisce nella struttura della macro. Quando l’azione scelta con una qualsiasi di queste tecniche entra nella struttura di una macro, si presenta come un riquadro composto da caselle di testo e/o caselle combinate predisposte per digitare o selezionare gli argomenti o parametri specifici di quell’azione. Per non restare troppo nel vago facciamo una semplice prova, inserendo in una nuova macro l’azione FinestraMessaggio, chiudendo poi il pannello del Catalogo azioni per poter vedere meglio il contenuto della macro. 1. Si apriranno due caselle di testo e due caselle combinate, per accogliere i quattro argomenti previsti per l’azione FinestraMessaggio: Messaggio, SegnaleAcustico, Tipo e Titolo (Figura 4.2). Appoggiando su ciascuno di questi controlli (senza fare clic) il puntatore del mouse compare una descrizione delle opzioni possibili e del loro significato. 2. Nella casella Messaggio digitiamo il testo da far comparire nella finestra di messaggio, per esempio Salve, sono una macro! 3. Facciamo clic sulla casella combinata SegnaleAcustico, che presenta due sole opzioni: Sì per fa emettere un bip alla comparsa del messaggio, No per non farlo. Selezioniamo Sì. 4. Un clic nella casella combinata Tipo fa uscire l’elenco dei tipi di finestra di messaggio disponibili. A ciascun tipo di messaggio corrisponde una combinazione di icona e di pulsanti di comando. Scegliamo Informazione. 5. L’ultimo argomento è riservato al titolo che dovrà comparire nella finestra di messaggio. Digitiamo nella casella di testo un titolo adatto, per esempio Macro di prova. 6. Per poter eseguire la macro dobbiamo salvarla, operazione che possiamo fare agendo sull’icona del dischetto nella Barra di accesso rapido e digitando un nome appropriato, come per esempio mcrMessaggio. Una volta salvata la nuova macro, che rimane tuttora aperta, per eseguirla possiamo fare clic sul pulsante con l’icona del punto esclamativo rosso che si trova in Strumenti macro/Progettazione/Strumenti. Quando la macro è chiusa, si trova nel Riquadro di spostamento, dal quale può essere eseguita con un doppio clic sul suo nome.

Semilavorati per programmare   111

Nella Figura 4.2 vediamo la struttura della macro dimostrativa e ciò che si ottiene eseguendola.

Figura 4.2  Una nuova macro con una sola azione.

L’esecuzione delle macro Come abbiamo appena visto, si può eseguire direttamente una macro dalla finestra di composizione oppure con un doppio clic sul suo nome nel Riquadro di spostamento. Tuttavia, salvo casi particolari, non si creano le macro per eseguirle con un comando diretto, ma per associarle a un evento che potrebbe verificarsi mentre l’applicazione Access è in uso, in modo tale che, per esempio, un clic su un pulsante di comando o la selezione di un valore in una casella combinata faccia eseguire la macro associata a quell’evento. Inoltre, la macro del nostro esempio è composta da una sola azione, ma niente impedisce di costruire macro con più azioni, da eseguire una dopo l’altra oppure subordinando l’esecuzione di ciascuna azione a condizioni particolari, che possono dipendere dall’evento associato alla macro o da altri fattori. Per creare macro di questo genere il pannello Catalogo azioni mette a disposizione alcuni strumenti che sono raggruppati sotto l’intestazione Flusso esecuzione programma, come vediamo nella Figura 4.3.

112  Capitolo 4

Figura 4.3  Gli strumenti per creare macro complesse.

Il primo strumento si chiama Commento e un doppio clic delimita uno spazio orizzontale nel corpo della macro al cui interno si può scrivere un testo esplicativo, un commento, appunto, che aiuti a capire meglio il significato delle azioni che compongono la macro. Il commento, una volta scritto, si stabilizza in un insieme di righe di colore verde, precedute dal simbolo /* e chiuse dal simbolo complementare */ : queste righe possono essere spostate tutte insieme, verso l’alto o verso il basso, agendo col mouse sulle frecce verdi che si trovano all’estremità del blocco oppure eliminate con un clic sul simbolo di cancellazione che sta a destra delle frecce verdi (si veda la Figura 4.4). La presenza di commenti non influisce minimamente sul modo di operare della macro, ma può rivelarsi molto utile quando, a distanza di mesi, torniamo a esaminare una macro complessa, formata da molte azioni, e non è detto che ricorderemo dopo tutto quel tempo con assoluta chiarezza le ragioni che ci hanno portato a scrivere la macro proprio in quel modo. In questa figura le due azioni ApriTabella e TrovaRecord sono rappresentate ciascuna con una sola riga, composta da più blocchi di caratteri, separati da un punto e virgola e corrispondenti il primo blocco, in grassetto, al nome dell’azione e i successivi agli argomenti richiesti dall’azione. Questa rappresentazione compatta delle azioni si può ottenere agendo sui comandi del gruppo Comprimi/Espandi della scheda Strumenti macro/Progettazione. Appoggiando il puntatore del mouse senza fare clic sulla riga compressa compare un cartiglio con l’elenco in chiaro degli argomenti di quell’azione e dei valori impostati per quegli argomenti. Una macro può contenere più azioni, fino al limite assurdamente elevato di 999 in Access 2010. Per gestire più comodamente macro composte da numerose azioni, possiamo utilizzare uno strumento che consente di aggregare gruppi di azioni entro una stessa macro. A questo scopo, dobbiamo avviare la creazione di una nuova macro e quindi fare doppio clic sul comando Gruppo nella sezione Flusso esecuzione programma del Catalogo azioni. Nel corpo vuoto della nuova macro viene inserita una sezione di intestazione chiamata Gruppo seguita dalla consueta casella combinata Aggiungi nuova azione e chiusa dalla dicitura Fine gruppo.

Semilavorati per programmare   113

Figura 4.4  Un commento inserito in una macro.

Possiamo assegnare a questo gruppo un nome qualsiasi e poi inserirvi tutte le azioni che ci interessano, creando eventualmente altri gruppi per tenere insieme azioni distinte. I gruppi di azioni creati in questo modo vanno considerati come contenitori, piuttosto che macro vere e proprie, perché salvando una macro che contiene uno o più gruppi e poi eseguendola vengono eseguite in sequenza tutte le azioni contenute nei gruppi, nell’ordine in cui si susseguono, il che non è detto sia il risultato che si voleva ottenere. Per eseguire selettivamente gruppi di azioni contenuti in una stessa macro bisogna utilizzare lo strumento chiamato Sottomacro, che descriviamo più avanti. Il terzo strumento del Flusso esecuzione programma si chiama Se e si utilizza per creare azioni da eseguire quando si verificano determinate condizioni. Un doppio clic sul nome di questo strumento mentre è aperta la struttura di una macro provoca l’effetto che vediamo nella Figura 4.5.

Figura 4.5  Il blocco condizionale If…Then appena inserito in una macro.

114  Capitolo 4

Digitiamo nella casella di testo che sta a destra della parola If una condizione o un vincolo e selezioniamo nel consueto elenco a discesa Aggiungi nuova azione che si trova subito sotto la casella If l’azione da eseguire nel caso che la condizione o il vincolo si verifichi, selezionando nell’elenco Aggiungi nuova azione che sta dopo End If l’azione da eseguire se la condizione non si verifica. La condizione da verificare può essere una qualunque funzione di Access o VBA, che è possibile comporre con l’aiuto del Generatore di funzioni, attivabile con un clic sul simbolo della bacchetta magica che sta a destra della casella per la condizione. Facciamo una semplice prova usando due funzioni VBA: Date(), che ricava la data del giorno dall’orologio del computer e Month(), che applicata su un valore che esprime una data estrae un numero da 1 a 12 che corrisponde al mese di quella data, per cui l’espressione: =Month(Date())

produce “12” se eseguita in un giorno qualsiasi del mese di dicembre e “6” se eseguita in giugno. La macro che vediamo nella Figura 4.6 presenta una finestra con il messaggio “Siamo in gennaio” se nel mese in cui è eseguita si verifica la condizione: If Month(Date()) = 1

altrimenti non succede nulla.

Figura 4.6  Una semplice macro condizionale.

Volendo avere comunque un messaggio, si può completare la macro con l’aggiunta di una clausola Else, facendo clic sull’indicazione Aggiungi Else che sta subito a destra della prima condizione, componendo poi l’azione da eseguire quando la condizione non si verifica: vediamo una possibile soluzione nella Figura 4.7.

Semilavorati per programmare   115

Figura 4.7  Una macro con due condizioni.

Nella sezione Flusso esecuzione programma c’è ancora un altro strumento, chiamato Sottomacro, che può essere utile in molti casi e quindi è opportuno sapere come è fatto e come funziona. Poco prima abbiamo visto lo strumento Gruppo, col quale si possono aggregare più macro in un unico contenitore; con Sottomacro si fa qualcosa di simile, ma con una funzionalità in più, molto importante: si crea una macro che ne contiene altre, dette appunto sottomacro, ciascuna eseguibile in modo indipendente. Nella Figura 4.8 vediamo una macro contenuta nell’applicazione dimostrativa Northwind: la macro si chiama Clienti e contiene due sottomacro, una chiamata ConvalidaID e l’altra Aggiorna elenco paesi. In entrambe le sottomacro sono stati inseriti opportunamente alcuni commenti per ricordarne le finalità e il modo di operare. Come si può notare, ogni sottomacro ha un nome distinto dalle altre e da quello della macro in cui si trova, per cui: 1. se si esegue la macro direttamente con un doppio clic sul suo nome nel Riquadro di spostamento viene eseguita soltanto la prima sottomacro; 2. il richiamo a una sottomacro da eseguire in corrispondenza con un evento si deve esprimere combinando assieme con l’operatore punto il nome della macro e quello della sottomacro: NomeMacro.NomeSottomacro La sottomacro ConvalidaID viene eseguita quando si scrive un nuovo valore nel campo IDCliente della tabella Clienti mediante la maschera omonima (si veda sotto, la Figura 4.9) ed è formata da due azioni, FinestraMessaggio e AnnullaEvento, che vengono eseguite una di seguito all’altra, se si verifica la condizione impostata nella casella If. . .Then, che è quella riportata qui di seguito: DLookUp("[IDCliente]";"[Clienti]";"[IDCliente] = Form.[IDCliente] ") Is Not Null

116  Capitolo 4

Figura 4.8  La macro Clienti contiene due sottomacro: ConvalidaID e Aggiorna elenco paesi.

La condizione è basata su una funzione di ricerca tabellare – DLookUp() – con la quale si controlla se il valore appena immesso per il campo IDCliente (che è la chiave primaria della tabella) esiste già nella tabella Clienti. Impostata in questo modo, la condizione fa sì che: •• se la ricerca tabellare trova un valore già esistente, viene eseguita prima l’azione FinestraMessaggio, con la quale si fa apparire un messaggio per spiegare che non è consentito avere doppioni nel campo IDCliente e – subito dopo il clic che l’operatore deve fare per chiudere la finestra di messaggio – viene eseguita l’azione AnnullaEvento, che rende nulla la modifica immessa in questo campo; •• se il nuovo valore immesso nel campo IDCliente è univoco, la condizione non si verifica e la sottomacro non viene eseguita. La sottomacro Aggiorna elenco paesi è formata dalla sola azione RieseguiQuery e viene eseguita quando è stato creato un nuovo record nella tabella Clienti: in questa tabella il contenuto del campo Paese è generato da una query, che deve essere rieseguita quando si aggiunge un nuovo record, nell’eventualità che il nuovo cliente risieda in un paese che non è già compreso nell’elenco dei paesi che alimenta la casella combinata associata a quel campo.

Un esempio più articolato L’applicazione dimostrativa Northwind contiene una maschera molto ben costruita e di aspetto attraente, chiamata Elenco telefonico clienti, basata su una trentina di azioni macro.Val la pena di esaminarla da vicino per capire meglio come si possano utilizzare in modo creativo questi oggetti. Nella Figura 4.10 vediamo che la maschera Elenco telefonico clienti presenta una serie di righe, tante quanti sono i clienti della società Northwind, costruendo le righe con dati provenienti dai campi Nome società, Contatto, Telefono e Fax dei record della tabella Clienti.

Semilavorati per programmare   117

Figura 4.9  Il richiamo alla sottomacro ConvalidaID.

Figura 4.10  La maschera Elenco telefonico clienti dell’applicazione Northwind offre un modo agevole per scorrere l’elenco dei clienti in base all’iniziale del nome della società.

118  Capitolo 4

In fondo alla maschera sono allineati 27 interruttori, i primi 26 sono contrassegnati ciascuno da una diversa lettera dell’alfabeto, l’ultimo con l’etichetta Tutti.All’apertura della maschera questo interruttore appare premuto e la maschera mostra, in ordine alfabetico, tutti i nomi delle società clienti. Un clic su uno degli interruttori con le lettere dell’alfabeto attiva un filtro, mostrando soltanto i nomi che iniziano con la lettera selezionata. Se non esistono società clienti il cui nome inizia con quella lettera, si viene informati da una finestra di messaggio, la cui chiusura provoca la visualizzazione di tutti i record, come se fosse stato premuto l’interruttore Tutti. Aprendo la maschera in visualizzazione Struttura possiamo notare che i 27 interruttori sono raccolti entro un controllo Gruppo di opzioni chiamato FiltriNomeSocietà. Ogni interruttore porta il nome della sua dicitura e la proprietà Dati/Valore opzione di ciascuno segue l’ordine alfabetico (A è 1, B è 2 e così via, fino a Tutti, che ha valore 27). Questa impostazione permette di individuare ogni interruttore in due modi: in base al suo nome (A oppure B oppure Tutti) o secondo il valore che ha nel gruppo di opzioni (1, 2 o 27). Un clic su uno qualunque dei 27 interruttori nel gruppo di opzioni FiltriNomeSocietà seleziona l’interruttore stesso, che assume l’aspetto “premuto” e genera l’evento Aggiornamento per il gruppo d’opzioni. A tale evento è associata la sottomacro Elenco telefonico clienti.Pulsanti alfabetici. In questa sottomacro sono disposte una dietro l’altra 26 azioni ApplicaFiltro, una per ciascuno dei primi 26 valori che potrebbe assumere il gruppo di opzioni FiltriNomeSocietà dopo l’aggiornamento. Ogni azione è subordinata al verificarsi di una condizione diversa, per cui se il valore del gruppo di opzioni FiltriNomeSocietà è 1 (corrispondente al clic sull’interruttore con la lettera A), viene applicato un filtro che ha come argomento Condizione WHERE il criterio: [NomeSocietà] Like "[AÀÁÂÃÄ]*"

che vuol dire: lascia vedere soltanto i record nei quali il campo NomeSocietà contiene un testo che inizia con una delle forme possibili della lettera A maiuscola. Se il valore del gruppo di opzioni FiltriNomeSocietà è 27, corrispondente all’interruttore Tutti, l’azione macro non è più ApplicaFiltro, ma MostraOgniRecord, cioè elimina il filtro eventualmente applicato in precedenza e la maschera visualizza tutti i record. Le condizioni associate a ciascuna azione hanno tutte la stessa struttura, vale a dire: If [FiltriNomeSocietà]=xx Then

dove xx è uno dei possibili valori che può avere il controllo FiltriNomeSocietà dopo essere stato aggiornato, cioè dopo che è stato fatto clic su uno dei 27 interruttori che questo controllo contiene. Delle 26 azioni ApplicaFiltro, quindi, ne viene eseguita una sola e, se il valore dell’opzione è 27, viene eseguita l’azione MostraOgniRecord. Le maschere hanno una proprietà CurrentRecord che è uguale al numero del record selezionato nella maschera stessa e che corrisponde al valore che compare nella casella del numero dei record nell’angolo inferiore sinistro della maschera. Se la maschera visualizza almeno un record, la proprietà CurrentRecord è diversa da zero. La sottomacro sfrutta questa proprietà per verificare se l’azione ApplicaFiltro attivata dalla selezione di un interruttore ha fatto passare almeno un record, per cui, se il filtro ha inibito tutti i record, in quanto non esistono società il cui nome inizi con la lettera selezionata viene eseguita un’azione FinestraMessaggio, che segnala il fatto con il messaggio:

Semilavorati per programmare   119

Non esiste alcun record corrispondente alla lettera. Quando si fa clic sul pulsante OK della finestra di messaggio, la condizione continua a valere, quindi viene eseguita l’azione MostraOgniRecord, che elimina il filtro, e infine l’azione ImpostaValore, che – assegnando il valore 27 al controllo FiltriNomeSocietà – fa assumere all’interruttore Tutti l’aspetto di pulsante premuto, che avrebbe se fosse stato l’operatore a scegliere di togliere il filtro. Nella Figura 4.11 vediamo la parte finale della sottomacro Elenco telefonico clienti.Pulsanti alfabetici.

Figura 4.11  La parte conclusiva della sottomacro Pulsanti alfabetici.

Che cosa si può fare Le azioni disponibili per costruire macro sono esattamente 70 in Access 2007 e 86 in Access 2010. Quando si lavora con Access 2010, però, alcune azioni delle versioni precedenti si possono utilizzare soltanto se il file database contiene già gli oggetti ai quali si riferiscono quelle azioni: è il caso delle azioni AggiungiMenu, ImpostaVoceMenu, MostraBarraStrumenti; perché queste azioni si possano attivare, il file database aperto deve essere obbligatoriamente nel formato da Access 2000 fino e non oltre Access 2003 e deve contenere almeno un menu o una barra degli strumenti personalizzati. La radicale differenza fra l’interfaccia utente basata sulla barra multifunzione di Access 2007 e 2010 rispetto a quella delle versioni precedenti, basata su barre dei menu e degli strumenti, non consente di usare le azioni macro AggiungiMenu, ImpostaVoceMenu, MostraBarraStrumenti per modificare la barra multifunzione di Access 2010, che può essere modificata e gestita con strumenti diversi, che descriviamo nel Capitolo 12. Quando si apre il Catalogo azioni dopo aver dato il comando Mostra tutte le azioni, vengono elencate tutte le azioni, comprese quelle considerate a rischio, raggruppate in queste otto categorie: 1. Comandi di interfaccia utente: raggruppa dieci azioni, una delle quali considerata a rischio, che possono modificare il modo in cui si presentano la finestra Database e il Riquadro di spostamento. 2. Comandi di sistema: delle dieci azioni aggregate in questa categoria sei sono considerate a rischio e tutte agiscono sull’intero database aperto o sullo stesso Ac-

120  Capitolo 4

cess, per chiudere il database o Access, stampare un oggetto selezionato nel database, presentare il puntatore del mouse in forma di clessidra per indicare che è necessario attendere e altro ancora. 3. Comandi macro: a questa categoria appartengono diciotto azioni, una sola considerata rischiosa (EcoSchermo) e tutte equivalenti a comandi per eseguire qualcosa di predisposto, come per esempio EseguiMacro, ArrestaMacro, ImpostaVarTemp e così via. 4. Filtro/Query/Ricerca: con le tredici azioni di questa categoria, fra le quali una è considerata rischiosa, si esplorano tabelle e maschere per cercare record, applicare e togliere filtri, eseguire query esistenti o create al momento. 5. Gestione finestre: le cinque azioni di questa categoria corrispondono ai comandi che si possono dare col mouse agendo sul pulsante di controllo contraddistinto dalla lettera A maiuscola che si trova all’estremità di sinistra della barra del titolo. 6. Importazione/esportazione dati: delle undici azioni di questa categoria cinque sono considerate a rischio e tutte consentono di eseguire operazioni di importazione o di esportazione di dati in vari formati. 7. Oggetti di database: questa categoria comprende sedici azioni, cinque delle quali a rischio, per lavorare su oggetti contenuti nel database, quindi aprire tabelle, maschere o report, eliminare oggetti o modificarne i nomi e così via. 8. Operazioni di immissione dati: la categoria contiene soltanto tre azioni, che sono in realtà richiami a comandi di menu per eliminare o salvare record. La descrizione completa delle azioni macro si trova nell’Appendice A.

I limiti storici delle macro Le macro possono essere un ottimo strumento di lavoro, una volta che si è preso un po’ di confidenza con le loro intrinseche rigidità. Il vantaggio principale delle macro sta, fra l’altro, proprio in questa rigidità: gli argomenti sono fissi, in molti casi hanno un valore predefinito, per cui il risultato si può ottenere con pochi clic e digitando soltanto qualche nome di oggetto. Perché, allora, a mano a mano che uscivano nuove versioni di Access il ruolo delle macro è stato progressivamente ridotto e da Access 2000 in poi si viene incoraggiati a farne a meno e sono disponibili, in pratica, soltanto per motivi di compatibilità con le applicazioni già in essere? Le ragioni sono essenzialmente due, una, diciamo così, politica e l’altra sostanziale. La ragione politica l’abbiamo già accennata all’inizio di questo capitolo: Microsoft punta a fare di Visual Basic for Applications (VBA) il linguaggio unico o almeno quello prevalente per tutti i prodotti che compongono la famiglia Office. Per questo motivo, le funzionalità date alle macro nelle prime versioni di Access sono tuttora disponibili, ma molte sono ormai inutili, quali le azioni per creare e modificare barre dei menu e degli strumenti, sostituite nelle versioni dalla 2000 in avanti da meccanismi interattivi più semplici e intuitivi da usare. Nello stesso spirito, già con Access 95 si è offerta la possibilità di convertire automaticamente le macro in routine VBA. Il messaggio, quindi, è chiaro: se volete usare le macro, fate pure, ma è meglio ricorrere al linguaggio VBA per automatizzare funzionalità e gestire eventi.

Semilavorati per programmare   121

La seconda ragione, molto più sostanziale, che induce a limitare il ricorso alle macro quando si crea un’applicazione, sta nel modo sconcertante, al limite ostile, con il quale le azioni macro reagiscono agli errori. Un semplice esempio può aiutarci a capire come stanno le cose. In un file database di prova creiamo una tabella tblIndirizzi con pochi record e generiamo automaticamente una maschera per quella tabella. Nella maschera così ottenuta, alla quale assegniamo il nome frmIndirizziTest, inseriamo una piccola modifica creando un pulsante di comando che chiamiamo cmdNext, impostando la sua dicitura su Prossimo record. Passiamo alla scheda Crea della barra multifunzione e scegliamo il comando Macro, quindi creiamo una macro composta dalla sola azione VaiARecord, impostandone gli argomenti come nella Figura 4.12; salviamo la macro col nome mcrTest1.

Figura 4.12  La macro mcrTest1 per la prova.

Torniamo alla visualizzazione Struttura della maschera frmIndirizziTest e associamo alla routine evento Su clic del pulsante di comando la macro mcrTest1. Salviamo la maschera così modificata, riapriamola in visualizzazione Maschera e facciamo clic a più riprese sul pulsante di comando che porta la dicitura Prossimo record. Disciplinatamente, la macro mcrTest1 esegue la sua azione, facendo avanzare la maschera sul prossimo record ogni volta che facciamo clic sul pulsante di comando. Raggiungiamo l’ultimo record, facciamo ancora clic sul pulsante e compaiono le segnalazioni di errore che possiamo vedere nella Figura 4.13. Dopo una prima finestra di messaggio abbastanza esplicativa, un clic sul pulsante OK chiude il primo messaggio e fa uscire una finestra che, con un linguaggio sibillino e per addetti ai lavori, specifica che l’azione VaiARecord questa volta non è riuscita. Grazie, e allora? Nel Capitolo 3 abbiamo parlato del problema degli errori che un operatore potrebbe commettere mentre lavora con un’applicazione e dell’importanza di gestirli in modo professionale, evitando che l’applicazione si blocchi e suggerendo con chiarezza al malcapitato che cosa fare per rimediare alla sua svista. Gli errori commessi dagli operatori possono provocare quelli che vengono detti in gergo “errori di run-time”, errori, cioè, che si manifestano durante l’esecuzione di un’applicazione.

122  Capitolo 4

Figura 4.13  Le segnalazioni di errore quando l’azione macro non riesce.

Esaminando qualche esempio dall’applicazione Northwind, abbiamo anche visto come si possono intercettare le segnalazioni di errore quando si manifestano in una routine Visual Basic e come sostituire i messaggi di errore predefiniti, sempre sibillini e spesso incomprensibili, con messaggi esplicativi, preparati dall’utente/realizzatore per venire incontro alle esigenze dell’utente/operatore. Il guaio delle macro – nelle versioni di Access precedenti alla 2007 – è che non è possibile intercettare le loro segnalazioni di errore, gestendole con opportune routine di controllo, per cui un’applicazione database costruita esclusivamente con macro è sempre esposta al rischio di repentine e sconcertanti interruzioni, nel caso di errori umani dell’utente/ operatore. E c’è di più. Se, invece di una sola azione, la macro ne contenesse un gruppo, non appena una delle azioni non riesce, la successione si interrompe e le altre azioni non vengono eseguite. Lavorando con puntigliosa tenacia, subordinando l’esecuzione di ogni azione – o almeno delle più importanti – al verificarsi di opportune condizioni di controllo, si riesce a tenere a bada la minaccia dell’errore bloccante accompagnato da una segnalazione sibillina, ma se bisogna fare tutta questa fatica, allora tanto vale rinunciare alle macro e scrivere le routine direttamente in VBA, dove è più agevole predisporre gli opportuni meccanismi di intercettazione e di gestione degli errori, evitando il blocco dell’applicazione.

Semilavorati per programmare   123

Proteggere le azioni macro Un esempio interessante di uso accorto delle azioni macro è proprio il gruppo di macro che viene usato nell’applicazione Northwind per gestire gli eventi della maschera Elenco telefonico clienti. Come abbiamo visto prima, nel paragrafo “Un esempio più articolato”, quella maschera serve per filtrare i record di un elenco telefonico in base all’iniziale del nome della società. Invece di utilizzare un controllo Casella di testo per chiedere all’operatore di immettere una lettera, chi ha realizzato la maschera ha pensato bene di eliminare alla radice ogni possibilità di errore di battitura predisponendo ben 27 controlli Interruttore, uno per ciascuna delle 26 lettere dell’alfabeto e uno per eliminare il filtro. Il realizzatore ha preferito fare la fatica di comporre un gruppo di opzioni con 27 interruttori, per evitare che l’operatore scriva una lettera iniziale in una casella di testo. Se avesse scelto questa soluzione, avrebbe dovuto sobbarcarsi tutto l’onere della gestione dei possibili errori dell’operatore, che di solito è capace di tutto: digita una cifra invece di una lettera, uno spazio invece di un carattere, un segno di interpunzione o niente del tutto, aspettandosi di avere poi una risposta sensata. Semplificato il problema da una parte, le difficoltà si ripresentano dall’altra. Avendo stabilito che i valori che possono assumere gli interruttori raccolti dal gruppo di opzioni FiltriNomiSocietà sono 27, il realizzatore ha dovuto predisporre altrettante azioni ApplicaFiltro tutte uguali, salvo l’argomento Condizione WHERE. Per far eseguire l’azione corrispondente, ha dovuto subordinare l’esecuzione di ciascuna delle possibili 27 azioni a una condizione diversa, basata sul confronto col numero assegnato come Valore opzione a ogni singolo interruttore. Una volta applicato il filtro, il realizzatore vuole agevolare l’operatore selezionando il controllo NomeSocietà, che è associato al campo col nome della società. Per far questo, adotta l’azione VaiAControllo, ma deve gestire la possibilità che il filtro abbia svuotato completamente l’insieme di record (il recordset, per chiamarlo col suo nome ufficiale) associato alla maschera. Se così fosse, l’azione VaiAControllo si arresterebbe, lasciando l’operatore di fronte a un messaggio di errore dal significato impenetrabile e arrestando l’applicazione. Per prevenire questa iattura, serve una condizione che verifichi due possibilità: il filtro lascia passare almeno un record o li esclude tutti? Nel primo caso, l’azione VaiAControllo si può eseguire, nel secondo no. E per non lasciare l’operatore di fronte a una maschera vuota, con altre due azioni lo si informa di quello che è accaduto (FinestraMessaggio) e si toglie il filtro troppo selettivo ripresentando l’intero recordset (MostraOgniRecord). La condizione che viene verificata – [CurrentRecord]>0  – utilizza una proprietà delle maschere. Così facendo, il realizzatore ha protetto l’azione macro dal rischio di fallimento e ha messo al riparo l’operatore da ogni possibile rischio. Ma a quale prezzo? La programmazione, che il ricorso alle azioni macro avrebbe dovuto escludere, si è insinuata di prepotenza nelle clausole If…Then, facendo del gruppo di macro associato alla maschera Elenco telefonico clienti una complessa routine di programmazione.

124  Capitolo 4

Le macro di Access 2010 Se la conclusione del paragrafo precedente è corretta, come mai Microsoft non soltanto conserva in Access 2010 tutte le 56 le azioni macro “storiche”, chiamiamole così, ma ne aggiunge altre 30? La disponibilità delle azioni precedenti consente di continuare a usare applicazioni database che usano macro e sono state create con altre versioni di Access. È lo stesso criterio adottato da Microsoft per il suo sistema operativo: ogni nuova versione di Windows aggiunge funzionalità alla precedente, ma consente di eseguire anche tutti i programmi che sono stati concepiti utilizzando le funzionalità delle versioni che l’hanno preceduta. Avendo deciso di mantenere in vita le macro e le loro azioni anche in Access 2007 e poi in Access 2010, Microsoft ha ritenuto opportuno arricchire il repertorio con altre azioni, fra le quali alcune consentono di: •• reagire con flessibilità agli errori; •• impostare variabili che possono essere utilizzate da oggetti Access e da azioni macro. Con queste nuove azioni si possono gestire gli errori di run-time, superando così una delle limitazioni più gravi delle macro.

Lavorare con le variabili Con l’azione ImpostaVarTemp si crea una variabile assegnandole contestualmente un valore; una variabile specifica creata in questo modo può essere eliminata con l’azione RimuoviTempVar, mentre l’azione RimuoviTutteLeVarTemp elimina tutte le variabili impostate. Queste tre azioni consentono di rendere le macro molto più flessibili, perché in tutti i casi in cui un’azione ha bisogno di un’espressione come argomento, se l’espressione utilizza variabili può essere modificata in corso d’opera invece di restare rigidamente definita all’atto della creazione della macro che contiene l’azione. Questa spiegazione è piuttosto sibillina, ma con un esempio dovrebbe essere tutto più chiaro. Per capire che cosa sono le variabili e come si usano, possiamo far riferimento a un foglio di lavoro Excel, uno strumento sicuramente noto ai nostri lettori. Se scriviamo in una cella questa formula: =SOMMA(2; 3; 7)

in quella cella avremo il valore 12, cioè il risultato della somma dei valori assoluti indicati nella funzione. Se scriviamo la formula in questo modo: =SOMMA(A1; B3; C4)

dopo aver inserito nelle celle A1, B3 e C4 rispettivamente i valori 2, 3 e 7, otteniamo lo stesso risultato, ovviamente. Se scriviamo valori diversi nelle tre celle A1, B3 e C4, la funzione SOMMA() produce un risultato diverso (e corretto) sommando i nuovi valori. Quello che abbiamo fatto è stato sostituire nella funzione i valori assoluti 2, 3 e 7 con i nomi delle celle A1, B3 e C4. I nomi delle celle usate nella formula sono le variabili, così dette perché il loro contenuto può variare. L’espressione (o formula, come viene

Semilavorati per programmare   125

detta in Excel) costruita con i nomi delle celle è più utile e flessibile di quella che usa i valori assoluti, perché facendo riferimento a variabili produce sempre la somma corretta di qualunque terna di numeri si scriva nelle celle. Nei fogli di lavoro Excel le variabili possibili hanno già un nome (le coordinate delle celle: A1, B2, e così via) e inserendo un valore in una cella si esegue l’operazione che nel gergo della programmazione si chiama “impostare una variabile”, che è la nuova azione macro ImpostaVarTemp. Dal momento che nel contesto della macro di Access non esistono variabili predefinite come le celle di Excel, l’azione ImpostaVarTemp richiede due argomenti: il nome della variabile e il valore impostato per quella variabile, che può anche essere definito con un’espressione che produce un valore: per esempio, se vogliamo creare una variabile chiamata strCognome e assegnarle come valore il contenuto della casella di testo Cognome della maschera frmIndirizzi, l’espressione da usare può essere [Maschere]![frmIndirizzi].[Cognome].[Value]

Per capire come si usa e come funziona questa azione, proviamo a costruire una macro come quella che vediamo nella Figura 4.14.

Figura 4.14  La macro mcrMostraCampo in visualizzazione Struttura.

La macro mcrMostraCampo si articola in sette azioni, che eseguono i seguenti passi: 1. apertura della maschera frmIndirizzi; 2. posizionamento sul record 12 della maschera: 3. posizionamento sul controllo casella di testo Cognome del record selezionato; 4. creazione di una variabile temporanea chiamata strCognome, che viene impostata contestualmente sul valore contenuto nella casella di testo selezionata con l’azione precedente: 5. visualizzazione in una finestra di messaggio del contenuto della variabile temporanea strCognome; 6. eliminazione di tutte le variabili temporanee;

126  Capitolo 4

7. chiusura della maschera frmIndirizzi. E queste sono le azioni, con i loro argomenti, che corrispondono ai passi: Passo 1

2

3 4

Azione ApriMaschera

VaiARecord

VaiAControllo ImpostaVarTemp

5

FinestraMessaggio

6 7

RimuoviTutteLeVarTemp Chiudi

Argomenti Nome maschera Visualizza Modalità finestra Tipo oggetto Nome oggetto Record Offfset Nome controllo Nome Espressione

Valore

Messaggio

=”Il campo Cognome del record 12 contiene: “ & [TempVars]![strCognome]

Tipo oggetto Nome oggetto Salva

Maschera

frmIndirizzi

Maschera Normale Maschera frmIndirizzi

Vai 12 Cognome

strCognome [Maschere]![frmIndirizzi]. [Cognome].[Value]

frmIndirizzi

No

Quando viene eseguita, la macro mcrMostraCampo produce il risultato che vediamo nella Figura 4.15.

Figura 4.15  La finestra di messaggio mostra il valore del campo selezionato e acquisito come valore della variabile temporanea.

Semilavorati per programmare   127

È importante tener presente che l’azione ImpostaTempVar crea una variabile temporanea nell’ambito di un oggetto Visual Basic chiamato TempVars, per cui, una volta creata una variabile assegnandole un nome, per utilizzarla è obbligatorio far riferimento al contesto al quale appartiene usando il suo nome completo, che si ottiene congiungendo con l’operatore punto esclamativo il nome dell’oggetto genitore, TempVars, con quello dell’oggetto figlio, in questo modo: TempVars]![strCognome]

Se si omette il riferimento a TempVars la variabile non viene riconosciuta e se si usa l’operatore di congiunzione punto invece del punto esclamativo, scrivendo il riferimento in questo modo: TempVars].[strCognome]

si ottiene una segnalazione di errore sconcertante che non è possibile intercettare né gestire, perché è un errore di sintassi e non di run-time (approfondiremo la tipologia degli errori nel Capitolo 12). L’esempio ci aiuta a capire la tecnica per usare una variabile temporanea, ma non è certamente un esempio utile ai fini del nostro tema di fondo, quello della costruzione di applicazioni con Access. Qualcosa di più utile si potrebbe ottenere con una macro che chiamiamo mcrCurrentRecord composta da queste tre azioni: ImpostaVarTemp

    Nome           numPosizione     Espressione    =[Maschere]![frmIndirizzi].[CurrentRecord] FinestraMessaggio

    Messaggio     =“Siamo posizionati sul record: ” & [TempVars]![numPosizione] RimuoviTutteLeVarTemp

Se associamo all’evento Su corrente della maschera frmIndirizzi la macro mcrCurrentRecord, ogni volta che passiamo da un record all’altro si apre una finestra di messaggio con l’indicazione del numero del record ricavato dalla proprietà CurrentRecord della maschera e impostato nella variabile temporanea col nome numPosizione.

Gestire gli errori In precedenza, nel paragrafo “I limiti storici delle macro”, abbiamo visto (Figura 4.10), che cosa succede quando si verifica un errore durante l’esecuzione di una macro. Con le azioni SuErrore e CancellaErroreMacro, introdotte in Access 2007 e disponibili anche in Access 2010, si possono evitare le segnalazioni predefinite degli errori, sostituendole con messaggi personalizzati, più chiari e utili all’utente. È anche possibile, entro certi limiti, rimediare a determinati errori, ma di questo ci occuperemo più a fondo nel Capitolo 12. Riprendiamo l’esempio dell’errore di run-time provocato dall’avanzamento su un record inesistente. Creiamo una macro con i nomi, le azioni e gli argomenti che vediamo nella Figura 4.16.

128  Capitolo 4

Figura 4.16  La macro mcrGestioneErrore contiene due sottomacro.

La macro mcrGestioneErrore contiene due sottomacro, chiamate Inizio e Sbaglio. La sottomacro Inizio viene eseguita a ogni clic sul pulsante di comando Prosegui della maschera di prova. La sottomacro inizia con l’azione SuErrore, che viene eseguita al verificarsi di un errore e prevede due argomenti: Vai a e Nome. Vai a può assumere tre valori distinti e predefiniti: Errore, nel qual caso la macro viene interrotta e compare la segnalazione di errore standard; Successivo, che fa eseguire l’azione macro che viene subito dopo e Nome macro. Scegliendo questa terza variante, è necessario scrivere nella casella dell’argomento Nome macro il nome di una macro, che può essere diversa da quella in esecuzione oppure, come nel nostro caso, il nome di una sottomacro contenuta nella stessa macro. Per questa sottomacro abbiamo scelto il nome Sbaglio. La sottomacro Inizio contiene una sola azione dopo la prima, che è l’azione VaiARecord, che specifica il record successivo della maschera corrente. Se si verifica un errore (nel nostro caso, se si passa a un record inesistente), viene eseguita la sottomacro Sbaglio, composta da tre azioni: 1. l’azione CancellaErroreMacro non ha argomenti ed elimina la segnalazione di errore standard; 2. l’azione successiva presenta la finestra di messaggio riportata nella Figura 4.17, che spiega con chiarezza che cosa è accaduto; 3. la terza e ultima azione è ancora una VaiARecord, che sposta la maschera sul record precedente e ultimo. Quando si vuole proteggere una macro da possibili errori, è necessario collocare l’azione SuErrore al primo posto nella sequenza di azioni che comnpongono la macro: in questo modo, se si verifica un errore, è possibile passare automaticamente alla macro o alla sottomacro indicata nell’argomento Nome macro dell’azione SuErrore. Concludiamo la rassegna delle nuove azioni macro di Access 2007/2010 descrivendo l’azione PassoAPasso, che può essere utilizzata con vantaggio quando si crea una macro con molte azioni. Questa azione non richiede argomenti ed è opportuno inserirla come prima azione di una macro complessa. Quando si esegue la macro per provarla, l’azione visualizza la finestra di dialogo Macro passo a passo che vediamo nella Figura 4.18.

Semilavorati per programmare   129

Figura 4.17  La segnalazione di errore personalizzata.

Figura 4.18  Nella finestra di dialogo Macro passo a passo si può seguire l’esecuzione delle singole azioni di una macro complessa.

In questa finestra di dialogo viene presentato il risultato delle diverse azioni, che vengono eseguite una alla volta, se si preme il pulsante Passo. Il nome dell’azione, i suoi argomenti, la condizione che ne determina l’esecuzione e l’eventuale stato di errore sono visualizzati nelle corrispondenti caselle di testo. Premendo il pulsante Continua l’esecuzione delle azioni riprende nella sequenza prestabilita, senza interruzioni, mentre un clic sul pulsante Interrompi tutte le macro arresta l’esecuzione delle azioni rimanenti. Se si inserisce l’azione PassoAPasso al primo posto di una sequenza di azioni, è possibile eseguire il gruppo delle macro un’azione per volta e controllare il risultato, individuando in questo modo eventuali errori o incongruenze, da correggere prima di rendere operativa la macro con tutte le sue azioni collaudate individualmente.

Le macro incorporate In tutte le versioni di Access, compresa l’ultima, le macro sono oggetti database che risiedono, individuate da un nome, nella Barra oggetti o nel Riquadro di spostamento, dal quale possono essere eseguite (se contengono una sola azione) o aperte in visualizzazione Struttura per modificarle.

130  Capitolo 4

In Access 2007/2010 è disponibile una nuova funzionalità, le macro incorporate, che non stanno nel Riquadro di spostamento, ma sono memorizzate entro l’oggetto (una maschera o un report, per lo più), che le utilizza. Questa caratteristica fa sì che se si esporta in o si esporta da un altro file database un oggetto che contiene una macro incorporata, l’importazione o l’esportazione coinvolge anche la macro oltre all’oggetto. Le macro incorporate vengono generate automaticamente quando si esegue una creazione guidata di un pulsante di comando. Per crearle direttamente si deve procedere in questo modo. 1. Nella Finestra della proprietà di un controllo entro una maschera, o della maschera stessa, si sceglie un evento al quale associare una macro, per esempio l’evento Su clic o Su apertura. 2. Nella casella della scheda Evento dove si effettua la selezione compare il pulsante Genera (quello con i tre puntini di sospensione). 3. Un clic su questo pulsante fa uscire la finestra di dialogo Scegli generatore, che vediamo nella Figura 4.19.

Figura 4.19  Selezionando Generatore di macro nella finestra di dialogo Scegli generatore si avvia la creazione di una macro incorporata.

Selezionando l’opzione Generatore di macro, viene aperta la struttura di una nuova macro che ha un nome predefinito, composto da tre elementi: nomeoggetto; nome controllo; nomeevento

Nella griglia di composizione della macro si inseriscono le azioni e i loro argomenti, eventualmente le condizioni e i nomi dei raggruppamenti di azioni, se necessario, quindi si chiude la griglia di composizione, facendo così apparire una finestra di messaggio che chiede: Salvare le modifiche alla macro e aggiornare le proprietà?

Dando OK, la finestra di messaggio si chiude e si torna alla struttura della maschera o del report, che nella casella corrispondente all’evento ora presenta l’indicazione Macro incorporata.

Semilavorati per programmare   131

Dopo aver salvato l’oggetto che ha la macro incorporata, questa diventa attiva e a tutti gli effetti si comporta come una macro indipendente, con la sola differenza che il suo nome non compare fra quelli elencati nella sezione Macro del Riquadro di spostamento. Se fosse necessario intervenire su una macro incorporata per modificarla, un clic sul pulsante Genera nella casella che contiene la dicitura Macro incorporata torna ad aprire la griglia di composizione della macro, dove si possono fare gli aggiustamenti necessari.

Le macro di dati Le macro che abbiamo descritto fin qui sono, come abbiamo detto più volte, oggetti Access che esistono da sempre in tutte le versioni di questo strumento, le uniche novità di rilievo in Access 2010 sono la nuova interfaccia per la composizione delle macro e la presenza di numerose nuove azioni. Tutto questo per quanto riguarda le macro così come le abbiamo sempre conosciute: in Access 2010 sono disponibili per la prima volta macro radicalmente nuove, appartenenti a una famiglia diversa rispetto a quelle note, che per differenza prendono il nome di macro di interfaccia, mentre le nuove macro sono dette nell’originale inglese data macro e indicate in italiano col nome macro di dati. Per alcuni versi queste nuove macro sono simili a quelle già note: eseguono azioni, utilizzando a seconda dei casi argomenti o parametri specifici e si possono attivare al verificarsi di un evento. La novità, e la differenza sostanziale rispetto alle macro di interfaccia, sta nel fatto che le macro di dati si possono associare soltanto a eventi che si manifestano nelle tabelle. Si può accedere a una tabella direttamente, aprendola in visualizzazione Foglio dati, oppure indirettamente, utilizzando una maschera. Una volta ottenuto l’accesso ai dati, si può modificare il contenuto di uno o più campi di un determinato record, eliminare un intero record o aggiungerne uno nuovo. Che si intervenga sui dati di una tabella tramite una maschera o agendo nella visualizzazione Foglio dati, qualunque modifica è per la tabella un evento e in Access 2010 si distinguono cinque diversi eventi, come vediamo nella Figura 4.20, che riporta una sezione della scheda Strumenti tabella/Tabella della barra multifunzione. Come vediamo, dei cinque eventi possibili, due sono raggruppati fra i Pre-eventi e gli altri tre fra i Post-eventi: che cosa vuol dire? I termini suonano un po’ bislacchi, essendo la traduzione letterale dei nomi originali dei comandi in inglese, che sono Before Events e After Events, con i quali si fa riferimento a “eventi che accadono prima” e a “eventi che accadono dopo”. Ma “prima” e “dopo” che cosa? Quando si lavora su una tabella, direttamente in visualizzazione Foglio dati o indirettamente con una maschera, l’esecuzione di un comando di modifica o di eliminazione non è istantanea, ma comporta una elaborazione interna prima di diventare effettiva: il “prima”, quindi si riferisce al periodo, breve, ma non nullo, in cui si svolge questa elaborazione interna, quando il comando di modifica o di eliminazione è stato recepito ma non è stato ancora portato a termine. Di conseguenza, eseguendo una macro di dati in corrispondenza dell’evento Prima della modifica o dell’evento Prima della eliminazione è possibile intercettare il comando prima che vada a buon fine e annullarne l’effetto oppure far sì che venga eseguito effettivamente soltanto se si verifica una particolare condizione.

132  Capitolo 4

Figura 4.20  Il gruppo di comandi Strumenti tabella/Tabella con i comandi per gli eventi.

Lo stesso discorso vale per quelli che sono chiamati Post-eventi, che si verificano “dopo” che l’operazione di modifica, inserimento o eliminazione è stata portata a termine: anche per questi eventi, si può intervenire – con opportune azioni di macro di dati – per annullare un’operazione o eseguirla soltanto a determinate condizioni oppure ancora per annotare in altre tabelle predisposte per l’auditing o il controllo le modifiche che sono state fatte a una specifica tabella. Vediamo con qualche esempio concreto come si caratterizzano le macro di dati, quali sono le loro specifiche azioni e come si possono usare per migliorare il funzionamento di un’applicazione database.

Convalidare modifiche Nel Capitolo 2 abbiamo descritto le proprietà dei campi di una tabella, specificando che è consentito indicare fra le proprietà di ciascun campo una regola di convalida che potrebbe essere un valore numerico di riferimento, da considerare per esempio come un minimo accettabile oppure un massimo da non superare; la regola di convalida si scrive nella casella Valido se delle proprietà di un campo e può essere associata a un messaggio descrittivo o di allarme da far comparire quando la regola di convalida non viene rispettata; il testo di questo messaggio va scritto nella casella Messaggio errore. In Access 2010 possiamo impostare regole di convalida anche per l’intera tabella e non soltanto riferite a singoli campi, cosa che si può ottenere col comando Strumenti tabella/ Progettazione/Mostra/Nascondi/Finestra delle proprietà, provocando l’uscita della finestra che vediamo nella parte superiore della Figura 4.21, dove è stata impostata una regola di convalida per l’intera tabella e che prende in considerazione due campi diversi.

Semilavorati per programmare   133

Figura 4.21  L’impostazione di una regola di convalida a livello dell’intera tabella: la data di consegna non può precedere quella dell’ordine.

Non sempre, però, è possibile impostare direttamente, come in questo esempio, una regola di convalida che si applichi all’intera tabella impegnando due o più campi diversi. Quando si verificasse un’esigenza del genere, si può provare a risolverla utilizzando una macro di dati, come vediamo nel prossimo esempio. Supponiamo di dover lavorare con una tabella di clienti potenziali, che contiene dati anagrafici elementari delle persone più qualche altro dato, fra cui l’indicazione se la persona accetta di ricevere materiali promozionali tramite posta elettronica. Avremo, quindi, nel record della tabella tblPotenziali, un campo con tipo dati Collegamento ipertestuale chiamato E-mail, con l’indirizzo di posta elettronica della persona e un secondo campo, con tipo dati Sì/No, chiamato Autorizza e-mail. È evidente che non si può impostare su Sì (o Vero) il valore del campo Autorizza e-mail, se il campo E-mail dello stesso record è vuoto. Possiamo esprimere questa regola di convalida con una macro di dati che usa un’espressione condizionale associata all’evento Prima della modifica. Apriamo quindi la tabella tblPotenziali in visualizzazione Foglio dati, in modo da poter vedere subito l’effetto della nuova macro che andremo a costruire. Selezioniamo nella barra multifunzione il comando Strumenti tabella/Tabella/Pre-eventi/ Prima della modifica, ottenendo l’automatica apertura della finestra di composizione delle macro di dati, che è strutturalmente uguale a quella delle macro di interfaccia, con la differenza che il Catalogo azioni elenca azioni diverse da quelle che conosciamo.

134  Capitolo 4

Nella sezione Flusso esecuzione programma del Catalogo azioni facciamo doppio clic sul comando Se e otteniamo l’inserimento nella finestra di composizione del costrutto If. . . Then. Nella casella di testo della condizione If digitiamo quanto segue: [Autorizza e-mail]=Vero And IsNull([E-mail])

che nel parlar comune significa “se il valore del campo Autorizza e-mail è impostato su Vero e contemporaneamente il campo E-mail è vuoto, allora…” Fra le azioni disponibili nel Catalogo azioni, sezione Azioni per dati, scegliamo GeneraErrore, che andiamo a inserire all’interno della condizione If…Then, impostando i due argomenti di questa azione nel modo che vediamo nella Figura 4.22.

Figura 4.22  La nuova macro di dati è pronta.

L’azione richiede due argomenti, un numero dell’errore e una descrizione dell’errore, che verrà presentata in una finestra di messaggio al verificarsi dell’errore stesso. Nella casella Numero errore dobbiamo scrivere un numero qualunque scelto da noi e che non ha alcuna influenza sui criteri che Access usa al suo interno per identificare con un numero gli errori. Chiudiamo la finestra di composizione, Access ci chiede se vogliamo salvare il nostro lavoro, diamo conferma, la macro viene salvata senza che ci venga chiesto un nome (come avviene, invece, per le macro di interfaccia) e siamo pronti per provare se funziona. Scorriamo la nostra tabella, che era rimasta aperta in visualizzazione Foglio dati, e proviamo a commettere l’errore che vogliamo intercettare. Come possiamo vedere dalla Figura 4.23, la manovra viene arrestata, compare la finestra di messaggio con l’avvertimento che avevamo predisposto nella casella Descrizione errore dell’azione GeneraErrore, e la modifica rimane bloccata (simbolo della matita nel selettore del record) fino a quando non facciamo qualcosa. Se creiamo una maschera basata sulla tabella tblPotenziali, eseguendo la stessa operazione sulla maschera (impostare su Sì la casella di controllo Autorizza e-mail quando il campo E-mail è vuoto) si ottiene lo stesso risultato: la macro di dati viene eseguita e presenta la finestra di messaggio che vediamo nella Figura 4.23.

Semilavorati per programmare   135

Figura 4.23  La macro di dati ha bloccato la modifica.

Potremmo evitare di segnalare l’errore e correggerlo direttamente: se creiamo una copia della tabella tblPotenziali chiamandola tblPossibili nella copia è presente la stessa macro di dati. Con la tabella tblPossibili aperta in visualizzazione Foglio dati diamo il comando Strumenti tabella/Tabella/Pre-eventi/Prima della modifica e veniamo portati nella finestra di composizione della macro di dati esistente, nella quale sostituiamo l’azione GeneraErrore con l’azione ImpostaCampo, assegnando i parametri nel modo illustrato nella Figura 4.24

Figura 4.24  La macro di dati modificata corregge automaticamente l’errore se si imposta su Vero la casella di controllo Autorizza e-mail quando il campo con l’indirizzo e-mail è vuoto.

Come abbiamo visto, dopo aver creato una macro di dati bisogna salvarla per poterla rendere effettiva: diversamente, però, dalle macro di interfaccia, per le quali all’atto del salvataggio è necessario attribuire loro un nome, le macro di dati vengono salvate senza che Access chieda per loro un nome. Come facciamo a individuarle, allora e, se è il caso, a modificarle? Quando una tabella contiene almeno una macro di dati, nel momento in cui viene aperta, in visualizzazione Struttura o Foglio dati, il pulsante corrispondente all’evento al quale

136  Capitolo 4

è associata la macro di dati appare premuto (quindi attivo) nella barra multifunzione. Se facciamo clic su uno dei cinque pulsanti di comando associati agli eventi per le tabelle che stanno nei gruppi Pre-eventi e Post-eventi, nella scheda Strumenti tabella/Tabella, si apre la finestra di composizione delle macro di dati, che si presenta vuota se non esiste nella tabella aperta una macro di dati, oppure visualizza la struttura della macro di dati esistente, che può quindi essere modificata o eliminata con le tecniche ormai note. Nella scheda Strumenti tabella/Tabella, il gruppo Macro denominata mostra in un elenco a discesa tre comandi relativi alle macro denominate, che sono macro di dati che hanno un nome scelto dall’utente, diversamente dalle macro che abbiamo visto fin qui. Il terzo di questi comandi, Rinomina/Elimina macro, fa comparire una finestra di dialogo con l’elenco di tutte le macro di dati, quelle denominate (che vedremo fra poco) e le altre che sono presenti nell’intero database.

Audit delle modifiche Vediamo con un altro esempio, un po’ più impegnativo, come sfruttare le azioni disponibili per le macro di dati. Abbiamo una tabella chiamata tblProdotti che usiamo per registrare alcuni dati elementari su certi prodotti, con i campi organizzati nel modo seguente: tblProdotti ID 1 2 3

Prodotto Acciughe Aringhe Sgombri

UnitàMisura Scatole 125 gr Scatole 300 gr Barattoli 250 gr

Quantità 300 500 400

PrezzoUnitario € 12,00 € 25,00 € 40,00

La Direzione Commerciale ha la responsabilità di fissare il prezzo di vendita dei prodotti e gli aggiornamenti periodici, decisi per varie ragioni, sono delegati a un impiegato del magazzino, che esegue materialmente le modifiche nell’applicazione Access, in base a disposizioni che gli vengono fornite per e-mail o per telefono. Vogliamo controllare il rispetto di questa procedura, annotando per ogni variazione di prezzo quando è stata fatta, qual era il prezzo prima della modifica e qual è il prezzo attuale. A tale scopo abbiamo predisposto una tabella di controllo, che abbiamo chiamato tblProdotti-Audit, con questa struttura: ID Azione

NuovoValore

tblProdotti-Audit VecchioValore

DataModifica

Prodotto

Portiamo le due tabelle in visualizzazione Foglio dati, selezioniamo tblProdotti e diamo il comando Strumenti tabella/Tabella/Post-eventi/Dopo aggiornamento, ottenendo così l’apertura della finestra di composizione di una nuova macro di dati da eseguirsi quando si è verificato un evento di aggiornamento. L’evento “Dopo aggiornamento” si riferisce a un qualsiasi aggiornamento fatto sulla tabella selezionata, quindi dobbiamo specificare quale campo è stato aggiornato e agire di conseguenza. Avremo quindi bisogno del costrutto If…Then, che già conosciamo, e della funzione Updated("campo") , che è specifica di questo evento e serve a individuare il

Semilavorati per programmare   137

campo che è stato eventualmente aggiornato (è importante mettere il nome del campo da controllare fra virgolette doppie). Il primo elemento della nuova macro di dati avrà quindi la forma: If Updated("PrezzoUnitario") Then

e sarà seguito da un’azione CreaRecord nella tabella di controllo tblProdotti-Audit. Questa azione dovrà essere seguita da tante azioni ImpostaCampo quanti sono i vari campi che compongono il record della tabella tblProdotti-Audit, a eccezione, ovviamente, del campo ID che viene generato automaticamente da Access. L’azione ImpostaCampo richiede due argomenti, Nome (del campo da impostare) e Valore (da assegnare al campo), per cui dovremo specificare in questo modo il contenuto dei campi del nuovo record da creare nella tabella tblProdotti-Audit: Nome Azione NuovoValore VecchioValore DataModifica Prodotto

Valore “Variazione del prezzo” [tblProdotti].[PrezzoUnitario] [Precedente].[PrezzoUnitario] Date() [tblProdotti].[Prodotto]

La nuova macro dovrebbe alla fine presentarsi nel modo illustrato dalla Figura 4.25:

Figura 4.25  La struttura della macro di dati per l’audit delle modifiche al prezzo.

Il contenuto dei campi del nuovo record da inserire nella tabella di controllo è intuitivo, salvo forse per due campi: 1. DataModifica: utilizza la funzione Date() di Visual Basic, che ricava dall’orologio del sistema la data del giorno;

138  Capitolo 4

2. VecchioValore: utilizza l’oggetto [Precedente] che restituisce il contenuto del campo che gli è associato con l’operatore punto, ottenendo quindi il valore che quel campo aveva prima dell’aggiornamento. Possiamo provare il funzionamento della nuova macro di dati aprendo la tabella tblProdotti e cambiando qualche prezzo: aprendo successivamente la tabella tblProdotti-Audit troveremo registrate fedelmente le modifiche come nella Figura 4.26.

Figura 4.26  L’effetto della macro di dati eseguita dopo l’aggiornamento della tabella tblProdotti.

L’esecuzione di una macro di dati che crea nuovi record in una tabella diversa da quella sulla quale si sta operando comporta un rischio di errore da non sottovalutare. Per capire di che cosa si tratti, provochiamo deliberatamente un errore: apriamo la tabella tblProdotti-Audit in visualizzazione Struttura e subito dopo apriamo la tabella tblProdotti in visualizzazione Foglio dati; in questa tabella modifichiamo il prezzo in un record qualunque, confermando la modifica con un clic su un qualsiasi campo. La modifica del prezzo va naturalmente a buon fine, ma nell’estremità destra della barra di stato compare una dicitura nuova: “Nuovi errori applicazione” completata da una scritta che appare quando si appoggia il puntatore del mouse su quella dicitura senza fare clic. La scritta invita a fare clic sulla segnalazione nella barra di stato per visualizzare gli errori contenuti in una tabella chiamata USysApplicationLog. Apriamo questa tabella così come ci viene suggerito e possiamo vedere nella Figura 4.27 che cosa è accaduto. USysApplicationLog è una tabella di sistema, che di norma è nascosta, ma viene resa visibile quando si verificano errori nelle macro di dati. La disponibilità di questa tabella è segnalata anche dalla finestra Backstage, che se aperta sulla sezione Informazioni presenta nella fascia verticale centrale un nuovo pulsante, che ha questo aspetto:

Semilavorati per programmare   139

Un clic su questo pulsante fa aprire la tabella USysApplicationLog.

Figura 4.27  L’annotazione sull’errore nella tabella USysApplicationLog.

NOTA Si può rendere visibile la tabella USysApplicationLog anche facendo un clic destro sulla barra del titolo del Riquadro di spostamento, selezionando quindi Opzioni di spostamento dal menu contestuale che si apre e infine scegliendo l’opzione Mostra oggetti di sistema nella finestra di dialogo Opzioni di spostamento: il nome della tabella comparirà in chiaro nella sezione Tabelle del Riquadro di spostamento, insieme con un certo numero di nomi di tabelle di sistema il cui nome appare offuscato, ma che si possono aprire ed eventualmente modificare quando sono visibili: dal momento che è tassativamente proibito intervenire in qualsiasi modo sulle tabelle di sistema, è opportuno eseguire il comando Mostra oggetti di sistema soltanto quando si sa che cosa si sta facendo.

L’informazione contenuta nel campo Description dell’ultimo record della tabella USysApspiega con chiarezza qual è l’errore che si è verificato. Si osservi che a sinistra del campo Description c’è un campo chiamato Category, che contiene in questo caso la parola Execution. Possiamo usare questa tabella anche come deposito di dati corretti e non soltanto come raccoglitore di segnalazioni di errore: vediamo come. Riprendiamo la macro di dati che abbiamo visto all’opera finora e inseriamo entro il costrutto If…Then l’azione RegistraEvento, che ha come solo argomento una casella di plicationLog

140  Capitolo 4

testo intitolata Descrizione, e in questa digitiamo Aggiornato il prezzo unitario di tblProdotti. Salviamo la macro così modificata e torniamo alla tabella tblProdotti, facendo di nuovo qualche modifica al prezzo unitario. Riaprendo successivamente la tabella USysApplicationLog, noteremo che le nostre operazioni sono state registrate ciascuna in un record distinto di questa tabella, con il testo della nostra descrizione nel campo Description, mentre nello stesso record il campo Category contiene la parola User: come lascia capire la parte finale del suo nome, la funzione della tabella di sistema USysApplicationLog è quella di tenere un brogliaccio (“log”) di informazioni su determinati eventi, informazioni che possono essere decise dall’utente con l’azione RegistraEvento, nel qual caso vengono annotate nel campo Description e associate alla Category User oppure informazioni su errori di run-time ovvero di esecuzione, nel qual caso vengono associate alla Category Execution.

Sincronizzare tabelle Si può sfruttare la funzionalità di base delle macro di dati, quella di essere eseguite quando si verifica un evento in una tabella, per tenere sincronizzati i dati di due tabelle che sono concettualmente correlate, per esempio, per modificare la quantità disponibile in magazzino di un prodotto quando una determinata quantità di quel prodotto è stata ordinata da un cliente, secondo lo schema che è illustrato nella Figura 4.28.

Figura 4.28  Una modifica nella tabella tblOrdini fa scattare una modifica nella tabella tblProdotti.

Vogliamo ottenere questo risultato: l’aggiunta di un nuovo record (evento Dopo l’inserimento) nella tabella tblOrdini determina la sottrazione della quantità ordinata di quel determinato prodotto dalla sua disponibilità nella tabella tblProdotti.Vogliamo, però, contemplare anche il caso in cui un record della tabella tblOrdini venga eliminato (evento Dopo l’eliminazione), nel qual caso bisognerà ripristinare la quantità sottratta in precedenza dalla disponibilità di quel prodotto nella tabella tblProdotti e vogliamo gestire anche l’evento Dopo l’aggiornamento, sempre di un record della tabella tblOrdini e il relativo riflesso nella corrispondente giacenza nella tabella tblProdotti.

Semilavorati per programmare   141

Per ottenere tutti questi risultati insieme senza creare un numero eccessivo di macro di dati, partiamo da una macro particolare, detta macro denominata, caratterizzata dal fatto che viene salvata con un nome e può essere attivata chiamandola col suo nome da altre macro di dati. Apriamo quindi la tabella tblOrdini in visualizzazione Foglio dati e diamo il comando Strumenti tabella/Tabella/Macro denominate/Crea macro denominata. Si apre la finestra di composizione delle macro di dati, ma con una differenza: la fascia superiore della finestra contiene una sezione per definire dei parametri, che non sono obbligatori, ma servono, nell’esempio che stiamo costruendo, per fornire alla macro denominata informazioni provenienti dalla macro che la chiama. Abbiamo bisogno di due parametri: •• IDProdotto, che identifica il prodotto che si deve aggiornare; •• CambiaGiacenza, il numero corrispondente alla quantità ordinata da sommare alla disponibilità (se positivo) o da sottrarre (se negativo. Dopo aver impostato i due parametri, dobbiamo specificare le azioni che vanno eseguite: la prima azione è RicercaRecord, per reperire il record della tabella tblProdotti che corrisponde all’ID del prodotto ordinato; trovato il record si esegue l’azione ModificaRecord che richiede la specifica di un campo da impostare, cosa che si ottiene con l’azione ImpostaCampo, nella quale si specifica che il campo da modificare è il campo Disponibilità e la modifica da fare è: [Disponibilità]+[CambiaGiacenza]

Completata così con i parametri e le azioni necessarie, la macro denominata deve essere salvata con un nome, cosa che si ottiene chiudendola e digitando il nome che viene chiesto alla chiusura: digitiamo MacroDatiAggiornaGiacenza e otteniamo il risultato che è illustrato nella Figura 4.29.

Figura 4.29  La macro denominata MacroDatiAggiornaGiacenza.

142  Capitolo 4

Se in seguito vogliamo intervenire su questa macro per modificarla dobbiamo dare il comando Strumenti tabella/Tabella/Macro denominata/Modifica macro denominata e scegliere il nome della macro da modificare dall’elenco che si apre. Per cambiare il nome della macro o per eliminarla, dobbiamo aprire la finestra di dialogo Gestione macro di dati, che si apre col comando Strumenti tabella/Tabella/Macro denominata/Rinomina/Elimina macro: in questa finestra di dialogo compaiono i nomi di tutte le macro di dati presenti nel database, quelle definite come macro dati eventi si possono soltanto eliminare da questa finestra di dialogo, mentre si possono cambiare i nomi alle macro denominate oltre che eliminarle. NOTA La visualizzazione in forma di grossi pulsanti dei comandi nella sezione Pre-eventi e Posteventi della scheda Strumenti tabella/Tabella, come pure delle opzioni di eliminazione o di rinomina delle macro elencate nella finestra di dialogo Gestione macro di dati si ottiene soltanto quando è stata selezionata l’opzione Attiva modifiche a livello di struttura per le tabelle in visualizzazione Foglio dati nella sezione Database corrente delle Opzioni di Access.

La macro denominata che abbiamo appena creato non si può eseguire direttamente, ma deve essere chiamata per nome da specifiche macro di dati associate ciascuna a un particolare evento: queste macro sono formate da una sola azione, che si chiama EseguiMacroDati, che deve essere seguita dal valore specifico per i due parametri che sono stati impostati nella macro denominata, per cui, in corrispondenza dell’evento Dopo l’inserimento, la macro dati da eseguire avrà questo contenuto: EseguiMacroDati     Nome macro tblOrdini.MacroDatiAggiornaGiacenza Parametri     IDProdotto = [tblOrdini].[IDProdotto]     CambiaGiacenza = -[tblOrdini].[QuantitàOrdinata]

Per l’evento Dopo l’aggiornamento useremo questa macro: EseguiMacroDati     Nome macro tblOrdini.MacroDatiAggiornaGiacenza Parametri     IDProdotto = [tblOrdini].[IDProdotto]     CambiaGiacenza = [Precedente].[QuantitàOrdinata]-[tblOrdini].     [QuantitàOrdinata]

E infine, per l’evento Dopo l’eliminazione avremo la seguente macro: EseguiMacroDati     Nome macro tblOrdini.MacroDatiAggiornaGiacenza Parametri     IDProdotto=[Precedente].[ IDProdotto]     CambiaGiacenza =[Precedente].[QuantitàOrdinata]

Semilavorati per programmare   143

Applicazioni gestionali e macro di dati Con quest’ultimo esempio abbiamo visto come si potrebbe utilizzare un pacchetto di macro di dati ben correlate fra loro per ottenere un servizio di indubbio interesse applicativo. Tuttavia, quando si creano applicazioni gestionali, che è il tema di fondo di questo libro, è bene non dare per scontato che se qualcosa si può fare sia opportuno farla. Restando all’ultimo esempio, è senz’altro vero che un ordine di 100 pezzi del prodotto A diminuisce della stessa quantità la disponibilità a magazzino di quel prodotto, ma non è detto che questa diminuzione debba riflettersi subito in modo automatico nella situazione del magazzino merci: ragioni organizzative potrebbero portare a creare con i quantitativi ordinati una tabella di prenotazioni, da confrontare soltanto in un secondo tempo con i dati di giacenza per prendere decisioni più ponderate anche in considerazione di vincoli di riapprovvigionamento o di altri fattori. Le macro di dati sono una importante novità per Access, ma le azioni associate a eventi delle tabelle esistono da sempre nei sistemi di database basati su server, come Oracle o SQL Server di Microsoft, dove sono dette trigger e si usano per lo più per far scattare segnalazioni per chi amministra grandi database o per avviare procedure automatiche di backup quando il numero dei record di particolari tabelle supera una soglia considerata critica. L’introduzione delle macro di dati in Access 2010 fa pensare a una intenzione di Microsoft di rendere più agevole la migrazione delle applicazioni create con Access 2010 verso l’ambiente SQL Server, mettendo a disposizione già in Access i trigger, che nei sistemi di gestione di database basati su server sono strumenti ben noti e usati spesso.

Quando le macro sono indispensabili Le nuove azioni per le macro di interfaccia disponibili in Access 2010 – in particolare quelle che permettono di creare ed eliminare variabili, di eseguire una serie di azioni passo per passo e di intercettare gli errori di run-time – consentono sicuramente di creare macro più flessibili e potenti, ma in un certo modo testimoniano la sconfitta dell’idea originaria, probabilmente troppo generosa e ingenua, che si potessero creare applicazioni soltanto componendo insieme semilavorati di codice, da completare con opportuni argomenti. Se gli argomenti da impostare si complicano al punto da doverli formulare ricorrendo a frammenti di codice scritti in Visual Basic for Applications, tanto vale rinunciare alle macro e scrivere direttamente routine in quel linguaggio, da inserire in un modulo. Il ricorso alle macro è indispensabile in una sola circostanza: per eseguire operazioni in concomitanza con l’avvio di un’applicazione Access. L’impostazione predefinita di Access prevede che, all’apertura di un file database, venga visualizzato il Riquadro di spostamento, dal quale si può accedere a tutti gli oggetti dell’applicazione. Questo modo di operare è il meno adatto per le applicazioni professionali, nelle quali è opportuno che l’operatore non abbia un accesso indiscriminato a tutti gli oggetti, ma soltanto a quelli che sono stati predisposti per utilizzare correttamente l’applicazione.

144  Capitolo 4

Lavorando con l’interfaccia grafica, si possono cambiare le modalità di apertura predefinite, specificando, per un determinato file Access, una serie di opzioni che lo fanno aprire in un modo più consono all’applicazione. Queste opzioni si definiscono in Access 2010 dalla finestra Opzioni di Access, sezione Database corrente, dove si accede a un lungo elenco di possibili impostazioni, intitolato Opzioni del database corrente: nella Figura 4.30 sono riportati due segmenti con le opzioni rilevanti.

Figura 4.30  Le opzioni di personalizzazione del database corrente in Access 2010.

Le impostazioni date nella pagina Opzioni del database corrente diventano operative quando si apre torna ad aprire il file database. Le specifiche che si possono impostare in questo modo sono, per così dire, statiche, nel senso che hanno effetto soltanto sul modo in cui l’applicazione si apre, ma non sul modo in cui si avvia. Se è necessario far eseguire qualcosa all’applicazione non appena viene aperta, bisogna ricorrere a una o più azioni macro, perché sono l’unico tipo di routine che può essere eseguito automaticamente all’apertura di un’applicazione database. Per ottenere questo risultato, l’azione o le azioni devono essere memorizzate in una macro da salvare col nome Autoexec. Per impostazione predefinita, quando Access apre un file database che contiene una macro con questo nome, la esegue non appena ha caricato il file. Combinando, quindi, in modo opportuno le opzioni disponibili nella pagina Opzioni del database corrente con azioni impostate nella macro Autoexec, si può far sì che un’applicazione si attivi subito nel modo più adatto alle esigenze dell’operatore. Proviamo con semplice esempio a vedere il meccanismo in azione.

Semilavorati per programmare   145

La macro Autoexec Nello stesso database di prova che abbiamo utilizzato per provare le variabili, creiamo una macro con il seguente gruppo di azioni: Azione

Argomenti

Valore

RiduciAIcona ApriMaschera

Nome maschera Modalità finestra

frmIndirizzi

Normale

Salviamo la macro col nome Autoexec e chiudiamo il database. Riapriamolo subito dopo e possiamo constatare che il Riquadro di spostamento viene chiuso mentre al centro schermo si apre la maschera frmIndirizzi. Le azioni macro hanno determinato i seguenti effetti. 1. L’azione RiduciAIcona minimizza il Riquadro di spostamento. 2. L’azione ApriMaschera apre la maschera specificata nel suo argomento Nome maschera. Gli stessi risultati che si ottengono con questa semplicissima macro Autoexec si possono ottenere anche specificando il nome della maschera da aprire nella casella di testo Visualizza maschera della pagina Opzioni del database corrente e deselezionando la casella di controllo Visualizza riquadro di spostamento (si veda la Figura 4.30). La macro Autoexec viene eseguita automaticamente quando si apre il file database che la contiene dando il comando di apertura con un clic sul suo nome nella sezione Recente della finestra Backstage di Access 2010. Se, prima di fare clic sul nome del file da aprire, si tiene premuto il tasto Maiusc, la macro Autoexec non viene eseguita e il file Access si apre normalmente. Questo accorgimento non ha effetto, invece, sulle opzioni eventualmente personalizzate nella pagina Opzioni del database corrente. Nelle versioni precedenti di Access era piuttosto agevole creare barre dei menu e degli strumenti personalizzate, servendosi di semplici strumenti dell’interfaccia grafica e stabilire, tramite le opzioni del menu Avvio, quali barre dei menu e degli strumenti personalizzate far comparire nell’applicazione insieme con o al posto di quelle standard; queste scelte si potevano anche attivare con apposite azioni – AggiungiMenu, MostraBarreStrumenti, ImpostaVoceMenu – predisposte nella macro Autoexec. In Access 2007, la sezione Opzioni barra multifunzione e barra degli strumenti della pagina Opzioni del database corrente permetteva di selezionare una barra multifunzione diversa da quella standard, ma a condizione che fosse stata costruita in precedenza, con un’operazione notevolmente complessa e radicalmente nuova per Access, basata sul linguaggio XML. Con Access 2010 siamo tornati alla relativa semplicità delle versioni precedenti la 2007: si può personalizzare la barra multifunzione con semplici comandi dati col mouse nella pagina Personalizzazione barra multifunzione delle Opzioni di Access.

Pannelli di controllo e splash screen Talvolta un’applicazione ha bisogno di eseguire qualche attività preparatoria prima di poter essere utilizzata: collegare tabelle esterne, sullo stesso computer o in rete; eseguire query per aggiornare i dati o creare tabelle di servizio e altro ancora. Queste attività

146  Capitolo 4

possono durare a volte qualche decina di secondi, abbastanza per lasciare sconcertato l’utente, che non vede alcun segno di attività pur avendo aperto l’applicazione. Perplesso per la pausa prolungata, l’operatore potrebbe essere indotto a fare manovre inconsulte, tipo riavviare l’applicazione credendo di non averla avviata, perdendo così ancora altro tempo e rischiando magari di creare scompensi nei collegamenti in corso, che vengono bruscamente chiusi e riaperti. In situazioni di questo genere, chi realizza l’applicazione predispone una schermata particolare, detta in gergo splash screen, che serve a segnalare all’operatore che l’applicazione si è avviata correttamente e che deve pazientare per il tempo necessario a portare a termine le operazioni preliminari. L’esempio più diffuso e noto a tutti di splash screen con queste finalità lo vediamo quotidianamente quando facciamo partire un PC che lavora sotto il sistema operativo Windows. Nello splash screen di tutte le versioni di Windows, una barra colorata scorre in fondo allo schermo per segnalare graficamente che c’è un’attività in corso. Si può creare un effetto splash screen per un’applicazione Access predisponendo una maschera non associata, nella quale si colloca – mediante controlli Immagine o Cornice oggetto non associato – un gradevole elemento grafico. Insieme con la grafica, un controllo Etichetta può presentare una scritta per segnalare che l’applicazione si sta avviando e che tra qualche istante sarà disponibile. Un gruppo di azioni macro associate all’evento Su timer può: •• far chiudere automaticamente la maschera splash screen allo scadere di un intervallo temporale predefinito; •• nascondere la finestra Database e aprire una maschera che funge da pannello di controllo, cioè da centro di smistamento per passare alle varie attività previste per l’applicazione. Riprendendo l’esempio visto nel paragrafo precedente, mettiamo alla prova questo meccanismo. Come prima cosa creiamo una maschera non associata a dati del database, e vi inseriamo un controllo Immagine e un controllo Etichetta. Associamo un’immagine al controllo Immagine selezionandola fra quelle che abbiamo disponibili sul nostro computer. Digitiamo una scritta rassicurante nel controllo Etichetta. Impostiamo le proprietà Formato della maschera eliminando tutti i componenti standard delle maschere associate a record: pulsanti di spostamento, selettore record, pulsanti di ingrandimento e riduzione e quant’altro, in modo da avere soltanto una cornice e un titolo. Impostiamo inoltre su Sì la proprietà Centratura automatica. Nella scheda Evento, digitiamo mcrApertura nella casella Su timer e scriviamo 3000 nella casella Intervallo timer. Salviamo la maschera col nome frmSplash. Il risultato del nostro lavoro, in visualizzazione Struttura, è riprodotto nella Figura 4.31. A questo punto modifichiamo la macro Autoexec, in modo che l’ultima azione sia la seguente: ApriMaschera

Nome maschera Modalità finestra

frmSplash

Normale

Semilavorati per programmare   147

Figura 4.31  La struttura di una semplice maschera splash screen.

Creiamo poi una nuova macro, con le seguenti azioni: Azione

Argomenti

Valore

Chiudi

Tipo oggetto Nome oggetto Salva Nome maschera Modalità finestra

Maschera

ApriMaschera

frmSplash

No frmIndirizzi

Normale

Salviamo la macro col nome mcrApertura, chiudiamo il database e riapriamolo. 1. Per effetto della macro Autoexec, viene ridotta a icona la finestra Database e aperta la maschera frmSplash, che rimane visibile per tre secondi (come conseguenza del valore 3000 impostato per la proprietà evento Intervallo timer, che si esprime in millisecondi). 2. Scaduto l’intervallo così definito, viene eseguita la macro mcrApertura, associata all’evento Su timer (che si verifica quando il valore di Intervallo timer arriva a zero). 3. La macro chiude la maschera frmSplash e apre la maschera frmIndirizzi. Diversamente dalle maschere splash screen, che hanno soltanto lo scopo di segnalare che c’è un’attività in corso, le maschere chiamate convenzionalmente pannelli di controllo o

148  Capitolo 4

anche pannelli comandi hanno una funzione attiva, servono per orientare l’operatore fra le possibili attività che l’applicazione mette a disposizione. Anche un pannello di controllo può essere avviato in fase iniziale da una macro Autoexec, eventualmente subito dopo la chiusura di una maschera splash screen. Un esempio di pannello di controllo è riportato nella Figura 4.32, ripreso dall’applicazione dimostrativa Northwind.

Figura 4.32  La maschera che funge da pannello di controllo nell’applicazione Northwind.

Nelle versioni di Access precedenti la 2007 esistevano composizioni guidate per creare pannelli comandi, piuttosto laboriose da usare e che generavano pannelli/maschere decisamente scadenti. In Access 2007 e ancor più in Access 2010 lo strumento è decisamente migliorato, anche se consente di generare soltanto pannelli comandi molto spartani: diamo qualche indicazione su questo nuovo strumento e su altri aspetti della personalizzazione dell’interfaccia utente nell’Appendice B.

Convertire le macro Le macro, quindi, possono essere comode per creare rapidamente azioni da correlare con eventi, introducendo in un file database i primi rudimenti di codici di programma necessari per ottenere, se non un’applicazione professionale, almeno un discreto prototipo. Anche se è relativamente facile impostare macro combinando in modo opportuno gruppi di azioni, creare macro non è poi un’operazione banale e di tutto riposo, soprattutto quando si vogliono ottenere risultati di una certa complessità, come dimostra il gruppo di azioni che forma la complessa macro Elenco telefonico clienti dell’applicazione Northwind.

Semilavorati per programmare   149

Viene quindi spontanea la domanda se non esista un meccanismo per trasformare senza troppa fatica una serie di azioni macro in routine VBA, mettendo così a frutto l’investimento intellettuale impegnato nel costruire le macro. Il meccanismo esiste ed è disponibile mediante una semplice procedura interattiva, che viene eseguita con lievi e irrilevanti differenze in tutte le versioni di Access. Vediamo come funziona applicandolo a una macro esistente. 1. Col comando Crea/Macro e codice/Macro creiamo una macro elementare composta con la sola azione FinestraMessaggio, che salviamo col nome mcrMacroDiProva. Eseguendola otteniamo il risultato che si può osservare nella Figura 4.33. 2. Apriamo la macro in visualizzazione Struttura e diamo il comando Strumenti macro/ Progettazione/Strumenti/Converti macro in Visual Basic. 3. Compare una finestra di dialogo intitolata Converti macro: xxx, dove xxx è il nome della macro da convertire: mcrMacroDiProva, nel nostro caso. 4. Abilitiamo tutte e due le caselle di controllo: Aggiungi gestione errori alle funzioni generate e Includi commenti macro. Dopo il clic sul pulsante Converti, notiamo un po’ di movimento e poco dopo una finestra di messaggio ci conferma che l’operazione è andata a buon fine. La Figura 4.33 mostra i passi salienti di questa operazione.

Figura 4.33  La conversione di una macro in Visual Basic.

Convertire le macro incorporate Le macro incorporate in una maschera non sono elencate nella sezione Macro del Riquadro di spostamento, però è possibile convertirle quando la maschera è aperta in visualizzazione Struttura, facendo clic sullo stesso comando che vediamo nella figura precedente, che in quel contesto presenta la didascalia Converti macro della maschera in Visual Basic invece di Converti macro in Visual Basic.

150  Capitolo 4

Nella sezione Moduli del Riquadro di spostamento ora si trova un nuovo modulo, intitolato Macro mcrMacroDiProva convertita. Aprendolo in visualizzazione Struttura possiamo esaminarne il contenuto, che è il seguente: '-----------------------------------------------------------' mcrMacroDiProva ' '-----------------------------------------------------------Function mcrMacroDiProva() On Error GoTo mcrMacroDiProva_Err     ' Azione macro di prova     Beep     MsgBox "Eccomi qua, sono una macro!", vbInformation, "Macro di prova" mcrMacroDiProva_Exit:     Exit Function mcrMacroDiProva_Err:     MsgBox Error$     Resume mcrMacroDiProva_Exit End Function

La macro era strutturata nel modo seguente: Azione

Commento

FinestraMessaggio

Azione macro di prova

Argomento

Valore

Messaggio

Eccomi qua: sono una macro!

Segnale acustico Tipo Titolo

Sì Informazione Macro di prova

La conversione ha creato una routine VBA di tipo Function, che usa le istruzioni: •• Beep, per emettere il segnale acustico, e •• MsgBox, per presentare il messaggio. Gli argomenti di MsgBox sono gli stessi dell’azione FinestraMessaggio, l’unica differenza è data dal ricorso alla costante vbInformation, che serve per caratterizzare il tipo di finestra in cui compare il messaggio. Avendo scelto entrambe le opzioni disponibili nella finestra di dialogo Converti (si veda sopra, al punto 4), nella funzione è stato trasferito il commento predisposto nella struttura della macro e sono stati aggiunti alcuni enunciati che servono per la gestione degli errori. La macro è ancora presente nel file database. Per usare al suo posto la funzione VBA in cui è stata convertita, bisogna richiamare la funzione. Questo si può fare creando un pulsante di comando in una maschera e digitando nella casella corrispondente all’evento

Semilavorati per programmare   151

Su clic il codice che segue: = mcrMacroDiProva()

che richiama la routine VBA invece della macro: sono essenziali il segno di uguale all’inizio e la coppia di parentesi tonde in fondo. Eseguiamo queste semplici operazioni e modifichiamo l’enunciato VBA della funzione, in modo che si presenti come nella riga qui sotto: MsgBox "Eccomi qua: ora sono una funzione!", vbInformation, _     "Azione FinestraMessaggio convertita"

Aprendo la maschera alla quale è stato aggiunto un pulsante di comando con il richiamo alla routine Function mcrMacroDiProva(), un clic sul pulsante produce un risultato simile a quello riportato nella Figura 4.34.

Figura 4.34  La macro convertita in funzione e col testo modificato.

Gli enunciati per la gestione degli errori creati automaticamente nella funzione che sostituisce la macro sono, in questo caso, ridondanti: è molto improbabile che si verifichi un errore proprio nell’esecuzione di un’istruzione MsgBox, però è utile esaminarli per capirne la logica, utilizzabile in contesti più complessi. Diamo quindi un’occhiata a questi enunciati. Subito dopo il primo enunciato, che identifica la routine, troviamo On Error GoTo mcrMacroDiProva_Err

Questo enunciato predispone le cose in modo che, se si dovesse verificare un errore di qualsiasi natura, l’esecuzione della routine passerebbe agli enunciati che si trovano dopo il segnaposto mcrMacroDiProva_Err:. In una routine VBA un segnaposto è una qualunque stringa di caratteri chiusa con il carattere due punti (:). Subito dopo il segnaposto, si trovano gli enunciati che gestiscono l’errore: •• MsgBox Error$  Fa uscire una finestra di messaggio con la descrizione standard dell’errore. •• Resume mcrMacroDiProva_Exit  Riprende l’elaborazione dal segnaposto indicato.

152  Capitolo 4

Il segnaposto mcrMacroDiProva_Exit: è seguito dall’istruzione Exit Function

che chiude definitivamente l’esecuzione della funzione. Se non si verificano errori, dopo l’esecuzione delle istruzioni Beep e MsgBox il programma prosegue alla riga successiva, incontra il segnaposto mcrMacroDiProva_Exit:, che viene ignorato, ed esegue l’istruzione Exit Function, chiudendo così la funzione prima di arrivare agli enunciati per la gestione degli errori.

L’erede delle azioni macro: l’oggetto DoCmd Se provassimo a convertire la macro Elenco telefonico clienti dell’applicazione Northwind, un esame del modulo Macro Elenco telefonico clienti convertita, che verrebbe generato dalla conversione automatica, metterebbe in evidenza un enunciato che ricorre parecchie volte e che ha la forma DoCmd, seguito da un punto (.) e da una o più stringhe di caratteri. DoCmd in origine era una funzione VBA, ma nelle versioni di Access dalla 97 in poi è un costrutto di programmazione particolare, un oggetto. Un oggetto, come vedremo meglio nei prossimi capitoli, è un elemento software, che è identificato da un nome, contiene un programma ed è a disposizione di chi fa sviluppo. Inserire il nome di un oggetto in una routine significa attivare il programma interno all’oggetto nel momento in cui l’esecuzione della routine arriva alla riga che contiene il nome dell’oggetto. Ogni oggetto dispone di uno o più metodi. Facendo seguire al nome di un oggetto un carattere punto e subito dopo il nome di uno dei suoi metodi, l’oggetto si attiva ed esegue il metodo indicato. Semplificando al massimo, si può dire che un metodo è un’azione. Nel caso specifico, i metodi dell’oggetto DoCmd sono tutte le azioni previste per le macro. Analogamente alle azioni delle macro, i metodi di DoCmd prevedono l’indicazione di argomenti, che si specificano scrivendoli sulla stessa riga in cui viene invocato l’oggetto (si usa spesso il termine suggestivo “invocare” per indicare l’attivazione di un oggetto). Per non appesantire troppo l’esposizione, abbiamo riepilogato metodi e argomenti di DoCmd nell’Appendice A, “Le azioni macro e l’oggetto DoCmd”.

Parte III

Gli strumenti di sviluppo In questa parte •

Capitolo 5 Gli oggetti



Capitolo 6 L’ambiente di sviluppo



Capitolo 7 Lavorare con VBA



Capitolo 8 Gestire maschere e controlli con VBA



Capitolo 9 Lavorare con i DAO



Capitolo 10 Lavorare con gli ADO



Capitolo 11 La gestione degli errori



Capitolo 12 Altri strumenti

Capitolo 5

Gli oggetti

Per costruire in modo efficace ed efficiente applicazioni professionali utilizzando Access è necessario avere le idee molto chiare sulla struttura di questo strumento di sviluppo applicativo e sui vari modi che si hanno a disposizione per metterla al proprio servizio. La conoscenza della struttura è utile anche quando ci si limita a realizzare applicazioni usando l’interfaccia grafica e ricorrendo occasionalmente alla azioni macro per gestire eventi di mouse e di tastiera. Tale conoscenza diventa essenziale quando si devono gestire eventi complessi e correlati ed è necessario predisporre meccanismi di intercettazione e controllo di errori degli utenti/operatori o del sistema. In questi casi le azioni macro sono poco adeguate e si deve ricorrere ai linguaggi di programmazione interni ad Access, che sono il Visual Basic for Applications (in sigla VBA) e lo Structured Query Language, noto anche come SQL. Questi linguaggi non sono particolarmente complicati, anzi, sono stati concepiti originariamente proprio per semplificare l’insegnamento della programmazione (il BASIC, dal quale deriva il VBA) o per esplorare il contenuto delle tabelle di un database con istruzioni espresse in un linguaggio molto simile all’inglese parlato (SQL). Le difficoltà vengono dal fatto che, quando si scrivono programmi con VBA o si formulano query con SQL, le istruzioni e le variabili agiscono molto spesso su elementi interni di Access, chiamati genericamente oggetti, il cui utilizzo impone il ricorso a tecniche nient’affatto semplici e intuitive. In questa parte del libro ci proponiamo di far chiarezza sul modo in cui si utilizza il linguaggio

In questo capitolo •

Che cos’è un oggetto



Oggetti Access



Oggetti DAO



Oggetti ADO



Oggetti Visual Basic



Come si usano gli oggetti

156  Capitolo 5

VBA nello specifico contesto degli oggetti di Access. Prima di affrontare il Visual Basic for Applications, quindi, è opportuno esplorare gli oggetti sui quali si basa Access per capire come sono fatti e a che cosa servono.

Che cos’è un oggetto Non appena si avvia un personal computer che lavora sotto una qualunque versione di Windows, la prima cosa che salta all’occhio è un elemento grafico che si muove sullo schermo in stretta corrispondenza con gli spostamenti del mouse sul piano della scrivania. Quel che si vede è chiamato puntatore del mouse. Spostando il mouse sul piano di lavoro si generano impulsi elettrici, che vengono trasformati in segnali codificati e inviati a un programma. Questo programma interpreta quei segnali codificati come coordinate dello schermo e fa apparire un puntatore nella posizione definita da quelle coordinate. Uno spostamento del mouse provoca una variazione delle coordinate e il programma, disciplinatamente, visualizza il puntatore nella posizione corrispondente alle nuove coordinate. Il programma che acquisisce i valori delle coordinate e visualizza di volta in volta il puntatore del mouse in un punto corrispondente dello schermo è un oggetto, vale a dire un insieme di istruzioni che viene eseguito generando un output predefinito (la freccia bianca) in base a un input anch’esso predefinito (il flusso di segnali che arriva dal mouse). Anche gli elementi strutturali di Windows, le finestre, all’interno delle quali vengono visualizzati cartelle, documenti, immagini e quant’altro, sono oggetti.A stretto rigore, sono oggetti i programmi che fanno aprire le finestre e visualizzano il puntatore del mouse, ma per semplicità si conviene di chiamare oggetti anche quello che viene generato da tali programmi. Tutto ciò che è visibile in una schermata Windows è un oggetto, anche se non tutti gli oggetti contenuti in Windows sono visibili sullo schermo. In Access sono oggetti tutti i componenti di un database, a cominciare dal database stesso. Sono quindi oggetti le tabelle, le query, le maschere, i report, le macro e i moduli. E sono oggetti tutti i controlli che si trovano nelle maschere e nei report: caselle di testo, caselle di riepilogo, pulsanti di comando e così via. Sono oggetti anche il pulsante File, la barra di accesso rapido, la barra multifunzione e i singoli pulsanti che questa contiene. La caratteristica più significativa di un oggetto è che può svolgere le attività per le quali è programmato indipendentemente dal contesto in cui si trova, mantenendo sempre la sua specificità. L’oggetto puntatore del mouse è un caso tipico: quale che sia il contesto in cui si trova – il desktop di Windows, la finestra di un’applicazione, un’immagine – rileva sempre le variazioni dinamiche delle coordinate e si sposta di conseguenza. Quel che cambia, occasionalmente, è l’aspetto del puntatore, che di volta in volta può essere una punta di freccia, una barretta verticale, una freccia a due punte e così via. Nel caso del puntatore del mouse, la modifica dell’aspetto è indotta da un input che riceve dall’oggetto sul quale si trova. Questa è un’altra caratteristica significativa degli oggetti: quasi sempre sono in grado di interagire con altri oggetti con i quali entrano in contatto, reagendo a input che ricevono da quegli oggetti e inviando loro segnali o flussi di dati.

Gli oggetti   157

Metodi e proprietà Tutti gli oggetti sono caratterizzati da metodi e proprietà. Si chiamano metodi le operazioni che un oggetto può eseguire, mentre le proprietà sono le caratteristiche distintive di un oggetto (il modo in cui si presenta, per esempio) e le informazioni che contiene. Molti oggetti hanno, per esempio, una proprietàVisibile, che può avere valore logicoVero o Falso. Se questa proprietà è impostata su Vero, l’oggetto è visibile, altrimenti non compare nel contesto in cui si trova, cosa che non gli impedisce di essere presente ed eventualmente attivo in quello stesso contesto. Vi sono oggetti che ne contengono altri e per questo hanno una proprietà Conteggio, che enumera gli elementi che hanno al loro interno. Certe proprietà si possono impostare da programma, per esempio il colore o la visibilità, altre invece sono di sola lettura, cioè si possono conoscere ma non è consentito modificarle. Per quanto riguarda i metodi, con una metafora un po’ ardita possiamo immaginare gli oggetti di Access come tante marionette, che giacciono adagiate in una grande cesta. Un burattinaio, l’utente/realizzatore, può animare una di queste marionette, tirandola per i fili che ne comandano i movimenti, e farla agire manovrando opportunamente quei fili. Ogni marionetta può eseguire soltanto le azioni (i metodi) che le sono consentite dalla sua natura: un cavallo può andare al passo o trottare, ma non può tirare di spada, un metodo che è specifico del feroce Saladino o di D’Artagnan. Fuor di metafora, i metodi vengono attivati da opportuni input, che specificano quale metodo si vuole utilizzare e in quale contesto. Per esempio, certi oggetti contenitori hanno un metodo Aggiungi: attivandolo si crea un nuovo elemento entro il contenitore. Ciascun oggetto ha un nome generico, che lo distingue da tutti gli altri.Tutti gli oggetti risiedono in un ideale contenitore. Nel momento in cui si vuole usare un oggetto, se ne crea una copia, detta istanza, ricavandola dal contenitore in cui si trova e dando un nome specifico all’istanza. Per esempio, si può creare un’istanza dell’oggetto Casella di testo e darle il nome txtAlfa. L’istanza così creata ha tutti i metodi e le proprietà dell’oggetto Casella di testo da cui deriva. Per usare un metodo di un particolare oggetto, bisogna creare un’istanza dell’oggetto stesso, darle un nome e scrivere il nome del metodo subito dopo il nome dell’istanza. Alcuni oggetti non hanno metodi, ma soltanto proprietà, in quanto ciò che fanno, il loro metodo, è predeterminato e dipende da un altro oggetto, di livello superiore, dal quale tali oggetti dipendono gerarchicamente. Fra gli oggetti che compongono Access esiste, infatti, una rigida gerarchia, che condiziona sia il modo in cui si creano le istanze dei singoli oggetti, sia il loro utilizzo. Non è possibile, per esempio, accedere a un controllo di una maschera senza accedere prima all’oggetto maschera in cui si trova quel controllo. “Accedere” non vuol dire necessariamente “aprire” e neppure “rendere visibile”: si può agire su un oggetto che dipende gerarchicamente da un altro senza aprire né l’uno né l’altro; quello che non si può fare è utilizzare un metodo o una proprietà di un oggetto senza specificare il superiore gerarchico, ovvero il genitore, dell’oggetto stesso. Gli oggetti sono disponibili isolatamente o in raggruppamenti, detti in inglese collections. Nella versione italiana di Access si è scelto di tradurre collection con insieme e ci atterremo a questa convenzione, anche se in italiano normalmente il termine “insieme” si usa per tradurre la parola inglese set.

158  Capitolo 5

I nomi degli oggetti sono, ovviamente, in inglese e devono essere scritti esattamente come sono, non omettendo mai l’eventuale “s” finale, che indica il plurale e caratterizza i nomi degli insiemi. L’oggetto Application, per esempio, contiene un insieme Forms, formato da singoli oggetti Form. Un oggetto Form contiene un insieme Controls, che a sua volta può contenere singoli oggetti Control, secondo lo schema illustrato dalla Figura 5.1. (Incidentalmente, per chi non lo ricordasse, i forms sono le maschere.)

Figura 5.1  Un esempio di relazione gerarchica fra oggetti.

Le librerie di oggetti Tutti gli oggetti utilizzabili in Access sono contenuti in file particolari, detti in inglese object library. Library, come è noto, vuol dire biblioteca e di questo si tratta: biblioteche di oggetti a disposizione di un ipotetico lettore. Una svista grossolana, che risale ai primi tempi dello sviluppo dell’informatica in Italia, ha portato a usare la parola libreria, invece di biblioteca, per tradurre library. L’errore si è radicato nell’uso da un tal numero di anni che non avrebbe più senso, ormai, tentare di correggerlo. Parliamo quindi di librerie di oggetti per riferirci alle object libraries. Fisicamente, sono file di sistema riconoscibili per l’estensione .olb, che si trovano nella cartella Windows\System o in Programmi\Microsoft Office\Office.A volte l’estensione del nome di un file di libreria non è .olb, ma .dll, .tlb o altro ancora. Access utilizza normalmente quattro di questi file, che vengono trasferiti nel sistema insieme con numerosi altri dello stesso genere quando si installa Access o Microsoft Office su un computer. Si può vedere l’elenco di questi file creando un nuovo modulo col comando Crea/Macro e codice/Modulo, ottenendo in questo modo l’apertura della finestra dell’applicazione interna ad Access chiamata Editor di Visual Basic. Questa finestra è strutturata con le tradizionali

Gli oggetti   159

barre dei menu e degli strumenti. Dal menu Strumenti, il comando Riferimenti fa aprire la finestra di dialogo Riferimenti che è riprodotta nella Figura 5.2.

Figura 5.2  L’elenco delle principali librerie di oggetti utilizzate da Access 2010.

Come avremo modo di vedere più avanti, a queste librerie di oggetti fondamentali se ne possono aggiungere altre, selezionandole nel lungo elenco che si vede nella casella di riepilogo Riferimenti disponibili; l’impostazione di ulteriori riferimenti consente di lavorare con oggetti addizionali, che forniscono funzionalità complementari a un’applicazione Access.

Oggetti Access Come sappiamo, un file database Access – identificato dall’estensione .mdb nelle versioni precedenti ad Access 2007 e dall’estensione .accdb in quelle successive – può contenere tabelle, query, maschere, report, macro e moduli: tutti questi elementi sono oggetti e si possono manipolare con opportune istruzioni del linguaggio VBA che agiscono sugli oggetti. Gli oggetti contenuti in un file Access prendono collettivamente il nome di oggetti di Access e sono organizzati in due schemi gerarchici diversi, a seconda che siano aperti o chiusi. Questi due schemi gerarchici hanno entrambi come capostipite l’oggetto Application, che identifica l’applicazione attiva e contiene: 1. gli insiemi Forms, Reports e Modules e l’oggetto Screen; 2. gli oggetti CurrentData, CurrentProject, CodeProject, CodeData e DoCmd.

160  Capitolo 5

Pagine e Progetti Nelle versioni da Access 2000 ad Access 2003 si potevano utilizzare oggetti particolari detti pagine di accesso ai dati. Diversamente dagli altri oggetti, tutti interni al file Access, le pagine di accesso ai dati potevano esistere come file memorizzati all’esterno del file .mdb in cui venivano costruite. In Access 2010 tali oggetti particolari non sono più disponibili, ma è possibile lavorare con Access 2010 su un file creato con un versione precedente che fa riferimento a pagine di accesso ai dati, a condizione che i file corrispondenti alle pagine esistano e siano memorizzati in un percorso noto all’applicazione. Un’altra funzionalità delle versioni precedenti, i Progetti Access (memorizzati in file riconosciuti da Access con estensione .adp) non è più supportata da Access 2010, che però continua a disporre di strumenti specifici per i file .adp.

Nel primo gruppo sono comprese le maschere, i report e i moduli aperti, nonché, nell’oggetto Screen, l’oggetto selezionato (maschera, report o controllo in una maschera o in un report aperto), cioè quello che ha il focus, come si dice nel gergo di Access. I primi quattro oggetti del secondo gruppo sono contenitori di insiemi, che aggregano per tipo tutti gli oggetti del database corrente, che siano aperti oppure chiusi; gli insiemi e gli oggetti formano la gerarchia riepilogata nella Tabella 5.1. Tabella 5.1  Gli oggetti dipendenti da Application. L’oggetto:

Contiene gli insiemi:

CurrentProject

AllForms AllMacros AllModules AllReports AllDataAccessPages

CurrentData

AllTables AllQueries AllStoredProcedures AllViews AllFunctions AllDataBaseDiagrams

Che contengono informazioni su: Tutte le maschere Tutte le macro Tutti i moduli Tutti i report Tutte le pagine di accesso ai dati Tutte le tabelle del database Tutte le query del database Tutte le stored procedure Tutte le visualizzazioni Tutte le funzioni Tutti i diagrammi di database

L’oggetto DoCmd dipende direttamente da Application, non contiene altri oggetti o insiemi e si utilizza per eseguire azioni macro tramite codice VBA: è descritto analiticamente nell’Appendice A. In Access 2010 sono presenti anche alcuni oggetti che non dipendono da Application; essi sono: •• gli oggetti MacroError e TempVar, con i quali si gestiscono gli errori e le variabili temporanee delle macro; •• l’oggetto Printers, che contiene oggetti Printer e non è associato direttamente ad Application, ma a una sua proprietà.

Gli oggetti   161

Insiemi particolari L’oggetto CurrentProject contiene un insieme AllDataAccessPages, che fa riferimento a tutte le pagine di accesso ai dati del database. Nell’oggetto CurrentData sono contenuti gli insiemi: AllStoredProcedures, AllViews, AllFunctions (che fanno riferimento a componenti di applicazioni realizzate con il sistema Microsoft SQL Server) e AllDataBaseDiagrams, che si riferisce ai Progetti Access. Dal momento che nel libro non ci occuperemo di SQL Server né dei Progetti Access, trascuriamo questi insiemi e gli oggetti che ne fanno parte. Non ci occupiamo neppure delle caratteristiche degli oggetti CodeProject e CodeData, che consentono di creare e gestire raccolte di codici di programma e di oggetti personalizzati.

La gerarchia degli oggetti Access è riprodotta nella Figura 5.3. Gli oggetti Access – chiamati genericamente AccessObject in VBA – permettono di accedere, mediante routine VBA, agli elementi corrispondenti del file database, per visualizzarli, conteggiarli, ricavarne dati, ma non consentono di modificare i contenuti o le strutture di tabelle, query, maschere, report, macro e moduli, né è possibile usarli per aggiungere o eliminare elementi di struttura: per farlo bisogna agire con il VBA su uno degli altri due sistemi di oggetti disponibili in Access, chiamati DAO e ADO.

Oggetti DAO La sigla DAO viene dalle iniziali delle parole Data Access Objects, vale a dire oggetti per l’accesso ai dati. Questi oggetti sono stati gestiti per molto tempo da un sottosistema autonomo, chiamato Microsoft Jet Database Engine, che, come lascia intendere il suo nome, è un motore per database. Si chiama motore o engine uno strumento applicativo specializzato per eseguire una determinata famiglia di operazioni. Il motore Jet serviva per accedere a strutture di database e poteva essere utilizzato da Access come da altre applicazioni Microsoft o di terze parti. Per questa sua caratteristica di strumento generalizzato al servizio di più ambienti applicativi, il motore Jet stava per conto suo, non era una semplice libreria di oggetti, ma qualcosa di più complesso. Con l’uscita del sistema operativo Windows 2000, il Jet è stato integrato in Windows, dove ha perso la sua individualità separata e le sue funzionalità sono state trasferite a un nuovo e più potete motore per database chiamato Access Database Engine, in sigla ACE. La differenza fondamentale fra gli oggetti DAO e gli oggetti Access sta nel fatto che con i DAO si può accedere anche a oggetti di database non aperti, ed è possibile crearne di nuovi o modificare quelli esistenti, nel database corrente o in un altro.Vedremo più avanti come si lavora con i DAO, qui ci limitiamo a presentarne la gerarchia (Tabella 5.2).

162  Capitolo 5

Figura 5.3  La gerarchia degli oggetti Access.

Gli oggetti   163 Tabella 5.2  Gli oggetti DAO. Oggetto Connection Container Database DBEngine Document Error Field Index Parameter Property QueryDef Recordset Relation TableDef Workspace

Descrizione Connessione di tipo ODBC con un database in rete Informazioni sulla protezione, ai fini della sicurezza, di gruppi di oggetti del database Database aperto Il motore ACE per database Informazioni sulla protezione, ai fini della sicurezza, di singoli oggetti del database Informazioni sugli errori di accesso ai dati Campo in una tabella, query, set di record, indice o relazione Indice di tabella Parametro di query Proprietà di un oggetto Query in un database Gruppo di record definiti da una tabella o da una query Relazione fra due campi di tabelle o di query Tabella in un database Sessione attiva di Microsoft ACE

In Access 2010 i DAO comprendono anche gli oggetti Field2, Recordset2 e ComplexType, che servono per gestire i campi multi valore. Dal momento che non intendiamo usare questo genere di campi nelle nostre applicazioni, non stiamo neanche a elencare le caratteristiche di questi nuovi oggetti. Ogni oggetto elencato nella Tabella 5.2 appartiene a un insieme che ha lo stesso nome, ma al plurale (QueryDefs per gli oggetti QueryDef, Databases per l’oggetto Database e così via), a eccezione di DBEngine, che è un oggetto indipendente e sta al vertice della gerarchia, come si può vedere nella Figura 5.4.

Figura 5.4  La gerarchia degli oggetti DAO.

164  Capitolo 5

Gli oggetti DAO sono in tutto 15 (escludendo dal conteggio quelli usati per i campi multi valore): per non appesantire troppo lo schema grafico riportato nella Figura 5.4, non abbiamo rappresentato l’insieme Connections, che contiene gli insiemi QueryDefs e Recordsets eventualmente presenti in un database connesso mediante un particolare protocollo di connessione chiamato Open Database Connectivity, in sigla ODBC (alla connessione con database in rete è dedicato un intero capitolo). Per la stessa ragione, non è visualizzato nello schema della gerarchia dei DAO il fatto che ciascun oggetto DAO contiene un insieme Properties, formato da oggetti Property, che rappresentano le proprietà disponibili in ogni singolo oggetto. Se qualche lettore, arrivato a questo punto, avvertisse un leggero giramento di testa, non deve preoccuparsi: è normale, quando si incontra per la prima volta il mondo dei DAO. Con un po’ di pratica, come vedremo nei prossimi capitoli, si riesce ad aver ragione di questa gerarchia che, al primo impatto, mette soggezione a chiunque.

Oggetti ADO Anche ADO è una sigla, sta per ActiveX Data Objects e si riferisce a una nuova libreria di oggetti disponibile soltanto nelle versioni di Access dalla 2000 in poi. Per capire di che cosa si tratti occorre tornare un momento al tema dei DAO e del Microsoft Access Database Engine (ACE). Col motore ACE e i DAO è possibile accedere a una struttura di dati che abbia la forma di un database: un altro database Access, per esempio, o un database di altri produttori di software. Come fare, però, quando i dati che interessano non hanno struttura di database? Non è un problema da poco: esistono innumerevoli archivi di dati, su computer mainframe o in altri contesti, che contengono informazioni di grande interesse gestionale, ma che non sono stati organizzati secondo la logica dei database. La risposta di Microsoft a questo problema passa attraverso tre strumenti di programmazione che si chiamano COM, ActiveX e OLE DB. Procediamo con ordine, un passo alla volta, perché il percorso è tortuoso e non poco accidentato. COM viene da Component Object Model ed è una specifica di programmazione proposta da Microsoft per costruire, con qualunque linguaggio di programmazione, componenti software che possano lavorare sotto Windows. Con le tecniche COM si possono costruire oggetti o controlli ActiveX, che si inseriscono in una pagina web, dove vivono di vita propria eseguendo operazioni a comando o in ciclo continuo. OLE è una sigla che sta per Object Linking and Embedding, una tecnica messa a punto da Microsoft parecchio tempo fa e continuamente arricchita di funzionalità, mediante la quale è possibile incorporare (embed) o collegare (link) un componente software (un disegno, per esempio, ottenuto con un programma di grafica) in un altro (un documento Word o un foglio di lavoro Excel). OLE DB è un ulteriore passo avanti nella filosofia OLE, ed è una tecnica che permette di applicare il meccanismo OLE a strutture di database. Infine, capitalizzando su COM, ActiveX e OLE DB, ecco che si arriva agli ADO, ovvero gli ActiveX Data Objects, una serie di strumenti per accedere, da programma, a fonti di dati esterne (collegate in rete in una struttura client/server) che non si prestano, per come sono fatte, a essere raggiunte e utilizzate mediante il motore ACE e i suoi DAO.

Gli oggetti   165

Mediante istruzioni del linguaggio VBA si accede ai dati di un database Access o di altro tipo utilizzando metodi e proprietà di oggetti che possono essere DAO o ADO. La logica di utilizzo degli ADO è alquanto diversa da quella dei DAO, come avremo modo di vedere approfonditamente nei prossimi capitoli: qui basterà ricordare che uno dei vantaggi di ADO rispetto a DAO è una minore complessità strutturale. Lo schema gerarchico degli ADO, riportato nella Figura 5.5, è molto più snello, perché si articola in soli quattro oggetti di base.

Figura 5.5  Il modello degli oggetti ADO è più semplice di quello dei DAO.

Oggetti Visual Basic La libreria di oggetti Visual Basic in Access 97 metteva a disposizione soltanto tre oggetti: Collection, Debug ed Err. Nelle versioni successive sono stati aggiunti altri insiemi e oggetti, per cui gli strumenti complessivamente disponibili sono quelli riepilogati nella Tabella 5.3. Gli oggetti VBA, diversamente dagli oggetti Access e DAO, non sono in relazione gerarchica fra loro. Tabella 5.3  Gli oggetti Visual Basic for Applications. Oggetto

Descrizione

Insieme Drives

Contiene gli oggetti Drive (unità a disco)

Insieme Files Insieme Folders Insieme UserForms

Contiene gli oggetti File Contiene gli oggetti Folder (cartelle) Contiene gli oggetti UserForm (form in documenti Word o Excel) (continua)

166  Capitolo 5 Tabella 5.3  Gli oggetti Visual Basic for Applications. (segue) Oggetto Collection Debug Dictionary Drive Err File FileSystemObject Folder TextStream UserForm

Descrizione Raccolta di oggetti definita dall’utente Rappresenta la finestra Debug nell’Editor di Visual Basic Oggetto specializzato per creare coppie di stringhe Rappresenta una unità a disco nell’insieme Drives Contiene informazioni sugli errori di run-time Rappresenta un file nell’insieme Files Consente di accedere al sistema dei file Rappresenta una cartella contenuta nell’insieme Folders Rappresenta un flusso di testo entro un file aperto. Rappresenta un form definito dall’utente nell’insieme UserForms

Come si usano gli oggetti Gli oggetti si possono creare, modificare e gestire dall’interfaccia grafica, con semplici operazioni interattive mediante mouse e tastiera. Per utilizzarli da programma, bisogna scrivere routine in Visual Basic for Applications, facendo riferimento agli oggetti in base a una convenzione sintattica che varia a seconda che ci si riferisca a un oggetto indipendente o a un oggetto che appartiene a un insieme. Per gli oggetti che non fanno parte di insiemi o collezioni che dir si voglia, il riferimento si fa scrivendo semplicemente il nome generico dell’oggetto, quindi, scrivendo: Application

ci si riferisce all’oggetto Application, che è un oggetto indipendente. Quando un oggetto appartiene a un insieme, dobbiamo specificare l’insieme al quale appartiene e l’oggetto che interessa, separando i due nomi con l’operatore punto esclamativo (!), in questo modo: Forms!Prodotti QueryDefs![Prodotti sotto scorta]

Nella prima riga si individua la maschera Prodotti, che appartiene all’insieme Forms, mentre nella seconda riga si individua la query Prodotti sotto scorta, che fa parte dell’insieme QueryDefs. Si noti che, quando il nome di un oggetto contiene spazi, il riferimento va fatto racchiudendone il nome fra parentesi quadre. Per evitare il fastidio dell’uso delle parentesi quadre, è opportuno non attribuire mai agli oggetti nomi contenenti spazi; quando si ritiene opportuno utilizzare un nome esplicativo composto con più parole, congiungerle con l’iniziale maiuscola, per esempio ProdottiSottoScorta o con un trattino basso (Prodotti_sotto_scorta): si ricordi che il trattino normale, che equivale al segno meno, non è ammesso nei nomi degli oggetti e delle variabili che vi fanno riferimento. È consentita anche una diversa forma di riferimento, con la struttura seguente: Forms("Prodotti") QueryDefs("Prodotti sotto scorta")

Gli oggetti   167

I nomi degli oggetti si possono assegnare a variabili stringa, per cui, se sono state definite due variabili stringa in questo modo: strNomeMaschera = "Prodotti" strNomeQuery = "Prodotti sotto scorta"

il riferimento può essere fatto nel modo che segue: Forms(strNomeMaschera) QueryDefs(strNomeQuery)

Esiste, infine, una terza modalità per far riferimento a un oggetto che appartiene a un insieme. In un insieme gli oggetti hanno un numero indice che inizia da 0 e si incrementa di uno per ogni nuovo oggetto che viene creato in quell’insieme. Se la maschera Prodotti fosse il primo oggetto dell’insieme Forms e la query Prodotti sotto scorta fosse il secondo dell’insieme QueryDefs, le due espressioni che seguono li identificherebbero correttamente: Forms(0) QueryDefs(1)

Va, però, tenuto presente che il numero indice che identifica gli oggetti Access nei loro insiemi non è statico, ma dinamico, cioè dipende dall’ordine col quale vengono aperti, per cui se la maschera Prodotti è stata aperta per prima il suo numero indice nell’insieme Forms è 0, se (mentre è aperta Prodotti) si apre la maschera Categorie, il suo numero indice è 1, ma quando si chiude Prodotti lasciando aperta Categorie questa prende il numero indice 0. In altri termini, è meglio non fare assegnamento sul numero indice quando si lavora con oggetti Access. I numeri indice degli insiemi DAO e ADO, invece, sono statici, cioè non variano con l’ordine col quale vengono utilizzati.

Punto e punto esclamativo L’operatore punto esclamativo si utilizza per concatenare oggetti definiti dall’utente/realizzatore; in altri termini, quello che viene dopo il punto esclamativo è il nome di un oggetto creato nell’applicazione Access: una maschera o un controllo in una maschera. Per riferirsi, invece, a un metodo o a una proprietà di un oggetto, si usa l’operatore punto, per cui l’espressione: Forms!Impiegati!Foto.Visible

indica la proprietà Visible del controllo Foto nella maschera Impiegati, che appartiene all’insieme Forms.

Molti oggetti contengono uno o più insiemi di livello inferiore; fra questi ve n’è di solito uno che rappresenta l’insieme predefinito di quell’oggetto. Per esempio, l’oggetto Form contiene un insieme Controls che è il suo insieme predefinito. È consentito far riferimento

168  Capitolo 5

a un oggetto che appartiene a un insieme predefinito saltando il nome dell’insieme, per cui le espressioni: Forms!Prodotti!Controls!Disponibilità Forms!Prodotti!Disponibilità

si equivalgono. Gli insiemi predefiniti sono quelli indicati nella Figura 5.3 per gli oggetti Access e nella Figura 5.4 per i DAO. Un’ultima, importante, annotazione: dei quattro gruppi di oggetti che abbiamo sinteticamente descritto gli oggetti Access consentono di lavorare sugli elementi dell’interfaccia utente, cioè maschere e report; per lavorare da programma sui dati contenuti in un database bisogna utilizzare gli altri due gruppi di oggetti, i DAO o gli ADO, mentre per lavorare sui file si utilizzano gli oggetti Visual Basic.

Qualche esempio Gli oggetti AccessObject con i loro insiemi Allxxx sono molto semplici da utilizzare e possono dare utili informazioni, come quelle riprodotte nella Figura 5.6.

Figura 5.6  Finestre di messaggio con informazioni su moduli, maschere e tabelle del database dimostrativo Northwind.

Gli oggetti   169

Le tre finestre di messaggio sono state ottenute creando un nuovo modulo in una copia del file del database dimostrativo Northwind, digitando nel modulo le seguenti tre routine in Visual Basic for Applications ed eseguendole una dopo l’altra: Sub ElencaTuttiIModuli()     Dim obj1 As AccessObject, strMessaggio As String     For Each obj1 In Application.CurrentProject.AllModules         strMessaggio = strMessaggio & obj1.Name & vbCrLf     Next obj1     MsgBox "Elenco dei moduli" & vbCrLf & vbCrLf & strMessaggio End Sub

Sub ElencaTutteLeMaschere()     Dim obj1 As AccessObject, strMessaggio As String     For Each obj1 In Application.CurrentProject.AllForms         strMessaggio = strMessaggio & obj1.Name & vbCrLf     Next obj1     MsgBox "Elenco delle maschere" & vbCrLf & vbCrLf & strMessaggio End Sub

Sub ElencaTutteLeTabelle()     Dim obj1 As AccessObject, strMessaggio As String     For Each obj1 In Application.CurrentData.AllTables         strMessaggio = strMessaggio & obj1.Name & vbCrLf     Next obj1     MsgBox "Elenco delle tabelle" & vbCrLf & vbCrLf & strMessaggio End Sub

Tutte e tre le routineVBA hanno la stessa struttura e si differenziano soltanto per l’oggetto e l’insieme Access che utilizzano. Ogni routine definisce una variabile obj1 in modo che possa essere associata con un oggetto AccessObiect e una variabile strMessaggio destinata ad aggregare le informazioni che vengono raccolte percorrendo ciclicamente lo specifico insieme Allxxx richiamato in ciascuna routine. La prima routine richiama l’insieme AllModules dell’oggetto CurrentProject che dipende dall’oggetto Application: Application.CurrentProject.AllModules

Questo richiamo viene fatto all’interno di un costrutto del linguaggio VBA che si chiama For Each. . . Next. Lo esamineremo meglio nel capitolo sul Visual Basic. Qui ci basta sapere che questo costrutto percorre ciascuno degli oggetti obj1 contenuti nell’insieme AllModules e ne ricava la proprietà Name, assegnandola alla variabile strMessaggio. Ogni nuovo valore di Name che viene trovato viene aggiunto a strMessaggio e completato da una costante chiamata vbCrLf, che corrisponde al comando “vai a capo e inserisci una nuova riga”. Al termine dell’esplorazione dell’insieme AllModules, viene eseguita l’istruzione MsgBox, che visualizza nella finestra di messaggio il contenuto della stringa strMessaggio.

170  Capitolo 5

Lo stesso meccanismo di identificazione degli oggetti AccessObject viene utilizzato dalle altre due routine, specificamente: Application.CurrentProject.AllForms

individua le maschere utilizzando l’oggetto CurrentProjects, mentre con: Application.CurrentData.AllTables

si individuano le tabelle mediante l’oggetto CurrentData: con questo oggetto, infatti, si accede ai dati (tabelle e query), mentre con l’oggetto CurrentProject si accede agli elementi di interfaccia di un database, maschere e report, e a quelli che contengono codice VBA (macro e moduli). Si noti che l’elenco delle tabelle contiene quindici nomi che iniziano tutti con le lettere MSys e che non corrispondono alle ben note tabelle di Northwind. Non si tratta di un errore: sono le tabelle di sistema, presenti in tutti i file database di Access, anche in quelli appena creati e vuoti. Queste tabelle esistono, non sono visibili in condizioni normali, ma sono comunque comprese nell’insieme AllTables sul quale ha agito il ciclo For Each. . .Next. Le tre routine del nostro esempio hanno ricavato la proprietà Name di ciascun AccessObject trovato nei rispettivi insiemi Allxxx esaminati. Tutti gli oggetti AccessObject hanno le seguenti proprietà: CurrentView Vale soltanto per maschere e report aperti e restituisce una fra le seguenti costanti intrinseche, che indica in quale modalità di visualizzazione si trova l’oggetto: visualizzazione Foglio dati. •• acCurViewDatasheet visualizzazione Struttura. •• acCurViewDesign •• acCurViewFormBrowse visualizzazione Maschera. visualizzazione Layout. •• acCurViewLayout •• acCurViewPivotChart visualizzazione Grafico pivot. •• acCurViewPivotTable visualizzazione Tabella pivot. visualizzazione Anteprima di stampa. •• acCurViewPreview •• acCurViewReportBrowse visualizzazione Report Se si cerca di ottenere questa proprietà per altri tipi di oggetti o per oggetti chiusi si genera un errore di run-time. DateCreated Data e ora di creazione dell’oggetto. DateModified Data e ora dell’ultima modifica eseguita sull’oggetto. Name Il nome dell’oggetto come compare nella finestra Database. FullName Il nome, completo di percorso, del file che contiene una pagina di accesso ai dati. Per gli altri oggetti questa proprietà è una stringa vuota. IsLoaded Un valore di tipo booleano: True (Vero) se l’oggetto è al momento aperto nell’interfaccia utente, False (Falso) se non lo è. Type Una costante intrinseca che indica il tipo di AccessObject. Può essere: Diagramma di database •• acDiagram acForm Maschera •• Funzione o routine VBA •• acFunction

Gli oggetti   171

•• acMacro Macro Modulo di codice VBA •• acModule •• acQuery Query •• acReport Report •• acServerView Vista su SQL Server Stored procedure su SQL Server •• acStoredProcedure •• acTable Tabella Macro di dati •• acTableDataMacro Costanti intrinseche Molte proprietà possono assumere valori diversi, come nel caso della proprietà Type che vediamo qui sopra. A questi valori corrisponde un codice numerico: al suo posto si può utilizzare una stringa di caratteri prestabilita, che dà una descrizione sintetica del codice ed è più facile da ricordare; questo codice numerico (e la stringa di caratteri che lo rappresenta) si chiama genericamente costante intrinseca o anche costante di enumerazione e nel linguaggio VBA ve ne sono di quattro tipi, per gli oggetti Visual Basic, per gli oggetti Access, per i DAO e per gli ADO, che si distinguono per la coppia di lettere iniziali, che è rispettivamente vb, ac, db e ad.

Oggetti DAO e ADO Gli oggetti Access che abbiamo visto negli esempi precedenti contengono informazioni sulle caratteristiche degli oggetti che si trovano nel database, ma non danno accesso ai dati contenuti in quegli oggetti: per lavorare sui dati con il VBA bisogna utilizzare gli oggetti DAO o gli ADO. In entrambi i modelli di oggetti è presente un oggetto chiamato Recordset, che può essere utilizzato per accedere alle tabelle di un database, quello corrente, aperto, o un altro, chiuso, situato nello stesso computer o in qualche altro computer in rete. Pur avendo lo stesso nome nei due modelli, l’oggetto Recordset ha metodi e proprietà in DAO che sono diversi da quelli che lo caratterizzano in ADO.Approfondiremo queste differenze nei prossimi capitoli, qui ci limitiamo a costruire due routine dimostrative che producono lo stesso risultato utilizzando in un caso i DAO e in un altro gli ADO. RIFERIMENTI Le routine dimostrative che seguono funzionano a condizione che i riferimenti nell’Editor di Visual Basic siano quelli evidenziati nella Figura 5.2.

Le routine generano entrambe lo stesso risultato: presentano una finestra di messaggio che elenca i contenuti dei campi Nome e Cognome di una tabella tblIndirizzi, nell’ordine in cui i record sono disposti nella tabella; subito dopo, rielaborano gli stessi dati, ordinandoli in base al valore del campo Cognome e li visualizzano con una finestra di messaggio simile. La Figura 5.7 mostra le due finestre di messaggio generate una dopo l’altra da entrambe le routine.

172  Capitolo 5

Figura 5.7  Le finestre di messaggio create e visualizzate dalle due routine.

Quella che segue è la routineVBA che utilizza un Recordset DAO. Le righe qui riportate in corsivo iniziano con un apostrofo e sono commenti esplicativi. Sub DAO_OrdinaRecordset() 'Routine che utilizza i DAO per accedere a una tabella 'Dichiarazione delle variabili 'Le variabili per utilizzare oggetti DAO specificano 'che gli oggetti appartengono all'insieme DAO Dim db As DAO.Database Dim rstPersone As DAO.Recordset Dim strLista As String, strTitoloFinestra As String 'Imposta il riferimento al database corrente Set db = CurrentDb 'Esegue il metodo OpenRecordset dell'oggetto db, 'usando come argomento una query SQL che estrae 'i campi Nome e Cognome dalla tabella tblIndirizzi: 'assegna quindi il risultato di questa operazione 'alla variabile rstPersone, predisposta per accogliere un Recordset, 'specificando con la costante dbOpenDynaset le caratteristiche 'del Recordset (percorribile in entrambe le direzioni) Set rstPersone = db.OpenRecordset("Select Nome, Cognome from tblIndirizzi", dbOpenDynaset)

Gli oggetti   173 'Crea ciclicamente una stringa che contiene il contenuto dei campi Nome 'e Cognome della tabella tblIndirizzi Do Until rstPersone.EOF     strLista = strLista & rstPersone("Nome") _         & " " & rstPersone("Cognome") & vbCrLf     rstPersone.MoveNext Loop 'Imposta il titolo della finestra di messaggio 'e visualizza la finestra che contiene la variabile strLista 'con i nomi e i cognomi nell'ordine in cui si trovano nella tabella strTitoloFinestra = "Elenco non ordinato" MsgBox strLista, vbInformation, strTitoloFinestra '****************Seconda parte**************** 'Svuota la variabile strLista per riutilizzarla nella 'seconda parte della routine strLista = "" 'Imposta sul campo Cognome la proprietà Sort 'del recordset aperto rstPersone.Sort = ("Cognome") 'Apre di nuovo il recordset per estrarre i record ora ordinati 'in base al campo Cognome e ripete l'estrazione ciclica Set rstPersone = rstPersone.OpenRecordset Do Until rstPersone.EOF     strLista = strLista & rstPersone("Nome") _         & " " & rstPersone("Cognome") & vbCrLf     rstPersone.MoveNext Loop 'Imposta il titolo della finestra di messaggio 'e visualizza la finestra che contiene la variabile strLista 'con i nomi e i cognomi disposti in ordine alfabetico per Cognome strTitoloFinestra = "Elenco ordinato sul Cognome" MsgBox strLista, vbInformation, strTitoloFinestra 'Chiude l'oggetto Recordset rstPersone rstPersone.Close End Sub

La routine che utilizza un oggetto Recordset ADO si presenta come nel listato seguente. Sub ADO_OrdinaRecordset() 'Routine che utilizza gli ADO per accedere a una tabella 'Dichiarazione delle variabili 'Le variabili per utilizzare oggetti ADO specificano 'che gli oggetti appartengono all'insieme ADO Dim rstPersone As ADODB.Recordset Dim strLista As String, strTitoloFinestra As String 'Genera una nuova istanza dell'oggetto rstPersone Set rstPersone = New ADODB.Recordset

174  Capitolo 5 'Assegna il rstPersone al database corrente, 'utilizzando la proprietà Connection dell'oggetto CurrentProject rstPersone.ActiveConnection = CurrentProject.Connection 'Predispone il tipo di cursore rstPersone.CursorLocation = adUseClient 'Apre il recordset rstPersone mediante 'una istruzione SELECT di SQL rstPersone.Open "Select Nome, Cognome from tblIndirizzi" 'Usa gli stessi enunciati della routine DAO 'per estrarre ciclicamente i dati dal recordset Do Until rstPersone.EOF     strLista = strLista & rstPersone("Nome") _         & " " & rstPersone("Cognome") & vbCrLf     rstPersone.MoveNext Loop 'Prepara il titolo della finestra di messaggio 'e la visualizza con i nomi e cognomi nell'ordine 'in cui si trovano nella tabella strTitoloFinestra = "Elenco non ordinato" MsgBox strLista, vbInformation, strTitoloFinestra '****************Seconda parte**************** 'Imposta sul campo Cognome la proprietà Sort 'del recordset aperto rstPersone.Sort = ("Cognome") 'Svuota la variabile strLista per riutilizzarla nella 'seconda parte della routine strLista = "" 'Non deve tornare ad aprire il recordset, come nella routine DAO 'perché utilizza il cursore che adesso fa riferimento ai dati ordinati Do Until rstPersone.EOF     strLista = strLista & rstPersone("Nome") _         & " " & rstPersone("Cognome") & vbCrLf     rstPersone.MoveNext Loop 'Prepara il titolo della finestra di messaggio 'e la visualizza con i nomi e cognomi disposti 'in ordine alfabetico per Cognome strTitoloFinestra = "Elenco ordinato sul Cognome" MsgBox strLista, vbInformation, strTitoloFinestra 'Chiude il recordset rstPersone.Close End Sub

Gli oggetti   175

Le differenze di rilievo fra le due routine stanno negli enunciati preliminari, che impostano le variabili oggetto e il loro collegamento col database. Nel caso della routine DAO, la variabile oggetto db, che rappresenta un generico database, viene associata al database corrente mediante l’enunciato: Set db = CurrentDb

Fatta questa assegnazione, l’enunciato che segue: Set rstPersone = db.OpenRecordset("Select Nome, Cognome from tblIndirizzi", _     dbOpenDynaset)

ha questo significato:“esegui il metodo OpenRecordset del database corrente, selezionando con una query SQL i campi Nome e Cognome dalla tabella tblIndirizzi, ottenendo un Recordset nella modalità dbOpenDynaset, e assegnalo alla variabile rstPersone che rappresenta un Recordset”. Il meccanismo utilizzato per creare le stesse condizioni nella routine ADO si sviluppa, invece, nei seguenti enunciati: Set rstPersone = New ADODB.Recordset

Questo primo enunciato crea un nuovo Recordset ADO e lo assegna alla variabile rstPersone. L’enunciato successivo: rstPersone.ActiveConnection = CurrentProject.Connection

stabilisce un’associazione fra il nuovo Recordset (che è ancora vuoto) con il database corrente, assegnando alla sua proprietà ActiveConnection lo stesso valore della proprietà Connection dell’oggetto CurrentProject (cioè il database corrente). Prima di caricare nel nuovo Recordset i dati che interessano, bisogna stabilire la posizione del cursore, mediante l’enunciato che segue: rstPersone.CursorLocation = adUse Client

Che cosa vuol dire? Il termine “cursor” nel gergo dei database SQL (ai quali appartiene Access) può essere tradotto letteralmente con la parola italiana “cursore”, ma non ha nulla a che vedere con quell’elemento grafico intermittente che vediamo in molte schermate, dove segnala in che punto andrà a collocarsi il prossimo carattere che si batterà sulla tastiera. In questo caso il cursore è un insieme di riferimenti, creati nella memoria del computer, ai dati che si andranno a reperire dalla tabella vera e propria e che verranno inseriti nel Recordset. Approfondiremo questo argomento nel Capitolo 11, per ora limitiamoci a prendere atto che per lavorare con un Recordset ADO è necessario specificare se il cursore deve trovarsi sulla macchina client (adUseClient) o sulla macchina server (adUseServer).

176  Capitolo 5

Set e New Le variabili che si riferiscono agli oggetti, dette brevemente variabili oggetto, devono essere dichiarate esplicitamente, come per le variabili stringa o numeriche, quindi si può far nascere una variabile oggetto con l’enunciato: Dim rstPersone AS ADODB.Recordset

Successivamente, l’assegnazione della variabile così dichiarata a una nuova istanza dell’oggetto ADODB.Recordset si ottiene con l’enunciato: Set rstPersone = New ADODB.Recordset

È consentito fondere i due enunciati in uno solo, formulato in questo modo: Dim rstPersone AS New ADODB.Recordset

In questa formulazione, l’enunciato crea contemporaneamente la variabile oggetto rstPersone e una nuova istanza dell’oggetto ADODB.Recordset che assegna alla variabile appena creata.

Concluse queste operazioni preliminari, l’enunciato successivo: rstPersone.Open "Select Nome, Cognome from tblIndirizzi"

usa il metodo Open (che è specifico dei Recordset ADO e non vale per quelli DAO) per attivare una query SQL, con la quale si estraggono dalla tabella tblIndirizzi i campi che interessano. Gli enunciati che vengono dopo sono sostanzialmente uguali a quelli dell’altra routine, con una sola differenza: per ordinare sul campo Cognome i record contenuti in rstPersone basta l’enunciato rstPersone.Sort = ("Cognome")

ma non è necessario tornare ad aprire il Recordset (come nel caso della routine DAO) perché l’ordinamento viene fatto sul cursore. Non so immaginare quanto questi esempi abbiano contribuito a far capire le regole che si usano per lavorare con oggetti, metodi e proprietà. Una cosa comunque è certa: per capire davvero come sono fatti e come funzionano gli oggetti e tutto quello che sta loro intorno, bisogna usarli. E per usarli occorre servirsi del linguaggio di programmazione Visual Basic for Applications. Ce ne occupiamo a partire dal prossimo capitolo.

Capitolo 6

L’ambiente di sviluppo

Scrivere programmi non è un’attività letteraria, ma un paziente, minuzioso, spesso frustrante e a volte noiosissimo lavoro di composizione di stringhe, come si chiamano in gergo informatico, cioè sequenze di lettere e numeri. Fatica, noia e frustrazione nascono dal fatto che il computer è un essere del tutto sprovvisto di fantasia, immaginazione, senso dell’umorismo e intuizione. Per lui “le parole sono pietre”, nel senso più letterale di questa metafora. Con le stringhe che formano gli enunciati di programmazione si costruiscono sequenze di ordini, che il computer esegue con inflessibile determinazione: fa sempre quello che gli diciamo di fare, non quello che crediamo di avergli detto di fare. Nel vecchio BASIC, per esempio, se vogliamo far uscire la parola Ciao sullo schermo, dobbiamo scrivere: PRINT "Ciao"

Se dimentichiamo le virgolette e scriviamo, invece: PRINT Ciao

sullo schermo compare un messaggio di errore, invece della parola Ciao. Per rendere meno ardua la scrittura dei programmi, sono stati creati strumenti specializzati, detti editor di programmi, molto diversi dai word processor o elaboratori di testi che si usano per scrivere col computer documenti di qualunque natura. I primi editor di programmi non erano altro che elaboratori di testi privi di funzionalità per dare un

In questo capitolo •

L’Editor di Visual Basic



Gli strumenti per il debug



Gli errori di run-time

178  Capitolo 6

formato grafico a ciò che si scriveva: un programma è, per il computer che deve elaborarlo, una semplice successione di stringhe, distinte l’una dall’altra da un carattere di fine riga, quindi non servono corsivi, grassetti, corpi tipografici particolari, la cui presenza creerebbe soltanto confusione. Uno dei primi editor di programmi è ancora presente in Windows: si tratta del programma di scrittura Blocco note, col quale si possono creare soltanto semplici testi senza alcun abbellimento formale. Niente impedisce di scrivere una routine VBA con Blocco note, ma sarebbe come usare una bicicletta per andare in montagna quando si ha a disposizione un poderoso ed efficientissimo fuoristrada. Infatti, l’Editor di Visual Basic, col quale si scrivono le routine in Access, è molto più di un elaboratore di testi, perché – quando lo si attiva – crea un vero e proprio ambiente di sviluppo. Chiamato genericamente Integrated Development Environment o IDE, un ambiente di sviluppo è un elaboratore di testi specializzato, che esamina le parole mentre vengono scritte e attiva automaticamente varie funzionalità di controllo o di correzione non appena viene battuto qualche carattere particolare, per esempio il carattere di fine riga (col tasto Invio), un segno di interpunzione o un carattere che rappresenta un operatore logico o aritmetico. Queste funzionalità di controllo e correzione sono rese possibili dal fatto che, quando si scrive un programma, si compongono stringhe strutturate in base alle convenzioni sintattiche di quel linguaggio di programmazione; per esempio, ciascun enunciato contiene almeno una parola chiave, che deve essere scritta sempre nello stesso modo e in molti casi è soggetta a vincoli posizionali, vale a dire deve essere sempre la prima in una riga oppure deve essere presente su una riga se su quella stessa riga è stata già scritta una particolare parola chiave. Le funzionalità proprie dell’ambiente di sviluppo esaminano le stringhe a mano a mano che vengono composte da chi scrive e valutano la loro correttezza sintattica, attivando altre funzionalità che segnalano errori o improprietà quando la sequenza delle singole parole forma un costrutto formalmente sbagliato. Alcuni ambienti di sviluppo, particolarmente ben congegnati, sono dotati, inoltre, di funzionalità di suggerimento: quando avvertono l’immissione di un carattere o di una parola particolare, che a regola di sintassi può essere seguita da un solo termine scelto in un elenco predefinito, presentano tale elenco all’operatore, che può selezionare il termine giusto e inserirlo nella routine che sta scrivendo premendo Invio o Tab. Questo meccanismo, che in Access prende il nome di IntelliSense ed è attivo anche quando si scrivono parametri o argomenti per le azioni macro, rende molto più agevole scrivere programmi, perché elimina il rischio di errori di scrittura e, limitando le scelte possibili a quelle formalmente corrette in quel contesto, aiuta il programmatore a comporre enunciati sintatticamente corretti. L’ambiente di sviluppo formato dall’Editor di Visual Basic in Access è molto ricco di funzionalità, che esaminiamo separatamente nei prossimi paragrafi. Diversamente dall’interfaccia grafica per lavorare sugli oggetti del database, che in Access 2007 e ancor più in Access 2010 è radicalmente diversa da quella di tutte le versioni precedenti, le caratteristiche grafiche e funzionali dell’Editor di Visual Basic in Access 2010 sono identiche a quelle disponibili in Access 2002/2003.

L’ambiente di sviluppo   179

L’Editor di Visual Basic Le routine VBA in Access risiedono in oggetti database separati detti moduli, che sono disponibili in due tipi diversi: quelli standard, che sono accessibili dalla sezione Moduli del Riquadro di spostamento, e i moduli di classe. Questi possono essere indipendenti oppure associati a maschere e a report. Se un file database contiene moduli di classe indipendenti, i loro nomi, affiancati da un’icona particolare, sono visualizzati nella sezione Moduli, mentre i moduli di classe associati a maschere e a report non sono visibili direttamente nel Riquadro di spostamento. La Figura 6.1 mostra una sezione Moduli nella quale sono contenuti moduli standard e moduli di classe indipendenti.

Modulo standard    Modulo di classe Figura 6.1  Una sezione Moduli del Riquadro di spostamento che contiene moduli standard e moduli di classe indipendenti.

I moduli standard Si accede ai moduli standard dalla sezione Moduli del Riquadro di spostamento. La sezione presenta l’elenco dei moduli standard che si trovano nel file database aperto, quando ve ne sono. Per esaminare il contenuto di un modulo standard bisogna fare un clic destro sul suo nome e scegliere il comando Visualizzazione Struttura dal menu contestuale. Si crea un modulo standard nuovo dando il comando Crea/Macro e codice/Modulo nella barra multifunzione.

180  Capitolo 6

Quando una maschera è aperta in Visualizzazione Struttura, nel gruppo di comandi Strumenti della scheda Strumenti Struttura maschera è disponibile un pulsante Visualizza codice, che dà accesso al codice VBA associato a quella maschera. Tutti questi comandi fanno aprire l’Editor di Visual Basic, che è dotato di una barra dei menu e di una barra degli strumenti Standard, alla quale se ne possono aggiungere altre tre, che vediamo nella Figura 6.2.

Figura 6.2  La barra dei menu e quelle degli strumenti di Visual Basic. Le barre degli strumenti Debug, Modulo e UserForm si possono aggiungere scegliendole dalle opzioni del comando Visualizza/Barre degli strumenti.

L’Editor diVisual Basic presenta in apertura una finestra documento, destinata a contenere gli enunciati VBA, detti genericamente codice, che può essere attorniata da una o più finestre subalterne, che portano il titolo Progetto e Proprietà. Queste e altre finestre che si possono aprire all’interno dell’Editor si piazzano automaticamente in una posizione predefinita quando vengono aperte. Per chiuderle si fa clic sul pulsante con la croce di Sant’Andrea che sta all’estremità destra della loro barra del titolo e possono essere spostate e ridimensionate agganciando col mouse la loro barra del titolo e trascinandola mentre si tiene premuto il tasto Ctrl; la finestra del documento, quella principale che contiene il codice, può, invece, essere spostata e ridimensionata come una qualsiasi finestra Windows. Un clic sul primo pulsante della barra degli strumenti dell’Editor (quello con l’icona di Access) nasconde la finestra dell’Editor e porta in primo piano la finestra Database. Trascuriamo per il momento le finestre complementari e concentriamoci su quella principale. Il modulo standard vero e proprio, quando è appena stato aperto e non contiene ancora routine, non ha un aspetto particolarmente suggestivo: in sostanza, è una pagina bianca, nella quale si può scrivere quello che si vuole, a condizione, però, di rispettare le regole imposte dall’Editor di Visual Basic, che sono estremamente rigide. In testa al modulo, come si può vedere dalla Figura 6.3, vi sono due caselle di riepilogo, il cui scopo viene visualizzato quando vi si appoggia sopra il puntatore del mouse (senza fare clic). Se si fa clic sulla casella di sinistra esce un elenco di oggetti, mentre un clic su quella di destra fa uscire un elenco di routine. Scegliendo nella casella di riepilogo opportuna quello che si vuol vedere, viene portato nella pagina del modulo il codice corrispondente, in modo che sia possibile leggerlo, modificarlo o eseguire le routine che contiene.

L’ambiente di sviluppo   181

Figura 6.3  La finestra di un modulo standard vuoto aperto in visualizzazione Struttura.

Se non si sceglie nulla nelle caselle di riepilogo, in quella di sinistra è visibile la dicitura generale e in quella di destra la dicitura dichiarazioni.Vedremo più avanti che cosa significano. All’interno del modulo vero e proprio possono comparire due righe, per impostazione predefinita, che hanno questo contenuto: Option Compare Database Option Explicit

Per ora possiamo ignorarle, ne spiegheremo il significato al momento opportuno. Per vedere come funziona l’ambiente di sviluppo reso disponibile dall’Editor di Visual Basic, proviamo a scrivere una routine. Utilizzeremo per questa prova la routine seguente, le cui funzionalità verranno approfondite nel Capitolo 9. Sub ContaTabelleEffettive()     Dim dbTest As DAO.Database, tdfTabella As Object     Dim intNumTabelle As Integer, intTabelleEffettive As Integer     Dim strNomeDB As String, intContatore As Integer     strNomeDB = "C:\Esempi\NorthwindLavoro.accdb"     Set dbTest = OpenDatabase(strNomeDB)     intNumTabelle = dbTest.TableDefs.Count     For intContatore = 0 To intNumTabelle - 1     Set tdfTabella = dbTest.TableDefs(intContatore)         If Left(tdfTabella.Name, 4) "MSys" Then             intTabelleEffettive = intTabelleEffettive + 1         End If     Next intContatore     MsgBox "Il database " & strNomeDB & " contiene " & vbCrLf _         & intNumTabelle & " tabelle: " & intTabelleEffettive & _         " definite dall'utente e " & vbCrLf & _         intNumTabelle - intTabelleEffettive & " tabelle di sistema" End Sub

182  Capitolo 6

Digitiamo quindi, subito sotto le due righe predefinite del modulo nuovo, la prima riga della routine: sub ContaTabelleEffettive()

Non appena premiamo il tasto Invio per chiudere la riga e andare a capo, interviene l’Editor di Visual Basic, che produce l’effetto che si può vedere nella Figura 6.4: 1. la parola sub, che era stata digitata con l’iniziale minuscola, viene trasformata in Sub, con l’iniziale maiuscola; 2. viene generata una riga vuota subito sotto la prima; 3. sotto la riga vuota compare automaticamente la scritta End Sub; 4. la barretta verticale, che segnala il punto di inserimento del testo, viene collocata all’inizio della riga vuota; 5. le parole Sub e End Sub sono in colore azzurro, mentre il nome della routine è in nero; 6. nella casella di riepilogo Routine ora compare il nome della routine, ContaTabelleEffettive, senza le parentesi tonde; 7. una riga di separazione è stata inserita fra le scritte di intestazione e le righe della nuova routine.

Figura 6.4  L’Editor di Visual Basic si accorge che è iniziata la scrittura di una nuova routine Sub e interviene generando la riga di chiusura corrispondente.

Questo è un primo esempio, molto semplice, di come interviene l’ambiente di sviluppo. L’Editor di Visual Basic ha avvertito l’intenzione dell’operatore di scrivere una routine di tipo sub e ha quindi convertito nella grafia formale (Sub), la parola digitata con l’iniziale minuscola. Ha aggiunto automaticamente una riga di chiusura, perché le routine Sub devono sempre terminare con la riga End Sub e ha usato il colore azzurro per far risaltare le parole chiave (Sub ed End Sub, in questo caso). Lo stesso meccanismo sarebbe intervenuto se la prima parola digitata fosse stata function, invece di sub, con la differenza che la riga finale avrebbe avuto la forma End Function, invece di End Sub.

L’ambiente di sviluppo   183

Proseguiamo digitando la parte iniziale del primo enunciato di dichiarazione delle variabili: Dim dbTest As DAO.Database,

Non appena battiamo la barra spaziatrice dopo aver scritto as, interviene l’Editor di Visual Basic (Figura 6.5), aprendo subito sotto la riga in corso di scrittura una casella di riepilogo, nella quale si può visualizzare un lungo elenco di valori possibili, fra i quali scegliere quello che interessa. Scorrendo possiamo arrivare fino a DAO e far scrivere questa parola chiave automaticamente dopo as premendo il tasto Tab. Premiamo poi il tasto Invio e l’enunciato risulta scritto correttamente, con le parole chiave Dim, As e DAO in azzurro e con l’iniziale maiuscola, mentre i nomi delle variabili restano in nero e nella forma in cui sono stati digitati.

Figura 6.5  Dall’Elenco membri automatico si può scegliere con sicurezza e comodità l’elemento corretto per completare un enunciato in corso di scrittura.

Questa funzionalità dell’Editor di Visual Basic ha il nome un po’ goffo di Elenco membri automatico, corrisponde al meccanismo che in tutto Office si chiama IntelliSense ed è di grandissima utilità. A ciascuna voce dell’elenco è associata un’icona, che permette di identificare subito la natura dell’elemento selezionabile, secondo la convenzione grafica che riportiamo di seguito. Libreria

Tipo di dato

Applicazione Oggetto

184  Capitolo 6

Proseguiamo digitando gli altri enunciati di dichiarazione, quelli di assegnazione e il primo enunciato del ciclo For...Next. Notiamo che ogni volta l’Editor di Visual Basic interviene mettendo in caratteri azzurri e con le iniziali maiuscole le parole chiave, presentando inoltre, quando si digita as, l’elenco degli elementi possibili fra i quali scegliere. Dopo l’enunciato iniziale del ciclo For...Next iniziamo a scrivere su una riga nuova l’enunciato condizionale If, che si basa su un confronto fra la stringa “MSys” e i primi quattro caratteri del nome di una tabella. Nel momento in cui digitiamo la parentesi aperta che segue la parola chiave Left, si delinea – subito sotto la riga – una forma grafica come quella riportata nella Figura 6.6 A. È intervenuta un’altra funzionalità dell’ambiente di sviluppo, chiamata Informazioni rapide automatiche, che ha rilevato la digitazione di una parentesi aperta dopo la parola chiave Left e presenta lo schema sintattico di questa funzione.

Figura 6.6  La funzionalità Informazioni rapide automatiche mostra lo schema sintattico della funzione che si sta digitando.

Lo schema sintattico in questo caso ha la forma Left(String, Length As Long)

per segnalare che la sintassi corretta prevede che dopo la parola chiave Left e la parentesi aperta venga scritta una stringa, seguita da una virgola, da un valore per la lunghezza con tipo dati Long e da una parentesi chiusa. Il primo elemento (String) è in grassetto e resta con quel formato fino a quando si scrive la stringa (o il nome della variabile che la individua, nel nostro caso). Dopo la stringa digitiamo la virgola e lo schema sintattico passa a evidenziare in grassetto le specifiche per il secondo argomento obbligatorio della funzione, la lunghezza della sottostringa da estrarre (Figura 6.6 B). Dopo che abbiamo digitato il numero 4 e la parentesi, lo schema sintattico scompare.

L’ambiente di sviluppo   185

Informazioni sibilline I suggerimenti messi a disposizione dalla funzionalità Informazioni rapide automatiche non sempre sono chiari e intuitivi come quelli che compaiono nel caso della funzione Left(). Quando si digita uno spazio dopo aver scritto la parola chiave MsgBox, per esempio, lo schema sintattico che compare subito sotto ha questo aspetto:

che è piuttosto sconcertante, a dir poco. Se non si conosce già la sintassi della funzione MsgBox, il suggerimento non aiuta affatto, anzi, rischia di mettere fuori strada. Nel caso specifico, le difficoltà di lettura nascono dal fatto che chi ha scritto quel messaggio di orientamento ha cercato di far stare troppe informazioni in una sola riga, utilizzando inoltre As, una delle parole chiave del VBA, come termine colloquiale e non di programmazione, per di più con due sfumature semantiche diverse. Per ragioni di compattezza, inoltre, ha usato due termini che hanno l’aspetto di costanti VBA, ma che tali non sono. Insomma, un pasticcio. Tradotto in parlar comune, quel criptico suggerimento dice quanto segue: la funzione MsgBox restituisce un risultato (As VbMsgBoxResult) ed è composta da un Prompt (un testo di invito o di informazione); da pulsanti, che si specificano usando una costante di stile, come per esempio VbOKOnly (As VbMsgBoxStyle=VbOKOnly); da un Titolo; da un riferimento a un file di Guida e da un numero di contesto per la Guida. L’ignoto autore di questo schema sibillino ha commesso anche un errore. Stando alla convenzione usata abitualmente per rappresentare la sintassi di un enunciato VBA, gli elementi elencati fra parentesi quadre sono facoltativi, mentre tutto quello che è elencato entro parentesi tonde è obbligatorio. Dal modo in cui è scritto il testo di questa informazione rapida automatica, si è portati a concludere che gli argomenti Buttons, Title, Helpfile e Context siano facoltativi, il che è corretto, e che siano obbligatori l’argomento Prompt (corretto anche questo) e le virgole che segnalano eventuali argomenti omessi (il che è falso). Lo schema sintattico della funzione si rappresenta correttamente in questo modo: MsgBox(Prompt [, buttons] [, title] [, helpfile, context])

col quale si indica che gli argomenti facoltativi vanno scritti in quell’ordine, separandoli l’uno dall’altro con una virgola, da inserire soltanto quando si specifica l’argomento. Ancora un particolare, da non trascurare. Lo schema dell’informazione sintattica che compare automaticamente è riferito all’uso di MsgBox come funzione che restituisce un dato da utilizzare in seguito nella routine (un numero intero, che rappresenta il pulsante sul quale l’operatore fa clic nella finestra di messaggio per chiuderla). A questo si riferiscono le due parole che stanno in fondo al suggerimento (As VbMsgBoxResult). Quando non interessa utilizzare il dato restituito dalla funzione MsgBox, ma basta far comparire un messaggio, è sufficiente scrivere soltanto la parola chiave MsgBox e il testo del messaggio, senza metterlo fra parentesi: usata in questo modo, senza argomenti fra parentesi, MsgBox non è una funzione ma una istruzione.

186  Capitolo 6

Le funzionalità Elenco automatico membri e Informazioni rapide automatiche sono estremamente comode, soprattutto quando si usano funzioni o oggetti complessi, o ai quali si ricorre di rado. Si può sempre andare a consultare la Guida in linea di Visual Basic for Applications, ma si fa certamente prima a seguire le indicazioni di questi meccanismi di orientamento contestuale. Proviamo ora a commettere un errore marchiano. Subito dopo aver scritto la prima parte dell’enunciato If...Then, dopo la parentesi che chiude la funzione Left() premiamo il tasto Invio, invece di proseguire nella scrittura della componente Then dell’enunciato. Istantaneamente, un bip avverte che qualcosa non va, l’intera riga si colora di rosso e compare una finestra di messaggio che segnala “Errore di compilazione. Previsto Then oppure GoTo”, presentando due pulsanti di comando con l’indicazione OK e ? (Figura 6.7).

Figura 6.7  Il Controllo automatico sintassi denuncia un errore di sintassi.

In questo caso è intervenuta la funzionalità Controllo automatico sintassi dell’Editor diVisual Basic, che si attiva ogni volta che si preme Invio per chiudere una riga e iniziarne un’altra. Se la riga appena digitata e data per buona dall’operatore non rispetta una qualunque regola sintattica, il Controllo automatico la segnala, evidenziando in rosso l’intera riga, mentre la finestra di messaggio specifica, nei limiti in cui è individuabile, la natura dell’errore. In questo caso, l’errore è grossolano e facile da individuare anche senza l’intervento del Controllo automatico sintassi: non è consentito omettere la seconda parola chiave, Then, nell’enunciato condizionale If...Then. Facendo clic sul pulsante OK si chiude la finestra di messaggio che ha segnalato l’errore, ma la riga sbagliata resta colorata in rosso, per segnalare che si deve comunque fare qualcosa. Se si fa clic sul pulsante col punto interrogativo, viene aperta una pagina della Guida in linea di Visual Basic for Applications, che spiega in modo più articolato l’errore e a volte fornisce qualche suggerimento per eliminarlo.

L’ambiente di sviluppo   187

Non è obbligatorio correggere subito l’errore di sintassi, si può continuare a scrivere altri enunciati, ma la riga con l’errore resta evidenziata in caratteri rossi e la routine non può essere eseguita se prima non si corregge l’errore o non si elimina l’intera riga incriminata. In alcuni casi, chi sviluppa il codice di programma può decidere di scrivere provvisoriamente soltanto alcuni elementi di uno o più enunciati, ripromettendosi di completarli in un secondo momento. Gli enunciati incompleti o privi di parti essenziali (per esempio, la parte che sta a destra di un segno di eguale, negli enunciati di assegnazione) sono avvertiti come errori e marcati in rosso. Si può placare il rigore censorio del Controllo automatico sintassi mettendo un apostrofo in testa agli enunciati che si vogliono mantenere in sospeso. Così facendo, ai meccanismi di controllo dell’Editor di Visual Basic le righe cessano di apparire come enunciati sbagliati, ma sono viste come commenti. La diversa valutazione viene evidenziata con un cambio di colore, le righe tutte intere vengono colorate in verde e considerate come annotazioni o commenti dell’autore e come tali sono ignorate dal VBA. La presenza di un apostrofo in un qualunque punto di una riga trasforma tutto ciò che segue in un commento, quindi è possibile isolare dal Controllo automatico sintassi anche una parte soltanto di una riga, inserendovi un apostrofo: i caratteri che seguono il segno vengono evidenziati in verde e possono essere commenti veri e propri o bozze di istruzioni.

Commentate, commentate, qualcosa resterà Quando si sviluppa un’applicazione, qualunque sia lo strumento di programmazione utilizzato, è essenziale inserire opportune annotazioni esplicative nel codice, servendosi (nel caso del VBA) del semplice ed efficace apostrofo per marcare le righe come commenti. I commenti aiutano a spiegare e soprattutto a ricordare il perché di certe scelte logiche, che rilette a distanza di tempo possono risultare difficili da capire anche allo stesso autore del codice di programma. Le applicazioni sono congegni complessi, che molto spesso hanno bisogno di interventi di manutenzione. Il termine è improprio, perché un’applicazione non si logora con l’uso e quindi non ha bisogno di essere riparata con una manutenzione, ma col passare del tempo può rivelarsi inadeguata a gestire variazioni, grandi o piccole, che sono intervenute nell’esigenza applicativa che ha dato origine all’applicazione stessa. In questi casi si parla di interventi di manutenzione, vale a dire rifacimenti parziali, aggiunte di routine e altri ritocchi. Se i programmi che costituiscono l’applicazione non sono ben documentati con chiari commenti inseriti nei punti critici, chi deve eseguire questi interventi si trova a mal partito e a volte è costretto a rifare completamente una o più routine, perché quelle esistenti gli risultano incomprensibili. Può, inoltre, verificarsi il caso che un’applicazione ben collaudata e stabile, dopo mesi o magari anni di funzionamento soddisfacente, si inchiodi miseramente al verificarsi di una combinazione di input e di condizioni di verifica che non era stata prevista e non si era mai presentata prima e per la quale le routine esistenti non sono adeguate. In casi di questo genere, a maggior ragione, la disponibilità di annotazioni di commento puntuali e minuziose, inserite fra gli enunciati delle routine, può aiutare a individuare l’origine del problema e consentire un intervento di manutenzione efficace e a costi contenuti.

188  Capitolo 6

Quando il programmatore decide di tornare sugli enunciati che ha lasciato in sospeso e li completa, per renderli attivi deve eliminare l’apostrofo in testa a ciascuna riga: in questo modo, rilancia la funzione di controllo della sintassi, che colora i caratteri in rosso, se riscontra ancora qualche errore, oppure assegna il colore azzurro alle parole chiave e ne normalizza la grafia, segnalando così che la sintassi degli enunciati ora è formalmente corretta. Proseguiamo con l’immissione degli enunciati, osservando come si attivano le varie funzionalità che abbiamo fin qui descritto. Introduciamo una leggera modifica nell’enunciato che genera la finestra di messaggio, scrivendolo nel modo seguente:     MsgBox "Il database " & strNomeDB & " contiene " & vbCrLf _         & intNumTabelle & " tabelle: " & intTabelleEffettive & _         " definite dall'utente e " & vbCrLf & _         intTabelle - intTabelleEffettive & " tabelle di sistema"

La differenza consiste nell’usare una variabile chiamata intTabelle invece di intNumTabelle, come negli enunciati di dichiarazione scritti all’inizio della routine. L’Editor di Visual Basic accetta l’enunciato scritto in questo modo, ma si riserva di intervenire al momento opportuno. Proviamo, infatti, a eseguire la routine appena immessa, premendo il tasto di funzione F5 oppure facendo clic sul pulsante Esegui Sub/UserForm nella barra degli strumenti Standard (è quello con l’icona di un triangolo che punta verso destra). La routine non viene eseguita, compare una finestra di messaggio che segnale l’errore di compilazione Variabile non definita e, quando facciamo clic sul pulsante OK, la riga col titolo della routine viene evidenziata in giallo e il nome della variabile intTabelle è evidenziato in blu (Figura 6.8). Quando una routine viene bloccata da un errore di sintassi scoperto al momento dell’esecuzione, per proseguire il lavoro è necessario sbloccarla, facendo clic sul pulsante Ripristina, quello con l’icona di un quadratino blu, subito a destra del pulsante Esegui Sub/UseForm nella barra degli strumenti Standard. Le due segnalazioni di errore che abbiamo ricevuto parlano di un “errore di compilazione”, invece di un errore di sintassi: che cosa vuol dire? Le routine scritte nell’Editor di Visual Basic non vengono eseguite direttamente, ma diventano input di una fase preparatoria, detta compilazione, che genera le istruzioni in linguaggio macchina necessarie per eseguire quel che viene richiesto dagli enunciatiVBA. Durante la compilazione intervengono vari meccanismi di controllo, che non potrebbero essere attivati dall’ambiente di sviluppo. Uno di questi è la verifica delle variabili utilizzate nella routine. Per ciascuna variabile dichiarata espressamente con un enunciato Dim, la fase di compilazione crea uno spazio in memoria destinato a gestire il valore che sarà assegnato alla variabile. Questo spazio è dimensionato in base al tipo di dato indicato nella dichiarazione della variabile: per una variabile di tipo Integer sarà di 2 byte, di 16 byte per una variabile di tipo Double e così via. È facoltà del programmatore usare variabili anche senza definirle esplicitamente. Se il compilatore trova una variabile non definita, la gestisce comunque assegnandole uno spazio di memoria adeguato al tipo Variant, che è un tipo di dato specifico di Visual Basic, adatto a contenere qualsiasi cosa, da un numero intero a un oggetto. Come mai, allora, ci viene segnalato un errore di compilazione, evidenziando la variabile intTabelle come la colpevole?

L’ambiente di sviluppo   189

Figura 6.8  Il compilatore del codice VBA segnala un errore di compilazione. . . . . . evidenziando il punto in cui si origina e bloccando l’esecuzione della routine.

A determinare questo comportamento è la dichiarazione Option Explicit, collocata in testa al modulo. Con questa dichiarazione si informa in via preliminare il compilatore diVisual Basic che è intenzione del programmatore dichiarare esplicitamente le variabili prima di utilizzarle. Nel rispetto di questa dichiarazione, il compilatore inizia ad assegnare la memoria alle variabili, ne trova una (intTabelle) che non è stata dichiarata in alcuno degli enunciati Dim precedenti e blocca l’esecuzione della routine, segnalando contestualmente come errore di compilazione il fatto di non aver riconosciuto la variabile non dichiarata. Che cosa succede se si omette la dichiarazione Option Explicit in testa al modulo VBA? Facciamo una prova per constatarlo direttamente. Mascheriamo agli occhi dell’Editor di Visual Basic questa dichiarazione, digitando in testa alla riga un apostrofo. I caratteri della

190  Capitolo 6

riga, che prima erano in blu, trattandosi di due parole chiave, diventano di colore verde, per indicare che adesso la riga è un commento, che quindi verrà ignorato sia dall’Editor sia dal compilatore di Visual Basic. Eseguiamo la routine e siamo gratificati dalla finestra di messaggio riprodotta nella Figura 6.9.

Figura 6.9  La routine eseguita con una variabile non definita produce un risultato sconcertante.

In assenza del vincolo Option Explicit, il compilatore ha creato le opportune istruzioni in linguaggio macchina e le ha eseguite. La variabile non dichiarata intTabelle nel momento in cui viene utilizzata ha il valore 0, invece del valore 23 della variabile intNumTabelle, per cui sottraendo 8 (il valore della variabile intTabelleEffettive) da 0, si ottiene -8, come dichiara con impassibile fermezza la finestra di messaggio. Il meccanismo di controllo globale al momento della compilazione si attiva comunque, che sia stata impostata oppure no la dichiarazione Option Explicit, allo scopo di intercettare eventuali incoerenze nell’uso delle variabili e dei dati o degli oggetti ai quali le variabili fanno riferimento. La compilazione viene avviata automaticamente quando si dà l’ordine di eseguire una routine, ma è anche possibile compilare una routine senza eseguirla, giusto per verificare se contiene incoerenze rilevabili dal controllo di compilazione. Per eseguire la compilazione senza avviare la routine, si sceglie il comando Compila xxx dal menu Debug, dove xxx è il nome del file Access che contiene il modulo visualizzato nell’Editor. La compilazione si arresta al primo errore che intercetta, quindi è opportuno eseguirla di nuovo, dopo aver eliminato l’errore, per rilevarne eventuali altri. Se il modulo contiene, come spesso accade, più di una routine, la compilazione si effettua su tutte, mettendo in evidenza eventuali errori contenuti in routine diverse da quella che si sta provando. Le varie funzionalità per intercettare errori, proporre scelte e presentare schemi sintattici sono indubbiamente utili, ma in determinati casi potrebbero dimostrarsi troppo ingombranti e intralciare il lavoro di stesura del codice, invece di semplificarlo. Per fortuna, sono tutte funzionalità attivabili o disattivabili a comando, attraverso la finestra di dialogo Opzioni, che si apre dal menu Strumenti dell’Editor di Visual Basic. Selezionando opportunamente le caselle di controllo della scheda Editor, come si vede dalla Figura 6.10, il programmatore può scegliere quali funzionalità di assistenza e controllo avere a disposizione mentre scrive le sue routine.

L’ambiente di sviluppo   191

Figura 6.10  Dalla finestra di dialogo Opzioni si possono personalizzare le funzionalità dell’Editor di Visual Basic.

Il significato delle opzioni disponibili nel riquadro Impostazioni codice è abbastanza intuitivo, ma per maggior chiarezza lo riepiloghiamo qui di seguito: Controllo automatico sintassi: abilita o disattiva il controllo automatico della sintassi dopo che è stata digitata una riga di codice. Dichiarazione di variabili obbligatoria: stabilisce se le variabili devono essere dichiarate espressamente nei moduli. Quando si seleziona questa opzione, nella sezione Dichiarazioni dei nuovi moduli viene inserita l’istruzione Option Explicit. Elenco membri automatico: presenta un elenco degli elementi (oggetti, proprietà, metodi e altro) con cui è possibile completare logicamente l’istruzione in cui si trova il punto di inserimento. Informazioni rapide automatiche: mostra informazioni sulle funzioni e sui parametri corrispondenti durante la digitazione. Descrizione dati automatica: visualizza il valore della variabile su cui è posizionato il cursore. È disponibile soltanto quando si esegue una routine un passo per volta o quando la sua esecuzione viene deliberatamente sospesa. Rientro automatico: applica una tabulazione alla prima riga di codice; tutte le righe di codice successive inizieranno dalla posizione della tabulazione. Rientro tabulazione: permette di specificare le dimensioni del rientro, da 1 a 32 spazi; il rientro predefinito è di 4 spazi. Il mio personale suggerimento, basato su un’esperienza piuttosto lunga e articolata, è di attivare tutte le opzioni disponibili nella scheda Editor della finestra di dialogo Opzioni. Il piccolo fastidio che queste funzionalità talvolta possono creare, quando segnalano con una certa petulanza ogni minimo errore formale, è più che compensato dall’ottimo servizio che forniscono proprio nella prevenzione delle sviste più banali, che insidiano continuamente il laborioso lavoro di sviluppo del codice di programma.

192  Capitolo 6

I moduli di classe Come abbiamo accennato all’inizio del capitolo, si possono avere due tipi di moduli di classe: quelli indipendenti e quelli associati a maschere e a report. Con i moduli di classe indipendenti si creano nuovi oggetti, con loro proprietà e metodi, che si possono richiamare nelle routine VBA come se fossero oggetti Access, DAO o ADO. L’utilizzo e il funzionamento di questi moduli di classe sono piuttosto complessi e ne parleremo più avanti. Per quanto riguarda l’argomento di questo capitolo, l’Editor di Visual Basic, ci basterà ricordare che si può esaminare e modificare la struttura di un modulo di classe esistente selezionandolo nella sezione Moduli del Riquadro di spostamento, dove compare con il suo nome e con una sua icona particolare, e dando il comando Visualizzazione Struttura, che lo fa aprire nella finestra Codice dell’Editor. Per creare un nuovo modulo di classe indipendente, si deve scegliere il comando Modulo di classe dal menu Inserisci dell’Editor di Visual Basic: il comando crea un nuovo modulo col nome predefinito Class1 (se è il primo modulo di classe che viene creato) che può poi essere modificato a piacere. Un modulo di classe contiene enunciati VBA come un qualsiasi modulo standard. Per accedere al modulo associato a una maschera o di un report si deve aprire l’oggetto in visualizzazione Struttura e fare clic sul pulsante Visualizza codice che si trova nel gruppo di comandi Strumenti della scheda Struttura della barra multifunzione. La differenza più rilevante fra un modulo associato a una maschera o a un report e un modulo standard sta nelle caselle di riepilogo situate in testa al modulo associato. Se apriamo in visualizzazione Struttura una maschera di una certa complessità, come per esempio la maschera Ordini del database Northwind (Figura 6.11), un clic sulla freccia a discesa della casella di riepilogo di sinistra, fa uscire un elenco di tutti gli oggetti che compongono la struttura della maschera (i controlli e le sezioni della maschera). Con un clic sulla casella di riepilogo di destra si fa uscire un elenco di tutti gli eventi che si possono associare all’oggetto selezionato nella casella di sinistra. Se esiste nel modulo una routine evento per l’oggetto selezionato, il nome dell’evento è evidenziato in grassetto. Sebbene i moduli associati a maschere e report siano predisposti per contenere routine evento, è del tutto legittimo scrivervi anche routine Sub o Function non associate a eventi. Le funzionalità di controllo dinamico della sintassi e di aiuto per la programmazione (Elenco membri automatico e Informazioni rapide automatiche) fornite dall’Editor di Visual Basic per i moduli standard sono attive anche per i moduli associati e di classe, se sono state scelte nella scheda Editor della finestra di dialogo Opzioni. È anche disponibile il controllo sulle variabili non dichiarate, che anche per i moduli di classe associati a maschere e report si esegue al momento della compilazione. L’Editor di Visual Basic fornisce anche una serie di altre funzionalità attivabili su richiesta quando ci si trova nella fase delicata e complessa di prova e messa a punto delle routine VBA. Prima di descrivere queste funzionalità, però, è opportuno completare l’esplorazione dell’Editor di Visual Basic, descrivendo le altre finestre che può contenere.

Le altre finestre dell’Editor di Visual Basic Il linguaggio Visual Basic for Applications disponibile nelle versioni dalla 2000 in poi del pacchetto software Office è un ampio sottoinsieme del prodotto Microsoft Visual Basic, versione 6.0.

L’ambiente di sviluppo   193

Figura 6.11  Nel modulo associato alla maschera Ordini è presente una routine per l’evento Click sul pulsante di comando StampaFattura.

Creati all’inizio della loro storia ormai quasi trentennale come prodotti simili ma diversi, Visual Basic e VBA sono stati progressivamente avvicinati a ogni nuova versione dell’uno come dell’altro sistema di sviluppo e ormai le uniche differenze rimaste sono di tipo quantitativo: tutto quello che si può fare con VBA si fa anche in Visual Basic 6.0, nello stesso identico modo, mentre in Visual Basic 6.0 è disponibile un maggior numero di strumenti e funzionalità, trattandosi di un sistema di sviluppo generalizzato, concepito per creare applicazioni complete e autosufficienti, non associate a un contesto applicativo che le ospita, come avviene invece per il VBA. NOTA Il linguaggio di programmazione Visual Basic 6.0 è stato sostituito a partire dal 2003 dal linguaggio Visual Basic .NET, che ha ereditato tutte le funzionalità della versione 6.0 arricchendole con i servizi resi disponibili da un nuovo strumento chiamato Microsoft .NET FrameWork, in parte integrato nelle versioni del sistema operativo Windows successive a Windows XP.

Nella Figura 6.12 vediamo l’Editor di Visual Basic che presenta, oltre alla finestra del codice, anche due finestre complementari, Progetto e Proprietà. Queste due finestre vengono direttamente da Visual Basic 6.0 e rappresentano, la prima, l’intero file Access, chiamato Progetto, e la seconda le proprietà di ciascun singolo oggetto selezionato all’interno della finestra Progetto. La finestra Progetto presenta le icone di due o più cartelle, che seguono la convenzione grafica dell’interfaccia di Windows: un clic sulla casella col segno più (+) a lato di una cartella ne espande il contenuto. Nella Figura 6.13 vediamo la finestra Progetto con la cartella Moduli espansa e la cartella Microsoft Office Access Oggetti di classe compressa.

194  Capitolo 6

Figura 6.12  L’Editor di Visual Basic può visualizzare contemporaneamente più finestre.

Figura 6.13  La finestra Progetto presenta lo schema dei moduli dell’applicazione Northwind.

Nella fascia superiore della finestra Progetto, subito sotto la barra del titolo, troviamo tre pulsanti. Il terzo, Espandi/comprimi cartelle, serve per fare la stessa operazione che si esegue agendo sulla casella col segno più o col segno meno. Il primo, Visualizza codice, fa comparire nella finestra del codice le routine corrispondenti all’oggetto selezionato nell’elenco, e il secondo, Visualizza oggetto, riduce a icona la finestra complessiva dell’Editor di Visual Basic e mostra l’oggetto in modalità Struttura. Se si espande la cartella Microsoft Office Access Oggetti di classe, compare un elenco di tutti gli oggetti maschera e report presenti nel progetto. I nomi sono quelli che hanno nella finestra Database, ma preceduti da un prefisso che identifica il tipo di oggetto: Form_, per le maschere e Report_ per i report.

L’ambiente di sviluppo   195

Quando selezioniamo uno di questi oggetti non accade nulla. Un clic sul pulsante Visualizza oggetto fa vedere la struttura della maschera o del report. Tornando all’Editor di Visual Basic, possiamo notare che si è riempita la finestra Proprietà, con l’elenco delle proprietà del controllo o della sezione che ha una routine evento. Contestualmente, a destra, nella finestra del codice, compare la prima routine evento associata all’oggetto selezionato. Se vi sono più routine evento per lo stesso oggetto, vengono elencate una di seguito all’altra. La finestra Proprietà elenca tutte le proprietà di ciascun oggetto, con i loro nomi in inglese, anche nelle versioni italiane di Access. La Figura 6.14 mostra la finestra Progetto nella quale è selezionata una maschera e la finestra Proprietà corrispondente alla maschera selezionata.

Figura 6.14  Le finestre Progetto e Proprietà.

La finestra Proprietà è articolata in due schede: in quella intitolata Alfabetico sono elencate tutte le proprietà in ordine alfabetico, mentre in quella intitolata Per categoria, le proprietà sono raggruppate secondo le schede in cui si articola la Finestra delle proprietà quando si lavora su maschere e report in visualizzazione Struttura. In entrambe le schede, a destra del nome in inglese di ciascuna proprietà compare il valore che ha al momento. È possibile modificare il valore di qualunque proprietà direttamente dall’Editor di Visual Basic, senza passare per la visualizzazione Struttura dell’oggetto; la modifica ha effetto quando si chiudono l’Editor diVisual Basic e l’oggetto: una finestra di messaggio chiede se si intende salvare la versione modificata o lasciare l’oggetto immutato. Quando si lavora con un modulo standard, la finestra Progetto risulta meno articolata, e la finestra Proprietà è praticamente vuota. presentando soltanto il nome del modulo, che può essere modificato riscrivendolo nella casella che sta a fianco della proprietà Name. Questa opzione si dimostra utile per assegnare un nome personalizzato a un nuovo modulo di classe indipendente, che viene creato con il nome predefinito Class1 quando si sceglie il comando Modulo di classe dal menu Inserisci. Nell’Editor di Visual Basic sono disponibili numerose altre finestre, che illustreremo nei prossimi paragrafi.

196  Capitolo 6

Gli strumenti per il debug L’ambiente di sviluppo che l’Editor di Visual Basic mette a disposizione non esaurisce le sue funzionalità in quelle di controllo formale degli enunciati, come abbiamo visto fin qui. In tutte le versioni di Access l’Editor di Visual Basic offre una serie di strumenti per eseguire in modo controllato le routine, allo scopo di individuare ed eliminare eventuali errori logici, fino a ottenere routine che fanno esattamente quello che si vuole ottenere. Da sempre gli americani chiamano scherzosamente bug i malfunzionamenti nei programmi, quale che sia la loro natura, per cui il processo col quale si individuano e si eliminano prende il nome di debug. Qualcuno, traendo ispirazione dal termine gergale formattare, costruito sull’inglese format, dice in pseudo italiano debaggare, ma è un’espressione grottesca, da evitare con cura. La strada dello sviluppo delle applicazioni è ripida, scoscesa e costellata da trappole insidiose, i bug, appunto. L’ambiente di sviluppo, l’Editor di Visual Basic, nel nostro caso, dà una grossa mano a prevenire gli errori più semplici, quelli che si insinuano durante la stesura degli enunciati, a causa di banali sviste durante la digitazione sulla tastiera: caratteri invertiti, segni di interpunzione sbagliati o mancanti e quant’altro. Qualunque errore di digitazione provoca un errore di sintassi, quindi le segnalazioni tempestive dell’Editor di Visual Basic contribuiscono a eliminare questa famiglia di errori. Purtroppo, però, gli errori di sintassi non sono gli unici che si possono commettere quando si scrive una routine. Gli errori veri, i bug autentici, si manifestano quando il programma è perfettamente corretto dal punto di vista sintattico, la compilazione viene eseguita senza intoppi, ma il risultato che si voleva ottenere non è quello desiderato. Facciamo un esempio molto semplice, tanto per capire di che cosa si sta parlando. Nella nostra routine sperimentale invertiamo la posizione delle due variabili, intTabelleEffettive e intNumTabelle, in modo che l’enunciato MsgBox assuma questa forma: MsgBox "Il database " & strNomeDB & " contiene " & intNumTabelle & _ " tabelle: " & intTabelleEffettive & " definite dall'utente e " & _ intTabelleEffettive – intNumTabelle & " tabelle di sistema"

Al controllo dell’Editor di Visual Basic l’enunciato risulta sintatticamente corretto, il compilatore non ha nulla da eccepire perché le variabili sono state tutte dichiarate prima di venir utilizzate, ma il risultato prodotto dall’esecuzione della routine è, a dir poco, sconcertante, come si vede dalla Figura 6.15:

Figura 6.15  Un errore logico nella routine produce un risultato inatteso.

L’ambiente di sviluppo   197

In un caso semplice come questo, ci si accorge subito che il risultato è sbagliato, dato che non è pensabile che esista un numero negativo di tabelle di sistema, ed è altrettanto semplice risalire all’origine dell’errore logico (l’inversione fra le due variabili). Nella realtà pratica dello sviluppo applicativo, però, le cose non sono altrettanto semplici: risultati sbagliati a volte vengono fuori mescolati con risultati giusti, le variabili in gioco sono molto più numerose e l’insidia può nascondersi ovunque. Facciamo un esempio un po’ più complesso. La routine Sub che segue chiede un input all’utente e va a controllare, mediante una chiamata a una routine Function, se la risposta che ha ottenuto dall’utente è corretta. La routine farà sempre comparire la scritta “La risposta è sbagliata” nella finestra di messaggio, quale che sia la risposta dell’utente: Sub SbagliaSempre() On Error Resume Next Dim intRisposta As Integer intRisposta = InputBox("Quanto fa due più due?")     If ControllaInput(intRisposta) Then         MsgBox "La risposta è giusta"     Else         MsgBox "La risposta è sbagliata"     End If End Sub Function ControllaInput(intValore As Integer) As Boolean     Dim blnRestituisce As Boolean     If intValore = 4 Then blnRestituisce = True End Function

L’insidia qui viene dal fatto che il programmatore ha dimenticato di assegnare alla funzione il risultato del confronto, per cui la funzione restituisce sempre False alla Sub che l’ha chiamata. Prima dell’enunciato End Function, in questo caso, occorre un enunciato di assegnazione ControllaInput = blnRestituisce

grazie al quale viene restituito alla routine chiamante il valore (True o False che sia), assunto dalla variabile blnRestituisce. Anche nel migliore dei mondi possibili, l’ambiente di sviluppo non può prevenire gli errori logici: come potrebbe, infatti, l’Editor diVisual Basic accorgersi che bisogna sottrarre il valore di intTabelleEffettive da quello di intNumTabelle e non viceversa? Come potrebbe accorgersi che a una funzione manca un enunciato? Quando si scrivono programmi, lo stesso risultato si può ottenere, spesso, in molti modi diversi, dipende dallo stile di chi programma, dalle sue abitudini, oltre che dai dati che ha a disposizione: sarebbe, quindi, impossibile creare un ambiente di sviluppo in grado di capire se manca un enunciato o se ce n’è uno di troppo o se un’operazione aritmetica non è quella richiesta da un dato contesto. Paradossalmente, la programmazione è ancora una delle attività più libere e più intellettualmente stimolanti che si possano svolgere con un computer, un’attività nella quale domina incontrastata, con tutte le sue debolezze, la mente umana e non la macchina. Tutto quello che si può fare, quindi, per agevolare le operazioni di debug è dare la possibilità al programmatore di esaminare da vicino, enunciato per enunciato e variabile

198  Capitolo 6

per variabile, il modo in cui viene eseguita una routine sospetta, per arrivare a stanare la svista logica che sta provocando l’errore. In termini pratici, per capire dove si annida un errore logico bisogna poter: •• eseguire i singoli enunciati uno per volta, passo per passo; •• esaminare i diversi valori che le variabili assumono a mano a mano che vengono eseguiti i singoli enunciati. Si hanno a disposizione svariate funzionalità per eseguire questo tipo di operazioni di controllo, alcune semplici e intuitive da utilizzare, altre un po’ più complesse, ma non meno utili.

L’esecuzione passo per passo Come abbiamo visto in precedenza, per eseguire una routine da un modulo standard basta premere il tasto di funzione F5, quando il puntatore del mouse si trova entro la routine che interessa. Lo stesso risultato si ottiene scegliendo il comando Esegui Sub/ UserForm dal menu Esegui o dalla barra degli strumenti Standard. Se si sospetta che una routine dia un risultato sbagliato, rieseguirla sequenzialmente tutta di seguito non fa altro che produrre lo stesso risultato spurio. Premendo il tasto F8 invece di F5, la routine viene eseguita un enunciato per volta, con una segnalazione visiva dell’enunciato attivo nel momento in cui si preme F8 (l’intera riga dell’enunciato viene visualizzata nel modulo con uno sfondo giallo e una piccola freccia compare a sinistra dell’enunciato, nel bordo grigio del modulo). A ogni pressione di F8 viene eseguito l’enunciato successivo, che non è necessariamente quello che viene subito dopo nel listato della routine: dopo un enunciato condizionale, tipo If...Then...Else, premendo F8 si passa all’enunciato che viene abilitato dal confronto. Già questo meccanismo aiuta a trovare eventuali errori logici: un’espressione di confronto male impostata può dare un ordine di esecuzione degli enunciati diverso da quello che il programmatore aveva creduto di impostare. Quando è in corso l’esecuzione passo per passo, è possibile esaminare il valore che assumono le variabili semplicemente appoggiando il puntatore del mouse sul nome della variabile che interessa, come si può vedere dalla Figura 6.16. Nella Figura 6.16 possiamo osservare che l’enunciato If ControllaInput(intRisposta) Then è in esecuzione, dopo l’enunciato che ha acquisito l’input dell’utente, quindi il valore della variabile intRisposta è uguale al numero digitato nella casella della finestra di input. Continuando a premere F8 si procede con l’esecuzione della funzione ControllaInput, nella quale il valore di intRisposta viene messo a confronto con il numero 4.Anche all’interno della funzione si possono osservare i valori assunti dalle variabili, semplicemente facendovi scorrere sopra il puntatore del mouse. L’esecuzione passo per passo è lo strumento cardine del debug: quando si ha qualche dubbio sul corretto funzionamento di una routine, come prima cosa bisogna eseguirla un enunciato alla volta, agendo sul tasto di funzione F8. Può capitare che una routine Sub chiami diverse routine Function, alcune delle quali si rivelano perfettamente corrette a una prima esecuzione passo per passo con F8, mentre altre lasciano ancora qualche dubbio.

L’ambiente di sviluppo   199

Figura 6.16  Quando una routine viene eseguita passo per passo si possono osservare i valori assunti dalle variabili.

In casi di questo genere si può ridurre la noia e la frustrazione che nascono dal ripercorrere enunciato per enunciato funzioni già collaudate premendo la combinazione di tasti Maiusc+F8, invece di F8, quando si arriva all’enunciato nella Sub che chiama una Function già collaudata. Questo comando fa eseguire la Function in un colpo solo e l’esecuzione passo per passo si riattiva al rientro nel corpo della Sub. Il comando del menu Debug che corrisponde a Maiusc+F8 si chiama Esegui istruzione/ routine. Analogamente, se si desidera uscire da un’esecuzione passo per passo, perché sarebbe troppo lungo continuare o perché ormai è chiaro dove sta l’errore ed è inutile procedere col controllo, si può premere la combinazione di tasti Ctrl+Maiusc+F8, invece di F8, provocando l’esecuzione senza pause di tutti gli enunciati rimanenti. Il comando del menu Debug che corrisponde a Ctrl+Maiusc+F8 si chiama Esci da istruzione/routine. Nel caso di routine molto lunghe, con alcune parti già collaudate e che non è il caso di eseguire passo per passo, si hanno a disposizione due funzionalità: il punto di interruzione e il comando Esegui fino al cursore. Il punto di interruzione si imposta facendo clic alla sinistra dell’enunciato che interessa, nella fascia grigia che fa da bordo alla finestra del codice. Il clic genera un bollino scuro nella fascia grigia ed evidenzia in colore rosso mattone tutto l’enunciato. Premendo F5, l’intera routine viene eseguita fino al punto di interruzione,

200  Capitolo 6

che, quando viene raggiunto, cambia grafica (nel bollino rosso si inserisce una freccetta gialla). Da lì in poi si può procedere con F8, se si intende eseguire i successivi enunciati uno per volta, o con F5 per procedere speditamente fino in fondo. Il punto di interruzione si toglie con un clic sul bollino che lo segnala. Si possono predisporre più punti di interruzione in una routine, in modo da poter procedere direttamente da un blocco di enunciati a un altro. Col comando Esegui fino al cursore, dal menu Debug, il risultato è uguale a quello che si ottiene inserendo un punto di interruzione: gli enunciati che precedono quello nel quale è inserito il puntatore del mouse (qui chiamato cursore) vengono eseguiti in successione e l’esecuzione si arresta sull’enunciato dove si trova il cursore. La differenza rilevante fra questa tecnica e la precedente sta nel fatto che il comando Esegui fino al cursore va attivato di volta in volta, mentre con la tecnica precedente si possono predisporre più punti di interruzione.

Le finestre di controllo Mentre è in corso l’esecuzione passo per passo, con F8 e con le possibili combinazioni di F8 con Maiusc e Ctrl, si possono osservare i valori assunti progressivamente dalle variabili col semplice accorgimento di appoggiare il puntatore del mouse sulla riga in cui c’è il loro nome. Ma questa funzionalità, che è comoda e immediata quando si vuole seguire l’andamento di una o al massimo due variabili, è decisamente scomoda quando serve un quadro complessivo e dinamico dell’andamento di tutte le variabili, che è l’esigenza più diffusa quando si lavora sul serio. L’Editor di Visual Basic mette a disposizione due strumenti che agevolano l’esame dei valori delle variabili. Si tratta di due finestre, chiamate Variabili locali e Immediata. Il loro funzionamento è semplice e intuitivo. Prima di far partire l’esecuzione passo per passo con F8, si apre la finestra Variabili locali selezionandola dal menu Visualizza. Come si può vedere dalla Figura 6.17, la finestra Variabili locali mostra i valori di tutte e tre le variabili insieme. Quello che non si può vedere dalla figura, perché è un’istantanea, è che i valori vengono visualizzati dinamicamente, a mano a mano che cambiano in conseguenza dell’esecuzione dei singoli enunciati che vengono fatti passare uno dopo l’altro premendo F8. La finestra di controllo Immediata è utilizzabile per due funzionalità che possono dimostrarsi molto utili e comode in sede di prova. Quando si controlla un modulo standard che contiene molte routine Sub e Function, potrebbe essere necessario eseguire al volo una routine, per una qualunque ragione. Invece di portarsi sulla routine che interessa, per eseguirla premendo F5, è possibile restare sulla posizione nella quale ci si trova ed eseguire la routine digitandone semplicemente il nome nella finestra Immediata. Dopo aver scritto il nome della routine, la pressione di Invio la esegue. A questa prima funzionalità, che può essere molto comoda per eseguire rapidi controlli di funzionamento di qualunque routine, si aggiungono le funzionalità offerte da un oggetto Visual Basic disponibile soltanto nell’Editor di Visual Basic, che si chiama Debug. Questo oggetto è dotato di due metodi, chiamati Print e Assert.

L’ambiente di sviluppo   201

Figura 6.17  La finestra Variabili locali visualizza dinamicamente i valori assunti da tutte le variabili mentre la routine viene eseguita.

I metodi dell’oggetto Debug Il metodo Print dell’oggetto Debug, come il suo nome lascia intuire, serve per stampare, non su carta, ma all’interno della finestra Immediata, il valore di una variabile. Ciò che viene scritto mediante il metodo Print nella finestra Immediata vi rimane fino a quando non lo si cancella deliberatamente con un comando manuale (selezionando il testo e premendo il tasto Canc). Per questa sua caratteristica, il metodo Print si dimostra molto comodo, mentre si lavora in prova, per creare elenchi provvisori di risultati parziali, da esaminare con calma in un secondo tempo. Un piccolo esempio aiuterà a chiarire il meccanismo. Eseguendo la seguente routine in un modulo standard: Sub ListaTabelle()     Dim dbTest As DAO.Database, tdfTabella As Object     Dim intNumTabelle As Integer, intTabelleEffettive As Integer     Dim strNomeDB As String, intContatore As Integer     strNomeDB = "C:\Esempi\NorthwindLavoro.accdb"     Set dbTest = OpenDatabase(strNomeDB)     intNumTabelle = dbTest.TableDefs.Count     For intContatore = 0 To intNumTabelle - 1         Set tdfTabella = dbTest.TableDefs(intContatore)         Debug.Print tdfTabella.Name     Next intContatore End Sub

202  Capitolo 6

si ottiene, nella finestra Immediata dell’Editor di Visual Basic, un elenco come questo: Categorie Clienti Corrieri Dettagli ordini Fornitori Impiegati MSysAccessStorage MSysAccessXML MSysACEs MSysComplexColumns MSysIMEXColumns MSysIMEXSpecs MSysNameMap MSysNavPaneGroupCategories MSysNavPaneGroups MSysNavPaneGroupToObjects MSysNavPaneObjectIDs MSysObjects MSysQueries MSysRelationships MSysResources Ordini Prodotti

nel quale sono riconoscibili le tabelle di sistema (quelle il cui nome comincia con “MSys”) e le tabelle del database dimostrativo Northwind. Invece dell’enunciato: Debug.Print tdfTabella.Name

avremmo potuto usare l’istruzione MsgBox, con la forma: MsgBox tdfTabella.Name

ma così facendo: 1. avremmo ottenuto la comparsa di una nuova e distinta finestra di messaggio per ogni diverso valore dell’espressione tdfTabella.Name; 2. ogni volta avremmo dovuto chiudere la singola finestra di messaggio facendo clic sul suo pulsante OK; 3. alla fine del ciclo non sarebbe rimasta alcuna traccia utilizzabile dei nomi delle tabelle. Con Debug.Print, invece, i valori vengono depositati nella finestra Immediata, dalla quale se ne può estrarre una copia, da stampare su carta o da salvare come documento di testo o come foglio di lavoro Excel. La copia si fa con il normale procedimento di copia e incolla disponibile in tutte le applicazioni Windows, dopo aver selezionato la parte di testo che interessa all’interno della finestra Immediata. L’oggetto Debug è dotato di un ulteriore metodo oltre a Print, il metodo Assert, che si può utilizzare per predisporre un arresto condizionale nell’esecuzione di una routine.

L’ambiente di sviluppo   203

La sua sintassi è la seguente: Debug.Assert espressione booleana

dove espressionebooleana è un’espressione che produce un risultato logico Vero o Falso. Quando questa espressione diventa vera, nell’esecuzione della routine che la contiene, l’esecuzione si arresta nel punto in cui compare l’enunciato Debug.Assert e viene aperto l’Editor diVisual Basic in corrispondenza dell’enunciato Assert.Vediamo con un semplice esempio come funziona meccanismo. Scriviamo questa routine in un modulo standard: Sub Asserzione() On Error Resume Next     Dim intNumero As Integer     intNumero = InputBox("Digitare un numero maggiore di 10, per favore!")     Debug.Assert (intNumero > 10)     MsgBox "Hai digitato: " & intNumero End Sub

Salviamo il modulo ed eseguiamo la routine di prova. Se digitiamo un numero qualunque maggiore di dieci nella finestra di input, otteniamo in risposta la comparsa di una finestra di messaggio che conferma il valore immesso, mentre se inseriamo un numero minore di dieci otteniamo l’effetto che possiamo vedere nella Figura 6.18.

Figura 6.18  Il metodo Assert dell’oggetto Debug si è attivato e ha aperto l’Editor di Visual Basic, evidenziando la riga di codice che richiama il metodo.

Inserendo enunciati che richiamano il metodo Assert in punti strategici di una routine complessa si può verificare immediatamente quando il valore di una variabile logica

204  Capitolo 6

assegnata al metodo stesso passa da Vero a Falso, col vantaggio di trovarsi immediatamente posizionati, entro l’Editor di Visual Basic, sulla riga della routine che contiene l’enunciato di controllo.

Le espressioni di controllo Si danno casi in cui certe routine appaiono (e sono) perfettamente ben costruite dal punto di vista logico, eppure continuano a provocare in modo occasionale frustranti errori che non si riescono a stanare con gli strumenti di debug che abbiamo descritto fin qui. L’esperienza insegna che, nella maggior parte di questi casi, l’errore è provocato da un’anomalia nei dati sui quali si sta lavorando, piuttosto che da una svista logica nelle routine che li elaborano. Per esempio, supponiamo di elaborare una sequenza di campi che contengono date, che dovrebbero ricadere tutte in un determinato intervallo; la nostra routine prende ciascuna data e la sottrae da una data di riferimento, fissa, per calcolare un intervallo in giorni. Il valore così ottenuto diventa il divisore in un’espressione che calcola una media giornaliera. Se una di queste date, per qualunque ragione, fosse fuori scala, la sottrazione potrebbe generare un valore zero, che, utilizzato come divisore, provocherebbe un classico errore di divisione per zero. La nostra routine potrebbe avere una struttura simile a quella riportata qui di seguito: Sub MediaGiornaliera()     Dim db As Database, rst As DAO.Recordset     Dim dtmIniziale As Date, dtmFinale As Date     Dim lngQuantità As Long, lngDurata As Long, lngMedia As Long     Set db = CurrentDb     Set rst = db.OpenRecordset("tblVolumi")     Do While Not rst.EOF         dtmIniziale = rst.Fields("Inizio").Value         dtmFinale = rst.Fields("Fine").Value         lngQuantità = rst.Fields("Quantità").Value         lngDurata = dtmFinale - dtmIniziale         lngMedia = lngQuantità / lngDurata         Debug.Print lngMedia         rst.MoveNext     Loop     rst.Close End Sub

L’elaborazione è molto semplice: 1. si definiscono le variabili necessarie per il calcolo, che avverrà su una tabella nel database aperto chiamata tblVolumi; 2. si apre la tabella e si imposta un ciclo iterativo, che termina quando si arriva al valore EOF (End Of File) della tabella; 3. nel ciclo, per ogni record si prendono i contenuti dei campi Inizio, Fine e Quantità e li si assegna alle variabili definite in apertura; 4. si calcola la differenza fra le date Fine e Inizio (mediante le variabili dtmFinale e dtmIniziale) e si assegna il valore così ottenuto alla variabile lngDurata;

L’ambiente di sviluppo   205

5. si divide il valore del campo Quantità (variabile lngQuantità) per la variabile lngDurata; 6. con il metodo Print dell’oggetto Debug si stampa nella finestra Immediata il valore della media così ottenuta (variabile lngMedia). La routine viene eseguita senza problemi quando si preme F5 e, se andiamo a esaminare il contenuto della finestra Immediata, vediamo una successione di numeri decimali, che corrispondono ai valori via via ottenuti per la variabile curMedia. Se, però, uno dei record di tblVolumi contenesse la stessa data nei campi Inizio e Fine, l’esecuzione della routine, arrivata a quel record, si interromperebbe con la segnalazione di errore che vediamo nella Figura 6.19.

Figura 6.19  L’errore di run-time Divisione per zero blocca l’applicazione.

Come è noto, in aritmetica non è consentito dividere un numero per zero e tutti i linguaggi di programmazione contengono un meccanismo di controllo che intercetta eventuali richieste di divisione per zero, segnalando, come in questo caso, un errore di runtime, ovvero un errore che si verifica nel momento in cui il programma viene eseguito. Gli errori di questo tipo sono i veri nemici dei programmatori e alla loro prevenzione e gestione è dedicato l’intero Capitolo 10. Restando al nostro esempio, qual è la strada da seguire per eliminare questo bug? Ma è davvero un bug, cioè un errore logico nella routine?Verrebbe da rispondere di no, perché in questo caso la routine fa il suo dovere: l’errore sta nei dati. L’argomento è, però, debole e pretestuoso: in programmazione non si tratta mai di stabilire chi ha ragione o chi ha torto, quel che conta è il risultato. Se si ha il sospetto (sempre fondato) che i dati potrebbero non corrispondere alle attese, bisogna predisporre opportuni meccanismi di controllo per evitare errori di run-time. Prima, però, bisogna essere sicuri che il problema, in questo caso, venga dai dati. La segnalazione di errore parla di “Divisione per zero”, quindi l’errore si manifesta nel momento in cui viene eseguito l’enunciato che contiene la divisione. Mettiamo allora sotto osservazione le variabili coinvolte, prima di passare a eseguire la routine passo per passo con F8. L’enunciato che ci interessa è: lngMedia = lngQuantità / lngDurata

206  Capitolo 6

quindi le variabili da tenere sotto osservazione sono il divisore lngDurata e le due variabili che ne determinano il valore, cioè dtmFinale e dtmIniziale, dato che lngQuantità, il dividendo, non potrebbe provocare errori anche se fosse zero. Dal menu Debug scegliamo il comando Aggiungi espressione di controllo. Si apre una finestra di dialogo con lo stesso nome, che andiamo a compilare in modo che assuma l’aspetto di quella riprodotta nella Figura 6.20.

Figura 6.20  Con la finestra di dialogo Aggiungi espressione di controllo si possono selezionare espressioni e variabili da tenere sotto osservazione.

I valori che immettiamo nella finestra di dialogo sono: •• il nome della variabile lngDurata nella casella di testo Espressione; •• le caselle di riepilogo Routine e Modulo hanno già i valori che ci interessano, cioè identificano il contesto in cui si troverà l’espressione di controllo; •• per quanto riguarda il Tipo di espressione di controllo, selezioniamo il pulsante di opzione Interrompi se il valore cambia. In questo modo, la finestra Espressioni di controllo visualizzerà ogni variazione di valore di lngDurata. Chiudiamo la finestra di dialogo con un clic su OK e riapriamola altre due volte, per immettere nella casella di testo Espressione i nomi delle variabili dtmFinale e dtmIniziale, i cui valori determinano quello di lngDurata. Per queste due espressioni scegliamo il pulsante di opzione Espressione di controllo nel riquadro Tipo di espressione di controllo. Ogni volta che chiudiamo la finestra di dialogo Aggiungi espressione di controllo con un clic su OK, questa si chiude e al suo posto compare la finestra Espressioni di controllo, mostrando il nome della variabile che è stata appena aggiunta. A questo punto siamo pronti per eseguire in modo controllato la nostra routine, premendo ripetutamente F8 per eseguire gli enunciati nel ciclo While...Loop. Mentre eseguiamo questa operazione, possiamo osservare l’evoluzione dei valori delle variabili che abbiamo messo sotto controllo, fino a quando, come era da prevedersi, vediamo (Figura 6.21) che le due variabili dtmFinale e dtmIniziale assumono lo stesso valore, evidenziando il corrispondente valore che assume la variabile lngDurata, cioè zero, che è all’origine dell’errore.

L’ambiente di sviluppo   207

Figura 6.21  Tenendo sotto osservazione le variabili critiche si scopre l’origine dell’errore.

Se non siamo ancora convinti che l’errore nasca dal fatto che lngDurata ha assunto valore zero, possiamo interferire con l’esecuzione della routine selezionando il valore di lngDurata nella finestra Espressioni di controllo e scrivendo al suo posto un valore diverso da zero. Ulteriori pressioni di F8 fanno proseguire l’esecuzione della routine utilizzando il nuovo valore digitato per la variabile lngDurata e, dal momento che non si manifesta più l’errore di run-time Divisione per zero, la routine arriva correttamente in fondo alla tabella e si arresta. Quando si segue l’andamento di alcune espressioni di controllo, in una routine particolarmente complessa e ricca di variabili, potrebbe venir voglia di esaminare il valore che ha assunto una particolare variabile, non compresa fra quelle che vengono tenute sotto osservazione nella finestra Espressioni di controllo. Per soddisfare questa esigenza, basta selezionare il nome della variabile che interessa e scegliere il comando Controllo immediato dal menu Debug. Il nome della variabile compare in una finestra di dialogo intitolata Controllo immediato, con l’indicazione del contesto in cui si trova e del valore che ha in quel momento (Figura 6.22).

Figura 6.22  Si possono esaminare i valori istantanei assunti da una variabile mediante la funzionalità Controllo immediato.

Se non interessa continuare a tenerla sotto osservazione, si può chiudere la finestra di dialogo facendo clic su Annulla. Per aggiungere la variabile a quelle presenti nella finestra Espressioni di controllo, in modo da sorvegliarne l’evoluzione, si fa clic sul pulsante Aggiungi.

208  Capitolo 6

Lo stack di chiamate In programmazione si creano spesso routine annidate, che vengono chiamate da un’altra routine. E può capitare che l’annidamento si sviluppi su più livelli, per cui la routine B, chiamata dalla routine A, chiama a sua volta una routine C, per ottenere i risultati da fornire ad A. Stanare un errore logico in un contesto di routine annidate può essere un’impresa molto ardua, in particolar modo perché è difficile sapere quale routine è stata chiamata da quale altra e in quale momento. Per dare una mano a risolvere problemi di questo tipo esiste una funzionalità di debug chiamata Stack di chiamate, attivabile dal menu Visualizza quando una routine è in esecuzione passo per passo. Si chiama stack nel gergo della programmazione una serie di informazioni disposte idealmente una sull’altra in modo da formare una pila o una colonna. La finestra di dialogo Stack di chiamate visualizza appunto le chiamate alle routine, impilandole una sull’altra, la prima in fondo all’elenco e le altre sopra, nell’ordine in cui vengono effettuate. Ogni chiamata è identificata dal nome del modulo che contiene la routine, seguito sulla stessa riga dal nome della routine chiamata. Aprendo la finestra di dialogo Stack di chiamate, quando è in esecuzione una routine che contiene altre routine (Sub o Function) annidate, si può controllare l’ordine in cui le chiamate sono state effettuate (tenendo presente che la successione va seguita leggendo lo stack dal basso verso l’alto). Selezionando il nome di una particolare routine nello stack e facendo clic sul pulsante Mostra, si viene portati sulla routine specificata. Per vedere all’opera lo stack e capire il funzionamento della finestra di dialogo Stack di chiamate, modifichiamo la routine di prova che abbiamo usato finora, sostituendo con un paio di funzioni alcuni enunciati e creiamo una nuova routine, che chiameremo Pilota, che attiva la routine così modificata. Il risultato delle modifiche assume l’aspetto seguente: Sub Pilota()     Call MediaGiornaliera2 End Sub Sub MediaGiornaliera2()     Dim db As Database, rst As DAO.Recordset     Dim dtmIniziale As Date, dtmFinale As Date     Dim strTabella As String     Dim lngCosto As Long, lngDurata As Long, lngMedia As Long     Set db = CurrentDb     strTabella = Quale()     Set rst = db.OpenRecordset(strTabella)     Do While Not rst.EOF         dtmIniziale = rst.Fields("Inizio").Value         dtmFinale = rst.Fields("Fine").Value         lngCosto = rst.Fields("Quantità").Value         lngDurata = Durata(dtmFinale, dtmIniziale)         lngMedia = lngCosto / lngDurata         Debug.Print lngMedia         rst.MoveNext     Loop End Sub

L’ambiente di sviluppo   209 Function Quale() As String     Quale = InputBox("Quale tabella?") End Function Function Durata(Finale As Date, Iniziale As Date) As Long     Durata = Finale – Iniziale End Function

Eseguiamo ora la routine Pilota passo per passo con F8, proseguiamo fino a quando viene chiamata la funzione Quale(), e a questo punto apriamo la finestra di dialogo Stack di chiamate. Come possiamo vedere dalla Figura 6.23, le chiamate sono elencate dal basso verso l’alto, prima quella eseguita dalla routine Pilota, poi quella effettuata da MediaGiornaliera2 e poi quella di Quale(). Selezionando il nome della prima chiamata (quella in fondo allo stack) e premendo il pulsante Mostra, compare un segnalatore (punta di freccia verde) in corrispondenza del nome della chiamata selezionata.

Figura 6.23  La finestra di dialogo Stack di chiamate aiuta a capire la successione delle chiamate nel caso di routine annidate.

Tutte le funzionalità per il debug con le loro relative finestre di servizio che abbiamo descritto in questo paragrafo si possono attivare sia selezionandole dalla voce di menu Debug, sia, più intuitivamente e più rapidamente, facendo clic sul pulsante di comando corrispondente in una barra degli strumenti chiamata Debug, che si presenta come nella figura che segue:

210  Capitolo 6

Per poter utilizzare questa comoda barra degli strumenti bisogna attivarla selezionando il comando Visualizza/Barre degli strumenti/Debug dal menu Standard dell’Editor di Visual Basic

Gli errori di run-time L’ambiente di sviluppo creato dall’Editor di Visual Basic mette a disposizione, come abbiamo visto, numerose funzionalità per rendere più agevole la scrittura di codice di programma libero da errori di sintassi. È materialmente impossibile scrivere una routine VBA che contenga questo genere di errori: gli strumenti di controllo li intercettano con implacabile rigore e ne impongono la correzione, perché una routine che contiene un errore di sintassi non viene compilata, quindi non è eseguibile. Un notevole contributo alla stesura di routine formalmente corrette viene dato anche dalle funzionalità di suggerimento e di evidenziazione, come abbiamo visto, che al momento opportuno elencano gli unici elementi che è corretto inserire in un dato punto o presentano lo schema sintattico di un’istruzione. Pur con tutta la sua potenza e flessibilità, l’Editor di Visual Basic non può prevenire gli errori logici, che possono essere dei più vari tipi e sono all’origine delle vere difficoltà della programmazione. Molte volte, ma non sempre, quando si esegue un’applicazione un errore logico produce un errore di run-time, che talvolta può essere analizzato con l’aiuto dell’Editor di Visual Basic, fino a individuare le cause del malfunzionamento. Gli errori di run-time sono la peggiore insidia che possa minacciare un’applicazione e occorre mettere ogni impegno per prevenirli o almeno per gestirne correttamente le conseguenze, quando dovessero manifestarsi. L’argomento va ben oltre il contesto relativamente semplice dell’ambiente di sviluppo e verrà trattato a parte, nel Capitolo 10, dedicato specificamente a questo tema.

Capitolo 7

Lavorare con VBA

Con la sigla VBA ci si riferisce a un linguaggio di programmazione chiamato Visual Basic for Applications, che è disponibile in tutte le applicazioni che formano il pacchetto commerciale Microsoft Office.

VBA: un BASIC per gli oggetti Il nome di questo linguaggio contiene un riferimento (che è anche un omaggio, in un certo senso) a uno dei più vecchi linguaggi di programmazione che siano mai stati inventati, il BASIC, nato in un istituto tecnico americano nel remoto 1964 come strumento didattico. Il nome BASIC viene dalle iniziali della definizione di quel linguaggio, concepito per essere un Beginner’s All-purpose Symbolic Instruction Code, cioè, più o meno, un “Codice di istruzioni simboliche buono a tutti gli usi per principianti”. Concepito come strumento per insegnare la programmazione negli istituti tecnici e nelle scuole superiori, in un’epoca in cui non esistevano personal computer, ma soltanto grandi mainframe ai quali si cominciava a poter accedere da terminali a distanza, il BASIC rimase per una quindicina d’anni un supporto didattico, che aiutava i principianti a capire come mettere insieme variabili e istruzioni per far scrivere dal computer “Ciao mamma!” sulla telescrivente che allora si usava come stampante di servizio. Quando nacquero i primi microcomputer, verso la fine degli anni Settanta del secolo scorso, il BASIC venne in un certo senso riscoperto e fu adottato da tutti i costruttori di queste macchine. Fu proprio realizzando un interprete del linguaggio BASIC per

In questo capitolo •

VBA: un BASIC per gli oggetti



Gli schemi sintattici



Gli elementi degli enunciati



Le categorie degli enunciati

212  Capitolo 7

uno dei primi microcomputer che l’allora diciottenne Bill Gates si fece conoscere e apprezzare come programmatore e come imprenditore. Molti anni sono passati da allora, Bill Gates non è più un ragazzo prodigio, ma il cuore di programmazione di Microsoft Office porta ancora il nome Basic, sia pure con una grafia diversa, in ricordo dei primi passi compiuti dal fondatore di Microsoft lungo la strada dello sviluppo dei linguaggi di programmazione.

Un po’ di storia Qualche anno dopo l’uscita dei primi Personal Computer IBM nel 1981, si diffusero alcuni prodotti applicativi chiamati “integrati”, che presentavano un unico ambiente nel quale era possibile scrivere testi, creare fogli di calcolo e tabelle di database, passando con semplicità da un contesto all’altro ed eventualmente utilizzando lo stesso insieme di dati in più applicazioni. Alcuni di questi prodotti, per esempio FrameWork, erano dotati anche di un linguaggio di programmazione col quale si potevano scrivere procedure per elaborare dati condivisi fra le applicazioni. Pur trattandosi di soluzioni software pregevoli e non prive di eleganza, le limitate potenze elaborative disponibili allora e i pesanti vincoli imposti dal sistema operativo MS-DOS rendevano molto difficile creare applicazioni di un qualche rilievo. Gli integrati non fecero molta strada, ma l’idea rimase, per riprendere forma nel nuovo contesto applicativo creato dall’affermarsi di Windows. La Microsoft, che aveva creato Excel e Word per l’ambiente grafico dei Macintosh, ne sviluppò una versione per i PC sotto Windows e iniziò a distribuirli anche nella confezione che chiamò Microsoft Office, che non era un integrato, ma semplicemente una proposta commerciale (Office costava meno della somma dei prezzi dei prodotti acquistati separatamente). Col 1993 Microsoft mise in commercio una variante di Office che includeva anche Access, oltre a Excel e Word. I tre prodotti lavoravano in modo indipendente, ma era possibile scambiare dati da un ambiente applicativo a un altro con operazioni manuali o ricorrendo a complessi meccanismi di programmazione chiamati DDE (da Dynamic Data Exchange). A partire dalla versione per Windows 95 (che comprendeva Excel 5, Word 6 e Access 6), ciascun prodotto disponeva di un proprio linguaggio di programmazione interno, che era però diverso nei vari contesti: si chiamava VBA in Excel, WordBasic in Word e AccessBasic in Access. Con Office 97 i linguaggi vennero unificati e da allora tutti i prodotti che fanno parte del pacchetto (oltre a Word, Excel e Access ci sono anche PowerPoint e Outlook) condividono lo stesso linguaggio di programmazione, il Visual Basic for Applications, che un ampio sottoinsieme del Visual Basic.

Le molte difficoltà che si incontrano nello sviluppare applicazioni Access non vengono, dunque, dalla complessità delVBA, ma dai numerosissimi vincoli che si devono conoscere e rispettare quando si usano i semplici costrutti del Visual Basic per lavorare con oggetti, metodi, proprietà ed eventi, che, al contrario, non sono affatto semplici. In questo capitolo presentiamo una breve panoramica del VBA e della sua sintassi, in modo che i lettori abbiano chiare le funzionalità specifiche di questo linguaggio e sappiano distinguerle da quelle degli oggetti e degli eventi, che tratteremo nei prossimi capitoli di questa Parte III.

Lavorare con VBA   213

Gli schemi sintattici I linguaggi di programmazione, come le lingue parlate e scritte, hanno un lessico e una sintassi. Diversamente dalle lingue umane, questi linguaggi sono caratterizzati da un lessico limitatissimo (poche decine di parole chiave: per l’esattezza, 42 nel VBA) e da una sintassi molto più rigorosa, ma infinitamente meno complessa: imparare un linguaggio di programmazione è questione di qualche giorno di lettura e di pratica, mentre per imparare una lingua naturale diversa dalla propria a volte non basta un’intera vita. Con il Visual Basic for Applications si scrivono enunciati, l’equivalente delle frasi che si formulano con le lingue naturali. Un enunciato è formato con istruzioni, rappresentate con una o più parole chiave, che possono essere seguite da segni di interpunzione (parentesi tonde, punti, virgole, punti e virgola), da caratteri particolari che identificano operatori (simboli di uguale, maggiore di, minore di, più, meno e così via) e da nomi di elementi sui quali l’enunciato agisce. La scrittura degli enunciati VBA è regolata da norme di sintassi, che si rappresentano con una convenzione tipografica particolare, adottata da tutta la documentazione in linea del VBA e in tutti i testi, come questo, che descrivono il linguaggio. In base a questa convenzione tipografica: •• tutto ciò che è obbligatorio scrivere in un enunciato viene presentato in carattere grassetto; •• gli elementi sui quali l’enunciato agisce sono scritti in corsivo; •• eventuali parole chiave o elementi facoltativi sono racchiusi fra [parentesi quadre] •• se è consentito scegliere un elemento obbligatorio fra diversi elementi possibili, questi vengono indicati fra parentesi graffe e separati da {una | barra | verticale}; Vediamo un esempio nel quale sono presenti tutte le convenzioni tipografiche per rappresentare la struttura sintattica di un enunciato: Set variabileoggetto = {[New] espressioneoggetto | Nothing}

Questo schema dice che: •• la parola chiave Set è sempre obbligatoria; •• dopo la parola chiave Set è obbligatorio inserire un elemento che identifichi una variabile oggetto; •• dopo la variabile oggetto è obbligatorio il segno di uguale; •• dopo il segno di uguale è facoltativo l’uso della parola chiave New; •• che si sia usata oppure no la parola chiave New, è obbligatorio un elemento che identifichi un oggetto; •• in alternativa a tale elemento si può usare la parola chiave Nothing; •• non è consentito usare insieme New e Nothing in uno stesso enunciato Set. In termini pratici, sono legittime le seguenti formulazioni dell’enunciato Set: Set rstClienti = db.OpenRecordset("tblClienti") Set cmdPulsante1 = New CommandButton Set rstClienti = Nothing

214  Capitolo 7

Gli elementi degli enunciati Quelli che abbiamo indicato col termine generico “elementi” nel paragrafo precedente sono stringhe di caratteri che compongono parole chiave, operatori, variabili, costanti ed espressioni. Delle parole chiave abbiamo già parlato, formano il lessico di VBA in senso stretto e ogni enunciato ne contiene almeno una. Descriviamo brevemente gli altri elementi che si possono trovare in un enunciato.

Operatori Si tratta di simboli o parole chiave usati per rappresentare operazioni (aritmetiche, logiche, di confronto e di concatenamento), da cui il nome. Si distinguono quattro famiglie di operatori: Aritmetici, per eseguire calcoli aritmetici: +, -, * (moltiplicazione), / (divisione), ^ (elevamento a potenza), \ (divisione con risultato intero), Mod (divisione, dà come risultato soltanto il resto, se esiste).Vediamo qualche esempio per Mod e \: Dim Risultato Risultato = 10 Mod 5       'Risultato Risultato = 10 Mod 3       'Risultato Risultato = 12 Mod 4.3     'Risultato Risultato = 12.6 Mod 5     'Risultato

0. 1. 0. 3.

Dim Valore Valore = 11 \ 4   'Valore 2. Valore = 9 \ 3    'Valore 3. Valore = 100 \ 3  'Valore 33.

Di confronto, per eseguire confronti fra dati: = (uguale a), > (maggiore di), < (minore di), >= (maggiore di o uguale a), "4")     'Risultato Vero. Var1 = "5": Var2 = 4        'Inizializza le variabili. Risultato = (Var1 > Var2)   'Risultato Vero.

Di concatenamento, per concatenare fra loro stringhe di caratteri: & (concatena stringhe), + (esegue una somma, se i dati sui quali agisce l’operatore sono numerici; concatena, se i dati sono stringhe). Ecco qualche esempio di uso degli operatori di concatenamento: Dim StrProva StrProva = "Mi chiamo" & " Bond, James Bond" 'StrProva "Mi chiamo Bond, James Bond". StrProva = "Prova " & 123 & " Prova"         'StrProva "Prova 123 Prova".

Lavorare con VBA   215 Dim Numero, Var1, Var2 Numero = 2 + 2             'Numero 4. Numero = 4257.04 + 98112   'Numero 102369,04. Var1 = "34": Var2 = 6      'Inizializza le variabili miste. Numero = Var1 + Var2       'Numero 40. Var1 = "34": Var2 = "6"    'Inizializza le variabili con stringhe Numero = Var1 + Var2       'Numero "346" (stringhe concatenate).

Logici, utilizzati per eseguire operazioni logiche: And, Or, Xor, Not, Eqv (equivalenza), Imp (implicazione). Qualche esempio per gli operatori Eqv e Imp: Dim A, B, A = 10: B Verifica = Verifica = Verifica = Verifica =

C, D, Verifica = 8: C = 6: D = Null   'Inizializza le variabili. A > B Eqv B > C        'Verifica Vero. B > A Eqv B > C        'Verifica Falso. A > B Eqv B > D        'Verifica Nullo. A Eqv B                'Verifica -3.

Dim A, B, A = 10: B Verifica = Verifica = Verifica = Verifica = Verifica = Verifica =

C, D, Verifica = 8: C = 6: D = Null   'Inizializza le variabili. A > B Imp B > C        'Verifica Vero. A > B Imp C > B        'Verifica Falso. B > A Imp C > B        'Verifica Vero. B > A Imp C > D        'Verifica Vero. C > D Imp B > A        'Verifica Nullo. B Imp A                'Verifica -1.

Variabili Una variabile è una posizione di memoria, che viene riservata da VBA per contenere un dato, il cui valore può essere variato durante l’esecuzione del programma, da cui il nome. Una variabile è identificata da una stringa che: •• deve iniziare sempre con una lettera; •• non può essere più lunga di 255 caratteri; •• non può contenere i caratteri punto, punto esclamativo, spazio, @, &, $, #; •• deve essere diversa da qualunque parola chiave VBA. Affinché ilVBA riconosca una variabile, è necessario dichiararla espressamente nella routine che la utilizza (se è stata impostata l’opzione Dichiarazione di variabili obbligatoria). In alternativa,VBA considera come nome di variabile qualsiasi stringa diversa da una parola chiave che trova in un enunciato. Le conseguenze a volte possono essere sconcertanti.

Tipi di dati La classificazione dei dati in tipi, che in Access è fondamentale, è altrettanto importante in VBA, ma con minore rigidità. Ai tipi di dati Access, che abbiamo esaminato e descritto nel Capitolo 2 parlando della struttura delle tabelle, corrispondono tipi analoghi in VBA, secondo lo schema riportato nella Tabella 7.1.

216  Capitolo 7 Tabella 7.1  Corrispondenza fra tipi di dati Access e VBA. Tipo di dato Access

Tipo di dato VBA

Testo

String

Memo Numerico Data/ora Valuta Numerazione automatica Sì/No Oggetto OLE Collegamento ipertestuale

Non disponibile Byte, Integer, Long, Single, Double Date Currency Non disponibile Boolean Object Non disponibile

I tipi di dati numerici utilizzabili in VBA sono gli stessi che si possono impostare nelle strutture delle tabelle Access e si caratterizzano come nella Tabella 7.2. Tabella 7.2  I tipi di dati numerici disponibili in Visual Basic for Applications. Tipo numerico Descrizione

Dimensione

Byte

Un numero intero e positivo compreso tra zero e 255.

1 byte

Integer Long

Un numero intero compreso tra -32.768 e 32.767. 2 byte Un numero intero compreso tra -2.147.483.648 e 2.147.483.647. 4 byte

Single

Un numero reale espresso in virgola mobile compreso tra -3.402823E38 e -1.401298E-45 per valori negativi e fra 1.401298E-45 e 3.402823E38 per valori positivi.

4 byte

Double

Un numero reale espresso in virgola mobile compreso fra -1.79769313486231E308 e -4.94065645841247E-324 per valori negativi e fra 1.79769313486231E308 e 4.94065645841247E324 per valori positivi.

8 byte

Currency

Un numero compreso fra -922.337.203.685.477,5808 e 922.337.203.685.477,5807.

8 byte

Diversamente da quanto accade per i tipi di dati delle tabelle Access, è disponibile in VBA un tipo di dato universale o generico, detto Variant. Una variabile che abbia questo tipo di dato può rappresentare qualunque informazione, dal minuscolo byte a una stringa di 65.000 caratteri. Quando si dichiara una variabile è consentito non dichiararne anche il tipo di dato, nel qual caso il VBA le attribuisce il tipo Variant. L’elasticità che si ottiene adottando il tipo Variant si paga con una maggiore occupazione di memoria: una variabile di tipo Variant occupa, infatti, da 16 a 22 byte. Una variabile può essere utilizzata per rappresentare anche una serie strutturata di informazioni, oltre che un singolo dato, numerico o stringa. Informazioni strutturate di questo genere sono sostanzialmente tabelle residenti in memoria, utilizzate per depositarvi provvisoriamente dati da ordinare o da gestire in modo sequenziale. Stiamo parlando di matrici, dette in VBA array.

Lavorare con VBA   217

Una matrice è un gruppo di elementi che hanno tutti lo stesso tipo di dato; ciascun elemento è caratterizzato da un numero indice univoco e sequenziale che lo individua. Una matrice può essere formata da un minimo di una colonna di elementi indicizzati fino a un massimo di 60 colonne. Fra le colonne di una matrice e quelle di una tabella esiste una differenza concettuale importante, che fa sì che le “colonne” di una matrice vengano chiamate “dimensioni”, termine che in questo caso non ha nulla a che fare con le dimensioni dello spazio fisico. Una matrice a una sola dimensione potrebbe rappresentare un libro, per esempio, con i valori degli indici di quell’unica dimensione che identificano le pagine di quel libro. Una matrice a due dimensioni può rappresentare, con la seconda dimensione, i libri che stanno su una mensola, con un indice che individua i singoli libri e un indice per le pagine di ogni libro. Con tre dimensioni si possono rappresentare le pagine di ogni singolo libro, i libri su una mensola e le mensole di una libreria. Quindi la riga di una matrice a tre dimensioni che contenga i valori 81, 7 e 10 può individuare la pagina 81 del settimo libro che sta nella decima mensola. Se in una stanza esistono più librerie, una quarta dimensione può distinguerle l’una dall’altra, e una quinta dimensione potrebbe individuare le varie stanze di una grande biblioteca e così via. Per individuare un singolo elemento in una matrice a più dimensioni si utilizzano due numeri indice, il primo per la riga e il secondo per la colonna. La numerazione degli elementi parte da zero, per cui l’espressione Vettore(3)

indica il quarto elemento di una variabile matrice unidimensionale chiamata mentre

Vettore,

Tavola(2, 1)

indica il terzo elemento della seconda colonna di una matrice a due dimensioni chiamata Tavola. Si può modificare l’impostazione predefinita della numerazione degli elementi mettendo in testa al modulo che ospita le routine VBA la dichiarazione Option Base 1: così facendo, la numerazione inizia da uno anziché da zero.

Nomi delle variabili In programmazione si fa un grande uso delle variabili, non soltanto per riferirsi a dati esistenti nelle tabelle o in altre parti del database, ma anche per creare valori intermedi, da utilizzare per calcoli o confronti. Anche se non è obbligatorio dichiarare esplicitamente le variabili e neppure assegnar loro un tipo di dato (grazie alla flessibilità del tipo Variant, attribuito per default), è buona regola: •• dichiararle prima di utilizzarle (costringendosi magari a farlo, impostando la dichiarazione Option Explicit in testa al modulo), •• assegnare a ciascuna variabile un tipo di dato corretto. Se si sceglie di lavorare in questo modo, conviene anche attribuire i nomi alle variabili adottando un qualche tipo di convenzione, in modo che sia facile distinguere, soltanto leggendone il nome, una variabile che rappresenta una stringa da una che rappresenta un numero intero o un numero a virgola mobile in doppia precisione.

218  Capitolo 7

Ciascuno è libero di fare come gli pare, inventandosi proprie regole per attribuire i nomi alle variabili. Dal momento, però, che esistono alcune convenzioni già codificate e largamente diffuse, prima di buttarsi a inventare l’acqua calda conviene esaminare queste convenzioni e decidere se adottarle in tutto o in parte. Per assegnare i nomi ai componenti del codice che sviluppano, i programmatori della Microsoft usano da parecchi anni una convenzione chiamata ungherese, in omaggio a un loro collega di nome Charles Simonyi, che l’aveva inventata ed era, appunto, di origine ungherese. (Incidentalmente, ricordiamo che Charles Simonyi è il programmatore che ha creato l’elaboratore di testi Word). La convenzione ungherese è diventata col passare degli anni una vera e propria dottrina, promossa da una società, la Kwery Corporation, che l’ha messa sotto copyright. Animatore principale di questa società è un signore che si chiama Stan Leszynski, e la convenzione adesso è nota col nome di Leszynski Naming Convention, o LNC per chi non vuole intrecciarsi la lingua mentre ne parla. La LNC è articolatissima, prevede nomi convenzionali per centinaia di elementi software, ma per fortuna niente impedisce di adottarne soltanto il nucleo essenziale, che si basa su questo semplice schema sintattico: [prefisso] tag NomeBase [Qualificatore] dove NomeBase è il nome che si decide di dare a un oggetto o a una variabile, tag è un marcatore che qualifica il tipo (nel caso di variabili VBA, il tipo di dato), prefisso e Qualificatore sono elementi che aggiungono chiarezza e univocità al nome. Per capire come il tutto funziona, possiamo esaminare la Tabella 7.3. Tabella 7.3  Esempi di nomi LNC. Nome

Prefisso

tblClienti aintCodProd strNomeCliPre

a

Tag

NomeBase

tbl

Clienti

int str

CodProd NomeCli

Qualificatore

Prev

I tag tbl, int e str stanno rispettivamente per Table, Integer e String. Il prefisso a sta per Array, precisando quindi che aintCodProd è una matrice di numeri interi. Il qualificatore Prev segue il nome strNomeCli, qualificandolo come il valore previous (precedente) della stessa variabile stringa. Dallo schema sintattico si ricava anche che i prefissi e i tag vanno scritti in caratteri minuscoli, mentre i NomiBase e i Qualificatori si scrivono con l’iniziale maiuscola. L’idea di fondo che sta alla base della LNC è senz’altro da condividere: dovendo dare comunque un nome a variabili e a oggetti, tanto vale fare un piccolo sforzo in più e aggiungere una sigla convenzionale (il tag, appunto) per rendere più chiaro almeno il tipo di dato delle variabili. Come spesso capita negli Stati Uniti, un’idea semplice e sensata come questa è stata trasformata in un business e sviluppata fino ad assumere dimensioni abnormi, per cui oggi il testo completo della LNC occupa più di 40 pagine a stampa. In tutti gli esempi di questo libro ci atteniamo a una versione semplificata della LNC, utilizzando soltanto i tag, sia per i nomi delle variabili VBA, sia per i nomi degli oggetti Access. La corrispondenza fra tag e nomi è riportata nella Tabella 7.4.

Lavorare con VBA   219 Tabella 7.4  Tag per le variabili VBA. Tipo di dato

Tag

Boolean

bln

Byte Currency Date Double Integer Long Object Single String Variant

byt cur dtm dbl int lng obj sng str var

Costanti Come il suo nome lascia intuire, una costante rappresenta una stringa o un valore numerico che rimane invariato. È come una variabile, nel senso che rappresenta un dato, ma il valore del dato rappresentato è, per definizione, sempre quello, mentre il valore di un dato rappresentato con una variabile può cambiare nel corso dell’elaborazione della routine che utilizza la variabile. Le costanti si dichiarano con un enunciato che ha la seguente struttura sintattica: [Public | Private] Const nomecostante [As tipo] = espressione

Con le costanti si possono rappresentare in modo semplice informazioni complesse, a patto che siano sempre quelle. Un tipico caso è il valore di pi greco: se in un’applicazione alcune routine VBA devono calcolare la circonferenza di un cerchio o la sua superficie in base al diametro, invece di scrivere ogni volta il valore numerico 3,14159265, si può creare una costante conPI, con un enunciato come il seguente Public Const conPI = 3.14159265

e poi si utilizza conPI nelle routine di calcolo geometrico, senza rischiare di sbagliare a digitare le cifre.

Virgole e punti Nel codice VBA la virgola decimale si rappresenta con un punto, come nell’esempio della costante per il valore di pi greco che abbiamo illustrato sopra, mentre la virgola serve per separare gli elementi di un enunciato.

In VBA sono disponibili numerose costanti predefinite, che servono a rappresentare in forma più mnemonica codici numerici che sarebbe difficile ricordare come tali. Per

220  Capitolo 7

esempio, l’aspetto della finestra di messaggio generata con un enunciato MsgBox è determinato da due codici numerici, uno che definisce l’icona (punto esclamativo, punto interrogativo e così via), l’altro che stabilisce quali pulsanti vadano visualizzati (solo OK; Sì, No, Annulla e così via). L’enunciato VBA MsgBox "Allarme critico + pulsanti Sì, No, Annulla", 16 + 3

produce una finestra di messaggio come quella riprodotta nella Figura 7.1.

Figura 7.1  Un tipico output di MsgBox generato con valori costanti predefiniti.

Lo stesso enunciato si può scrivere in modo più chiaro, utilizzando costanti predefinite di VBA: MsgBox "Allarme critico + pulsanti Sì, No, Annulla", vbCritical + _     vbYesNoCancel

Particolarmente comode sono le costanti predefinite per designare i colori, che all’interno del sistema sono rappresentati con numeri esadecimali, come si può vedere dalla Tabella 7.5. Le costanti predefinite, dette anche intrinseche, sono molto utilizzate nei vari contesti di programmazione usati da Access per assegnare valori a parametri e argomenti e vengono normalmente distinte per famiglia di utilizzo in base alle prime due lettere del loro nome, per cui i nomi delle costanti intrinseche di VBA iniziano sempre con vb, le costanti di Access hanno nomi che iniziano con ac, quelle dei DAO con db e quelle degli ADO con ad. Tabella 7.5  Le costanti VBA per rappresentare i codici numerici dei colori. Costante

Valore

Descrizione

vbBlack

0x0

Nero

vbRed vbGreen vbYellow vbBlue vbMagenta vbCyan vbWhite

0xFF 0xFF00 0xFFFF 0xFF0000 0xFF00FF 0xFFFF00 0xFFFFFF

Rosso Verde Giallo Blu Fucsia Azzurro Bianco

Lavorare con VBA   221

Espressioni Si dà il nome di espressione a una qualsiasi combinazione di parole chiave, operatori, variabili e costanti che eseguono un’operazione che dà come risultato (in gergo si dice “restituisce”) una stringa, un numero o un oggetto. Con un’espressione si eseguono calcoli o si manipolano caratteri di una stringa. In molti casi espressione ed enunciato coincidono. Per esempio, con l’espressione seguente: intNumCaratteri = Len("Precipitevolissimevolmente")

si mette nella variabile intNumCaratteri il numero di caratteri della parola Precipitevolissimevolmente (26, per l’esattezza), mentre l’espressione sngCirconferenza = conPI * 20

nell’ipotesi di aver definito la costante conPI per pi greco, assegna alla variabile sngCirconferenza il numero in virgola mobile 62,83185, pari alla circonferenza di un cerchio con un diametro di 20 unità.

Le categorie degli enunciati Completate con operatori, variabili, costanti ed espressioni, le istruzioni diventano enunciati e definiscono azioni di vario tipo, raggruppabili in tre categorie. •• Enunciati di dichiarazione, che creano una variabile, una costante o una routine, le assegnano un nome e possono anche specificare un tipo di dati per variabili e costanti. •• Enunciati di assegnazione, che assegnano un valore a una variabile o a una costante. •• Enunciati eseguibili, che attivano un’operazione che può coinvolgere: 1. elementi del sistema dei file (cartelle, file) o del sistema operativo (finestre, funzioni di sistema); 2. oggetti Access, DAO e ADO e dati che vi sono contenuti (tabelle, query) o loro metodi e proprietà; 3. l’ordine col quale vengono eseguiti gli enunciati che compongono la routine. Nel resto del paragrafo diamo una sintetica rassegna di queste tre categorie di enunciati.

Dichiarare Con gli enunciati di dichiarazione si fanno nascere esplicitamente variabili, costanti e routine. Anche se si possono utilizzare senza prima dichiararle, è bene dichiarare sempre le variabili da utilizzare in una routine. La stessa libertà di azione non è consentita con le routine. Che si tratti di una routine Sub o Function, questa va dichiarata espressamente, usando le parole chiave Sub/End Sub o Function/End Function, completate con il nome della routine, seguito da una coppia di parentesi tonde, destinate a contenere eventuali argomenti: anche se non si prevedono

222  Capitolo 7

argomenti, le parentesi tonde devono essere sempre presenti in coda al nome di una routine quando questa viene dichiarata. Nel dichiarare variabili e costanti è facoltativo specificarne il tipo di dati e l’ambito di validità. Del tipo di dati abbiamo già parlato, l’ambito di validità (detto in inglese scope, che sta per “portata”), è un concetto importante, che vale per tutto ciò che si può dichiarare, quindi anche per le routine.

Pubblico e privato Routine e variabili possono essere pubbliche, quindi disponibili in tutti i moduli standard, di classe indipendenti e di classe associati a maschere e a report, che si trovano in un’applicazione, oppure private, cioè disponibili soltanto nel modulo in cui sono contenute. La distinzione si attua, per le routine, al momento della dichiarazione, utilizzando le parole chiave Public o Private. Questa distinzione consente di creare funzionalità che si utilizzano soltanto in un determinato contesto, per esempio in una specifica routine evento associata a un particolare controllo in una maschera. In fase di debug, se una routine è stata definita come Private, è più facile metterla sotto osservazione, perché non è possibile che venga attivata fuori dal contesto di cui fa parte. Per impostazione predefinita, l’Editor diVisual Basic assegna la proprietà Private a tutte le routine evento che si fanno nascere in un modulo associato a una maschera o a un report. Le variabili dichiarate entro una routine sono per definizione private, cioè valgono soltanto all’interno della routine in cui sono state dichiarate. Se si vuole rendere pubblica una variabile, in modo che il valore che via via assume sia disponibile in tutta l’applicazione, basta dichiararla fuori da una routine, scrivendola nella parte iniziale del modulo standard, fra le Dichiarazioni. Per le costanti è possibile dichiararle pubbliche o private utilizzando la parola chiave Public o Private prima della parola chiave Const.

La variabile oggetto Me Nei moduli che contengono routine specifiche di una maschera o di un report (sono per lo più routine evento, ma non solo) è disponibile una variabile particolare che non deve essere dichiarata e identifica l’oggetto stesso, maschera o report che sia. Questa variabile si chiama sempre Me e può essere usata per identificare compiutamente altri oggetti che si trovano nella maschera o nel routine, tipicamente i controlli, per cui Me.txtCasellaNome, identifica il controllo chiamato txtCasellaNome nella maschera alla quale appartiene il modulo della maschera. L’ambito di validità della variabile Me è limitato al modulo in cui si trova, ma vale per tutte le routine che possono essere create nel modulo stesso.

L’enunciato Dim Per dichiarare variabili si utilizza l’enunciato Dim, che prevede due strutture sintattiche, una semplice, che descriviamo qui, e un’altra più complessa, da utilizzare in relazione con gli oggetti ActiveX, che qui possiamo trascurare.

Lavorare con VBA   223

La sintassi semplificata di Dim è, quindi, la seguente: Dim nomevariabile[([indici])] [As [New] tipo]

dove nomevariabile è una stringa di caratteri composta tenendo conto delle limitazioni che abbiamo già ricordato nei paragrafi precedenti. L’unica parola chiave obbligatoria è Dim, As è facoltativa e, quando viene usata, deve essere seguita dal nome formale di un tipo dati. Le variabili dichiarate senza esplicitarne il tipo dati con As vengono create di tipo Variant. È consentito dichiarare più variabili con un solo enunciato Dim, specificando oppure no il tipo per ciascuna variabile dichiarata.Tutte le variabili dichiarate in un solo enunciato Dim per le quali non si specifica il tipo assumono il tipo Variant. La parola chiave New è facoltativa, si usa soltanto per le variabili oggetto e ne vedremo l’utilizzo nei prossimi capitoli. Se si intende dichiarare una variabile matrice, dopo nomevariabile bisogna inserire una coppia di parentesi tonde. Fra queste parentesi è consentito inserire un valore per indici, se si vogliono definire a priori in modo rigido le dimensioni della matrice. I valori indicati per indici possono essere: 1. un solo numero intero, nel qual caso si avrà una matrice monodimensionale con un numero di elementi pari a indici, più uno (se non è stata modificata la dichiarazione predefinita Option Base); 2. due numeri interi, separati da virgole, il primo dei quali imposta le righe e il secondo le colonne (o dimensioni); 3. una serie di valori, definiti secondo la seguente sintassi:     [inferiore To] superiore [, [inferiore To] superiore] . . .

che consentono di indicare limiti inferiore e superiore per gli indici, sia di riga, sia di colonna, prevalendo sull’impostazione di Option Base. Lasciando solo le parentesi senza immettere un valore per indici, la matrice viene dichiarata come dinamica, cioè il numero dei suoi elementi potrà essere modificato con opportuni enunciati durante l’esecuzione della routine che se ne serve. Immettendo un valore per indici, la matrice resta statica, cioè il numero degli elementi rimane fisso durante l’esecuzione della routine. Quando vengono dichiarate, le variabili vengono anche inizializzate, vale a dire viene loro attribuito un valore generico, del tipo di dati espresso con As, per cui le variabili di tipo numerico vengono inizializzate con uno zero, le variabili stringa con un una stringa vuota e le Variant con Empty, un valore che segnala a VBA che non è stato assegnato alcun valore iniziale Qui di seguito riportiamo alcuni esempi di uso dell’enunciato Dim. ' Non essendo specificata la parola chiave As, ' ValoreQualsiasi e UnValore ' vengono dichiarate come Variant, con valori impostati su Empty. Dim ValoreQualsiasi, UnValore ' Dichiara in modo esplicito una variabile di tipo Integer. Dim Numero As Integer

224  Capitolo 7 ' Più dichiarazioni sulla stessa riga. AltraVar è di ' tipo Variant perché non è indicato il suo tipo. Dim AltraVar, Scegli As Boolean, DataNascita As Date ' MatriceGiorni è una matrice di valori Variant che contiene ' 50 elementi da 0 a 49. Questo nell'ipotesi che nel ' modulo Option Base sia impostato su 0 ' (valore predefinito). Dim MatriceGiorni(49) ' MatriceBiDim è una matrice bidimensionale di interi. Dim MatriceBiDim(3, 4) As Integer ' MatriceTridim è una matrice tridimensionale di valori Double ' con limiti espliciti. Dim MatriceTridim(1 To 5, 4 To 9, 3 To 5) As Double ' GiornoNascita è una matrice di date con indici da 1 a 10. Dim GiornoNascita(1 To 10) As Date ' MatriceDin è una matrice dinamica di valori Variant. Dim MatriceDin()

Quando si dichiara una variabile matrice come dinamica, lasciando vuote le parentesi tonde che seguono il nome della variabile, bisogna poi utilizzare l’istruzione ReDim all’interno della routine che la utilizza per definire il numero di righe e di dimensioni della matrice.

I valori Empty e Null Le variabili di tipo Variant possono contenere valori Empty o Null, che sono diversi fra loro e diversi da zero. È importante aver ben chiare queste differenze per evitare insidiosi errori di run-time. •• Si utilizza Null nei database per indicare che il contenuto di un campo è assente o sconosciuto: quando in una tabella tblListinoPrezzi il record di un articolo ha un valore Null nel campo PrezzoVendita, questo non vuol dire che quell’articolo è gratis, ma che il suo prezzo non è stato ancora definito. I valori Null non si possono usare nei confronti e nelle operazioni aritmetiche e non sono uguali a zero. •• Il valore Empty è diverso da Null e da zero ed è il valore che riceve una variabile di tipo Variant quando viene inizializzata e rimane tale fino a quando non le si assegna deliberatamente un valore. Quando è necessario verificare se un campo di un record o un controllo in una maschera contiene il valore Null si può utilizzare la funzione IsNull() di VBA, che restituisce il valore logico Vero se l’elemento indicato fra le parentesi tonde è Null. Per le variabili di tipo Variant si può utilizzare la stessa funzione per stabilire se contengono Null o la funzione IsEmpty() per verificare se il valore contenuto è Empty.

Tipi definiti dall’utente Fra i tipi di dati disponibili inVBA ne esiste uno chiamato user-defined type, che in italiano si rende abitualmente con tipo definito dall’utente. Il termine può lasciare perplessi, perché i tipi di dati sono in sostanza numeri e lettere, e per quanta fantasia o creatività possa avere un utente programmatore è difficile accet-

Lavorare con VBA   225

tare l’idea che gli riesca di creare un tipo di dato che non sia un numero o una stringa. In realtà, i tipi definiti dall’utente sono una via di mezzo fra una variabile e un oggetto, che possono dimostrarsi molto utili quando in un’applicazione si utilizza spesso una struttura di dati o un record che deve avere sempre la stessa organizzazione dei campi. Dichiarando in un modulo standard o di classe un tipo definito dall’utente, si predispone uno schema che contiene più variabili, con i tipi di dati usati abitualmente in VBA, alle quali si può far riferimento con un unico nome collettivo, seguito dal nome della singola variabile che si vuole utilizzare. Per creare un tipo definito dall’utente si utilizza la parola chiave Type, preceduta da una delle due clausole Public o Private, seguita da un nome e da un elenco di tutte le variabili che si vogliono accorpare sotto quel nome, specificando il tipo dati di ciascuna. Ecco un esempio: Public Type SchedaPersonale     Matricola As Long     Cognome As String     Nome As String     Nascita As Date     Assunzione As Date End Type

Le dichiarazioni dei tipi definiti dall’utente vanno collocate nella sezione Dichiarazioni del modulo in cui verranno utilizzate. Più avanti, nei paragrafi dedicati ai costrutti di programmazione With...End With, diamo un esempio pratico di come si potrebbe utilizzare questo particolare tipo definito dall’utente

Assegnare Per assegnare un valore a una variabile dopo averla dichiarata, si crea un enunciato di assegnazione mettendo semplicemente un segno di uguale (=) fra il nome della variabile e il valore che si desidera assegnarle, così: strStringaProva = "Questa è una stringa di prova" intNumeroIntero = 327

Il segno di uguale si usa anche per assegnare (cioè impostare) valori di proprietà, come per esempio: Controls![txtNomeSocietà].FontBold = True

che imposta su grassetto il carattere del testo contenuto nel controllo txtNomeSocietà. Nel caso di variabili oggetto, che fanno riferimento, cioè, a un oggetto Access, DAO o ADO, come per esempio una tabella o un database, l’assegnazione va fatta con un enunciato che usa la parola chiave Set, in questo modo: Set objQuestoDatabase = CurrentDb Set rstTabella1 = objQuestoDatabase.OpenRecordset("tblClienti")

Si noti che la sintassi degli ultimi tre esempi è quella degli oggetti DAO, non quella diVBA.

226  Capitolo 7

Eseguire azioni Una volta dichiarate le variabili e le costanti che servono e dopo aver loro assegnato opportuni valori, resta da fare la parte più ardua e creativa, vale a dire, scrivere gli enunciati eseguibili della routine, quelli, cioè, che producono il risultato che si vuole ottenere. Gli enunciati eseguibili si costruiscono usando parole chiave, variabili, costanti, operatori ed espressioni e per farlo bisogna avere una discreta conoscenza delle funzionalità che caratterizzano le istruzioni e le parole chiave di VBA. Una descrizione particolareggiata di tutte le istruzioniVBA – a questo punto – porterebbe via molto tempo, sarebbe mortalmente noiosa e ci allontanerebbe dal nostro obiettivo, che è quello di capire il ruolo di Visual Basic for Applications quando si sviluppano applicazioni professionali con Access. A questo scopo, le istruzioni da conoscere in modo approfondito sono quelle per il controllo del flusso di esecuzione delle routine. Quanto alle altre, gli esempi che presenteremo via via nel resto del libro potranno bastare per capirne il funzionamento.

Controllo del flusso Di norma, in qualsiasi linguaggio di programmazione gli enunciati che compongono un programma vengono eseguiti in modo sequenziale, cominciando con la prima riga e proseguendo una riga dopo l’altra fino all’ultimo enunciato. In alcuni casi, è opportuno che questa successione venga interrotta o sospesa, passando a eseguire, da un certo punto in poi, un altro gruppo di enunciati nello stesso programma o addirittura un altro programma, per poi tornare al flusso principale e riprendere l’esecuzione sequenziale dal punto in cui era stata sospesa. Meccanismi di questo genere prendono il nome di diramazioni o salti e di solito vengono innescati sulla base del risultato di un’espressione usata come controllo: se l’espressione ha restituito zero, per esempio, si passa al blocco di enunciati alternativo, altrimenti si continua col principale. In altri casi, una determinata serie di enunciati deve essere eseguita più volte, variando ogni volta i dati che vengono acquisiti in input, fino a quando l’insieme dei dati da elaborare è stato esaurito oppure si è verificata una condizione particolare, dopo di che si procede con l’esecuzione sequenziale degli altri enunciati. In casi di questo genere si chiama ciclo il gruppo di enunciati che viene eseguito più volte (ciclicamente, appunto), prima di tornare al flusso principale. Diramazioni e cicli sono essenziali per sviluppare routine produttive e il VBA mette a disposizione diverse istruzioni con le quali costruire enunciati per gestire ed eseguire cicli e diramazioni. Alcune di queste istruzioni in VBA vengono direttamente dal vecchio BASIC, altre sono state concepite apposta per lavorare con oggetti Access, le loro proprietà e i loro metodi. Determinate istruzioni si sviluppano su più righe, pur formando un tutto unico e vengono dette costrutti. Gli elementi che formano un costrutto si chiamano clausole.

Il costrutto If...Then...Else Con questo costrutto si valuta se una determinata condizione (l’espressione che segue la parola chiave If) è vera o falsa. Se è vera, viene eseguito l’enunciato (o il gruppo di enunciati) che si trova subito dopo la parola chiave Then; se è falsa, viene eseguito l’e-

Lavorare con VBA   227

nunciato (o il gruppo di enunciati) che sta subito dopo la parola chiave Else, posto che questa parola chiave esista, perché è facoltativa e il costrutto può ridursi alla forma If... Then..., che si chiude obbligatoriamente con il costrutto End If. Ecco un esempio, dove l’espressione che viene valutata nell’enunciato If...Then è il risultato dell’operazione aritmetica Modulo 2: intRisposta Mod 2

Con questa operazione si ottiene il resto di una divisione per 2: se il numero identificato dalla variabile intRisposta è pari, l’operazione dà come risultato zero, altrimenti (else, in inglese) dà un numero diverso da zero. Sub PariODispari() on Error Resume Next     Dim intRisposta As Integer, strDomanda As String     strDomanda = "Inserisci un numero intero"     intRisposta = InputBox(strDomanda)     If intRisposta Mod 2 = 0 Then         MsgBox "Hai immesso un numero pari: " & intRisposta     Else         MsgBox "Hai immesso un numero dispari: " & intRisposta     End If End Sub

È consentito scrivere un costrutto If...Then su una sola riga, senza la clausola di chiusura End If e senza la clausola Else, come nell’esempio che segue: Sub IfSuUnaSolaRiga()     Dim intRisposta As Integer, strDomanda As String     strDomanda = "Inserisci un numero pari"     intRisposta = InputBox(strDomanda)         If intRisposta Mod 2 0 Then MsgBox "Ho detto un numero pari!" End Sub

Come si può vedere, sebbene sia sintatticamente legittimo, questo formato del costrutto If...Then è poco flessibile e inoltre produce una riga affastellata e di difficile lettura. Aggiungendo una clausola intermedia, ElseIf, si possono aumentare le possibili diramazioni di un costrutto If...Then, come nell’esempio che segue: Sub IfRipetuti()     Dim intRisposta As Integer, strDomanda As String     strDomanda = "Inserisci un numero intero"     intRisposta = InputBox(strDomanda)     If intRisposta = 1 Then         MsgBox "Hai immesso un 1"     ElseIf intRisposta = 2 Then         MsgBox "Hai immesso un 2"     ElseIf intRisposta >= 3 And intRisposta _         1000000, "Ottimo!", "Poveretto!") End Sub

In questa routine evento, associata al doppio clic sulla casella di testo txtStipendio di una maschera, l’etichetta (Caption) della casella txtCommento diventa “Ottimo!”, se il valore della casella di testo txtStipendio è maggiore di 1 milione (di euro), e “Poveretto!” se inferiore. La funzione IIf si usa spesso nei controlli calcolati su maschere o su report. Uno degli utilizzi più frequenti è per verificare se il valore contenuto in un controllo è Null. Se fosse Null, si può far restituire uno zero o una stringa vuota, altrimenti la funzione restituisce il valore contenuto nel controllo. La funzione IIf potrebbe essere impostata in questo modo nella casella della proprietà Origine dati di un controllo: =IIf(IsNull(Forms!frmOrdini!txtTrasporto), 0 , Forms!frmOrdini!txtTrasporto)

Se il controllo txtTrasporto della maschera frmOrdini non contiene alcun valore (cioè è Null), la funzione visualizza 0, altrimenti visualizza il valore contenuto nel controllo txtTrasporto.

Il costrutto Select Case Quando le diramazioni possibili sono numerose, invece di utilizzare il costrutto If... Then...ElseIf, che già dopo la terza diramazione comincia a somigliare a un labirinto, conviene ricorrere al costrutto Select Case, che è molto più chiaro da impostare e più facile da capire quando lo si legge.

Lavorare con VBA   229

Andare a capo Nell’esempio precedente, l’enunciato che forma la routine è in realtà una sola riga. L’Editor di Visual Basic consente di andare a capo nella scrittura di enunciati particolarmente lunghi, per renderli più leggibili. Si può tagliare un enunciato in più righe a condizione di mantenerle unite mediante un carattere di sottolineatura (_) che può essere inserito soltanto prima o dopo un operatore aritmetico o prima dell’operatore punto che separa i componenti di un oggetto. Il carattere di sottolineatura, detto underscore in inglese, deve essere preceduto da uno spazio e non può essere seguito da uno spazio. Non si può usare questo separatore per tagliare una stringa lunga lasciando le doppie virgolette di apertura su una riga e quelle di chiusura sulla riga successiva: si otterrebbe subito una violenta reazione dell’Editor di Visual Basic, che denuncerebbe un errore di sintassi. Se occorre disporre su più righe una stringa particolarmente lunga, si può ricorrere all’artificio di scomporla in sottostringhe, dotate ciascuna di virgolette di apertura e di chiusura, combinandole poi insieme con l’operatore di concatenamento (&). Per esempio, la stringa: strDante = "Nel mezzo del cammin di nostra vita"

può diventare: strDante = "Nel mezzo del " & _     "cammin di nostra vita"

La sintassi di Select

Case

è la seguente:

Select Case valoretest [Case condizione-n     [enunciati-n]] ... [Case Else     [enunciati-else]] End Select

dove valoretest è la condizione da valutare e condizione-n è uno degli n valori che potrebbe assumere valoretest. Per ciascuno degli n valori che vengono presi in considerazione, si predispongono opportuni enunciati che seguono la clausola Case. Se nessuno dei valori assunti da valoretest corrisponde con uno dei valori indicati in una condizione-n, vengono eseguiti gli enunciati-else, che si collocano dopo la clausola Case Else. Un esempio farà capire perché Select Case è meglio di If...Then...ElseIf. Sub ProvaCase()     Dim intRisposta As Integer, strDomanda As String     strDomanda = "Inserisci un numero intero"     intRisposta = InputBox(strDomanda)     Select Case intRisposta         Case 0             MsgBox "Ho detto un numero intero!"         Case 1             MsgBox "Hai immesso un 1"

230  Capitolo 7         Case 2             MsgBox "Hai immesso un 2"         Case 3 To 10             MsgBox "Hai immesso un numero fra 3 e 10"         Case Else             MsgBox "Hai immesso un numero maggiore di 10"     End Select End Sub

La routine di esempio esegue lo stesso tipo di confronti e produce gli stessi risultati di quella costruita con If...Then...ElseIf, ma è più lineare da leggere e più semplice e intuitiva da costruire. Si noti che il termine di confronto per una sottoclausola Case può essere anche un’espressione, che indica un intervallo di valori (nell’esempio: Case 3 To 10).

I costrutti per i cicli InVBA si hanno a disposizione svariati costrutti sintattici per eseguire cicli, cioè ripetizioni sistematiche di una serie di enunciati. I cicli sono strutture di programmazione essenziali per eseguire operazioni tipicamente ripetitive quali riempire una matrice con una serie di valori acquisiti da tastiera o da un file esterno, travasare record da una tabella di un database a un’altra tabella, generare una successione di righe da stampare e via enumerando. I costrutti per creare e governare esecuzioni di enunciati in ciclo sono due, chiamati Do...Loop e For...Next, ciascuno dei quali ammette diverse varianti.Vediamoli con alcuni semplici esempi.

I costrutti Do...Loop Questi costrutti si utilizzano quando non si conosce a priori il numero esatto di iterazioni da eseguire, ma si dispone di una informazione sulla quale basare l’arresto del ciclo. Per esempio, se si costruisce un ciclo per scorrere tutti i record di un file, ma non si sa quanti sono i record, si può arrestare il ciclo quando l’esplorazione del file arriva al marcatore EOF (End Of File), che segna la fine del file. La prima variante del costrutto Do...Loop ha la struttura sintattica: Do [{While | Until} condizione]     [enunciati] [Exit Do]     [enunciati] Loop

dove condizione è un’espressione il cui risultato può essere vero o falso. Se condizione è preceduta dalla coppia di parole chiave Do While, il blocco di enunciati che la segue viene ripetuto fintanto che condizione rimane vera. Se condizione è preceduta da Do Until, il blocco di enunciati che la segue viene ripetuto fino a quando condizione diventa vera. La clausola Exit Do provoca l’uscita forzata dal ciclo e può essere inserita in un punto qualunque del blocco degli enunciati, gestendola con un costrutto If...Then. La struttura sintattica della seconda variante del costrutto Do...Loop è la seguente:

Lavorare con VBA   231 Do     [enunciati] [Exit Do]     [enunciati] Loop [{While | Until} condizione]

la cui logica è sostanzialmente la stessa della prima variante, con la sola differenza che la valutazione di condizione avviene alla fine di ogni iterazione, invece che all’inizio, quindi gli enunciati che compongono il blocco iterativo vengono eseguiti almeno una volta. Nell’esempio che segue, l’espressione condizione è: intContatore < 6

La variabile intContatore viene inizializzata su 1 dopo essere stata dichiarata.Viene anche dichiarata una variabile matrice monodimensionale con cinque elementi. Nelle dichiarazioni è stata impostata l’opzione Option Base 1, in modo che gli indici delle matrici inizino da 1. Gli enunciati che vengono dopo le parole chiave Do While inseriscono nell’elemento della matrice che ha indice intContatore il valore di intContatore, stampano il valore dell’elemento nella finestra Immediata e quindi sommano uno a intContatore. Il ciclo viene ripetuto fintanto che intContatore è minore di 6. La Figura 7.2 mostra il risultato dell’elaborazione. Sub ProvaDoWhile()     Dim intContatore As Integer     Dim MatriceNumeri(5) As Integer     intContatore = 1     Do While intContatore < 6         MatriceNumeri(intContatore) = intContatore         Debug.Print MatriceNumeri(intContatore)         intContatore = intContatore + 1     Loop End Sub

La routine che segue usa la struttura Do...Loop While ed esegue il ciclo una sola volta, anche se intContatore è impostato su 5. Questo dipende dal fatto che la condizione viene valutata dopo l’esecuzione del blocco degli enunciati, che vengono eseguiti almeno una volta. Volendo ottenere il risultato corretto, bisogna modificare le impostazioni della variabile che stabilisce la condizione di iterazione, impostando intContatore su 1, inizialmente, e fissando la condizione di uscita nella forma: intContatore < 6. Sub ProvaDoLoopWhile()     Dim intContatore As Integer     Dim MatriceNumeri(5) As Integer     intContatore = 5     Do         MatriceNumeri(intContatore) = intContatore         Debug.Print MatriceNumeri(intContatore)         intContatore = intContatore + 1     Loop While intContatore < 5 End Sub

232  Capitolo 7

Figura 7.2  La routine ProvaDoWhile() viene eseguita correttamente.

Nei due esempi che seguono, si ottengono gli stessi risultati utilizzando la clausola Until invece della clausola While. La forma Do Until...Loop valuta la condizione prima di eseguire gli enunciati del ciclo, mentre la forma Do...Loop Until esegue comunque almeno una volta gli enunciati del ciclo, prima di valutare la condizione. Sub ProvaDoUntil()     Dim intContatore As Integer     Dim MatriceNumeri(5) As Integer     intContatore = 1     Do Until intContatore = 6         MatriceNumeri(intContatore) = intContatore         Debug.Print MatriceNumeri(intContatore)         intContatore = intContatore + 1     Loop End Sub Sub ProvaLoopUntil()     Dim intContatore As Integer     Dim MatriceNumeri(5) As Integer     intContatore = 1     Do         MatriceNumeri(intContatore) = intContatore         Debug.Print MatriceNumeri(intContatore)         intContatore = intContatore + 1     Loop Until intContatore = 6 End Sub

Quando si usano costrutti iterativi per eseguire ciclicamente gruppi di enunciati si corre sempre il rischio di creare inavvertitamente un ciclo infinito. Il ciclo infinito, classico errore logico dei programmatori alle prime armi o di quelli esperti che si distraggono, dura

Lavorare con VBA   233

perennemente fino a quando non si spegne il computer. Esiste fortunatamente un’alternativa a questa forma estrema di difesa, che consiste nel premere la combinazione di tasti Ctrl+Break, che interrompe l’esecuzione del ciclo. La routine che segue è un esempio di ciclo infinito: la condizione di uscita è stabilita con Loop Until intContatore = 5

ma, malauguratamente, la variabile di controllo intContatore è stata impostata su 5 fuori dal ciclo e il ciclo Do inizia sommandovi 1, per cui la condizione di uscita (intContatore = 5) non si verifica mai e la routine viene eseguita all’infinito oppure fino a quando si genera un errore di sistema, dovuto al superamento del valore limite consentito per le variabili di tipo Integer (che è 32.767). Sub CicloInfinito()     Dim intContatore As Integer     intContatore = 5     Do         Debug.Print intContatore         intContatore = intContatore + 1     Loop Until intContatore = 5 End Sub

Per difendersi dal rischio di incappare in un ciclo infinito, è opportuno impostare una condizione di uscita forzata, al superamento di un determinato valore, per esempio, ricorrendo alla clausola Exit Do, come nell’esempio che segue: Sub EsempioExitDo()     Dim intContatore, intNumero As Integer     intNumero = 9     Do Until intNumero = 10         intNumero = intNumero – 1         intContatore = intContatore + 1         If intNumero = 0 Then             Exit Do         End If         Loop     MsgBox "Il ciclo è stato eseguito " & intContatore & " volte." End Sub

Se non vi fosse la clausola Exit Do entro la struttura If...Then, il ciclo continuerebbe a sottrarre 1 da intNumero, fino a generare l’errore di sistema dovuto al superamento del limite inferiore per un numero di tipo Integer negativo (che è -32.768). Esiste una variante, più compatta, del costrutto Do While...Loop, che ha la seguente struttura sintattica: While condizione     [enunciati] Wend

Con questo costrutto si eseguono enunciati fin tanto che condizione è True. Il vantaggio della maggior compattezza si paga in termini di minor flessibilità, quindi è preferibile utilizzare la struttura più completa che abbiamo descritto prima.

234  Capitolo 7

Righe rientrate In tutti gli esempi di codice VBA di questo libro si può notare che alcune righe iniziano dalla prima posizione utile e altre sono spostate verso destra. Le righe spostate verso destra si dicono rientrate (in inglese si dice indented, ma chi dicesse in italiano indentate rischierebbe di essere scambiato per un odontotecnico in vacanza). I rientri servono per evidenziare gruppi di righe che formano costrutti omogenei, facendo risaltare meglio eventuali costrutti annidati entro altri. Non è obbligatorio usarli, ma aiutano a individuare blocchi specifici di enunciati all’interno di routine particolarmente lunghe e complesse e, come i commenti, semplificano la lettura del codice. L’Editor di Visual Basic inserisce automaticamente un rientro pari a quattro spazi ogni volta che si preme il tasto Tab. La dimensione del rientro può essere personalizzata dalla finestra di dialogo Opzioni immettendo il valore che interessa (da 1 a 32) nella casella Rientro tabulazione della scheda Editor.

I costrutti For...Next Con questi costrutti si eseguono ciclicamente gruppi di enunciati per un numero predefinito di volte. Si possono quindi utilizzare nei casi in cui sia noto a priori il numero delle iterazioni da eseguire: per esempio, per copiare 30 o 300 o 3000 record da un file a un altro, o per riempire una matrice con un numero prestabilito di elementi. La struttura sintattica dei costrutti For...Next si presenta in questo modo: For contatore = inizio To fine [Step incremento]     [enunciati] [Exit For]     [enunciati] Next [contatore]

dove contatore è una variabile numerica che si utilizza per conteggiare il numero delle iterazioni, inizio è il valore iniziale attribuito alla variabile e fine è il limite superiore. L’avanzamento del conteggio è pari a uno per tutto l’intervallo che va da inizio a fine. È possibile, ricorrendo alla parola chiave facoltativa Step, modificare il passo di avanzamento predefinito, stabilendo un incremento. Con Step si può anche modificare la direzione dell’avanzamento del conteggio, che è ascendente per default, ma può essere reso discendente dando un valore negativo a incremento. Se si usa un valore negativo per incremento, il valore della variabile inizio deve essere superiore a quello della variabile fine. Vediamo due semplici esempi. Eseguendo la routine EsempioForNext(), compare per cinque volte la finestra di messaggio, segnalando ogni volta il testo “Questo è il passo numero: ” seguito dal numero dell’iterazione. Sub EsempioForNext()     Dim intContatore As Integer     For intContatore = 1 To 5         MsgBox "Questo è il passo numero: " & intContatore     Next intContatore End Sub

Lavorare con VBA   235

La routine EsempioForNextStep() svolge la stessa operazione, ma, per effetto della clausola Step, la finestra di messaggio compare solo tre volte, una per il passo 1, una seconda per il passo 3 e l’ultima per il passo 5, in corrispondenza dei valori che assume la variabile intContatore. Sub EsempioForNextStep()     Dim intContatore As Integer     For intContatore = 1 To 5 Step 2         MsgBox "Questo è il passo numero: " & intContatore     Next intContatore End Sub

I costrutti With e For Each I costrutti che abbiamo passato in rassegna fin qui sono disponibili in VBA per lascito ereditario, potremmo dire, in quanto presenti fin dalle primissime versioni del vecchio BASIC. Per consentire una gestione più semplice dei cicli iterativi quando si eseguono su variabili oggetto, sono stati aggiunti a Visual Basic for Applications due nuovi costrutti che non erano presenti nel BASIC: si tratta di With...End With e di For Each...Next. La loro funzionalità peculiare è quella di prestarsi molto bene a eseguire cicli iterativi di enunciati riferiti alle diverse proprietà di un oggetto (With...End With) o a diversi oggetti che appartengono a un insieme o sono inseriti in un altro oggetto (For Each...Next). Per poterli vedere all’opera, è opportuno preparare una maschera di prova, non associata, inserirvi alcuni controlli e creare le routine evento dimostrative di questi costrutti. La maschera di prova potrebbe presentarsi come quella riportata nella Figura 7.3, con tre pulsanti di comando e una casella di testo.

Figura 7.3  La maschera per dimostrare le routine evento.

Dopo aver creato la maschera e averla salvata, restando in visualizzazione Struttura associamo una routine per l’evento Su clic di ciascuno dei tre pulsanti di comando. Il primo di questi, che avremo chiamato cmdColoreCeleste, avrà quindi la seguente routine evento: Private Sub cmdColoreCeleste_Click()     Dim ctlControllo As Control

236  Capitolo 7     For Each ctlControllo In Controls         ctlControllo.ForeColor = vbBlue     Next ctlControllo End Sub

La variabile ctlControllo è una variabile oggetto, che identifica un oggetto Controllo. Il ciclo iterativo che inizia con le parole chiave For Each percorre tutti gli oggetti controllo che trova nella maschera e imposta, mediante l’enunciato: ctlControllo.ForeColor = vbBlue

sul celeste la proprietà colore di primo piano (ForeColor) di ciascun controllo. Salvata la maschera e riaprendola in visualizzazione Maschera, possiamo verificare se funziona facendo clic sul pulsante che porta la dicitura Colore celeste: tutte le scritte (che sono in primo piano) sui controlli (i pulsanti e l’etichetta che fa da titolo alla maschera), ora sono in celeste. Per ottenere un risultato di questo genere, cioè agire su tutti i componenti di un insieme oggetto, non esiste alternativa al costrutto For Each...Next, che, come dice la sua seconda parola chiave, agisce su ciascuno (each, in inglese) degli elementi ai quali fa riferimento mediante la variabile di controllo. La sintassi formale di questo costrutto è la seguente: For Each elemento In gruppo     [enunciati] [Exit For]     [enunciati] Next [elemento]

che, come si può vedere, è sostanzialmente identica alla sintassi del tradizionale costrutto For...Next, compresa la clausola Exit For, che svolge le stesse funzioni di uscita forzata dal ciclo. La variabile usata per elemento può essere soltanto di tipo Variant o una variabile oggetto. Prepariamo ora una routine per l’evento Su clic del pulsante di comando che porta l’etichetta Cambia testo. La routine dovrebbe presentarsi come segue: Private Sub cmdCambiaTesto_Click()     With Me.txtCasella         .BackColor = vbRed         .ForeColor = vbBlue         .Value = "Ciao!"         .FontName = "Times New Roman"         .TextAlign = 2         .FontSize = 10     End With End Sub

Salviamo la maschera dopo le modifiche, riapriamola in modalità Maschera e un clic sul pulsante di comando che ha l’etichetta Cambia testo imposta su rosso il colore di sfondo della casella di testo che prima era bianca, vi fa comparire la stringa “Ciao!” in carattere Times New Roman, in colore celeste, corpo 10 e centrata nella casella.

Lavorare con VBA   237

Il costrutto With...End With che abbiamo utilizzato per ottenere tutte queste modifiche in un colpo solo non è propriamente un costrutto per le iterazioni, ma è piuttosto un risparmiatore di fatica. Come abbiamo visto nel Capitolo 5, l’identificazione formale di una proprietà o di un metodo di un oggetto comporta la scrittura di una stringa che a volte può essere piuttosto complessa, nella quale si dispongono per primo il nome dell’oggetto, poi un punto, poi la proprietà o il metodo sul quale si vuole agire (impostare la proprietà, per esempio, o eseguire il metodo). Volendo eseguire una serie di impostazioni di proprietà, bisognerebbe riscrivere ogni volta il nome completo, nella forma oggetto.proprietà

Se l’oggetto è identificabile soltanto attraverso una successione di qualificatori, come per esempio database.Forms![Maschera1].Controls![txtCasellaTesto1]

ripetere ogni volta questa qualificazione per riferirsi a una proprietà del controllo txtCasellaTesto1 da modificare, oltre a essere faticoso, induce sicuramente a errori di battitura e conseguenti perdite di tempo per mettere a posto l’errore quando il compilatore di VBA si impunta perché non capisce di che cosa stiamo parlando. Ricorrendo al costrutto With...End With, l’oggetto viene identificato una sola volta, mettendone il nome completo subito dopo la parola chiave With.Tutti gli enunciati esecutivi che seguono With iniziano con il carattere punto seguito immediatamente dal nome della proprietà sulla quale si vuole agire. Le parole chiave End With segnalano quando è esaurito l’elenco delle proprietà interessate dagli enunciati di assegnazione. Si possono mettere insieme la potenza del costrutto For Each con la comodità offerta dal costrutto With, come possiamo vedere dall’esempio che segue. In visualizzazione Struttura, assegniamo all’evento Su Clic del pulsante di comando cmdCarattere la seguente routine evento: Private Sub cmdCarattere_Click()     Dim ctlControllo As Control     For Each ctlControllo In Controls         With ctlControllo             .ForeColor = vbGreen             .FontName = "Times New Roman"             .FontSize = 10         End With     Next ctlControllo End Sub

Salvate le modifiche e riaperta la maschera, possiamo vedere che un clic sul pulsante di comando cmdCarattere ha trasformato il colore di primo piano, il carattere e il corpo tipografico di tutti i controlli presenti nella maschera (Figura 7.4). Come abbiamo già accennato e come si può notare chiaramente in questi ultimi esempi, anche se si lavora con la versione italiana di Access negli enunciati VBA i riferimenti ai nomi di proprietà e metodi vanno fatti in inglese. Questi nomi si possono ricavare dal Visualizzatore oggetti, che descriviamo più avanti, nel paragrafo sulle funzioni intrinseche.

238  Capitolo 7

Figura 7.4  Con un costrutto For...Each che ripete ciclicamente un costrutto With si può intervenire su tutti i controlli e su alcune delle loro proprietà.

Il costrutto With...End With si presta particolarmente bene per lavorare con i tipi definiti dall’utente. Dopo averne definito uno, il suo utilizzo potrebbe assumere la forma seguente: Public Sub Scheda() Dim Impiegato As SchedaPersonale With Impiegato     .Matricola = 12     .Cognome = "Rossi"     .Nome = "Antonio"     .Nascita = #12/11/1975#     .Assunzione = #10/10/2000# End With With Impiegato     Debug.Print .Matricola, .Nome, .Cognome, .Nascita, .Assunzione End With End Sub

Come si può vedere dalla Figura 7.5, quando si scrivono specifici enunciati di assegnazione per gli elementi di un tipo definito dall’utente, non appena si digita il carattere punto interviene la funzionalità Elenco membri automatico dell’Editor diVisual Basic, presentando quelle che a tutti gli effetti sono proprietà della variabile oggetto che è stata definita. A proposito dell’Editor di Visual Basic, quando si scrive una routine che utilizza un tipo definito dall’utente può far comodo vedere insieme la definizione del tipo, che sta nella sezione Dichiarazioni, e la routine che se ne serve, che potrebbe trovarsi molto più avanti nell’area delle routine. Trascinando col mouse la barretta che sta nella finestra dell’Editor di Visual Basic subito sopra la barra di scorrimento verticale, si può suddividere la finestra dell’Editor in due sezioni (Figura 7.5), in modo da poter avere sott’occhio contemporaneamente sia la dichiarazione del tipo definito dall’utente sia la routine in corso di scrittura nella quale il tipo viene usato.

Lavorare con VBA   239

Figura 7.5  Quando si assegnano valori a una variabile che rappresenta un tipo di dati definito dall’utente, interviene la funzionalità Elenco membri automatico dell’Editor di Visual Basic, come quando si selezionano proprietà di un oggetto.

Le funzioni in VBA Le funzioni, come sappiamo, sono routine che restituiscono un valore quando vengono eseguite e formano uno dei capisaldi dei linguaggi di programmazione: tutti i linguaggi contengono funzioni predefinite, dette intrinseche o incorporate, che si possono richiamare direttamente negli enunciati per ottenere risultati sicuri, velocemente e con poco sforzo. Il linguaggio VBA contiene numerosissime funzioni intrinseche e permette di definirne altre, scrivendo opportune routine di tipo Function, che vengono genericamente chiamate funzioni definite dall’utente. Vediamo più da vicino di che cosa si tratta, cominciando da queste ultime.

Funzioni definite dall’utente Sia le routine Sub, sia le Function possono ricevere argomenti o parametri quando vengono eseguite. La coppia di parentesi tonde che deve obbligatoriamente trovarsi in fondo al nome di qualunque routine serve proprio a questo: a ricevere parametri, se la routine li prevede. Accomunate in questo, i due tipi di routine si differenziano per il fatto che soltanto le Function restituiscono valori. Vediamo un esempio di routine Sub che passa parametri a un’altra Sub che li utilizza.

240  Capitolo 7

L’esempio è per una routine associata al clic su un pulsante di comando chiamato cmdSigla. Il pulsante si trova su una maschera nella quale sono presenti tre caselle di testo: txtNome, txtCognome e txtIniziali, associate ai campi Nome, Cognome e Iniziali in una tabella. Nella tabella il campo Iniziali è vuoto.Vogliamo ottenere automaticamente la sigla, composta dalla prima lettera del nome e del cognome, facendo clic sul pulsante di comando cmdSigla. La routine evento si può costruire in questo modo: Private Sub cmdSigla_Click()     Call CreaSigla(Me.txtNome, Me.txtCognome) End Sub

La routine è composta da un solo enunciato, Call, che “chiama”, cioè manda in esecuzione, un’altra routine, chiamata CreaSigla, inserendo due parametri o argomenti fra le parentesi finali: il riferimento al contenuto dei campi Nome e Cognome, che viene fatto col solito sistema di richiamare la maschera in cui si trovano gli oggetti con il nome della variabile oggetto Me, seguito da un punto. La routine CreaSigla, che esegue il lavoro su chiamata della routine evento, ha questo contenuto: Sub CreaSigla(strPrimo, strSecondo As String)     Me.txtIniziali.Value = Left$(strPrimo, 1) _         & Left$(strSecondo, 1) End Sub

La routine è predisposta per ricevere due parametri, con tipo dati stringa, chiamati strPrimo e strSecondo. I parametri non sono altro che variabili e, quando la routine viene chiamata, assumono il valore delle variabili oggetto che sono state indicate nella chiamata. L’enunciato esecutivo imposta il valore del controllo Iniziali, che sta nella stessa maschera, su una stringa creata concatenando la prima lettera della prima variabile e la prima lettera della seconda. La prima lettera viene estratta con una funzione interna di VBA, che risale ai tempi del primo BASIC, chiamata Left$.

Chiamate di routine La parola chiave Call non è obbligatoria. Per chiamare una routine Sub da un’altra Sub si può anche mettere semplicemente il nome della routine da chiamare su una riga per conto suo. Se la Sub da eseguire ha bisogno di parametri, come nell’esempio che abbiamo appena visto, quando non si usa Call i parametri vanno semplicemente elencati, separati da virgole, senza inserirli fra parentesi tonde. Nell’esempio, le due forme che seguono si equivalgono: Call CreaSigla(Me.txtNome, Me.txtCognome) CreaSigla Me.txtNome, Me.txtCognome

È buona norma utilizzare sempre Call, in modo che il richiamo di una routine risulti documentato con maggior chiarezza nel codice.

Lavorare con VBA   241

Il risultato che abbiamo ottenuto con la chiamata di una Sub si può ottenere con una routine Function, come nell’esempio che segue: Private Sub SiglaFunzione_Click()     Dim strIniziali As String     strIniziali = RestituisceSigla(Me.txtNome, Me.txtCognome)     Me.txtIniziali.Value = strIniziali End Sub Function RestituisceSigla(strPrimo, strSecondo As String) As String     RestituisceSigla = Left$(strPrimo, 1) & _         Left$(strSecondo, 1) End Function

La routine evento SiglaFunzione dichiara una variabile stringa strIniziali e le assegna il risultato che sarà restituito dalla funzione RestituisceSigla. Il richiamo alla funzione avviene indicando due parametri, che sono i contenuti dei controlli txtNome e txtCognome. La routine Function RestituisceSigla è predisposta in modo da ricevere due argomenti con tipo dati stringa, che si chiamano genericamente strPrimo e strSecondo. Dopo la parentesi tonda che chiude l’elenco dei parametri troviamo il qualificatore As String: questo predispone la funzione a restituire un valore con tipo dati stringa. Se il tipo di dati non viene dichiarato esplicitamente in questo modo, il valore che viene restituito comunque dalla funzione avrà tipo dati Variant. L’enunciato esecutivo della funzione non fa altro che assegnare al nome della funzione stessa il risultato dell’operazione di concatenamento delle iniziali delle variabili, come la routine dell’esempio precedente. Quando l’esecuzione della funzione termina, la routine che l’ha chiamata riceve il suo risultato e se ne serve per inserirlo nel controllo casella di testo txtIniziali.

Funzioni intrinseche Le funzioni predefinite, dette intrinseche o incorporate, si possono richiamare direttamente negli enunciati per ottenere risultati sicuri, velocemente e con poco sforzo.Vediamo un semplice caso. Quando si lavora su flussi di dati, per esempio una serie di record, molto spesso serve conoscere la lunghezza esatta di una stringa contenuta nel campo di un record o in un controllo. Invece di costringere il programmatore a utilizzare istruzioni elementari da eseguire in ciclo per conteggiare ogni singolo carattere, il linguaggio VBA gli mette a disposizione una funzione intrinseca, con una sintassi molto semplice, per esempio: Len(variabile)

che restituisce il numero di caratteri del dato individuato dalla variabile. L’espressione Len(variabile) non è un’istruzione né un enunciato, ma una funzione intrinseca, richiamabile con un enunciato, che potrebbe avere la forma: intLunghezza = Len(strIndirizzo)

per cui, se la variabile strIndirizzo individuasse la stringa “Corso Garibaldi, 85”, la variabile intLunghezza assumerebbe il valore 19.

242  Capitolo 7

Un altro semplice esempio. La parola chiave MsgBox può essere usata per costruire un enunciato che genera una finestra di messaggio oppure come una funzione, che oltre a presentare un messaggio restituisce un valore, che si può assegnare a una variabile. Per utilizzare il valore restituito da MsgBox quando è usata come funzione, si deve predisporre una variabile di tipo Integer e scrivere un enunciato in questo modo: intRisposta = MsgBox("Va bene così? " & strRisultato, vbYesNoCancel)

L’esecuzione dell’enunciato farà sì che la variabile intRisposta assuma il valore corrispondente al codice numerico del pulsante sul quale l’operatore ha fatto clic per chiudere la finestra di messaggio, che presenta tre pulsanti con la dicitura Sì, No, Annulla (il codice restituito è, rispettivamente, 6, 7 o 2). Ottenuto questo valore, la routine che usa la funzione può utilizzarlo in un costrutto If...Then...Else o in un costrutto Select Case per prendere decisioni su come procedere. In tutte le librerie disponibili in Access – VBA, DAO, ADO – esiste una vasta gamma di funzioni intrinseche per eseguire moltissime operazioni che potrebbero essere utili quando si scrivono applicazioni. L’idea base è quella di risparmiare tempo, di evitare di inventare la ruota ogni volta che occorre qualcosa che rotola. Quando si programma, si ha spesso bisogno di aprire un file su un disco, di estrarre un certo numero di blocchi di record, di acquisire un breve input dall’operatore o di eseguire un’operazione matematica o trigonometrica. Per tutte queste esigenze, per loro natura ricorrenti, esistono intere famiglie di funzioni, che si attivano semplicemente inserendole, complete di parametri o argomenti, in un enunciato. Per sapere se esiste una funzione per eseguire una certa operazione e per imparare come utilizzarla, con quali argomenti e con quali vincoli, si ha a disposizione, nell’Editor di Visual Basic, un comodo e articolato strumento, chiamato Visualizzatore oggetti, il cui aspetto è quello che possiamo vedere nella Figura 7.6. Nella casella combinata Progetto/libreria si seleziona il contesto da visualizzare. Le funzioni incorporate stanno nelle librerie, mentre nei progetti (cioè nelle applicazioni Access presenti nella macchina con cui si lavora) si trovano le funzioni definite da chi ha sviluppato i singoli progetti. Se si ha già un’idea del nome della funzione da esaminare, o della classe alla quale appartiene, si può provare a digitarlo nella casella combinata Testo da cercare, facendo poi clic sul pulsante Cerca in (quello con l’icona del binocolo). Se il testo che interessa esiste, compare una nuova sezione nella finestra del Visualizzatore oggetti, che elenca le librerie, le classi e i membri delle classi in cui il testo cercato è presente. Il pulsante Mostra risultati ricerca, dopo questa operazione, appare premuto e la sua descrizione diventa Nascondi risultati ricerca, per cui – facendo clic sullo stesso pulsante – la casella supplementare si chiude, restando disponibile sullo sfondo, se si vuole riattivarla direttamente. Le due grandi caselle di riepilogo che occupano la parte centrale del Visualizzatore oggetti sono intitolate Classi quella di sinistra e Membri di... quella di destra, che serve a visualizzare ciò che è contenuto nella voce selezionata nella casella di sinistra. Il termine Classi è sinonimo di oggetti. La nomenclatura delle voci contenute nella casella di riepilogo Classi è piuttosto pasticciata, oltre alle classi vi si trovano moduli, elenchi di costanti detti Enum e altro ancora.

Lavorare con VBA   243

Avanti e indietro Progetto/libreria

   Copia negli Appunti

Guida

    Testo da cercare     Visualizza definizione    Mostra risultati ricerca

    Casella di riepilogo Classi   Cerca in   Casella di riepilogo Membri Schema sintattico  

Figura 7.6  La finestra del Visualizzatore oggetti.

Le funzioni di VBA sono raggruppate per famiglie, dette Module. Quando si seleziona un modulo, per esempio Strings nella casella Classi, a destra si visualizza un elenco delle funzioni disponibili in quel modulo: nel caso specifico, quelle per lavorare sulle stringhe. Un clic sul nome di una delle funzioni fa uscire nella casella di testo in fondo alla finestra lo schema sintattico della funzione selezionata. Per visualizzare la Guida relativa a quella funzione, basta fare clic sul pulsante col punto interrogativo che sta nella parte superiore della finestra del Visualizzatore oggetti. L’intero schema sintattico della funzione evidenziata nella casella in basso si può selezionare, copiare e incollare direttamente nel modulo standard o di classe sul quale si sta lavorando, ottenendo così uno schema generico già scritto, facilmente adattabile poi alle esigenze specifiche della routine, inserendo i nomi delle variabili o le espressioni che occorrono.

244  Capitolo 7

La Figura 7.6 mostra il Visualizzatore oggetti nel quale è stata selezionata la libreria VBA. Nella casella di riepilogo Classi è selezionato il modulo Strings e in quella di destra si vede un elenco di funzioni VBA per lavorare sulle stringhe e che fanno parte di quel modulo. I nomi dei diversi oggetti che si possono esaminare nel Visualizzatore oggetti sono tutti affiancati da un’icona che identifica il gruppo di appartenenza: moduli, oggetti, funzioni e così via. Le funzioni VBA sono presentate raggruppate per tipo, secondo una classificazione che è riportata nella Tabella 7.6. Tabella 7.6  Le funzioni intrinseche di VBA raggruppate per tipo. Tipo

Gruppo di funzioni

Conversione

CBool, CByte, CCur, CDate, CDbl, CDec, CInt, CLng, CSng, CStr, CVar, CVDate, CVErr, Error, Fix, Hex, Int, Oct, Str,Val

Data/ora

DateAdd, DateDiff, DatePart, DateSerial, DateValue, Day, Hour, Minute, Month, Second, TimeSerial, TimeValue, Weekday,Year

Finanziarie

DDB, FV, Ipmt, IRR, MIRR, Nper, NPV, Pmt, PPmt, PV, Rate, SLN, SYD

Informazione

Err, IMEStatus, IsArray, IsDate, IsEmpty, IsError, IsMissing, IsNull, IsNumeric, IsObject, QBColor, RGB, TypeName,VarType

Interazione

CallByName, Choose, CreateObject, DoEvents, Environ, GetAllSetting, GetObject, GetSetting, IIf, InputBox, MsgBox, Partition, Shell, Switch,TypeName

Matematiche Sistema dei file

Abs, Atn, Cos, Exp, Log, Randomize, Rnd, Round, Sgn, Sin, Sqr, Tan CurDir, Dir, EOF, FileAttr, FileDateTime, FileLen, FreeFile, GetAttr, Loc, LOF, Seek

Testo

Asc, Chr, Filter, Format, FormatCurrency, FormatDateTime, FormatNumber, FormatPercent, InStr, InstrB, InstrRev, LCase, Left, Len, LTrim, Mid, MonthName, Replace, Right, RTrim, Space, Split, StrComp, StrConv, String, StrReverse, UCase, WeekdayName,Trim

Abbiamo già utilizzato alcune di queste funzioni, in particolare quelle per i messaggi – InputBox e MsgBox – e altre ne useremo nel resto del libro. Salvo casi particolarmente complessi, le daremo per conosciute. Chi volesse saperne di più, può ricavare spiegazioni ed esempi accedendo alla Guida attraverso il Visualizzatore oggetti: in generale, i testi relativi alle funzioni VBA e a quelle delle altre librerie disponibili in Access sono piuttosto chiari ed esaurienti. In particolare, la Guida alla quale si accede dal Visualizzatore oggetti si rivela preziosa per individuare i nomi delle proprietà, dei metodi e degli eventi di tutti gli oggetti disponibili nelle varie librerie di oggetti utilizzate da Access.

Accedere al sistema dei file Nelle versioni di Access dalla 2000 in poi sono disponibili oggetti VBA che permettono di accedere da un’applicazione al sistema dei file disponibili sul computer col quale si lavora o su un altro collegato in rete: questi oggetti e le loro funzionalità sono riepilogati nella Tabella 7.7.

Lavorare con VBA   245 Tabella 7.7  Gli oggetti VBA per operare sul sistema dei file. Oggetto

Descrizione

Insieme Drives Insieme Files Insieme Folders Drive File FileSystemObject Folder TextStream

Contiene gli oggetti Drive (unità a disco). Contiene gli oggetti File. Contiene gli oggetti Folder (cartelle). Rappresenta una unità a disco nell’insieme Drives. Rappresenta un file nell’insieme Files. Consente di accedere al sistema dei file. Rappresenta una cartella contenuta nell’insieme Folders. Rappresenta un flusso di testo entro un file aperto.

Per utilizzare questi oggetti è necessario impostare preliminarmente un riferimento alla libreria di tipi che li contiene, operazione che si esegue dalla finestra dell’Editor di Visual Basic, scegliendo il comando Riferimenti dal menu Strumenti. Nella finestra di dialogo che si apre bisogna scorrere il lungo elenco contenuto nella casella di riepilogo intitolata Riferimenti disponibili fino a trovare la riga che dice Microsoft Scripting Runtime, quindi fare clic sulla sua casella di controllo per selezionare il riferimento (Figura 7.7) e poi clic sul pulsante OK per confermare la selezione e chiudere la finestra di dialogo.

Figura 7.7  Nella finestra di dialogo Riferimenti si imposta il riferimento alla libreria di tipi Microsoft Scripting Runtime.

L’oggetto principale fra quelli elencati nella Tabella 8.7 si chiama FileSystemObject, ed è quello che permette di interagire con il sistema operativo per acquisire informazioni su file e cartelle, creare file e cartelle, copiarli, eliminarli o spostarli, tutto questo restando

246  Capitolo 7

in Access. Si attiva l’oggetto FileSystemObject con una istruzione variabile nel modo seguente:

Set,

impostando una

Set oggettofilesystemobject = CreateObject("Scripting.FileSystemObject")

dove oggettofilesystemobject è una variabile di tipo Object opportunamente dichiarata all’inizio della routine. A partire da questa impostazione si possono attivare i metodi dell’oggetto FileSystemObject, 24 in tutto, che sono elencati nella Tabella 7.8. Tabella 7.8  I 24 metodi dell’oggetto FileSystemObject. Metodo BuildPath CopyFile CopyFolder CreateFolder CreateTextFile DeleteFile DeleteFolder DriveExists FileExists FolderExists GetAbsolutePathName GetBaseName GetDrive GetDriveName GetExtensionName GetFile GetFileName GetFolder GetParentFolderName GetSpecialFolder GetTempName MoveFile MoveFolder OpenTextFile

Operazione svolta Accoda un nome a un percorso esistente. Copia uno o più file da una posizione a un’altra. Copia una cartella da una posizione a un’altra. Crea una cartella. Crea il file specificato e restituisce un oggetto TextStream col quale si può leggere dal file o scrivere nel file. Elimina un file specificato. Elimina una cartella specificata e il relativo contenuto. Restituisce True se l’unità a disco specificata esiste; False in caso contrario. Restituisce True se il file specificato esiste, False in caso contrario. Restituisce True se la cartella specificata esiste, False in caso contrario. Restituisce un percorso completo a partire da un percorso specificato. Restituisce una stringa contenente il nome di base dell’ultimo componente di un percorso, senza l’estensione di file. Restituisce un oggetto Drive corrispondente all’unità a disco di un percorso specificato. Restituisce una stringa contenente il nome dell’unità a disco di un percorso specificato. Restituisce una stringa contenente il nome dell’estensione dell’ultimo componente di un percorso. Restituisce un oggetto File corrispondente al file di un percorso specificato. Restituisce l’ultimo componente di un percorso specificato che non fa parte dell’unità specificata. Restituisce un oggetto Folder corrispondente a una cartella nel percorso specificato. Restituisce una stringa contenente il nome della cartella principale dell’ultimo componente di un percorso specificato. Restituisce la cartella speciale specificata. Restituisce un nome di cartella o di file temporaneo generato in modo casuale, che è possibile utilizzare per eseguire operazioni che richiedono una cartella o un file temporaneo. Sposta uno o più file da una posizione a un’altra. Sposta una o più cartelle da una posizione a un’altra. Apre un file specificato e restituisce un oggetto TextStream col quale si può leggere il file o inserire informazioni alla fine del file.

Lavorare con VBA   247

Questi metodi dell’oggetto FileSystemObject sono documentati con una discreta chiarezza nella Guida di Access 2010; in tutti i casi, i lettori troveranno nel CD-ROM allegato al libro un file chiamato VBLR6.chm, che è un file in linguaggio HTML compilato: un doppio clic lo apre e viene visualizzata una guida in linea di Visual Basic che contiene tutte le informazioni che possono servire per usare il FileSystemObject, i suoi metodi e le sue proprietà, per cui possiamo risparmiarci di dare qui ulteriori particolari. Vediamo ora, con l’aiuto di alcuni esempi, come si può utilizzare l’oggetto FileSystemObject e alcuni dei suoi metodi più interessanti.

Autorizzazioni Se si prova a eseguire una qualunque routine fra quelle dimostrative che seguono, l’esecuzione potrebbe essere bloccata subito visualizzando un messaggio di errore “Autorizzazione negata”: per evitare questo inconveniente, accertarsi di indicare unità a disco e directory che siano state impostate nell’elenco delle Posizioni attendibili utilizzando le Opzioni di Access.

Gli insiemi Drives, Files, Folders e gli oggetti Drive, File, Folder e TextStream sono tutti accessibili a partire dall’oggetto FileSystemObject, dal quale dipendono gerarchicamente. Numerose e interessanti proprietà sono accessibili per questi oggetti, come per esempio lo spazio disponibile per gli oggetti Drive o le date di creazione, di ultima modifica e di ultimo accesso per gli oggetti File e Folder. Supponiamo di aver bisogno di sapere quanto spazio sia disponibile su un disco. Per farlo, possiamo creare questa routine che prevede di essere attivata con un parametro corrispondente al nome dell’unità a disco da esaminare: Sub MostraSpazioDisponibile(strNomeUnità)     Dim objSistemaFile, objDrive As Object, strInfoDrive As String     'Crea un'istanza dell'oggetto FileSystemObject     Set objSistemaFile = CreateObject("Scripting.FileSystemObject")     'Imposta la variabile oggetto objDrive sul risultato del metodo     'GetDrive dell'oggetto FileSystemObject     Set objDrive = objSistemaFile.GetDrive(Left(strNomeUnità, 1))     'Prepara la stringa da visualizzare nella finestra di messaggio     strInfoDrive = "Disco " & UCase(strNomeUnità) & " - "     'Ricava le proprietà VolumeName e AvailableSpace dell'oggetto     'identificato dalla variabile objDrive     strInfoDrive = strInfoDrive & objDrive.VolumeName & vbCrLf     strInfoDrive = strInfoDrive & "Spazio disponibile: " & _         FormatNumber(objDrive.AvailableSpace / 1024, 0)     strInfoDrive = strInfoDrive & " KB"     MsgBox strInfoDrive     'Elimina il richiamo al FileSystemObject     Set objSistemaFile = Nothing End Sub

248  Capitolo 7

Dopo le dichiarazioni delle variabili, si imposta l’oggetto di riferimento FileSystemObject. Successivamente si imposta una variabile oggetto objDrive assegnandole il risultato dell’esecuzione del metodo GetDrive, che prevede la seguente sintassi: oggetto.GetDrive (riferimento)

dove riferimento è una lettera che identifica una unità a disco esistente. A questo punto si può accedere alle proprietà dell’oggetto individuato da objDrive e si utilizzano VolumeName per avere il nome del volume nell’unità a disco e AvailableSpace, per avere il dato sullo spazio disponibile. La seconda informazione che si ottiene è un numero di byte, per cui gli enunciati successivi provvedono a montare il contenuto della variabile stringa strInfoDrive in modo che il risultato sia espresso in KB. Fornendo a questa routine il parametro che le serve mediante l’esecuzione della seguente Sub: Sub TestMostraSpazioDisponibile()     Dim strNomeUnità As String     strNomeUnità = "C:\"     MostraSpazioDisponibile (strNomeUnità) End Sub

si ottiene un risultato simile a quello presentato nella Figura 7.8.

Figura 7.8  La finestra di messaggio generata dalla routine MostraSpazioDisponibile.

Un altro metodo interessante dell’oggetto FileSystemObject è CreateTextFile che, come il suo nome lascia immaginare, consente di creare un file di testo e di inserirvi un testo qualunque, generato direttamente dal codice di programma o acquisito per mezzo di un’interazione con l’utente. La routine che segue crea nella cartella \Esempi del disco C un file di testo col nome Test.txt e inserisce contestualmente nel file una stringa formata dalle parole “Ho scritto questa riga.”. Sub CreaFileTesto()     Dim objSistemaFile, objFileTest As Object     Set objSistemaFile = CreateObject("Scripting.FileSystemObject")     Set objFileTest = _                     objSistemaFile CreateTextFile("C:\Esempi\Test.txt", True)     objFileTest.WriteLine ("Ho scritto questa riga.")

Lavorare con VBA   249     objFileTest.Close End Sub

Come si può vedere, il meccanismo è sempre lo stesso: nel primo enunciato operativo, dopo la definizione delle variabili, si crea un’istanza dell’oggetto FileSystemObject assegnandola a una variabile oggetto (in questo caso l’abbiamo chiamata objSistemaFile). Si chiama successivamente il metodo CreateTextFile di quell’oggetto, dando al metodo due argomenti: 1. il nome, completo di percorso, del file da creare (nell’esempio “C:\Esempi\Test. txt”, ma avremmo potuto utilizzare anche una variabile stringa impostata sul nome di un file); 2. un valore booleano, che può essere True o False: nel primo caso si autorizza la sovrascrittura di un eventuale file che si trovasse nello stesso percorso con lo stesso nome indicato nel primo argomento; se si indica False, il file non viene soprascritto. Questo argomento è facoltativo e se viene omesso il metodo non sovrascrive il file. L’esecuzione del metodo CreateTextFile genera un oggetto TextStream, che è dotato di vari metodi. Si utilizza il metodo WriteLine dandogli come argomento la stringa di testo effettiva da scrivere nel file appena creato. Anche in questo caso avremmo potuto utilizzare una variabile stringa invece della stringa effettiva. Non avendo altro da fare, nell’enunciato successivo usiamo il metodo Close per chiudere il file. Il complemento della scrittura è la lettura, e per leggere da un file di testo si può usare la stessa logica che abbiamo visto nell’esempio precedente, servendosi dei metodi OpenTextFile invece di CreateTextFile e ReadLine invece di WriteLine: Sub LeggeDaTest()     Dim objFileSys As Object, objFileLetto As Object     Dim strLettura As String     Set objFileSys = CreateObject("Scripting.FileSystemObject")     Set objFileLetto = _         objFileSys.OpenTextFile("C:\Esempi\Test.txt", 1)     strLettura = objFileLetto.ReadLine     MsgBox strLettura     objFileLetto.Close End Sub

L’esecuzione di questa routine dimostrativa produce l’effetto che possiamo vedere nella Figura 7.9.

Figura 7.9  La lettura del file Test.txt.

250  Capitolo 7

Il metodo OpenTextFile usa questo schema sintattico: oggetto.OpenTextFile(nomefile[, modoIO[, crea[, formato]]])

dove: •• oggetto è l’oggetto FileSystemObject; •• nomefile è una stringa col nome del file, indicato direttamente o espresso con una variabile stringa; •• modoIO è un parametro facoltativo che indica la modalità di input/output e si può esprimere con un valore numerico: 1, per aprire il file in sola lettura e 8 per aprirlo in lettura e scrittura; •• crea è una variabile booleana facoltativa che può essere utilizzata per stabilire che cosa fare quando il file nomefile non esiste: True se viene creato un nuovo file, False se non viene creato; •• formato è una costante facoltativa per indicare il tipo di dati che sarà contenuto nel file; se omessa i dati saranno in formato ASCII.

Capitolo 8

Gestire maschere e controlli con VBA Nel Capitolo 2 abbiamo visto brevemente come costruire una semplice applicazione usando soltanto strumenti dell’interfaccia grafica per creare tabelle, query e maschere; nell’Appendice B abbiamo raccolto alcuni suggerimenti pratici per la creazione delle maschere e il piazzamento dei controlli. I risultati ottenuti con questi strumenti possono (e in determinati casi devono) essere migliorati in modo significativo ricorrendo agli strumenti di programmazione disponibili in Access. Vediamo ora come si può utilizzare il linguaggio VBA per rendere più professionale l’interfaccia di un’applicazione, gestendo con apposite routine eventi, proprietà e metodi di maschere e controlli. Ricordiamo che tutte le maschere, le sottomaschere e i controlli che si creano interattivamente con l’interfaccia grafica sono oggetti dotati di specifiche proprietà, ognuna impostata su valori predefiniti nel momento in cui si crea un determinato oggetto. Quando creiamo una maschera, piazzandovi sopra vari controlli col mouse, di solito impostiamo manualmente, nel modo che riteniamo più opportuno, le loro proprietà: se decidiamo, per esempio, di evidenziare con un bordo più spesso una determinata casella di testo, apriamo la sua Finestra delle proprietà, selezioniamo la scheda Formato e facciamo clic sulla casella combinata associata alla proprietà Spessore bordo, scegliendo poi nell’elenco a discesa un valore diverso da Sottilissimo, che è quello predefinito. Le stesse operazioni che si eseguono col mouse e la tastiera per selezionare e impostare le proprietà delle maschere e dei controlli si possono eseguire anche con istruzioni del linguaggio VBA, come abbiamo

In questo capitolo •

Le proprietà Evento



Le routine evento

252  Capitolo 8

visto nel capitolo precedente: in quel contesto gli esempi servivano per dimostrare il funzionamento dei costrutti With. . .End With e For Each. Riprendiamo adesso l’argomento per capire come si può intervenire con routine VBA sulle proprietà dei controlli e in quali circostanze può essere opportuno farlo.

Le proprietà Evento Maschere e controlli sono dotati di proprietà potenziali, che si rendono disponibili al verificarsi di eventi particolari e per questa ragione sono chiamate proprietà Evento. Col termine “evento” ci si riferisce a qualcosa che accade a un oggetto, che può essere un’azione eseguita dall’operatore o un’azione del sistema. Un tipico evento è l’apertura di una maschera, che può essere provocata da un comando dell’operatore o da una routine VBA. Altri eventi tipici sono la selezione di un valore da una casella combinata o l’inserimento di un nuovo dato in una casella di testo; anche questi eventi possono essere attivati da un comando dell’operatore o da codice VBA. Quando si manifesta un evento su un oggetto Access, la proprietà associata a quell’evento da potenziale diventa effettiva e si può sfruttare questa circostanza per eseguire una procedura VBA, detta routine evento, in concomitanza con quel particolare evento. Per esempio, quando l’operatore sceglie un valore in una casella combinata, una routine associata a quell’evento di selezione può: 1. utilizzare quel valore come parametro di una query, 2. eseguire la query, 3. utilizzare i record estratti dalla query come origine dei dati di una maschera, 4. aprire la maschera con quei dati, affiancandola a quella attiva, presentando così informazioni che integrano quelle della maschera dove è stato selezionato un valore nella casella combinata. In sintesi, le routine evento consentono di reagire alle interazioni fra l’utente e l’applicazione. Più un oggetto è ricco di funzionalità, più numerose sono le proprietà Evento di cui dispone: una maschera di Access 2000, per esempio, avverte più di trenta eventi (che arrivano a una cinquantina in Access 2010), mentre un’etichetta ne riconosce soltanto cinque. Diamo qui di seguito una breve descrizione degli eventi, raggruppandoli in base agli oggetti ai quali si riferiscono.

Eventi delle maschere La Tabella 8.1 riepiloga gli eventi prestabiliti per le maschere disponibili in tutte le versioni di Access: non prendiamo in considerazione la ventina di nuovi eventi che sono stati creati in Access 2002 per la gestione delle tabelle e dei grafici pivot, perché si tratta di funzionalità tipiche di Excel, sostanzialmente estranee ad Access.

Gestire maschere e controlli con VBA    253 Tabella 8.1  Gli eventi delle maschere. Evento Su apertura (Open) Su applicazione filtro (ApplyFilter) Su attivato (GotFocus) Su attivazione (Activate) Su caricamento (Load) Su chiusura (Close) Su clic (Click) Su corrente (Current) In attesa (Dirty) Su disattivato (LostFocus) Su disattivazione (Deactivate) Dopo aggiornamento (AfterUpdate) Dopo conferma eliminazione (AfterDelConfirm) Dopo inserimento (AfterInsert) Su doppio clic (DblClick) Su eliminazione (Delete) Su errore (Error) Su filtro (Filter) Su mouse spostato (MouseMove) (*) Su pressione (KeyPress) Prima di aggiornare (BeforeUpdate) Prima di conferma eliminazione (BeforeDelConfirm) Prima di inserire (BeforeInsert)

Si verifica Immediatamente dopo il caricamento della maschera, ma prima che vengano caricati i controlli o i dati Quando viene premuto il pulsante Filtro in base a selezione nella barra degli strumenti Maschera Quando la maschera riceve il punto di focalizzazione Dopo che la maschera è diventata attiva Dopo che la maschera è stata caricata ed è stata aperta la sua origine dei dati Quando la maschera viene chiusa Quando è stato fatto clic sul selettore del record della maschera con uno dei due pulsanti del mouse. Dopo il primo caricamento (evento Load) e ogni volta che si passa a un nuovo record Quando sono stati immessi dati nella maschera, ma non sono ancora stati salvati Quando la maschera perde il punto di focalizzazione. Quando si passa a un’altra maschera Quando vengono salvate le modifiche fatte sul record corrente Dopo che è stato eliminato un record oppure dopo che è stato annullato il comando di eliminazione di un record Dopo che è stato salvato un nuovo record Quando è stato fatto clic sul selettore del record della maschera con uno dei due pulsanti del mouse. Immediatamente prima dell’eliminazione del record Quando si è verificato un errore di run-time Quando viene premuto il pulsante Filtro in base a maschera nella barra degli strumenti Maschera Quando si sposta il mouse il mouse sul selettore del record o su una zona vuota della maschera Quando viene premuto un tasto della tastiera Immediatamente prima che vengano salvate le modifiche fatte al record Dopo che i record specificati sono stati eliminati, ma prima di visualizzare la finestra di dialogo che chiede la conferma dell’eliminazione Quando vengono inseriti dati per la prima volta in un record nuovo (continua)

254  Capitolo 8 Tabella 8.1  Gli eventi delle maschere. (segue) Evento Su pulsante mouse giù (MouseDown) Su pulsante mouse su (MouseUp) Su ridimensionamento (Resize) Su scaricamento (UnLoad) (*) Su tasto giù (KeyDown) (*) Su tasto su (KeyUp) Su timer (Timer) (**) Su annullamento (Undo)

Si verifica Quando il pulsante del mouse viene premuto sul selettore del record o su una zona vuota della maschera Quando viene rilasciato il pulsante del mouse Quando viene modificata la dimensione della maschera e comunque subito dopo l’evento Open. Quando si avvia il processo di chiusura della maschera, ma prima che la maschera venga effettivamente chiuse Quando viene premuto un tasto della tastiera Quando viene rilasciato un tasto che è stato premuto Quando si esaurisce il conteggio dei millisecondi impostati nella proprietà Intervallo timer (TimerInterval) Prima che venga eseguita un’operazione di annullamento richiesta dall’operatore con la pressione del tasto Esc o della combinazione di tasti Ctrl+Z o col comando Annulla dal menu Modifica Quando viene mossa la rotellina del mouse.

(**) Su rotellina mouse (MouseWheel) (*)  Questi eventi dipendono dall’impostazione della proprietà Anteprima tasti: se è impostata su No gli eventi si verificano soltanto se è selezionato il selettore del record. (**)  Questi eventi sono stati introdotti con Access 2002.

Alcuni eventi si manifestano isolatamente, mentre altri sono concatenati fra loro, per cui avvengono in successione, non appena accade il primo evento della catena. È quanto accade all’apertura di una maschera, che fa scattare nell’ordine, uno dopo l’altro, gli eventi: Open Load Resize Activate Current

Analogamente, la chiusura di una maschera determina questa successione di eventi: Unload Deactivate Close

Quando si dà un comando di eliminazione del record corrente, un clic sul pulsante Elimina record in Home/Record attiva questa catena di eventi: Delete Current BeforeDelConfirm

A questo punto compare la finestra di messaggio standard che chiede la conferma dell’eliminazione. Se si annulla il comando, vengono eseguiti in successione questi eventi:

Gestire maschere e controlli con VBA    255 Activate AfterDelConfirm Current

Se si conferma l’eliminazione, la sequenza degli eventi è: AfterDelConfirm Current

Premendo il pulsante di navigazione con l’icona dell’asterisco o il pulsante Nuovo record in Home/Record si avvia la sequenza di eventi associata all’inserimento di un nuovo record: Current BeforeInsert

Alla prima modifica effettuata in un qualunque controllo, cioè quando compare l’icona della matita, si verifica l’evento: Dirty

Quindi, quando si stabilizza l’inserimento del nuovo record, si attiva questa successione di eventi BeforeUpdate AfterUpdate AfterInsert Current

L’ultimo evento (Current) si manifesta quando il nuovo record è stabilizzato e viene visualizzato nella maschera.

Eventi dei controlli I controlli che hanno poche funzionalità hanno anche pochi eventi, mentre quelli più ricchi di funzionalità hanno molti eventi in comune con le maschere più alcuni che sono loro specifici. La Tabella 8.2 riepiloga gli eventi per i controlli più semplici e la Tabella 8.4 riassume gli eventi dei controlli più potenti. Tabella 8.2  Gli eventi dei controlli semplici. Eventi Su clic (Click) Su doppio clic (DblClick) Su modifica (Change) Su mouse spostato (MouseMove)

Controlli Struttura a schede Struttura a schede Struttura a schede Struttura a schede

Etichetta

Immagine

Sezione

Etichetta

Immagine

Sezione

Etichetta

Immagine

Sezione (continua)

256  Capitolo 8 Tabella 8.2  Gli eventi dei controlli semplici. (segue) Eventi Su pressione (KeyPress) Su pulsante mouse giù (MouseDown) Su pulsante mouse su (MouseUp) Su tasto giù (KeyDown) Su tasto su (KeyUp) Su INVIO (Enter) Su uscita (Exit)

Controlli Struttura a schede Struttura a schede Struttura a schede Struttura a schede Struttura a schede

Etichetta

Immagine

Sezione

Etichetta

Immagine

Sezione

Sotto maschera Sotto maschera

Gli eventi che sono comuni alle maschere e ai controlli hanno le stesse caratteristiche, mentre per i controlli esistono cinque eventi specifici, non disponibili nelle maschere, che vediamo riepilogati nella Tabella 8.3. Tabella 8.3  Eventi specifici dei controlli. Evento Su modifica (Change) Su non in elenco (NotInList) Su aggiornato (Updated) Su INVIO (Enter) Su uscita (Exit)

Si verifica Ogni volta che cambia qualcosa nei dati contenuti nel controllo Quando in una casella combinata si inserisce un valore diverso da quelli predisposti nell’elenco a discesa. L’evento si attiva soltanto se la proprietà Solo in elenco è impostata su Sì Quando l’operatore inserisce nella cornice un nuovo oggetto OLE o l’oggetto viene modificato dall’esterno Quando ci si sposta su un controllo, ma prima che questo diventi attivo Quando si esce da un controllo, ma prima che cessi di essere attivo

Vediamo ora, con l’aiuto di qualche esempio, come si possono utilizzare le proprietà Evento per migliorare l’interattività fra l’operatore e l’interfaccia grafica di un’applicazione.

Le routine evento Il codice VBA per gestire gli eventi risiede in un modulo di classe legato alla maschera, che viene generato automaticamente la prima volta che si associa una routine a un evento specifico operando nella scheda Evento della Finestra delle proprietà di una maschera o di un controllo. La Figura 8.1 mostra il semplice meccanismo per la creazione di una routine evento: selezionato l’evento che interessa nell’elenco della scheda Evento, un clic sulla freccia a discesa mostra un elenco di opzioni, fra le quali si seleziona il nome generico Routine evento. Un successivo clic sul pulsante Genera (quello con i tre puntini di

Su attivato (GotFocus) Su clic (Click) Su disattivato (LostFocus) Dopo aggiornamento (AfterUpdate) Su doppio clic (DblClick) Su INVIO (Enter) Su modifica (Change) Su mouse spostato (MouseMove) Su non in elenco (NotInList) Su pressione (KeyPress) Prima di aggiornare (BeforeUpdate) Su pulsante mouse giù (MouseDown) Su pulsante mouse su (MouseUp) Su tasto giù (KeyDown) Su tasto su (KeyUp) Su uscita (Exit) Su aggiornato (Updated) Su annullamento (Undo)

Casella combinata

Casella combinata Casella combinata Casella combinata Casella combinata Casella combinata Casella combinata Casella combinata Casella combinata Casella combinata Casella combinata Casella combinata Casella combinata Casella combinata Casella combinata Casella combinata Casella combinata

Casella di riepilogo Casella di riepilogo Casella di riepilogo Casella di riepilogo Casella di riepilogo Casella di riepilogo

Casella di riepilogo Casella di riepilogo Casella di riepilogo Casella di riepilogo Casella di riepilogo Casella di riepilogo Casella di riepilogo Casella di riepilogo

Casella di controllo Casella di controllo Casella di controllo Casella di controllo Casella di controllo Casella di controllo

Casella di controllo

Casella di controllo Casella di controllo Casella di controllo Casella di controllo Casella di controllo Casella di controllo Casella di controllo

Tabella 8.4  Gli eventi dei controlli complessi. Eventi

Casella di testo

Casella di testo

Casella di testo

Casella di testo

Casella di testo

Casella di testo

Casella di testo

Casella di testo

Casella di testo

Casella di testo

Casella di testo

Casella di testo

Casella di testo

Casella di testo

Casella di testo

Casella di testo

Cornice oggetto OLE Cornice oggetto OLE Cornice oggetto OLE Cornice oggetto OLE Cornice oggetto OLE Cornice oggetto OLE Cornice oggetto OLE Cornice oggetto OLE

Cornice oggetto OLE

Cornice oggetto non associato Cornice oggetto non associato

Cornice oggetto non associato Cornice oggetto non associato

Cornice oggetto non associato

Controlli Cornice Cornice oggetto oggetto OLE non associato Cornice Cornice oggetto oggetto OLE non associato Cornice Cornice oggetto oggetto OLE non associato Cornice oggetto OLE Cornice Cornice oggetto oggetto OLE non associato Cornice Cornice oggetto oggetto OLE non associato

Pulsante Pulsante di comando di opzione Pulsante di opzione Pulsante Pulsante di comando di opzione Pulsante Pulsante di comando di opzione Pulsante Pulsante di comando di opzione Pulsante Pulsante di comando di opzione Pulsante Pulsante di comando di opzione

Pulsante Pulsante di comando di opzione

Pulsante di opzione Pulsante di opzione Pulsante di opzione Pulsante di opzione Pulsante Pulsante di comando di opzione Pulsante Pulsante di comando di opzione

Pulsante di comando Pulsante di comando Pulsante di comando

Gruppo di opzioni

Gruppo di opzioni Gruppo di opzioni Gruppo di opzioni

Gruppo di opzioni

Gruppo di opzioni Gruppo di opzioni Gruppo di opzioni

Gruppo di opzioni

Interruttore

Interruttore

Interruttore

Interruttore

Interruttore

Interruttore

Interruttore

Interruttore

Interruttore

Interruttore

Interruttore

Interruttore

Interruttore

Interruttore

Gestire maschere e controlli con VBA    257

258  Capitolo 8

sospensione) a lato della casella fa aprire il modulo di classe nel quale è stato predisposto lo schema vuoto della nuova routine evento, che per definizione è Private, cioè il suo ambito di validità è limitato al modulo nel quale è contenuta.

Figura 8.1  La nascita di una nuova routine evento.

Il modulo di classe associato a una maschera contiene tutte le routine evento che si decide di creare, riferite a eventi della maschera o dei singoli controlli. Nello stesso modulo si possono scrivere anche routine Sub o Function, pubbliche o private, non attivate da eventi particolari, per avere localmente funzionalità utilizzabili dalle routine evento. Tutte le routine contenute nel modulo di classe di una maschera, comprese le routine evento, possono richiamare routine contenute in moduli standard, se queste sono state dichiarate pubbliche. Per aprire il modulo di classe associato a una maschera senza prima creare una routine evento, si preme il pulsante Visualizza codice nella scheda Strumenti Struttura maschera/ Progettazione/Strumenti. La finestra del codice di un modulo associato a una maschera ha lo stesso aspetto della analoga finestra di un modulo standard, ma con una differenza fondamentale: dalla ca-

Gestire maschere e controlli con VBA    259

sella combinata che si trova in altro a sinistra, che contiene la dicitura (generale), scende un elenco con il nome della maschera e i nomi di tutti i controlli che questa contiene; quando si seleziona il nome di un oggetto in questo elenco, dalla casella combinata di destra, quella con la dicitura (dichiarazioni), si può scegliere una delle proprietà Evento specifiche dell’oggetto selezionato nell’elenco a discesa di sinistra: in questo modo è possibile creare con due soli clic lo schema di una qualsiasi routine evento direttamente nella finestra del codice, senza passare prima per la scheda Evento della Finestra delle proprietà della maschera. Nella casella combinata di sinistra la maschera è individuata con il nome generico Form, mentre i singoli controlli sono elencati con i nomi che sono stati loro attribuiti quando sono stati creati e piazzati sulla maschera. Vediamo ora, con l’aiuto di alcuni esempi pratici, come si possono utilizzare eventi e routine evento per rendere più professionale l’interfaccia utente di un’applicazione Access.

Controllare la modifica dei dati Nella nostra applicazione vogliamo consentire all’operatore di consultare mediante una maschera i dati contenuti in una tabella, ma non vogliamo che – per errore o per altre ragioni – modifichi i dati esistenti: intendiamo, però, consentirgli di inserire nuovi record e digitare nuovi valori nelle caselle di testo, eventualmente modificandoli prima di salvare un nuovo record. Possiamo stabilire questi vincoli impostando preliminarmente alcune proprietà della maschera quando la stiamo creando in visualizzazione Struttura: basta impostare su No le proprietà Consenti modifiche e Consenti eliminazioni che sono elencate nella scheda Dati della Finestra delle proprietà della maschera. Quando si apre la maschera con queste impostazioni, l’operatore può scorrere i record utilizzando i pulsanti di spostamento, ma qualunque tentativo di modifica del contenuto delle caselle di testo non gli riesce, fin tanto che si trova su un record esistente. Se seleziona un intero record con un clic sul selettore del record e poi dà il comando di eliminazione premendo il tasto Canc, l’operazione non riesce, riceve un avviso acustico e nella barra di stato compare una scritta avvertendolo che è “Impossibile eliminare i record con questa maschera”. Se, invece, l’operatore fa clic sul pulsante col simbolo dell’asterisco, all’estrema destra dei pulsanti di spostamento, gli compare un nuovo record vuoto, nel quale può inserire dati e modificarli fino a quando non abilita la creazione effettiva del record agendo sui pulsanti di spostamento (tornando indietro o creando un altro record nuovo). Le proprietà impostate in questo modo sono permanenti ovvero, come si dice in gergo informatico, “hard coded”: il comportamento della maschera è sempre quello, a meno di modificare le proprietà agendo sulla visualizzazione Struttura della maschera stessa. Supponiamo che la nostra applicazione abbia due tipi di utenti/operatori: un gruppo che vogliamo autorizzare a modificare i dati esistenti e a inserire nuovi record e un altro che intendiamo abilitare soltanto a inserire dati nuovi, senza la possibilità di modificare i dati esistenti. Prepariamo quindi due maschere distinte, una con le proprietà Consenti modifiche e Consenti eliminazioni impostate su Sì e un’altra con le stesse proprietà impostate su No. Nel nostro esempio, la tabella sulla quale agiscono le due maschere si chiama tblClientiUSA e chiame-

260  Capitolo 8

remo le due maschere rispettivamente frmClientiUSAModificheOK e frmClientiUSANoModifiche. Creiamo una terza maschera che chiamiamo frmClientiUSA, con la quale verifichiamo se l’operatore è abilitato oppure no a modificare i dati esistenti. La maschera si presenterà come quella che vediamo nella Figura 8.2.

Figura 8.2  La maschera frmClientiUSA.

La maschera frmClientiUSA non è associata ad alcuna tabella, serve soltanto per acquisire due dati (Nome e Password) e si caratterizza come Popup, cioè quando si apre si sovrappone a eventuali maschere già aperte. Nella scheda Formato della sua Finestra delle proprietà abbiamo impostato su No tutte le proprietà che non ci servono, quindi: Consenti visualizzazione Foglio dati Consenti visualizzazione Tabella pivot Consenti visualizzazione Grafico pivot Barre scorrimento Selettori record Pulsanti spostamento Casella menu controllo Pulsanti ingrand./riduzione Pulsante chiusura Abbiamo impostato su Sì le proprietà: Consenti visualizzazione Maschera Dimensioni automatiche Centratura automatica Le due caselle di testo si chiamano rispettivamente txtNome e txtPassword. In quest’ultima, abbiamo impostato su Password la proprietà Maschera di input (contenuta nella scheda Dati): per effetto di questa impostazione, i caratteri che si scrivono nella casella di testo vengono visualizzati come asterischi, mentre vengono inseriti i caratteri effettivamente digitati. I due pulsanti di comando che portano le diciture Procedi e Annulla si chiamano rispettivamente cmdProcedi e cmdAnnulla. Con la maschera frmClientiUSA in visualizzazione Struttura scegliamo il pulsante di comando cmdAnnulla e selezioniamo la casella Su clic nella scheda Evento della sua Finestra delle proprietà. Impostiamo questa proprietà sul valore Routine evento e apriamo il modulo di classe della maschera con un clic sul pulsante Genera.

Gestire maschere e controlli con VBA    261

La routine evento da associare al clic su questo pulsante è molto semplice: Private Sub cmdAnnulla_Click() 'Chiude la maschera se si fa clic sul pulsante Annulla     DoCmd.Close End Sub

La routine è composta da una riga esplicativa e da un solo enunciato, che richiama il metodo Close dell’oggetto DoCmd: questo oggetto, che abbiamo descritto nel Capitolo 4, è dotato di un ampio assortimento di metodi, che corrispondono sostanzialmente alle azioni delle macro di interfaccia; nel caso specifico, il metodo Close, richiamato senza altre indicazioni, ha l’effetto di chiudere la maschera attiva. La routine che andremo ad associare al clic sul pulsante di comando cmdProcedi deve fare un lavoro più complesso, vale a dire: 1. predisporre un elenco di nomi, ciascuno associato a una password; 2. acquisire separatamente il nome che è stato digitato nella casella di testo txtNome e la password contenuta nella casella di testo txtPassword e confrontare le due stringhe con l’elenco degli autorizzati; 3. se il nome e la password sono nell’elenco e corrispondono, chiude la maschera frmClientiUSA e apre la maschera frmClientiUSAModificheOK, altrimenti chiude la maschera frmClientiUSA e apre la maschera frmClientiUSANoModifiche. Qui di seguito vediamo la routine evento che esegue queste operazioni. Private Sub cmdProcedi_Click() 'Crea una matrice a due dimensioni 'e vi memorizza Nomi e Password     Dim arrAutorizzati(5, 2) As String     Dim intPunta As Integer     Dim strNome As String, strPassWord As String     arrAutorizzati(0, 0) = "Amedeo"     arrAutorizzati(0, 1) = "azalea"     arrAutorizzati(1, 0) = "Enrico"     arrAutorizzati(1, 1) = "Ambra"     arrAutorizzati(2, 0) = "Elena"     arrAutorizzati(2, 1) = "selene"     arrAutorizzati(3, 0) = "Gianni"     arrAutorizzati(3, 1) = "eureka"     arrAutorizzati(4, 0) = "Marco"     arrAutorizzati(4, 1) = "Banzai" 'Se le caselle di testo txtNome e txtPassword 'contengono dati li assegna alle variabili 'strNome e strPassWord     If Not IsNull(Me.txtNome.Value) Then _         strNome = Me.txtNome.Value     If Not IsNull(Me.txtPassword) Then _         strPassWord = Me.txtPassword.Value 'Crea tre variabili di tipo booleano 'e le imposta su False     Dim bolNomeOK As Boolean

262  Capitolo 8     Dim bolPWOK As Boolean     Dim bolAutorizzato As Boolean     bolNomeOK = False     bolPWOK = False     bolAutorizzato = False 'Esplora la matrice arrAutorizzati e verifica 'se trova una corrispondenza con i valori delle 'variabili strNome e strPassWord, nel qual caso commuta 'su True il valore della variabile booleana corrispondente     For intPunta = 0 To 4         If strNome = arrAutorizzati(intPunta, 0) Then             bolNomeOK = True             Exit For         End If     Next intPunta     For intPunta = 0 To 4         If strPassWord = arrAutorizzati(intPunta, 1) Then             bolPWOK = True             Exit For         End If     Next intPunta 'Esaurita la verifica imposta la variabile bolAutorizzato 'su True se ha trovato corrispondenza sia 'sul nome sia sulla password     If bolNomeOK = True And bolPWOK = True Then         bolAutorizzato = True     Else         bolAutorizzato = False     End If 'Chiude la maschera attiva     DoCmd.Close 'In funzione del valore della variabile bolAutorizzato 'apre una delle due maschere predisposte 'per accedere ai clienti USA     If bolAutorizzato = True Then         DoCmd.OpenForm "frmClientiUSAModificheOK", _             acNormal, , , acFormPropertySettings     Else         DoCmd.OpenForm "frmClientiUSANoModifiche", _             acNormal, , , acFormPropertySettings     End If End Sub

I commenti (che qui sono stampati in corsivo, mentre nella finestra del codice compaiono in verde) dovrebbero essere sufficienti per capire appieno come funziona questa routine evento.

Gestire maschere e controlli con VBA    263

Password Il modo in cui viene eseguito il controllo della password in questa routine evento è molto rozzo e serve solo ai fini dimostrativi della routine: in VBA il semplice confronto fra variabili di tipo String produce un risultato True anche se le stringhe rappresentate dalle variabili usano in modo diverso le maiuscole e le minuscole, quindi nell’enunciato If strUno = strDue Then

le due variabili risultano uguali anche se strUno rappresenta la stringa “azalea” e strDue si riferisce alla stringa “Azalea”. Volendo costruire una routine efficace per il controllo delle password, è necessario eseguire un confronto carattere per carattere, basato sul codice ASCII dei singoli caratteri, che si ottiene utilizzando la funzione intrinseca di VBA chiamata Asc(). Qui di seguito diamo un esempio di routine per verificare l’identità di due stringhe carattere per carattere. Sub VerificaIdentitàStringhe()     Dim strBase As String, strDue As String     Dim intLunghezza1 As Integer, intLunghezza2 As Integer, _         intConta As Integre 'Predispone due matrici per ricevere i codici ASCII 'dei caratteri delle stringhe da confrontare 'Non prestabilisce le dimensioni delle matrici     Dim arrBase() As String, arrDue() As String 'Stringhe da confrontare     strBase = "azalea"     strDue = "Azalea" 'Individua la lunghezza delle stringhe     intLunghezza1 = Len(strBase)     intLunghezza2 = Len(strDue) 'Se le lunghezze sono diverse, 'sono diverse anche le stringhe 'quindi lo segnala ed esce dalla routine     If intLunghezza1 intLunghezza2 Then         MsgBox "Le stringhe sono diverse!"         Exit Sub     Else 'Se le lunghezze coincidono, prepara il confronto 'sul codice ASCII di ciascun carattere delle due stringhe 'Ridimensiona la matrice arrBase sulla lunghezza 'della prima stringa     ReDim arrBase(intLunghezza1) ‘Trasferisce il codice ASCII di ogni singolo carattere ‘nella prima matrice     For intConta = 1 To intLunghezza1         arrBase(intConta) = Asc(Mid(strBase, intConta, 1))     Next intConta

(continua)

264  Capitolo 8

(segue) 'Ripete l'operazione per la seconda stringa     ReDim arrDue(intLunghezza2)     For intConta = 1 To intLunghezza2         arrDue(intConta) = Asc(Mid(strDue, intConta, 1))     Next intConta     End If 'Confronta uno per uno i codici ASCII dei 'singoli caratteri di ciascuna stringa 'Se trova una sola differenza la segnala ed 'esce dalla routine     For intConta = 1 To intLunghezza1     If arrBase(intConta) arrDue(intConta) Then         MsgBox "Le stringhe sono diverse!"         Exit Sub     End If     Next intConta 'Se il confronto ha dato risultato Vero per ogni singolo 'carattere le due stringhe sono effettivamente uguali     MsgBox "Le stringhe sono uguali!" End Sub

Confermare l’inserimento di nuovi record I pulsanti di spostamento sono comodi elementi grafici che compaiono di norma nell’angolo inferiore sinistro di una maschera e consentono all’operatore di spostarsi agevolmente in avanti e all’indietro fra i record visualizzati dalla maschera. Un clic sull’ultimo pulsante a destra, quello contrassegnato da un asterisco, apre un nuovo record vuoto, nel quale l’operatore può inserire nuovi dati: il nuovo record va a inserirsi in fondo alla tabella sulla quale si basa la maschera non appena l’operatore dà un qualsiasi comando con i pulsanti di spostamento: torna indietro sul record precedente, si porta in un colpo solo sul primo record oppure preme ancora una volta il pulsante con l’asterisco per creare un altro record nuovo. L’estrema semplicità (e rapidità) con la quale i pulsanti di spostamento di una maschera consentono di creare e inserire nuovi record in una tabella non sempre sono un vantaggio: un operatore distratto o poco preparato potrebbe far nascere nuovi record per errore, contaminando così la qualità dei dati contenuti nella tabella. D’altra parte, i servizi resi dai pulsanti di spostamento sono molto utili e comodi ai fini della normale navigazione all’interno di una serie di record, che provengano da una tabella o da una query; inoltre, il pacchetto di controlli formato dai pulsanti di spostamento si presenta sempre con lo stesso aspetto in tutte le maschere e sottomaschere, viene piazzato automaticamente sempre nella stessa posizione e un operatore, una volta che ha imparato a utilizzarli, sa dove trovarli e come servirsene in qualsiasi maschera dell’applicazione. Possiamo conservare tutti i vantaggi offerti dai pulsanti di spostamento in una maschera, disabilitando soltanto la funzionalità di creazione e inserimento di nuovi record, così da eseguire questa importante e cruciale operazione in un modo più controllato.Vediamo con un esempio pratico come si può fare, utilizzando eventi di maschera e di controlli.

Gestire maschere e controlli con VBA    265

Creiamo una maschera associata alla tabella tblClientiUSA, che salviamo col nome frmAggiunteControllate. Inseriamo nella sezione Corpo due semplici controlli Casella di testo che chiamiamo txtNomeSocietà e txtCittà. Come abbiamo fatto per la maschera frmClientiUSANoModifiche dell’esempio precedente, impostiamo su No le proprietà Consenti modifiche e Consenti eliminazioni. Creiamo uno spazio addizionale inserendo Intestazione e Piè di pagina e in questa seconda sezione piazziamo due pulsanti di comando, ottenendo un risultato simile a quello che vediamo nella Figura 8.3.

Figura 8.3  La maschera frmAggiunteControllate in Visualizzazione Struttura.

Modifichiamo i nomi predefiniti dei pulsanti in cmdSalva e cmdAnnulla, inseriamo in questi le diciture Salva e Annulla e per entrambi impostiamo su No le proprietà Visibile e Abilitato: in questo modo, all’apertura della maschera i due pulsanti di comando non solo saranno invisibili, ma un eventuale clic fatto inavvertitamente su di essi non avrà alcun effetto perché sono disabilitati. Desideriamo che la nostra nuova maschera frmAggiunteControllate consenta all’operatore di navigare liberamente fra i record utilizzando i pulsanti di spostamento, ma non gli consentiamo di modificare i record esistenti. Gli diamo la possibilità di aggiungere un nuovo record quando preme il pulsante con l’asterisco: a quel punto rendiamo visibili e abilitati i due pulsanti di comando e nascondiamo i pulsanti di spostamento: l’operatore si troverà di fronte a una maschera vuota, abilitata ad accogliere nuovi dati, ma per inserire effettivamente il nuovo record nella tabella tblClientiUSA dovrà – dopo aver scritto i nuovi valori nelle caselle di testo txtNomeSocietà e txtCittà – fare clic sul pulsante Salva. Se, per una qualsiasi ragione (ha fatto una manovra sbagliata o non ha dati da inserire), non intende creare il nuovo record, potrà annullare senza rischi l’operazione di inserimento facendo clic sul pulsante Annulla. Rendiamo inoltre obbligatorio l’inserimento di dati completi, cioè, un clic sul pulsante Salva non avrà effetto se non sono stati immessi nuovi dati in entrambe le caselle di testo.

266  Capitolo 8

Quando è in corso l’inserimento di un nuovo record la maschera si presenta come nella Figura 8.4.

Figura 8.4  La maschera frmAggiunteControllate in visualizzazione Maschera, mentre si inserisce un nuovo record.

Dopo che l’operatore ha concluso il lavoro sul nuovo record, salvandolo o annullandolo, la maschera si sposta sull’ultimo record e i due pulsanti di comando tornano a essere invisibili e disabilitati, mentre ricompaiono, con le loro funzionalità complete, i pulsanti di spostamento. Per ottenere queste prestazioni abbiamo bisogno di tre routine evento, una per l’evento Current della maschera e una per l’evento Click di entrambi i pulsanti di comando. Ecco le routine, con molte righe di commento che dovrebbero chiarire la logica di programmazione. Private Sub Form_Current() 'Quando l'operatore sceglie di creare un nuovo record 'i pulsanti di spostamento spariscono e 'vengono abilitati e resi visibili i due 'pulsanti di comando cmdSalva e cmdAnnulla     Dim bolCondizione As Boolean     bolCondizione = Me.NewRecord     If bolCondizione = True Then         Me.cmdAnnulla.Visible = True         Me.cmdSalva.Visible = True         Me.cmdAnnulla.Enabled = True         Me.cmdSalva.Enabled = True         Me.NavigationButtons = False     Else         Me.cmdAnnulla.Visible = False         Me.cmdSalva.Visible = False         Me.cmdAnnulla.Enabled = False         Me.cmdSalva.Enabled = False         Me.NavigationButtons = True     End If End Sub

Gestire maschere e controlli con VBA    267

Come sappiamo, l’evento Current si verifica ogni volta che una maschera presenta un record, quindi tutte le volte che si preme uno dei pulsanti di spostamento. Quando una maschera è su un record esistente, la sua proprietà NewRecord, che è di tipo booleano, ha valore logico False. Il comando di aprire un nuovo record vuoto, che arriva alla maschera dalla pressione del pulsante di spostamento che ha l’asterisco, commuta il valore logico della proprietà NewRecord da False a True. Sfruttiamo questa circostanza con l’aiuto di una variabile di tipo booleano che abbiamo chiamato bolCondizione per mantenere invisibili e disabilitati i due pulsanti di comando cmdSalva e cmdAnnulla fin tanto che l’operatore si limita a navigare fra i record esistenti con l’aiuto dei pulsanti di spostamento (che si chiamano NavigationButtons in VBA). Quando l’operatore avvia l’inserimento di un nuovo record, disattiviamo i NavigationButtons e rendiamo visibili e abilitati i nostri due pulsanti di comando. Private Sub cmdAnnulla_Click() 'Prima di eseguire il comando di annullamento verifica se 'è stato immesso qualcosa nelle caselle di testo, nel qual 'caso esegue il comando Undo, che elimina il record     If Not IsNull(Me.txtNomeSocietà) Or Not IsNull(Me.txtCittà) Then         DoCmd.RunCommand Command:=acCmdUndo     End If 'Quindi torna a visualizzare il record precedente, 'generando una serie di eventi fra i quali l'evento Current 'della maschera, il che provoca l'esecuzione della 'routine evento corrispondente     DoCmd.GoToRecord , , acPrevious End Sub Private Sub cmdSalva_Click() 'Prima di salvare il nuovo record controlla che entrambe 'le caselle di testo contengano dati: se è così, esegue 'il comando SaveRecord     If Not IsNull(Me.txtNomeSocietà) And Not IsNull(Me.txtCittà) Then         DoCmd.RunCommand Command:=acCmdSaveRecord 'altrimenti se sono stati immessi dati in una sola casella di testo         ElseIf Not IsNull(Me.txtNomeSocietà) Or Not IsNull(Me.txtCittà) Then         'esegue il comando Undo             DoCmd.RunCommand Command:=acCmdUndo     End If     DoCmd.GoToRecord , , acPrevious 'Visualizza il nuovo record     DoCmd.GoToRecord , , acNext End Sub

Campi obbligatori Come sappiamo, quando si crea una nuova tabella in Visualizzazione Struttura è possibile specificare per un qualsiasi campo il vincolo Richiesto, la cui impostazione su Sì obbliga a inserire un valore in quel campo quando si inserisce un nuovo record nella tabella.

268  Capitolo 8

Omettendo di inserire un dato in un campo qualificato come Richiesto si riceve da Access la segnalazione che vediamo nella Figura 8.5, che – come accade quasi sempre per questo genere di messaggi – è formulata in modo piuttosto brusco.

Figura 8.5  L’avvertimento di Access quando si omette di inserire un dato in un campo impostato come Richiesto.

Per evitare la comparsa di questo avvertimento e al tempo stesso assicurarci che uno o più campi di un record nuovo o modificato contengano sempre un valore, possiamo evitare di impostare la proprietà Richiesto nella struttura della tabella e utilizzare una routine evento, che verifichi la presenza di dati nel campo obbligatorio e ne segnali l’omissione all’operatore in un modo chiaro e garbato. Supponendo che tutte le caselle di testo di una maschera debbano contenere dati, creiamo una funzione che può risiedere in un modulo standard o nel modulo di classe della maschera, e richiamiamo questa funzione in corrispondenza dell’evento Prima di aggiornare della maschera. La routine evento contiene un solo enunciato: Private Sub Form_BeforeUpdate(Cancel As Integer)     If DatoObbligatorio(Me) Then Cancel = -1 End Sub

L’enunciato chiama la funzione DatoObbligatorio (che vuole un parametro, il nome della maschera) e restituisce un valore booleano. Se il valore restituito è True, l’argomento Cancel della routine evento viene impostato su -1, che equivale al valore logico False, quindi l’aggiornamento non viene eseguito. La funzione DatoObbligatorio può essere costruita in vari modi, qui mostriamo un esempio che esamina ciclicamente il contenuto di tutti i controlli Casella di testo presenti nella maschera e, se ne trova uno che ha valore Null o che contiene una stringa vuota, apre una finestra di messaggio che utilizza il nome del controllo per segnalare che è vuoto e restituisce il valore logico True. Ecco la funzione: Function DatoObbligatorio(ByVal Maschera As Form) As Boolean 'Controlla che siano stati inseriti dati in tutte le caselle di testo     Dim Ctl As Control     Dim Num As Integer     DatoObbligatorio = False     Num = 0

Gestire maschere e controlli con VBA    269 'Esamina ciclicamente tutti i controlli 'Casella di testo e quando ne trova uno vuoto 'imposta su 1 la variabile di controllo Num 'ed esce dal ciclo     For Each Ctl In Maschera         If Ctl.ControlType = acTextBox Then             If Ctl = "" Or IsNull(Ctl) Then                 Num = 1                 Exit For             End If         End If     Next Ctl     If Num = 1 Then 'La finestra di messaggio usa la proprietà Name della casella 'di testo risultata vuota, dando così una segnalazione 'molto puntuale         MsgBox "Dato obbligatorio nel campo " & Ctl.Name & "," & vbCr & _         "inserirlo, per favore.", _         vbInformation, "Dato obbligatorio..."         DatoObbligatorio = True     Else         DatoObbligatorio = False     End If End Function

Possiamo vedere nella Figura 8.6 l’effetto che si ottiene quando si cerca di salvare un record senza aver inserito un dato nella casella di testo Area.

Figura 8.6  La funzione DatoObbligatorio in azione.

270  Capitolo 8

Per questo esempio abbiamo utilizzato il costrutto For Each per percorrere ciclicamente tutti i controlli di tipo Casella di testo presenti nella maschera, ma ovviamente la funzione si può riscrivere limitando la verifica soltanto ad alcuni controlli individuati singolarmente in base al nome e al tipo. L’esempio serve anche per dimostrare come funziona la proprietà ControlType, che identifica il tipo di controllo in base a una costante intrinseca. La Tabella8.5 elenca i valori di queste costanti che permettono di identificare il tipo di controllo presente in una maschera. Tabella 8.5  Le costanti intrinseche per individuare la proprietà ControlType. Costante acBoundObjectFrame acCheckBox acComboBox acCommandButton acCustomControl acImage acLabel acLine acListBox acObjectFrame acOptionButton acOptionGroup acPage acPageBreak acRectangle acSubform acTabCtl acTextBox acToggleButton

Controllo Cornice di oggetto associato Casella di controllo Casella combinata Pulsante di comando Controllo ActiveX (personalizzato) Immagine Etichetta Linea Casella di riepilogo Cornice di oggetto non associato o grafico Pulsante di opzione Gruppo di opzioni Pagina (di Struttura a schede) Interruzione di pagina Rettangolo Sottomaschera/sottoreport Struttura a schede Casella di testo Interruttore

Selezioni guidate La casella combinata è uno dei controlli più potenti e flessibili disponibili per dare funzionalità alle maschere e per questo dispone di numerose proprietà Evento. La si utilizza per offrire all’operatore un elenco predefinito di valori fra i quali scegliere con un semplice clic, riducendo quasi a zero la possibilità di errori, perché non deve digitare da tastiera il valore che gli serve. Il dato scelto così agevolmente in una casella combinata può essere poi utilizzato da una macro o da una routine evento per eseguire una qualunque operazione, semplice o complessa. Se l’uso di una casella combinata è semplice e intuitivo per l’operatore, non altrettanto semplici e intuitivi sono i suoi meccanismi interni, per cui conviene quasi sempre utilizzare la Creazione guidata per inserire in una maschera, rapidamente e senza errori, una nuova casella combinata, sulla quale si può poi sempre intervenire per migliorarne gli aspetti grafici o aggiungere funzionalità mediante codice VBA.

Gestire maschere e controlli con VBA    271

Vediamo con un esempio come si può utilizzare una casella combinata per consentire a un operatore di raggiungere rapidamente un determinato record visualizzato con una maschera. Prepariamo una maschera di prova che chiamiamo frmTestCombinata, come quella che si vede nella Figura 8.7, associandola alla stessa tabella tblClientiUSA che abbiamo utilizzato per l’esempio precedente.

Figura 8.7  La maschera frmTestCombinata in Visualizzazione Struttura.

I tre controlli nella sezione Corpo sono semplici caselle di testo associate ai campi con lo stesso nome nella tabella d’origine dei dati. Attribuiamo il nome cboSelNome alla casella combinata, piazzata nella sezione Intestazione della maschera, impostando successivamente le proprietà che sono elencate qui di seguito: Proprietà Nome elemento Numero colonne Larghezza colonne Colonna associata Solo in elenco Tipo origine riga Origine riga:

Impostazione cboSelNome 2 0 cm;4 cm 1 Sì Tabella/query SELECT IDCliente, NomeSocietà FROM tblClientiUSA ORDER BY NomeSocietà;

272  Capitolo 8

La casella della proprietà Origine riga contiene una stringa di caratteri che compone una query in linguaggio SQL: anche se non abbiamo ancora affrontato questo linguaggio, non è difficile capire che con questa istruzione si estraggono i campi IDCliente e NomeSocietà dalla tabella tblClientiUSA, ordinando i record estratti in base al contenuto del campo NomeSocietà . La proprietà Numero colonne specifica che la casella combinata presenterà tutte e due le colonne generate dalla query, mentre la proprietà Larghezza colonne indica che la prima colonna avrà larghezza zero, quindi non sarà visualizzata. Però, anche se non è visibile nella casella combinata, sarà proprio la prima colonna (quella indicata nella proprietà Colonna associata) a essere memorizzata dalla casella combinata quando si seleziona una delle voci che presenta nel suo elenco. L’elenco visualizzato, inoltre, comprenderà tutte e solo le righe generate dalla query, in base a quanto specificato dalla proprietà Solo in elenco: l’operatore, quindi, non avrà altra possibilità che scegliere uno dei valori che compariranno nell’elenco a discesa quando farà clic sulla freccia della casella combinata. Se la proprietà Solo in elenco fosse impostata su No, opzione consentita, l’operatore potrebbe scegliere uno dei valori dell’elenco, ma, in alternativa, potrebbe anche digitarne uno nuovo.Vedremo più avanti, con un altro esempio, come si gestisce questa possibilità. Dopo aver preparato la maschera e la sua casella combinata cboSelNome impostando le proprietà nel modo che abbiamo appena descritto, vediamo come utilizzare questa casella combinata per facilitare il lavoro dell’operatore. In condizioni normali l’operatore può raggiungere un record che lo interessa agendo sui pulsanti di spostamento e scorrendo via via i record. Se conosce il numero d’ordine del record che vuole esaminare può digitare questo numero nella casella che sta fra i pulsanti di spostamento per visualizzare immediatamente il record. La casella combinata che abbiamo costruito può rendere più veloce e sicuro il posizionamento su un record particolare, individuando velocemente l’elemento più significativo di ciascun record, il campo NomeSocietà: vogliamo far sì che, quando l’operatore seleziona uno di questi nomi, la maschera gli visualizzi il record corrispondente. La selezione di un valore in una casella combinata attiva il suo evento Dopo aggiornamento: dobbiamo quindi creare una routine che venga eseguita in corrispondenza con questo evento. La routine evento potrebbe essere costruita nel modo seguente: Private Sub cboSelNome_AfterUpdate()     Dim strCerca As String     'La variabile strCerca contiene il criterio di ricerca     'Attenzione alle virgolette!     strCerca = "IDCliente = " & " ' " & Me.cboSelNome & " ' "     'Trova la società selezionata nella casella combinata     Me.Recordset.FindFirst strCerca     'Se non trova la società lo segnala     If Me.Recordset.EOF Then         MsgBox "Non ho trovato la società"     End If End Sub

Gestire maschere e controlli con VBA    273

Possiamo vedere nella Figura 8.8 il risultato che si ottiene selezionando un nome nella casella combinata.

Figura 8.8  La selezione di un nome nella casella combinata sposta la maschera sul record corrispondente.

La routine evento fa tutto il suo lavoro con due enunciati esecutivi. Un clic sulla casella combinata cboSelNome genera l’evento AfterUpdate, che fa assumere alla casella combinata il valore contenuto nel campo IDCliente (la colonna numero 1 indicata nella proprietà Colonna associata). Alla variabile strCerca viene quindi associata una stringa composta da una parte fissa (la sequenza di caratteri IDCliente =) e da una parte variabile, il contenuto di cboSelNome. Quindi, se il valore memorizzato nella casella combinata è SAVEA, il contenuto della variabile strCerca diventa: IDCliente = 'SAVEA'

La variabile strCerca viene successivamente utilizzata come argomento del metodo FindFirst dell’oggetto Recordset, cioè dell’insieme di record sui quali agisce la maschera (quelli ricavati dalla query SQL che abbiamo inserito nella casella della proprietà Origine dati). Il metodo FindFirst trova il primo record che corrisponde all’argomento indicato subito dopo la sua parola chiave. Questo argomento deve essere una stringa, che contiene un criterio di confronto, sostanzialmente la clausola WHERE di un comando SELECT senza la parola chiave WHERE: tradotto in linguaggio corrente, l’enunciato     Me.Recordset.FindFirst strCerca

significa: trova, nell'insieme di record sui quali si basa questa maschera, il primo record nel quale il campo IDCliente contiene il valore "SAVEA".

274  Capitolo 8

Le virgolette utilizzate per creare il contenuto della variabile strCerca sono estremamente importanti; infatti, se non ci fossero, la variabile strCerca conterrebbe questa stringa di caratteri: IDCliente = SAVEA

Se non è racchiusa fra virgolette, il VBA non considera la successione di caratteri SAVEA come una stringa da prendere così com’è, ma la interpreta come se fosse il nome di una variabile, per cui l’enunciato operativo avrebbe questo significato: trova, nell'insieme di record sui quali si basa questa maschera, il primo record nel quale il campo IDCliente contiene il valore rappresentato dalla variabile SAVEA

E interpretato in questo modo provocherebbe un errore di run-time, perché la variabile SAVEA non esiste.

Colonne di una casella combinata Il codice VBA per la routine evento che abbiamo descritto sopra si riferisce in questo modo al valore selezionato nella casella combinata:     strCerca = "IDCliente = " & " ' " & Me.cboSelNome & " ' "

Siccome nella scheda Dati della Finestra delle proprietà la proprietà Colonna associata della casella combinata cboSelNome è impostata su 1, il riferimento individua correttamente il contenuto della prima delle due colonne in cui si articola la casella combinata (il contenuto esiste, ma non si vede perché nella stessa scheda Dati la larghezza della prima colonna è impostata su zero). Possono esistere casi in cui è necessario utilizzare in una routine una colonna diversa da quella dichiarata come Colonna associata. In casi del genere non è sufficiente richiamare il nome della casella combinata, ma bisogna specificare la posizione che la colonna ha all’interno della casella combinata, tenendo presente che, per il VBA, la numerazione delle colonne inizia da zero, quindi l’enunciato precedente andrebbe riscritto in questo modo:     strCerca = "IDCliente = " & "'" & Me.cboSelNome.Column(0) & "'"

per riferirsi correttamente alla prima colonna, cioè a quella con indice zero.

Possiamo utilizzare lo stesso meccanismo che abbiamo appena illustrato per modificare dinamicamente l’intero contenuto di una sottomaschera al variare di un valore selezionato in una casella combinata. Utilizzando una copia della tabella Clienti dell’applicazione dimostrativa Northwind generiamo con una query una tabella che chiameremo tblClientiAree, quindi inseriamo nella nuova tabella una colonna Area e dopo aver aperto la tabella in visualizzazione Foglio dati inseriamo nel campo Area di ciascun record un nome basato sul contenuto del campo Paese, come si vede dalla Figura 8.9.

Gestire maschere e controlli con VBA    275

Figura 8.9  Alcuni record della tabella di prova tblClientiAree.

Utilizzeremo per il campo Area i seguenti valori: •• Europa Centro; •• Europa Nord; •• Europa Sud; •• Nord America; •• Sud America. Con l’aiuto della Creazione guidata otteniamo rapidamente una maschera in formato Tabulare, che chiameremo sfrmClientiAree e si dovrà presentare come nella Figura 8.10.

Figura 8.10  La maschera sfrmClientiAree in Visualizzazione Struttura.

276  Capitolo 8

Creiamo poi una maschera non associata, che chiamiamo frmClientiAree, eliminando tutte le proprietà che non servono (la maschera farà da contenitore per sfrmClientiAree, che useremo come sottomaschera) e inserendo un piè di pagina e una intestazione. Dimensioniamo opportunamente la nuova maschera e dalla sezione Maschere del riquadro di spostamento trasciniamo col mouse la maschera sfrmClientiAree entro la sezione Corpo della maschera frmClientiAree. Modifichiamo il risultato assegnando alla proprietà Nome elemento della sottomaschera il nome ClientiAree ed eliminiamo l’etichetta della sottomaschera. Nell’intestazione della maschera frmClientiAree piazziamo una casella combinata, che chiamiamo cboAree, per la quale impostiamo le proprietà elencate qui di seguito Proprietà Nome elemento Numero colonne Colonna associata Valore predefinito Tipo origine riga Origine riga:

Impostazione cboAree 1 1 “Tutte” Elenco valori “Tutte”; “Europa Centro”; “Europa Nord”; “Europa Sud”; “Nord America”; “Sud America”

A questo punto non ci resta che creare la routine per l’evento Dopo aggiornamento della casella combinata, che potrebbe presentarsi nel modo seguente: Private Sub cboAree_AfterUpdate()     Dim strArea As String     strArea = Me.cboAree.Value     If strArea "Tutte" Then 'Compone la query SQL per estrarre i record dell'area selezionata     strArea = "SELECT NomeSocietà, Contatto, Città, Area FROM " _         & "tblClientiAree WHERE Area = '" & strArea & "'"     Else 'Compone la query SQL per estrarre tutti i record         strArea = "SELECT * FROM tblClientiAree"     End If 'Assegna la query come valore della proprietà 'Origine dati della sottomaschera     Me!ClientiAree.Form.RecordSource = strArea End Sub

Anche in questa routine bisogna fare attenzione all’uso delle virgolette semplici e doppie nell’enunciato che compone la prima query SQL. La parte operativa della routine funziona in questo modo: l’istruzione SQL generata in base al valore selezionato nella casella combinata cboAree viene utilizzata per impostare il valore della proprietà RecordSource dell’oggetto ClientiAree, individuato come Form contenuto nella maschera principale. La Figura 8.11 mostra l’effetto che si ottiene selezionando l’area Europa Nord.

Gestire maschere e controlli con VBA    277

Figura 8.11  La sottomaschera mostra i record corrispondenti all’area selezionata nella maschera principale.

Se, in una routine evento contenuta nel modulo di classe associato a una maschera con sottomaschera, si vuole far riferimento a una proprietà della sottomaschera o di un suo controllo, per individuare correttamente l’oggetto e la sua proprietà bisogna ricorrere alla notazione usata nell’ultimo enunciato della routine che abbiamo visto sopra:     Me.ClientiAree.Form.RecordSource = strArea

Con questa notazione si intende:“assegna strArea alla proprietà RecordSource della maschera ClientiAree contenuta in questa maschera”. Trattandosi di una notazione non immediatamente intuitiva, abbiamo riepilogato nella Tabella 8.6 gli schemi per far riferimento a proprietà di controlli che si trovano in una maschera principale, in una sottomaschera e in una sottomaschera di livello inferiore. I nomi utilizzati per gli esempi sono: •• Maschera per la maschera di primo livello •• SubMasch1 è il nome del controllo Sottomaschera inserito in Maschera •• SubMasch2 è il nome del controllo Sottomaschera inserito in SubMasch1. •• NomeControllo è un qualsiasi controllo inserito in Maschera, SubMasch1 o SubMasch2 Come abbiamo spiegato nel Capitolo 5, negli enunciati di riferimento si conviene di usare il punto esclamativo al posto del punto quando il nome a destra del segno è definito dal programmatore invece di essere il nome Access di un oggetto o di una proprietà. Si tratta di una convenzione grafica, per il VBA il punto e il punto esclamativo si equivalgono in questo tipo di enunciati.

278  Capitolo 8 Tabella 8.6  Notazioni per far riferimento a sottomaschere e controlli. Quando si è su Maschera principale Sottomaschera 1 Per riferirsi alla proprietà di una maschera, per esempio RecordSource Sulla Maschera Me.RecordSource Me.Parent.RecordSource Sulla sottomaschera 1 Me!SubMasch1.Form. Me.RecordSource RecordSource Sulla sottomaschera 2 Me!SubMasch1.Form!SubMasch2. Me!SubMasch2.Form. Form.RecordSource RecordSource Per riferirsi a un controllo Sulla Maschera Me!NomeControllo Me.Parent!NomeControllo Sulla sottomaschera 1 Me!SubMasch1. Me!NomeControllo Form!NomeControllo Sulla sottomaschera 2 Me!SubMasch1.Form!SubMasch2. Me!SubMasch2. Form!NomeControllo Form!NomeControllo Per riferirsi alla proprietà di un controllo, per esempio Enabled Sulla Maschera Me!NomeControllo.Enabled Me.Parent!NomeControllo.Enabled Sulla sottomaschera 1 Me!SubMasch1. Me!NomeControllo.Enabled Form!NomeControllo.Enabled Sulla sottomaschera 2 Me!SubMasch1.Form!SubMasch2. Me!SubMasch2. Form!NomeControllo.Enabled Form!NomeControllo.Enabled Per riferirsi alla proprietà di un controllo sottomaschera, per esempio SourceObject Sulla Maschera N/D N/D Sulla sottomaschera 1 Me!SubMasch1.SourceObject N/D Sulla sottomaschera 2 Me!SubMasch1.Form!SubMasch2. Me!SubMasch2.SourceObject SourceObject Quando si è su Sottomaschera 2 Per riferirsi alla proprietà di una maschera, per esempio Sulla Maschera Me.Parent.Parent.RecordSource Sulla sottomaschera 1 Me.Parent.RecordSource Sulla sottomaschera 2

Me.RecordSource

Non in queste maschere RecordSource Forms!Maschera.RecordSource Forms!Maschera!SubMasch1.Form. RecordSource Forms!Maschera!SubMasch1. Form!SubMasch2. Form.RecordSource

Per riferirsi a un controllo Sulla Maschera Me.Parent.Parent!NomeControllo Forms!Maschera!NomeControllo Sulla sottomaschera 1 Me.Parent!NomeControllo Forms!Maschera!SubMasch1. Form!NomeControllo Sulla sottomaschera 2 Me!NomeControllo Forms!Maschera!SubMasch1. Form!SubMasch2. Form!NomeControllo Per riferirsi alla proprietà di un controllo, per esempio Enabled Sulla Maschera Me.Parent.Parent!NomeControllo. Forms!Maschera!NomeControllo. Enabled Enabled Sulla sottomaschera 1 Me.Parent!NomeControllo. Forms!Maschera!SubMasch1. Enabled Form!NomeControllo.Enabled

Gestire maschere e controlli con VBA    279 Quando si è su Sottomaschera 2 Non in queste maschere Per riferirsi alla proprietà di un controllo sottomaschera, per esempio SourceObject Sulla Maschera N/D N/D Sulla sottomaschera 1 N/D Forms!Maschera!SubMasch1. SourceObject Sulla sottomaschera 2 N/D Forms!Maschera!SubMasch1. Form!SubMasch2.SourceObject

Vediamo ora che cosa bisogna fare per consentire a un operatore di scrivere in una casella combinata un valore diverso da quelli predisposti nell’elenco. Creiamo una tabella Corrieri composta da due soli campi, inserendovi alcuni dati di prova, in modo da ottenere una situazione simile a quella che vediamo qui sotto Corrieri IDCorriere 1 2 3 4

Nome EspressoNord TransAlp EuroTransit WeDeliver

In questa tabella il campo IDCorriere è la chiave primaria ed è di tipo Contatore, mentre il tipo di dati del campo Nome è Testo. Facciamo una copia della tabella tblClientiUSA salvandola col nome tblClientiUSA2 e modifichiamone la struttura aggiungendovi un campo con tipo dati Testo che chiamiamo Corriere: in questo campo vogliamo inserire il nome di un corriere scegliendolo fra quelli contenuti nella tabella Corrieri.A questo scopo generiamo automaticamente una maschera standard che salviamo col nome frmTestCombinata2, quindi la apriamo in Visualizzazione Struttura e modifichiamo il controllo casella di testo creato automaticamente per il campo Corriere, sostituendolo con una casella combinata (l’operazione si esegue molto agevolmente selezionando la casella di testo da cambiare con un clic destro e scegliendo Casella combinata dal menu contestuale Cambia in). Attribuiamo alla casella combinata il nome cboCorriere e impostiamo nel modo elencato qui di seguito le sue proprietà più rilevanti. Proprietà Nome elemento Numero colonne Larghezza colonne Colonna associata Solo in elenco Tipo origine riga Origine riga:

Impostazione cboCorriere 2 0 cm;4 cm 2 Sì Tabella/query SELECT DISTINCTROW IDCorriere, Nome FROM Corrieri ORDER BY Nome;

280  Capitolo 8

Se impostassimo su No la proprietà Solo in elenco, l’operatore potrebbe inserire un nome qualunque nella casella, invece di sceglierlo fra quelli generati dalla query contenuta nella casella Origine riga. Una scelta di questo genere crea un disallineamento fra la tabella Corrieri e quella dei clienti, e se accadesse più volte dovremmo farci carico di aggiornare la tabella dei corrieri con i nuovi nomi che sono stati inseriti nel campo Corriere della tabella dei clienti. Meglio, quindi, predisporre su Sì il valore della proprietà Solo in elenco e gestire con una opportuna routine evento il caso in cui l’operatore decidesse di scrivere un nome di corriere diverso da quelli elencati nella casella combinata. Nella scheda Evento della Finestra delle proprietà della nuova casella combinata cboCorriere scegliamo quindi di avere una routine evento per il caso Su non in elenco, quindi ci portiamo sulla finestra del codice e osserviamo che lo schema della routine evento si presenta in questo modo: Private Sub cboCorriere_NotInList(NewData As String, Response As Integer) End Sub

Diversamente dallo schema di altre routine evento, che hanno di solito una struttura sintattica più semplice, senza argomenti fra le parentesi, la gestione dell’evento Non in elenco prevede due argomenti, che si chiamano NewData e Response, generati automaticamente da Access e che dobbiamo gestire: •• NewData è il nome della stringa che contiene il testo scritto nella casella combinata al posto di uno dei valori predefiniti; •• Response è una costante intrinseca, che può assumere tre valori diversi e indica in che modo deve essere gestito il valore non in elenco. I valori assunti da Response possono essere: 1. acDataErrDisplay, che è l’impostazione predefinita: se non è stata predisposta una routine evento, questa impostazione presenta la finestra di messaggio che vediamo nella Figura 8.12 e l’utente non può fare altro che selezionare un dato fra quelli disponibili nell’elenco. 2. acDataErrContinue: consente di sostituire il messaggio predefinito con uno predisposto nella routine evento, col quale si può proporre all’operatore di scegliere se aggiornare l’elenco della casella combinata con il nuovo valore oppure rinunciare e scegliere un valore esistente; se si imposta questo valore per Response la routine evento deve contenere gli enunciati necessari per presentare una finestra di messaggio e gestire la risposta dell’utente. 3. acDataErrAdded: abilita l’aggiunta della nuova voce all’elenco della casella combinata, aggiunta che non avviene automaticamente, ma deve essere effettuata con adeguate istruzioni VBA.

Gestire maschere e controlli con VBA    281

Figura 8.12  La finestra di messaggio che Access presenta quando si inserisce in una casella combinata un valore non in elenco.

Tenendo presenti queste indicazioni, possiamo scrivere la routine evento nel modo che vediamo qui sotto: Private Sub cboCorriere_NotInList(NewData As String, Response As Integer)     Dim db As DAO.Database, rs As DAO.Recordset ' Variabili per gestire oggetti DAO     Dim strMsg As String    'Variabile stringa per la finestra di messaggio     'Costruisce la stringa per la domanda da visualizzare     'nella finestra di messaggio usando l'argomento intrinseco NewData     'e la costante vbCrLf di VBA     strMsg = "'" & NewData & "' non è un nome di corriere disponibile"     strMsg = strMsg & vbCrLf & "Desidera aggiungerlo all'elenco dei corrieri?"     strMsg = strMsg & vbCrLf & "Clic su Sì per aggiungerlo, su No " & _             "per selezionarne uno esistente."     'Presenta l'alternativa all'operatore     If MsgBox(strMsg, vbQuestion + vbYesNo, _             "Aggiungere un nuovo nome?") = vbNo Then         'Se l'operatore rinuncia a modificare l'elenco,         'imposta il valore della costante intrinseca Response         Response = acDataErrContinue     Else     'Se, invece, l'operatore sceglie di aggiungere il nuovo valore,     'apre la tabella Corrieri e vi inserisce un nuovo     'record corrispondente al valore digitato         Set db = CurrentDb         Set rs = db.OpenRecordset("Corrieri", dbOpenDynaset)         rs.AddNew         'Esegue il metodo AddNew specificando il campo Nome             rs!Nome = NewData         'Inserisce materialmente il nuovo valore         'nella tabella e poi la chiude         rs.Update         rs.Close                  'Imposta un valore di uscita         'per la costante intrinseca Response             Response = acDataErrAdded     End If End Sub

282  Capitolo 8

In questa routine abbiamo utilizzato oggetti, metodi e proprietà della famiglia DAO, che descriveremo più avanti. Per il momento basterà spiegare che il ricorso a questi strumenti ci ha permesso di acquisire il nuovo valore digitato nella casella combinata e di aggiungere un nuovo record alla tabella Corrieri con il nuovo valore nel campo Nome. Nel codice si usa anche la costante vbCrLf, che in VBA richiama i comandi di “a capo” e “inserimento nuova riga”; l’uso di questa costante nell’enunciato:     strMsg = "'" & NewData & "' non è un nome di corriere disponibile"     strMsg = strMsg & vbCrLf & "Desidera aggiungerlo all'elenco dei corrieri?"     strMsg = strMsg & vbCrLf & "Clic su Sì per aggiungerlo, su No " & _             "per selezionarne uno esistente."

inserisce nella stringa caratteri speciali che vengono interpretati dalla successiva funzione MsgBox come comandi di a capo, presentando il messaggio su più righe, per renderlo più leggibile. Se ora proviamo ad aprire la maschera frmTestCombinata2, scrivendo un nome diverso da quelli proposti nella casella combinata, otteniamo l’effetto che si vede nella Figura 8.13.

Figura 8.13  La routine evento chiede conferma della scelta di un valore diverso.

Se confermiamo il nuovo nome, non soltanto questo viene inserito nella casella combinata e quindi nel campo corrispondente del record sottostante, ma da adesso in poi si aggiunge alle opzioni presentate dalla casella combinata. Dopo aver chiuso la maschera possiamo aprire la tabella Corrieri e constatare che ora contiene il nuovo valore.

Gestire maschere e controlli con VBA    283 Corrieri IDCorriere 1 2 3 4 5

Nome EspressoNord TransAlp EuroTransit WeDeliver La Rapida

Quando torniamo ad aprire la maschera, se facciamo scendere l’elenco dei valori della casella combinata possiamo vedere (Figura 8.14) che il nuovo nome è disponibile nell’elenco ordinato alfabeticamente dalla query SQL che determina l’origine dei dati della casella combinata.

Figura 8.14  La casella combinata presenta un elenco di valori aggiornato.

Con questo ultimo esempio ci siamo addentrati nel vasto campo dei Data Access Objects, uno dei pilastri di Access, che esaminiamo nel prossimo capitolo.

Capitolo 9

Lavorare con i DAO

Gli strumenti di programmazione disponibili col nome collettivo di Data Access Objects, in sigla DAO, sono stati la struttura portante di tutte le versioni di Access fino alla 97. Nelle versioni dalla 2000 in poi sono tuttora disponibili, ma vengono affiancati dagli ActiveX Data Objects, ADO, che svolgono le stesse funzionalità e ne aggiungono altre, ma con logiche alquanto diverse. Agli ADO e alle loro articolate specificità è dedicato il prossimo Capitolo 10. Come dice il loro nome, i DAO sono oggetti per accedere ai dati. Tramite i DAO è possibile: •• analizzare la struttura di un database esistente; •• creare nuovi database; •• creare tabelle e query e aggiungerle a un database esistente; •• modificare tabelle e query; •• lavorare su insiemi di record; •• lavorare su database diversi da quello corrente; •• modificare dati contenuti nelle tabelle. Con i DAO, quindi, si può accedere direttamente agli elementi che costituiscono la parte essenziale di un database – tabelle e query – per eseguire su e con tali oggetti operazioni da programma, rigorosamente controllate e senza intervento dell’operatore. Si tratta, quindi, di strumenti essenziali per realizzare applicazioni con Access. Esiste una forte correlazione fra Data Access Objects e Visual Basic for Applications: i DAO consentono di accedere ai dati contenuti nei database e il VBA è il linguaggio che permette di manipolare i DAO e di

In questo capitolo •

Primi esperimenti



Un po’ di teoria



Gli oggetti Recordset



Gli oggetti DAO nelle applicazioni

286  Capitolo 9

ottenere risultati concreti. Il modo migliore per cominciare a capire come utilizzare il VBA per lavorare con i DAO consiste nell’esaminare qualche semplice esempio.

Primi esperimenti Da Windows creiamo una cartella che chiamiamo \Esempi e in questa salviamo una copia di lavoro del database dimostrativo Northwind che avremo chiamato NorthwindLavoro.accdb; poi apriamo l’applicazione. Creiamo quindi un nuovo modulo standard e immettiamo la seguente routine VBA: Sub ContaTabelle()     Dim dbTest As DAO.Database     Dim intNumTabelle As Integer     Dim strNomeDB As String     Set dbTest = CurrentDb     strNomeDB = dbTest.Name     intNumTabelle = dbTest.TableDefs.Count     MsgBox "Il database " & strNomeDB & " contiene: " _         & intNumTabelle & " tabelle." End Sub

Ambiguità Alcuni oggetti di importanza fondamentale – Recordset e Database – hanno lo stesso nome nei DAO e negli ADO. Per evitare ambiguità che possono portare a errori di sintassi o di run-time, è opportuno far precedere il nome di questi oggetti, quando vengono richiamati in un enunciato, dal nome della famiglia alla quale appartengono, quindi DAO.Database (come nella routine che stiamo commentando) oppure DAO.Recordset. Il prefisso per gli omonimi oggetti ADO è ADODB, come vedremo meglio nel prossimo capitolo.

Salviamo quindi il modulo col nome mdlProveDAO ed eseguiamo la routine: otteniamo la finestra di messaggio riprodotta nella Figura 9.1.

Figura 9.1  La finestra di messaggio generata dalla routine ContaTabelle.

Se andiamo a esaminare la sezione Tabelle del Riquadro di spostamento, però, possiamo constatare che le tabelle sono soltanto otto: dove sono le altre dodici?

Lavorare con i DAO   287

In realtà, il file database NorthwindLavoro.accdb contiene dodici tabelle di sistema, che non sono visibili, per cui la routine VBA ha operato correttamente, conteggiando 20 tabelle fra visibili e non visibili. Esaminiamo le singole righe di quest’esempio per avere una prima idea dei meccanismi che vengono attivati. Con i tre enunciati Dim si dichiarano: 1. una variabile dbTest, che è destinata a rappresentare un database DAO; 2. una variabile intNumTabelle, definita per rappresentare un numero intero; 3. una variabile strNomeDB, prevista per contenere una stringa. Dopo aver dichiarato le variabili, si attribuisce loro un valore, con i tre enunciati di assegnazione, che hanno strutture sintattiche diverse. Per le variabili che hanno tipo dati stringa e numero intero, l’assegnazione viene fatta con un semplice segno di uguale (=), mentre la variabile dbTest, che rappresenta un oggetto, viene assegnata a un oggetto DAO. Database, mediante la parola chiave Set Fin qui siamo ancora nel Basic tradizionale. Con CurrentDb si comincia a lavorare con i DAO. CurrentDb, infatti, è una funzione DAO che restituisce il nome del database aperto (quello corrente, appunto). Essendo una funzione, andrebbe scritta CurrentDb(), ma l’Editor di Visual Basic l’accetta anche senza la coppia finale di parentesi tonde. L’enunciato di assegnazione, quindi, attribuisce alla variabile dbTest il compito di rappresentare il database nel quale stiamo lavorando (che è il database NorthwindLavoro. accdb, per chi l’avesse dimenticato). Anche nelle due assegnazioni successive compare il mondo DAO. A destra del segno di uguale, infatti, non troviamo un valore numerico o un valore letterale, ma oggetti, metodi e proprietà. Con l’espressione dbTest.Name, infatti, si fa riferimento alla proprietà Name dell’oggetto al quale è assegnata la variabile dbTest. Siccome dbTest è il database corrente (come risulta dall’enunciato di assegnazione visto prima), l’enunciato: strNomeDB = dbTest.Name

fa sì che la variabile stringa strNomeDB contenga il nome del database corrente. Successivamente, troviamo l’espressione dbTest.TableDefs.Count, formata da tre nomi, separati ciascuno da un punto. Leggendo questa espressione da destra verso sinistra, ci rendiamo conto che identifica la proprietà Count dell’insieme TableDefs dell’oggetto dbTest. La proprietà Count rappresenta il conteggio di qualcosa, come dice con chiarezza il suo nome: nella fattispecie, è il conteggio degli oggetti contenuti nell’insieme TableDefs del database corrente. Questo insieme è il contenitore di tutte le tabelle del database, per cui l’enunciato: intNumTabelle = dbTest.TableDefs.Count

assegna alla variabile intNumTabelle, che è un numero intero, il valore della proprietà Count, cioè il numero che risulta dal conteggio delle tabelle che si trovano nel database aperto, NorthwindLavoro.accdb, nel nostro caso. Con l’ultimo enunciato, quello che inizia con la parola chiave MsgBox, i valori acquisiti dalle due variabili che interessa vedere in output (strNomeDB e intNumTabelle) vengono

288  Capitolo 9

combinati con alcune parole esplicative, per generare il testo visualizzato nella finestra di messaggio riportata nella Figura 9.1. Si osservi che strNomeDB contiene l’intero percorso della nostra copia del file NorthwindLavoro.accdb, non semplicemente il nome del file database. L’esempio che abbiamo appena visto illustra abbastanza bene il modo in cui la sintassi di VBA consente di utilizzare oggetti DAO facendo riferimento alle loro proprietà e ai loro metodi. Dal punto di vista pratico, però, l’esempio è un po’ povero. Proviamo a migliorarlo in modo che dia un’informazione utile e non fuorviante: le tabelle di sistema non ci interessano, perché è comunque vietato utilizzarle o modificarle in qualunque modo; vogliamo sapere quante sono le tabelle effettive nel database Northwind e vogliamo saperlo con una routine VBA, invece di esaminare direttamente la sezione Tabelle del Riquadro di spostamento. Per raggiungere il nostro scopo dobbiamo introdurre altre variabili e far riferimento ad altre proprietà di oggetti database. Sappiamo che i nomi delle tabelle di sistema iniziano tutti con i quattro caratteri “MSys”, quindi impostiamo la nostra routine in modo che scarti dal conteggio tutte le tabelle che hanno quei caratteri all’inizio del nome. La nuova routine potrebbe presentarsi nel modo seguente: Sub ContaTabelleEffettive()     Dim dbTest As DAO.Database, tdfTabella As DAO.TableDef     Dim intNumTabelle As Integer, intTabelleEffettive As Integer     Dim strNomeDB As String, intContatore As Integer     Set dbTest = CurrentDb     strNomeDB = dbTest.Name     intNumTabelle = dbTest.TableDefs.Count     For intContatore = 0 To intNumTabelle - 1         Set tdfTabella = dbTest.TableDefs(intContatore)         If Left(tdfTabella.Name, 4) "MSys" Then             intTabelleEffettive = intTabelleEffettive + 1         End If     Next intContatore     MsgBox "Il database " & strNomeDB & " contiene " & intNumTabelle & _         " tabelle: " & vbCrLf & intTabelleEffettive & " definite dall'utente e " & _         intNumTabelle - intTabelleEffettive & " tabelle di sistema" End Sub

Abbiamo introdotto due nuove variabili con tipo dati Integer (intTabelleEffettive e intContatore) e un’altra variabile oggetto (tdfTabella). Con le variabili intContatore e tdfTabella possiamo creare un ciclo For...Next, per scorrere a una a una tutte le tabelle esistenti nel database e conteggiare soltanto quelle i cui nomi non cominciano con i quattro caratteri “MSys”. Eseguiamo la verifica durante il conteggio ricorrendo a un costrutto If...Then e alla funzione VBA intrinseca Left(). L’enunciato di assegnazione per la nuova variabile oggetto tdfTabella si presenta in questo modo: Set tdfTabella = dbTest.TableDefs(intContatore)

e assegna la variabile tdfTabella all’oggetto tabella che ha il numero indice pari al valore della variabile intContatore. Le variabili che hanno tipo dati numerico assumono valore

Lavorare con i DAO   289

zero nel momento in cui vengono dichiarate. Gli oggetti tabella nell’insieme TableDefs sono individuabili per nome o in base a un numero indice, il cui conteggio parte da zero, per cui, siccome la variabile intContatore all’inizio del ciclo è impostata su zero, l’assegnazione: dbTest.TableDefs(intContatore)

significa “la prima tabella nell’insieme delle tabelle del database dbTest”. Inserendo questa assegnazione nel ciclo For...Next che segue: For intContatore = 0 To intNumTabelle – 1     Set tdfTabella = dbTest.TableDefs(intContatore)     If Left(tdfTabella.Name, 4) "MSys" Then         intTabelleEffettive = intTabelleEffettive + 1     End If Next intContatore

la variabile intContatore viene progressivamente incrementata di uno a partire dal valore zero iniziale, per cui fa riferimento una dopo l’altra a tutte le tabelle dell’insieme TableDefs. All’interno del ciclo, un costrutto If...Then si serve della funzione Left() per esaminare i primi quattro caratteri della proprietà Name di ciascuna tabella e incrementa di uno la variabile intTabelleEffettive ogni volta che trova un nome che non inizia con la stringa “MSys”. Per il numero di iterazioni del ciclo For...Next, si sfrutta il valore assunto all’inizio dalla variabile intNumTabelle, che ha preso il suo valore dalla proprietà Count dell’insieme TableDefs. Siccome i numeri indice che individuano le singole tabelle nell’insieme iniziano da zero, il valore finale del contatore usato nel ciclo For...Next è pari a intNumTabelle-1. L’enunciato MsgBox finale utilizza i valori ottenuti con gli enunciati precedenti e genera la finestra di messaggio riprodotta nella Figura 9.2, che fornisce informazioni più utili di quelle ottenute con la versione precedente della routine.

Figura 9.2  La nuova versione della routine fornisce maggiori informazioni.

La routine VBA che abbiamo appena esaminato fornisce maggiori informazioni della prima versione e aiuta a farsi un’idea di come si lavora con VBA, ma non ci dice nulla di nuovo: sappiamo già, senza dover ricorrere a VBA, da una semplice occhiata al Riquadro di spostamento, che il database corrente contiene otto tabelle. Possiamo, però, modificare

290  Capitolo 9

la routine, in modo che dia un’informazione davvero utile, ricavandola da un database che si trova nel nostro computer, ma che non è aperto. Se avessimo la curiosità di sapere quante tabelle contiene il database Banche.accdb, che si trova nella cartella \Esempi del disco C, basta sostituire l’enunciato di assegnazione Set dbTest = CurrentDb, con un enunciato come il seguente: Set dbTest=DBEngine.OpenDatabase("C:\Esempi\Banche.accdb", True)

per ottenere, senza alcun’altra modifica, la finestra di messaggio mostrata nella Figura 9.3.

Figura 9.3  Con DAO e VBA si può lavorare anche su database diversi da quello corrente.

Il nuovo enunciato assegna alla variabile oggetto dbTest il metodo OpenDatabase dell’oggetto DBEngine, specificando il percorso completo del database come argomento del metodo. Gli argomenti di questo metodo possono essere molti e complessi, come si vedrà più avanti. L’aspetto interessante di quest’ultimo esperimento è che ci ha mostrato come si possa ricorrere a Visual Basic for Applications e ai Data Access Objects per lavorare da un database attivo su altri database. Se l’applicazione gestionale che si sta costruendo lo richiede, si possono ricavare – mediante codice VBA – informazioni da altri database, o modificarne contenuti e struttura, superando il vincolo imposto dall’interfaccia grafica, che non consente di avere più file database aperti contemporaneamente.

Un po’ di teoria Nel paragrafo precedente abbiamo dato qualche semplice esempio concreto di utilizzo dei DAO mediante VBA: lo scopo era quello di entrare subito in argomento, facendo vedere che cosa si può ottenere con questi due strumenti di lavoro. Prima di proseguire, però, sarà il caso di fornire qualche base teorica e concettuale all’esposizione, approfondendo le indicazioni di carattere generale sugli oggetti che abbiamo passato in rassegna brevemente nel Capitolo 5. In primo luogo, perché occorrono i DAO? Uno degli aspetti qualificanti di Access è l’utilizzo del file .mdb nelle versioni precedenti e .accdb nelle versioni 2007 e 2010 come unico contenitore generale di tutto quello che serve in un database e in un’applicazione database. L’organizzazione interna di questo file è molto complessa e non sarebbe prudente consentire di esaminarla, leggerne i contenuti e modificarli con istruzioni che

Lavorare con i DAO   291

accedono direttamente alla struttura del file. I DAO servono per incanalare lungo linee rigorosamente predefinite gli accessi a un file .mdb o .accdb, sfruttando le funzionalità tipiche dei linguaggi di programmazione orientati agli oggetti, che permettono di lavorare su strutture di dati, leggerle, modificarle, crearle, senza doversi preoccupare del modo in cui i dati vanno a collocarsi nei file. Con l’aiuto di variabili oggetto opportunamente dichiarate e impostate si creano istanze degli oggetti contenuti in un file Access; queste istanze risiedono in memoria e ci si può lavorare sopra in tutta sicurezza, perché gli originali dai quali derivano rimangono su file, senza modifiche. Se serve cambiare qualcosa, aggiungere un record a una tabella, modificare il contenuto di qualche campo, queste variazioni vengono trasferite in modo controllato nel file originale. Le istruzioni del linguaggio VBA possono quindi lavorare sui contenuti di un file .mdb o .accdb tramite variabili che rappresentano oggetti DAO, seguendo la stessa logica con la quale lavorano su variabili che individuano stringhe o valori numerici; siccome questi oggetti hanno metodi e proprietà, gli enunciati del codice vanno costruiti attenendosi ai loro particolari schemi sintattici, che sono basati su una rigorosa gerarchia. Per capire come sono fatti e come funzionano i DAO, quindi, bisogna in primo luogo conoscerne e capirne la gerarchia.

La gerarchia dei DAO Gli oggetti per l’accesso ai dati sono strutturati secondo uno schema gerarchico al cui vertice si trova l’oggetto DBEngine. Come un suo illustre antenato (del quale non ha la minima consapevolezza) il DBEngine si può considerare il motore immobile di tutti i database che utilizzano il motore ACE. L’oggetto DBEngine contiene e controlla tutti gli altri oggetti nella gerarchia dei DAO. Non è possibile creare altri oggetti DBEngine e mentre tutti gli altri DAO hanno un insieme (collection), il DBEngine non ne ha alcuno: sta da solo. La gerarchia dei DAO discende dal DBEngine per insiemi e oggetti, secondo lo schema seguente: •• Direttamente da DBEngine dipendono gli insiemi Errors e Workspaces. •• Dall’insieme Workspaces dipende l’oggetto Workspace. •• Dall’oggetto Workspace dipendono gli insiemi Databases, Groups e Users: gli ultimi due insiemi non sono più presenti nella versione dei DAO per Access 2010. •• Dall’insieme Databases dipende l’oggetto Database. •• Dall’oggetto Database dipendono gli insiemi Containers, QueryDefs, Recordsets, Relations e TableDefs. •• Da ognuno di questi insiemi dipendono rispettivamente gli oggetti Container, QueryDef, Recordset, Relation e TableDef. La gerarchia continua con insiemi e oggetti Documents, Document, Fields, Field, Parameters, Parameter, Indexes e Index, ma conviene fermarsi qui, prima di andare in confusione. Illustriamo brevemente il significato di ciascuno di questi nomi. Per seguire il filo delle descrizioni che seguono, potrà far comodo tenere sott’occhio lo schema grafico della gerarchia dei DAO, che abbiamo mostrato nel Capitolo 6 e che ripresentiamo qui nella Figura 9.4 per comodità di lettura.

292  Capitolo 9

Figura 9.4  La gerarchia dei DAO.

L’insieme Workspaces e gli oggetti Workspace L’insieme Workspaces contiene oggetti Workspace.Tali oggetti definiscono un’area di lavoro nella quale opera un determinato utente. Tutte le operazioni che hanno a che fare con la sicurezza di un database si svolgono all’interno di uno specifico oggetto Workspace, che in italiano si può rendere con area di lavoro.

Gli insiemi Users e Groups e gli oggetti User e Group Questi oggetti e insiemi non sono più disponibili nella versione dei DAO per Access 2010, perché si tratta di elementi che erano utilizzati per la gestione della sicurezza dei database nelle versioni precedenti di Access, che si basava su un sistema di autorizzazioni attribuite a gruppi e a utenti. In Access 2010 la sicurezza viene gestita in un modo radicalmente diverso, che non utilizza più questi oggetti e insiemi. All’argomento della sicurezza in Access è dedicato l’intero Capitolo 13.

L’insieme Databases e gli oggetti Database L’insieme Databases contiene tutti gli oggetti Database che sono aperti al momento in un particolare Workspace. Si tenga presente che, mentre dall’interfaccia utente è possibile aprire e vedere un solo database per volta, il motore ACE che gestisce i DAO consente l’apertura e l’utilizzo di più database contemporaneamente, che possono essere database Access o di altro tipo, interni al sistema o esterni (collegati in rete o direttamente). Un oggetto Database individua un database specifico all’interno dell’insieme Databases. Qui di seguito vediamo un esempio di routine che apre due database ed elenca i nomi di tutti quelli aperti (il database corrente e i due aperti dalla routine). Quando viene eseguita, la finestra Immediata potrebbe presentarsi come nella Figura 9.5.

Lavorare con i DAO   293

Figura 9.5  La routine dimostrativa con i suoi risultati. Sub EnumeraDatabase()     Dim wksLocale As Workspace     Dim dbsLocale(2) As DAO.Database     Dim intConta As Integer     Set wksLocale = DBEngine(0)     Set dbsLocale(0) = CurrentDb     Set dbsLocale(1) =             wksLocale.OpenDatabase("C:\Esempi\Base.mdb")     Set dbsLocale(2) =     wksLocale.OpenDatabase("C:\Esempi\Banche.accdb")         For intConta = 0 To 2             Debug.Print "(" & intConta & ")",         dbsLocale(intConta).Name         Next intConta     dbsLocale(1).Close     dbsLocale(2).Close End Sub

La notazione DBEngine(0) indica il primo oggetto Workspace dell’insieme Workspaces che appartiene all’oggetto DBEngine. La routine EnumeraDatabase utilizza una variabile matrice con tipo dati Database e dimensionata con tre elementi (la numerazione degli indici parte da zero). I tre elementi di questa variabile matrice sono altrettanti oggetti Database che fanno parte dell’oggetto Workspace dichiarato con la variabile oggetto wksLocale. Il primo dei tre database aperti è quello corrente, assegnato alla variabile dbsLocale(0) mediante la funzione CurrentDb. Il secondo e il terzo vengono aperti usando il metodo OpenDatabase dell’oggetto Workspace, che a sua volta è stato dichiarato con la variabile wksLocale. Dopo il ciclo For...Next, il metodo Close, applicato ai due database esterni, li chiude.

Creare un database da programma Con l’oggetto Database si possono creare database, oltre ad aprirli, utilizzando il suo metodo CreateDatabase, che ha la seguente struttura sintattica: Set database = arealavoro.CreateDatabase (nome, internazionali [, opzioni])

294  Capitolo 9

dove database è una variabile oggetto che identifica il database da creare, arealavoro è l’oggetto Workspace in cui lo si vuol creare, nome è il nome da attribuire al nuovo database, completo di percorso, internazionali è una costante intrinseca che identifica la lingua nazionale da usare per il database (serve per definire le regole per gli ordinamenti alfabetici, tenendo conto delle lettere accentate e di altre specificità delle diverse lingue). L’argomento opzioni è facoltativo e di solito è una costante intrinseca che rappresenta la versione di Access da utilizzare per creare il nuovo database da programma.Volendo, quindi, creare un nuovo database vuoto, di nome ProvaCreazione nella cartella \Esempi del disco C della stessa macchina con la quale si sta lavorando, l’enunciato da costruire con il metodo CreateDatabase potrebbe essere il seguente: set dbs = CreateDatabase("C:\Esempi\ProvaCreazione",_     dbLangGeneral, dbVersion30)

dove l’argomento arealavoro non è indicato, perché si sottintende il Workspace nel quale si sta già lavorando, dbLangGeneral è la costante DAO che identifica le lingue inglese, tedesca, francese, portoghese, italiana e spagnola e dbVersion30 identifica la versione 3.0 del motore Jet, che è compatibile con le versioni successive 3.5 (quella utilizzata da Access 97) e 3.6 (utilizzata da Access 2000, 2003 e 2007). Ecco un esempio di funzione VBA che utilizza il metodo CreateDatabase. Function CreaUnNuovoDb(strNomeNuovoDb As String) As Boolean     ' Crea un nuovo database nella directory corrente.     '     ' Argomenti:     ' strNomeNuovoDb: Variabile stringa che specifica     ' il nome del file il nuovo database. Non deve terminare con .mdb     ' Può comprendere un percorso.     '     ' Restituisce:     ' un valore logico (True/False) che indica se l'operazione è     ' andata a buon fine.     Dim dbs As DAO.Database     ' Crea il database, specificando il nome, la lingua nazionale     ' e la versione.     Set dbs = CreateDatabase(strNomeNuovoDb, dbLangGeneral, dbVersion120)     MsgBox "Il database '" & dbs.Name & "' è stato creato."     CreaUnNuovoDb = True     dbs.Close     Set dbs = Nothing End Function

Per utilizzare questa funzione, si potrebbe creare una routine esecutiva come quella che segue: Sub TestNuovoDatabase()     ' Collauda la funzione CreaUnNuovoDb     Dim strNomeNuovoDb As String     strNomeNuovoDb = "C:\Esempi\ProvaCreazione"     CreaUnNuovoDb strNomeNuovoDb End Sub

Lavorare con i DAO   295

I codici delle versioni di Access I valori della costante che identifica la versione possono essere i seguenti: dbVersion10 Crea un database che utilizza il formato file del motore per database Microsoft Jet versione 1.0. dbVersion11 Crea un database che utilizza il formato file del motore per database Microsoft Jet versione 1.1. dbVersion20 Crea un database che utilizza il formato file del motore per database Microsoft Jet versione 2.0. dbVersion30 Crea un database che utilizza il formato file del motore per database Microsoft Jet versione 3.0 (compatibile con la versione 3.5, quella di Access 97 e 3.6, quella di Access 2000). dbVersion40 Crea un database che utilizza il formato file del motore per database Microsoft Jet versione 4.0. dbVersion120 Crea un database che utilizza il formato file di Access 2007 (utilizzato anche da Access 2010). I valori effettivi delle versioni dei database Access sono codici di cinque caratteri, nel formato XX.XX. Questi codici si possono visualizzare utilizzando la proprietà Version dell’oggetto Database.

L’insieme TableDefs e gli oggetti TableDef L’insieme TableDefs contiene tutte le tabelle che si trovano entro un particolare database. Gli oggetti contenuti nell’insieme TableDefs si chiamano TableDef. La routine che segue presenta un esempio molto semplice di accesso agli oggetti TableDef contenuti nell’insieme TableDefs del database corrente: Sub ElencaTabelle()     Dim dbsLocale As DAO.Database     Dim tblLocale As DAO.TableDef     Set dbsLocale = CurrentDb     For Each tblLocale In dbsLocale.TableDefs         Debug.Print tblLocale.Name     Next tblLocale End Sub

Nella finestra Immediata vengono stampati i nomi di tutte le tabelle del database corrente, comprese quelle di sistema. Per accedere alle tabelle di un database diverso da quello corrente, bisogna passare per l’intera gerarchia DAO: •• da DBEngine a Workspaces •• da Workspaces a Workspace •• da Workspace a Databases •• da Databases a Database •• da Database a TableDefs

296  Capitolo 9

L’esplicitazione della gerarchia può avvenire in molti modi, ricorrendo a variabili oggetto che si assegnano ai singoli oggetti o indicando un numero indice per riferirsi a un oggetto all’interno del suo insieme. L’esempio che segue utilizza un misto di queste tecniche. Sub ElencaTabelle2()     Dim dbsAltro As DAO.Database     Dim tblAltro As DAO.TableDef     Set dbsAltro = DBEngine.Workspaces(0).OpenDatabase("C:\Esempi\Base.mdb")     For Each tblAltro In DBEngine.Workspaces(0)(1).TableDefs         Debug.Print tblAltro.Name, tblAltro.DateCreated     Next tblAltro     DBEngine.Workspaces(0)(1).Close End Sub

Si noti che il costrutto: DBEngine.Workspaces(0)(1).TableDefs

letto da destra verso sinistra, significa: •• insieme TableDefs, •• del secondo oggetto Database (indice 1), •• dell’insieme Databases, •• nel primo Workspace (indice 0), •• dell’insieme Workspaces, •• dell’oggetto DBEngine per cui con il costrutto iterativo For Each...Next: For Each tblAltro In DBEngine.Workspaces(0)(1).TableDefs . . . Next tblAltro

si percorrono tutte le tabelle del secondo database (il primo è quello corrente, nel quale si esegue la routine). L’enunciato esecutivo all’interno del ciclo stampa nella finestra Immediata le proprietà Name e DateCreated, cioè il nome e la data di creazione di ciascuna tabella.

Creare una tabella da programma Con enunciati VBA che utilizzano opportuni metodi e proprietà dei DAO si possono creare tabelle da programma. Ecco un esempio molto semplice di una funzione VBA che si serve del metodo CreateTableDef dell’oggetto Database. Function CreaTabellaConDAO(strPercorsoDb As String) As Boolean     ' Crea una nuova tabella in un database esistente usando i DAO.     ' Argomenti:     ' strPercorsoDb: il percorso per il database.     ' Restituisce:     ' Un valore logico (True/False) per indicare se l'operazione     ' è andata a buon fine.

Lavorare con i DAO   297     'Costante per l'errore di run-time "Tabella esistente"     Const conTabellaEsiste = 3010     Dim dbs As DAO.Database     Dim tdf As DAO.TableDef     Dim fld As DAO.Field     On Error GoTo Err_CreaTabellaConDAO     ' Apre il database.     Set dbs = OpenDatabase(strPercorsoDb)     ' Crea un oggetto TableDef.     Set tdf = dbs.CreateTableDef("TabellaDiProva")     ' Crea un oggetto Field.     Set fld = tdf.CreateField("CampoProva", dbText)     ' Accoda il campo.     tdf.Fields.Append fld     ' Accoda l'oggetto TableDef appena creato     ' all'insieme Tabledefs del database.     dbs.TableDefs.Append tdf     CreaTabellaConDAO = True Exit_CreaTabellaConDAO:     On Error Resume Next     dbs.Close     Set dbs = Nothing     Exit Function Err_CreaTabellaConDAO:     If Err = conTabellaEsiste Then     If MsgBox("La tabella esiste già. La elimino e la creo di nuovo?", _         vbYesNo + vbExclamation) = vbYes Then         dbs.TableDefs.Delete "TabellaDiProva"         Resume     End If     Else         MsgBox "Errore: " & Err & vbCrLf & Err.Description     End If     CreaTabellaConDAO = False     Resume Exit_CreaTabellaConDAO End Function

In questa funzione sono inseriti alcuni enunciati per gestire possibili errori di run-time: per il momento li si prenda così come sono, verranno spiegati in modo approfondito nel Capitolo 11 Per utilizzare la funzione, si può scrivere una routine esecutiva come la seguente: Sub TestCreaTabella()     Dim strPercorso As String     strPercorso = "C:\Esempi\ProvaCreazione.accdb"     CreaTabellaConDAO strPercorso End Sub

Si osservi che l’argomento della funzione CreaTabellaConDAO indicato nell’esempio con la variabile strPercorso deve comprendere il nome di un file database esistente.

298  Capitolo 9

L’insieme QueryDefs e gli oggetti QueryDef Tutte le query di un database e le informazioni che le riguardano formano altrettanti oggetti QueryDef, che fanno parte dell’insieme QueryDefs del database. La routine che segue usa le stesse tecniche delle precedenti per percorrere l’insieme QueryDefs del database corrente (nel quale sono state importate le query del database dimostrativo Northwind.mdb) e ne visualizza il nome e l’enunciato SQL che le definisce. Sub ElencaQuery()     Dim dbsLocale As DAO.Database     Dim qryLocale As DAO.QueryDef     Set dbsLocale = CurrentDb     For Each qryLocale In dbsLocale.QueryDefs         MsgBox qryLocale.Name & vbCrLf & vbCrLf & qryLocale.SQL     Next qryLocale End Sub

Il nome della query e il suo enunciato SQL sono ricavati rispettivamente dalle proprietà Name e SQL di ciascun oggetto QueryDef. Per variare, invece di inviare i risultati del ciclo For Each...Next alla finestra Immediata in questa routine si alimenta una finestra di messaggio. La Figura 9.6 mostra il complicatissimo enunciato SQL della query Fatture.

Figura 9.6  Accedendo alla proprietà SQL di un oggetto QueryDef si può esaminare l’enunciato SQL che lo caratterizza.

Lavorare con i DAO   299

Oltre a visualizzare l’enunciato SQL di qualunque oggetto QueryDef, è possibile modificarne il contenuto, sempre con strumenti di programmazione basati sui DAO. Per le specificità del linguaggio SQL e dei suoi costrutti sintattici rimandiamo al Capitolo 13, “Il linguaggio SQL”.

Creare una query da programma Per creare con VBA e DAO un oggetto QueryDef si deve usare il metodo dell’oggetto Database, che ha la seguente sintassi:

CreateQueryDef

Set querydef = oggetto.CreateQueryDef (nome, testosql)

dove querydef è una variabile oggetto che rappresenta la query da creare, oggetto è una variabile oggetto che rappresenta l’oggetto Database, nome è una stringa (può essere una variabile) col nome da assegnare al nuovo oggetto QueryDef e testosql è una stringa (che può essere rappresentata da una variabile) che contiene l’enunciato SQL che la query deve eseguire. Vediamo come si potrebbe costruire una funzione per creare un oggetto QueryDef che entra a far parte in modo permanente di un database. Function CreaQueryDefPermanente(strPercorsoDb As String, _     strNomeQuery As String, strSQL As String) As Boolean     Const conObjectExists As Integer = 3012     ' Crea un oggetto QueryDef permanente.     ' Argomenti:     ' strPercorsoDb: Il percorso per il database.     ' strNomeQuery: Il nome della nuova query.     ' strSQL: L'enunciato SQL sul quale è basata la query .          ' Restituisce :     ' Un valore logico (True/False) che indica     ' se l'operazione è andata a buon fine.     On Error GoTo Err_CreaQuerydefPermanente     Dim dbs As DAO.Database     Dim qdf As DAO.QueryDef     Set dbs = OpenDatabase(strPercorsoDb)     ' Crea l'oggetto QueryDef.     Set qdf = dbs.CreateQueryDef(strNomeQuery)     ' Imposta la sua proprietà SQL.     qdf.SQL = strSQL     qdf.Close     CreaQueryDefPermanente = True Exit_CreaQuerydefPermanente:     On Error Resume Next     dbs.Close     Set dbs = Nothing     Exit Function Err_CreaQuerydefPermanente:     If Err = conObjectExists Then

300  Capitolo 9     Dim strMsg As String     strMsg = "La query " & strNomeQuery & " esiste già. " _         & vbCrLf & "La elimino e la creo di nuovo?"     If MsgBox(strMsg, vbYesNo) = vbYes Then         dbs.QueryDefs.Delete strNomeQuery         Resume     End If     Else         MsgBox "Errore: " & Err & vbCrLf & Err.Description     End If     CreaQueryDefPermanente = False     Resume Exit_CreaQuerydefPermanente End Function

Anche questa funzione contiene alcuni enunciati per la gestione di possibili errori, che saranno chiariti più avanti. Una routine che usi la funzione precedente potrebbe essere scritta come quella che riportiamo qui di seguito. Si tenga presente che questa routine opera su un database che contiene le tabelle Ordini, Dettagli ordini e Impiegati copiate dal database dimostrativo Northwind.mdb. Sub CreaQueryOrdiniLuglio()     ' Crea un oggetto QueryDef permanente che restituisce     ' il totale degli ordini per impiegato nel mese di luglio 2006     ' Chiama la funzione CreaQueryDefPermanente.     Dim strSQL As String, strPercorsoDb As String, strNomeQuery As String     strNomeQuery = "Ordini_Luglio2006"     strPercorsoDb = CurrentDb.Name     ' Definisce l'enunciato SQL.     strSQL = "SELECT Ordini.IDImpiegato, " & _     "Sum((PrezzoUnitario * Quantità)-Sconto) AS PrezzoComplessivo " & _     "FROM Ordini INNER JOIN [Dettagli ordini] ON " & _     "Ordini.IDOrdine = [Dettagli ordini].IDOrdine " & _     "WHERE (((Ordini.DataOrdine) " & _     "Between #7/1/06# And #7/31/06#)) " & _     "GROUP BY Ordini.IDImpiegato;"     If CreaQueryDefPermanente(strPercorsoDb, strNomeQuery, strSQL) Then         MsgBox "Query creata correttamente."     End If End Sub

Si osservi che l’intervallo temporale (dal 1° al 31 luglio 2006) è indicato nella clausola WHERE della query con l’espressione: Between #7/1/06# And #7/31/06#

nella quale le due date limite sono scritte secondo la convenzione americana, che esprime le date nella forma mese/giorno/anno. Questa modalità per indicare le date è obbligatoria negli enunciati SQL.

Lavorare con i DAO   301

Farsi aiutare dall’interfaccia grafica Chi ha poca confidenza con la sintassi di SQL può ottenere, senza fatica e rapidamente, enunciati in questo linguaggio ricorrendo alla griglia di struttura delle query, il cui utilizzo non richiede alcuna conoscenza delle parole chiave e della sintassi di SQL. I passi da eseguire sono questi: 1. si imposta nella griglia di struttura lo schema della query che si vuole ottenere; 2. la si esegue per collaudarla ed eventualmente la si modifica fino a ottenere esattamente i risultati che servono; 3. si passa alla Visualizzazione SQL e da questa si copia il testo dell’enunciato SQL che corrisponde alla query; 4. si incolla questo enunciato nel modulo VBA dove si sta preparando la definizione dell’oggetto QueryDef e lo si trasforma in una unica stringa, racchiudendolo fra doppie virgolette; 5. si assegna la stringa alla variabile che corrisponde all’argomento testosql del metodo CreateQueryDef. L’oggetto QueryDef creato in questo modo produrrà sicuramente il risultato che si vuole ottenere.

L’insieme Indexes e gli oggetti Index Gli oggetti Index specificano l’ordine col quale si accede ai record delle tabelle dei database e stabiliscono anche se sono ammessi oppure no record duplicati. L’insieme Indexes contiene tutti gli oggetti Index definiti per un oggetto TableDef. È possibile visualizzare da programma oggetti Index e modificarli.

L’insieme Fields e gli oggetti Field Tutti gli oggetti TableDef, QueryDef, Index, Relation e Recordset contengono insiemi Fields. L’insieme Fields di ciascuno di questi oggetti raggruppa tutti gli oggetti Field che stanno nell’oggetto genitore. Il principio, ovvio, è che un oggetto articolato in campi (field, appunto) come può essere una tabella o una query, o che utilizza campi, come è il caso di una relazione o di un indice, contiene oggetti Field aggregati in un insieme Fields. Partendo dall’oggetto genitore, per esempio un oggetto TableDef, si può accedere al suo insieme Fields e, tramite questo insieme, ai singoli Field che lo compongono. Ecco un semplice esempio. Sub EnumeraCampi()     Dim dbsLocale As DAO.Database     Dim tblLocale As DAO.TableDef     Dim fldLocale As DAO.Field     Set dbsLocale = CurrentDb     For Each tblLocale In dbsLocale.TableDefs         Debug.Print tblLocale.Name             For Each fldLocale In tblLocale.Fields                 Debug.Print fldLocale.Type, fldLocale.Name             Next fldLocale         Debug.Print

302  Capitolo 9     Next tblLocale End Sub

Questa routine elenca nella finestra Immediata tutte le proprietà Name di tutti gli oggetti Field contenuti in tutte le tabelle del database; nella stessa riga, stampa anche il valore della proprietà Type per ciascun campo, cioè il suo tipo di dati, come possiamo vedere dalla Figura 9.7.

Figura 9.7  Il contenuto della finestra Immediata dopo l’esecuzione della routine EnumeraCampi.

Esaminando i risultati, si noterà che la proprietà Type è rappresentata con un numero. Il numero è un codice che corrisponde a una costante. Così come esistono costanti VBA, che hanno un nome in inglese preceduto dalla coppia di caratteri vb, esistono costanti DAO che hanno la stessa funzione mnemonica e sono individuate con la coppia di caratteri db. La corrispondenza fra i valori delle costanti DAO usate per rappresentare la proprietà Type e i tipi di dati Access è riportata nella Tabella 9.1. Nella Tabella 9.1 non sono presenti i tipi di dati Numerazione automatica e Collegamento ipertestuale. In DAO, un dato di tipo Numerazione automatica è semplicemente un numero intero lungo, quindi è rappresentabile con la costante dbLong. Il tipo Collegamento ipertestuale è considerato dai DAO come un Memo, quindi è rappresentato dalla costante dbMemo.

Lavorare con i DAO   303 Tabella 9.1  Le costanti DAO che rappresentano i tipi di dati. Tipo di dato Access

Costante DAO

Valore

Sì/No

dbBoolean

1

Byte Intero Intero lungo Valuta Precisione singola Precisione doppia Data/ora Testo Oggetto OLE Memo

dbByte

2 3 4 5 6 7 8 10 11 12

dbInteger dbLong dbCurrency dbSingle dbDouble dbDate dbText dbLongBinary dbMemo

Creare un campo da programma Con il metodo CreateField dell’oggetto TableDef si può creare, usando una routine VBA, un nuovo campo in una tabella esistente. La sintassi di questo metodo è fatta così: Set campo = oggetto.CreateField (nome, tipo [, dimensione])

dove campo è una variabile che individua l’oggetto Field nel quale si vuole inserire il nuovo campo, nome è il nome da dare al nuovo campo, tipo è una costante intrinseca che individua il tipo di dati e dimensione è un argomento facoltativo, che permette di stabilire una dimensione diversa da quella predefinita per il campo che viene creato. Ecco un esempio di funzione che utilizza il metodo CreateField: Function AggiungeCampoATabledef(strPercorsoDb As String, _     strNomeTabella As String, _     strNomeCampo As String, intTipoDati As Integer, _     Optional intDimensione As Integer) As Boolean     ' La funzione aggiunge un nuovo campo a una tabella che le viene     ' specificata e imposta il valore e la posizione di default del     ' campo nella tabella.     ' Se il campo esiste già, chiede all'operatore se vuole     ' eliminarlo e crearlo di nuovo.     '     '     '     '     '     '     '     '     '     '

Argomenti: strPercorsoDb: Percorso per il database. strNomeTabella: Nome della tabella. strNomeCampo: Nome del campo. intTipoDati: Tipo di dati del campo. intDimensione: Valore intero facoltativo che specifica la dimensione del campo. Restituisce: Un valore logico (True/False) per indicare se l'operazione è andata a buon fine.

304  Capitolo 9     Dim dbs As DAO.Database     Dim tdf As DAO.TableDef     Dim fld As DAO.Field     Const conCampoEsisteGià As Integer = 3191     On Error GoTo Err_AggiungeCampoATabledef     Set dbs = OpenDatabase(strPercorsoDb)     Set tdf = dbs.TableDefs(strNomeTabella)     ' Verifica se è stato passato l'argomento intDimensione.     If IsMissing(intDimensione) Then         ' In caso negativo, crea un campo della dimensione predefinita.         Set fld = tdf.CreateField(strNomeCampo, intTipoDati)     Else         ' Crea un campo della dimensione specificata.         Set fld = tdf.CreateField(strNomeCampo, intTipoDati, intDimensione)     End If     ' Imposta la posizione ordinale del campo.     fld.OrdinalPosition = 1     tdf.Fields.Append fld     AggiungeCampoATabledef = True Exit_AggiungeCampoATabledef: On Error Resume Next     dbs.Close     Set dbs = Nothing     Exit Function Err_AggiungeCampoATabledef:     If Err = conCampoEsisteGià Then         Dim strMsg As String         strMsg = "Il campo esiste già. Lo elimino e lo creo di nuovo?"         If MsgBox(strMsg, vbYesNo) = vbYes Then             tdf.Fields.Delete strNomeCampo             Resume         End If     Else         MsgBox "Errore: " & Err & vbCrLf & Err.Description     End If     AggiungeCampoATabledef = False     Resume Exit_AggiungeCampoATabledef End Function

Una routine esecutiva per creare un nuovo campo usando questa funzione potrebbe essere costruita nel modo seguente: Sub TestAggiungeCampo()     Dim strPercorso As String     Dim strNomeTabella As String     Dim strNomeCampo As String     Dim intTipoDati As Integer     Dim intDimensione As Integer

Lavorare con i DAO   305     strPercorso = "C:\Esempi\ProvaCreazione.accdb"     strNomeTabella = "TabellaDiProva"     strNomeCampo = "CampoAggiunto"     intTipoDati = dbText     intDimensione = 72     AggiungeCampoATabledef strPercorso, strNomeTabella, _         strNomeCampo, intTipoDati, intDimensione End Sub

Questa routine chiama la funzione AggiungeCampoATabledef passandole tutti i parametri che prevede, compreso quello facoltativo che corrisponde all’argomento dimensione.Viene quindi creato un campo di tipo dati Testo (dbText), chiamato CampoAggiunto, con una lunghezza predeterminata di 72 caratteri. Se fosse stato omesso il parametro dimensione, rappresentato nella routine dalla variabile intDimensione, il campo CampoAggiunto sarebbe stato creato sulla dimensione predefinita per i campi di tipo testo, che è di 255 caratteri. Le costanti intrinseche per definire i tipi di dati sono quelle elencate nella Tabella 9.1.

L’insieme Parameters e gli oggetti Parameter Come sappiamo, si possono creare query con parametri, che al momento dell’esecuzione, richiedono all’operatore l’immissione di uno o più criteri che verranno usati per la selezione, come per esempio una data di inizio e una di fine periodo, un valore minimo e uno massimo e così via. Un parametro può essere acquisito, oltre che da un input dell’operatore, dal contenuto di un controllo di una maschera aperta o tramite codice VBA. Ciascun oggetto QueryDef ha un insieme Parameters che è formato da oggetti Parameter. Agendo sulle proprietà di questi oggetti si possono esaminare da programma query con parametri, creare nuovi parametri e modificare quelli esistenti. Ecco un esempio. Sub EnumeraParametri()     Dim dbsLocale As DAO.Database     Dim qryLocale As DAO.QueryDef     Dim prmLocale As DAO.Parameter     Set dbsLocale = CurrentDb     For Each qryLocale In dbsLocale.QueryDefs         Debug.Print "--------" & qryLocale.Name & "---------"         For Each prmLocale In qryLocale.Parameters             Debug.Print prmLocale.Name         Next prmLocale     Next qryLocale End Sub

Questa routine elenca i nomi di tutti gli oggetti QueryDef nel database corrente e gli oggetti Parameter contenuti nell’insieme Parameters di ciascuno. Se nel database locale sono contenute tutte le query dell’applicazione Northwind.mdb, nella finestra Immediata compariranno i nomi di tutte le query, seguiti dalle specifiche dei loro parametri, quando esistono. In questo caso, le tre query con parametri generano l’output che segue: --------Filtro fatture--------Forms!Ordini!IDOrdine

306  Capitolo 9

--------Vendite impiegato per paese--------[Data di inizio] [Data di fine] --------Vendite per anno--------Forms![Finestra Vendite per anno]!DataInizio Forms![Finestra Vendite per anno]!DataFine

Come si può vedere, la proprietà Name di un oggetto Parameter può contenere soltanto il prompt per l’utente (nel caso della query Vendite impiegato per paese) oppure il nome completo del controllo che contiene il parametro (nel caso delle query Filtro fatture e Vendite per anno).

Sfruttare bene la finestra Immediata Quando servono elenchi da visualizzare con semplicità e velocemente, la finestra Immediata è lo strumento ideale. Il metodo Debug.Print rispetta molte delle regole del BASIC canonico, che possono essere sfruttate per ottenere semplici effetti di impaginazione, come per esempio le righe di separazione della routine precedente. Il carattere virgola (,) dopo un valore da stampare crea una tabulazione fissa di cinque spazi, mentre il carattere punto e virgola (;) tiene uniti gli elementi di una riga (la virgola e il punto e virgola non vengono stampati). Il contenuto della finestra Immediata può essere selezionato e copiato o tagliato con le normali tecniche Windows, per incollarlo poi in un documento diverso. È la tecnica che ho usato per trasferire gli output di Debug.Print nei documenti Word che formano i capitoli di questo libro.

L’insieme Relations e gli oggetti Relation L’insieme Relations appartiene all’oggetto Database e contiene tutti gli oggetti Relation che sono definiti entro quell’oggetto Database. Con la routine che segue si esplorano tutti gli oggetti Relation del database corrente (una copia di Northwind.mdb) stampando nella finestra Immediata le proprietà Table e ForeignTable di ciascun oggetto Relation. Sub EnumeraRelazioni()     Dim dbsLocale As DAO.Database     Dim relLocale As DAO.Relation     Set dbsLocale = CurrentDb     For Each relLocale In dbsLocale.Relations         Debug.Print relLocale.Table & " Correlata con: " _             & relLocale.ForeignTable     Next relLocale End Sub

La finestra Immediata conterrà, dopo l’esecuzione della routine, la seguente lista: Corrieri Correlata con: Ordini Impiegati Correlata con: Ordini Ordini Correlata con: Dettagli ordini Categorie Correlata con: Prodotti

Lavorare con i DAO   307 Clienti Correlata con: Ordini Prodotti Correlata con: Dettagli ordini Fornitori Correlata con: Prodotti

L’insieme Containers e gli oggetti Container L’insieme Containers appartiene all’oggetto Database e raccoglie informazioni sull’oggetto Database stesso, sotto forma di oggetti Container. Gli oggetti Container sono interni al sistema, quindi non è possibile crearne di nuovi o eliminare quelli esistenti. Gli oggetti Container possono essere Databases, Forms, DataAccessPages, Modules, Relationships, Report, Scripts, SysRel e Tables. Gli Scripts sono le macro, i Forms sono le maschere, mentre i SysRel sono oggetti di sistema. Bisogna fare attenzione a non confondere gli oggetti Container con gli insiemi che hanno lo stesso nome. L’oggetto Container che si chiama Databases è diverso dall’insieme Databases. Il Container Databases fa parte dell’insieme Containers e fa riferimento a tutti gli oggetti salvati che sono del tipo Database. L’insieme Databases fa riferimento solo agli oggetti Database aperti nell’area di lavoro (Workspace) alla quale appartengono. E chi ci capisce qualcosa è bravo, verrebbe spontaneo aggiungere. L’ambiguità nasce dal fatto che il DBEngine è stato concepito per essere indipendente dall’applicazione che lo usa; inoltre, riconosce soltanto oggetti database, cioè tabelle e query e i loro oggetti figli (record, campi, indici e relazioni).Viene utilizzato da Microsoft Access, ma se ne servono anche altre applicazioni, e dovunque si comporta sempre nello stesso modo. Un’applicazione che utilizza il motore ACE (e quindi DBEngine), come è il caso di Access, ha bisogno di un meccanismo per immagazzinare i propri oggetti specifici, nella fattispecie maschere, report, macro e moduli, che non sono oggetti database. Benevolmente, il motore ACE mette a disposizione di Access uno scatolone, un Container, appunto, che permette di far conoscere al DBEngine l’esistenza di oggetti specifici di Access senza contaminare la purezza strutturale del DBEngine, che rimane indipendente dall’applicazione. L’insieme Containers, quindi, è tutt’altra cosa rispetto agli insiemi che abbiamo visto finora e non a caso i suoi oggetti Container hanno nomi per certi versi sconcertanti, che possono confondersi con quelli degli insiemi, come per esempio Databases, o ambigui, come Tables (che sono indifferentemente tabelle e query). Per orientarsi meglio, sarà utile tener presente che il genitore degli oggetti Container Databases, Tables, Relationships e SysRel è il motore ACE, mentre il genitore di Forms, Reports, Scripts e Modules è l’oggetto Application, nel nostro caso, Microsoft Access. Fortunatamente con l’insieme Containers si lavora di rado, giusto per definire da programma parametri per la sicurezza, che peraltro si possono impostare in molti altri modi, più semplici e intuitivi.

L’insieme Documents e gli oggetti Document Ogni oggetto Container ha un insieme Documents formato da oggetti Document. Un Document contiene informazioni sugli oggetti che si trovano in un oggetto Container. Sembra un gioco di specchi, ma non lo è. Per capire meglio di che cosa si tratti, si può eseguire la seguente routine:

308  Capitolo 9 Sub EnumeraMaschere()     Dim dbsLocale As DAO.Database     Dim cntLocale As DAO.Container     Dim docLocale As DAO.Document     Set dbsLocale = CurrentDb     Set cntLocale = dbsLocale.Containers!Forms     For Each docLocale In cntLocale.Documents         Debug.Print docLocale.Name     Next docLocale End Sub

La variabile oggetto cntLocale viene assegnata al Container Forms, che appartiene all’insieme Containers. La variabile oggetto docLocale viene utilizzata nel ciclo For Each...Next per percorrere tutti gli oggetti Document Forms che si trovano nell’insieme Documents. Si osservi che gli oggetti Forms non sono DAO, ma oggetti Access.

L’insieme Properties e gli oggetti Property Ciascun Data Access Object ha un insieme Properties che elenca tutte le proprietà di quel particolare oggetto, individuate da singoli oggetti Property. Questi oggetti si possono esaminare per avere informazioni sulle proprietà di un oggetto e si possono modificare per variarle. È anche possibile, in determinati casi, aggiungere a un oggetto DAO proprietà definite dall’utente, creando un nuovo oggetto Property. Per avere una lista nella finestra Immediata di tutte le proprietà dell’insieme Properties definite per il Container Forms, si può utilizzare una routine come questa: Sub EnumeraProprietà()     Dim dbsLocale As DAO.Database     Dim cntLocale As DAO.Container     Dim docLocale As DAO.Document     Dim prpLocale As DAO.Property     Set dbsLocale = CurrentDb     Set cntLocale = dbsLocale.Containers!Forms     For Each docLocale In cntLocale.Documents         Debug.Print docLocale.Name         For Each prpLocale In docLocale.Properties             Debug.Print prpLocale.Name & « = « & prpLocale.Value         Next prpLocale     Next docLocale End Sub

Gli altri insiemi e oggetti DAO In questa rassegna abbiamo deliberatamente lasciato fuori alcuni insiemi DAO con i loro relativi oggetti. In particolare, non abbiamo descritto gli insiemi Errors e Recordsets. L’omissione non è casuale: alle funzionalità degli insiemi Errors e degli oggetti Error è dedicato il Capitolo 11, mentre gli insiemi Recordsets di oggetti Recordset meritano, per la loro importanza, una sezione apposita.

Lavorare con i DAO   309

Gli oggetti Recordset L’oggetto Recordset è probabilmente l’oggetto DAO che si usa con maggior frequenza in programmazione e rappresenta – come dice il suo nome inglese – un insieme di record. Non potendo usare la parola “insieme” perché l’abbiamo già spesa per tradurre “collection”, diciamo che un Recordset è un gruppo di record. La differenza fra un Recordset e un oggetto TableDef sta nel fatto che un TableDef esiste, mentre un Recordset può non esistere. Detto in modo meno paradossale, un oggetto TableDef è una tabella reale, che sta in un database, mentre un oggetto Recordset è una tabella virtuale, che viene creata in memoria da programma, eventualmente ricavando record da una o più tabelle esistenti, per eseguire un qualche tipo di operazione. Se serve, si può trasformare un Recordset in un TableDef creando fisicamente una tabella con quel Recordset, ma di solito i Recordset vengono eliminati dalla memoria non appena esauriscono la loro funzione nella routine che li ha creati e utilizzati. Il motore ACE consente di creare cinque tipi diversi di Recordset: tabella, dynaset, snapshot, forward-only, dinamico, che si caratterizzano in questo modo: Recordset di tipo tabella: rappresenta un’intera tabella effettiva di un database Access. Agendo su un oggetto Recordset di tipo tabella si agisce direttamente sulla tabella che questo rappresenta, per cui le modifiche fatte ai record di un Recordset si riflettono sulla tabella sottostante. Un oggetto Recordset di tipo tabella può contenere dati da una sola tabella. Recordset di tipo dynaset: rappresenta una tabella virtuale, che può essere formata dai risultati di una query o con dati provenienti da più tabelle. Le tabelle possono essere interne al database o collegate e possono anche non essere tabelle di database Access. Come dice il suo nome, l’insieme dei dati gestibili con un Recordset di tipo dynaset è dinamico, le modifiche fatte al Recordset si riflettono nelle tabelle sottostanti e, viceversa, il Recordset recepisce le modifiche che vengono effettuate sulle tabelle mentre è attivo. Recordset di tipo snapshot: come quello di tipo dynaset, questo tipo di Recordset può rappresentare dati da più tabelle o i risultati di una query. La differenza sostanziale è che i dati sono fissi, equivalgono a un’istantanea dei dati scattata al momento della creazione del Recordset, che rimane statica durante l’elaborazione, anche se altri utenti nel frattempo intervengono sui record all’origine del Recordset e li modificano. Recordset di tipo forward-only: strutturalmente identico a quello di tipo snapshot, un Recordset di tipo forward-only ha un’ulteriore limitazione: è possibile scorrere i record soltanto in avanti e i record non possono essere aggiornati. La limitazione al solo scorrimento in avanti offre il vantaggio di un’elevata velocità di consultazione. Questo tipo di Recordset trova il suo utilizzo nelle situazioni in cui può bastare una semplice scorsa di un insieme di record per ottenere il risultato che interessa, ad esempio, per generare un report. Recordset di tipo dinamico: analogo al Recordset di tipo dynaset, è disponibile soltanto nelle aree di lavoro ODBCDirect, che non sono più supportate in Access 2010. Se si desidera accedere a origini dati esterne senza utilizzare il motore di database ACE di Access 2010 bisogna usare gli ADO. La potenza (e la complessità) dell’oggetto Recordset è testimoniata dal fatto che mette a disposizione 24 metodi e 33 proprietà, che consentono di controllare e gestire con estremo rigore e grande flessibilità i record di un database. Si può ricorrere all’insieme Fields di questo oggetto e ai metodi e alle proprietà dei suoi oggetti Field per operare a livello dei singoli campi.

310  Capitolo 9

Creare una variabile Recordset Per utilizzare in una routine Sub o Function un oggetto Recordset bisogna dichiarare una variabile oggetto di tipo Recordset e quindi assegnarla. L’assegnazione si fa con la parola chiave Set, come per tutte le variabili oggetto, richiamando il metodo OpenRecordset. Questo metodo è disponibile dagli oggetti Database, TableDef, QueryDef e da oggetti Recordset esistenti. La sua sintassi è la seguente: Set recordset = oggetto.OpenRecordset (origine [tipo [, opzioni [, bloccomodifiche]]]) Set recordset = oggetto.OpenRecordset ([tipo [, opzioni [, bloccomodifiche]]])

La prima forma si usa quando il metodo è richiamato da un oggetto Database. La seconda forma quando viene richiamato da tutti gli altri oggetti che hanno il metodo OpenRecordset. Il significato degli argomenti è il seguente: •• recordset è il nome del nuovo oggetto Recordset. •• oggetto è l’oggetto dal quale si crea il nuovo Recordset; oggetto può essere un oggetto Database, TableDef o QueryDef. •• origine è un oggetto TableDef o QueryDef esistente nel database oppure un enunciato SQL che restituisce uno o più record; l’argomento va esplicitato soltanto quando il metodo si usa da un oggetto Database: nel caso di oggetti TableDef, QueryDef o Recordset, l’argomento origine è automaticamente l’oggetto stesso e non va esplicitato. In altri termini, quando si opera al livello più alto della gerarchia degli oggetti entro un database, quello dell’oggetto Database, bisogna indicare il nome dell’origine dei dati a partire dalla quale si vuole costruire il Recordset, cioè una tabella o una query esistente; quando si esegue il metodo OpenRecordset al livello inferiore nella gerarchia degli oggetti, cioè al livello di una tabella o di una query, il metodo OpenRecordset dà per scontato che i dati per il Recordset verranno dalla tabella o dalla query e non richiede il nome (cioè il parametro origine) come argomento. •• tipo è una costante DAO intrinseca che specifica il tipo di oggetto Recordset che si intende creare; le costanti disponibili sono: ■■

dbOpenTable

■■

dbOpenDynamic

■■

dbOpenDynaset

■■

dbOpenSnapshot

■■

dbOpenForwardOnly

e corrispondono ai cinque tipi di oggetti Recordset che si possono creare; non specificando tipo, viene creato un oggetto Recordset di tipo tabella.

Lavorare con i DAO   311

Un’insidia In molte realtà organizzative, dove si utilizzano personal computer collegati fra loro tramite una rete locale, è opportuno creare applicazioni database articolate su due file .accdb: uno che contiene soltanto le tabelle, che risiede sul server della rete locale, e un altro file .accdb, che contiene soltanto gli oggetti di interfaccia utente (maschere e report) che agiscono sulle tabelle collegate. Una copia di questo secondo file .accdb risiede su ciascuna macchina client e accede ai dati dal server. Questo modo di organizzare il lavoro (e di strutturare l’applicazione Access) riduce il carico di lavoro sulla rete e consente di dare agli utenti un servizio migliore. Va tenuto però presente che l’utilizzo della costante dbOpenTable per l’argomento tipo provoca un errore di run-time quando origine si riferisce a un oggetto TableDef che rappresenta una tabella collegata. Lo stesso errore si manifesta se oggetto si riferisce a un oggetto QueryDef o a un oggetto Recordset di tipo dynaset o di tipo snapshot.

••

opzioni è un argomento facoltativo col quale si possono specificare varie funzionalità per l’oggetto Recordset, utilizzando le seguenti costanti DAO: ■■ dbAppendOnly Al Recordset si possono accodare nuovi record, ma non è consentita la modifica o l’eliminazione dei record esistenti. ■■ dbSQLPassThrough  Passa un enunciato SQL a un database esterno connesso al database Access. Solo per Recordset di tipo snapshot. ■■ dbSeeChanges  Provoca un errore di run-time se un altro utente modifica gli stessi dati. Solo per Recordset di tipo dynaset. Opzione da adottare nelle applicazioni dove più utenti possono accedere simultaneamente in lettura/scrittura agli stessi dati. ■■ dbDenyWrite  Impedisce ad altri utenti di modificare o aggiungere record a un Recordset di tipo tabella o dynaset. ■■ dbDenyRead  Impedisce ad altri utenti di leggere i dati nella tabella. Solo per Recordset di tipo tabella. ■■ dbReadOnly  Impedisce di utilizzare l’oggetto Recordset per modificare dati. Utile quando si usa il Recordset per presentare dati in una maschera che consente immissione di dati. L’uso della costante con lo stesso nome nell’argomento bloccomodifiche produce lo stesso risultato. ■■ dbInconsistent  Consente aggiornamenti non coerenti. Solo per Recordset di tipo snapshot e di tipo dynaset. ■■ dbConsistent  Consente solo aggiornamenti coerenti (valore predeterminato). Solo per Recordset di tipo snapshot e di tipo dynaset. Se non si usa l’argomento opzioni, bisogna scrivere 0 al suo posto. •• bloccomodifiche (in molta documentazione viene indicato come lockedits) consente di specificare le opzioni di blocco dei record quando sulla stessa applicazione agiscono contemporaneamente più utenti, che potrebbero ritrovarsi a lavorare sullo stesso record nello stesso momento. Si definisce con una delle seguenti costanti DAO:

312  Capitolo 9

■■

■■

■■

dbReadOnly 

Impedisce agli altri utenti di modificare l’oggetto Recordset. Si può usare questa costante o nell’argomento opzioni o in questo argomento, ma non in entrambi, altrimenti si genera un errore di run-time. dbPessimistic  Usa il criterio pessimistico per stabilire il blocco degli accessi simultanei allo stesso record. Il criterio pessimistico blocca la pagina che contiene il record in corso di modifica non appena viene richiamato il metodo Edit dell’oggetto Recordset. dbOptimistic  Usa il criterio ottimistico per stabilire il blocco degli accessi simultanei allo stesso record. Con il criterio ottimistico la pagina che contiene il record in corso di modifica viene bloccata soltanto quando viene richiamato il metodo Update dell’oggetto Recordset.

Usare l’oggetto Recordset Ecco alcuni esempi di uso del metodo OpenRecordset (rstProva è una variabile oggetto di tipo Recordset e dbsNorthwind è una variabile oggetto di tipo Database). Apertura di recordset di tipo forward-only dove l’origine è un oggetto QueryDef rstProva = dbsNorthwind.OpenRecordset( _ "Dettagli ordini complessivi", dbOpenForwardOnly)

Apertura di recordset di tipo dynaset, di sola lettura, dove l’origine è un enunciato SQL Set rstProva = dbsNorthwind.OpenRecordset( _ "SELECT * FROM Impiegati", dbOpenDynaset, dbReadOnly)

Quella che segue è una semplice routine che dimostra alcune specificità dell’uso del metodo OpenRecordset: Sub EsempiOpenRecordset()     Dim rstProva As DAO.Recordset, rstFiltrato As DAO.Recordset     Dim dbsNorthwind As DAO.Database     Set dbsNorthwind = CurrentDb     Set rstProva = dbsNorthwind.OpenRecordset( _             "Dettagli ordini complessivi")     'Esplicita l'argomento origine         MsgBox "Il recordset rstProva contiene: " _             & rstProva.RecordCount & " record"         rstProva.Filter = "NomeProdotto = 'Ravioli Angelo'" 'Imposta un filtro sul Recordset         Set rstFiltrato = rstProva.OpenRecordset() 'Non esplicita l'argomento origine         rstFiltrato.MoveLast    'Si porta sull'ultimo record         MsgBox "Il recordset rstFiltrato contiene: " _             & rstFiltrato.RecordCount & " record"         rstProva.Close         rstFiltrato.Close End Sub

Questa routine esegue la query Dettagli ordini complessivi dell’applicazione Northwind e visualizza il numero dei record che questa contiene. Successivamente, applica un filtro al

Lavorare con i DAO   313

recordset generato dalla query, in modo da lasciar passare soltanto i record del prodotto Ravioli Angelo e conteggia il numero dei record filtrati, mostrando il risultato del conteggio in una finestra di messaggio. Possiamo controllare quali informazioni dovremmo ottenere dalla routine selezionando la query Dettagli ordini complessivi nel Riquadro di spostamento ed eseguendola manualmente. Il conteggio dei record estratti dà 2.155. Selezioniamo un campo Prodotto della query (che corrisponde al campo NomeProdotto dell’origine dei dati) che contenga il nome Ravioli Angelo e applichiamo il filtro su quel valore col comando Home/Ordina e filtra/Selezione. Il numero dei record filtrati risulta pari a 23. Chiudiamo la query e torniamo al modulo standard nel quale abbiamo salvato la routine. Eseguendola, ricaviamo le stesse informazioni che avevamo ottenuto prima con varie operazioni manuali. Esaminiamo ora da vicino gli enunciati che compongono la routine. Il primo enunciato Set della routine assegna la variabile oggetto rstProva di tipo Recordset a una query esistente nel database dimostrativo Northwind (“Dettagli ordini complessivi”). Questa assegnazione viene eseguita usando l’argomento origine, in quanto l’enunciato utilizza il metodo OpenRecordset dell’oggetto Database. Subito dopo, con MsgBox, si genera una finestra di messaggio che visualizza la proprietà RecordCount del recordset che è stato aperto. Questa proprietà indica il numero dei record se – come in questo caso – il recordset è stato aperto come di tipo tabella. Nel caso specifico, la finestra di messaggio dichiara 2.155 record. Sull’insieme di record aperti si imposta un filtro, applicando la proprietà Filter. Il recordset rstProva ora contiene soltanto una parte dei record complessivi generati dalla query alla quale fa riferimento. Col secondo enunciato Set viene creato un nuovo recordset, rstFiltrato, basato sul rstProva. In questo caso non occorre l’argomento origine, perché si usa il metodo OpenRecordset del recordset rstProva. rstFiltrato contiene soltanto una parte dei record, quindi, per definizione, non è un recordset di tipo tabella, che per sua natura contiene tutti i record della tabella (o della query, nel nostro caso) alla quale si riferisce. In questo momento sono aperti due recordset: rstProva e rstFiltrato. Se eseguissimo lo stesso enunciato MsgBox di prima, riferendolo alla proprietà RecordCount di rstFiltrato, otterremmo un valore pari a uno. Sappiamo che in realtà sono 23: come ottenere il numero corretto? Nel momento in cui nascono, i recordset di tipo dynaset, snapshot o forward-only contengono soltanto il primo dei dati ai quali fanno riferimento, quindi la loro proprietà RecordCount non indica il numero complessivo dei record, ma è impostata su 1. Per ottenere il numero effettivo dei record bisogna accedere almeno una volta a tutti i record del recordset. Questa operazione si esegue attivando il metodo MoveLast sul recordset rstFiltrato, con l’enunciato rstFiltrato.MoveLast. Così facendo, vengono fatti scorrere tutti i record del recordset, fino all’ultimo. A questo punto la proprietà RecordCount ha un valore corretto anche per rstFiltrato, che viene visualizzato nella finestra di messaggio (Figura 9.8). Possiamo costruire una routine analoga utilizzando il metodo OpenRecordset di un oggetto TableDef, per lavorare su una tabella invece che su una query. La routine potrebbe essere come quella dell’esempio che segue: Sub EsempioConTableDef()     Dim tdfProva As DAO.TableDef, rstProva As DAO.Recordset

314  Capitolo 9     Dim rstFiltrato As DAO.Recordset     Dim strNome As String, lngNumRec As Long     Dim dbsNorthwind As DAO.Database     Set dbsNorthwind = CurrentDb     Set tdfProva = dbsNorthwind.TableDefs("Impiegati")     strNome = tdfProva.Name     lngNumRec = tdfProva.RecordCount 'Usa la proprietà RecordCount     MsgBox "La tabella " & strNome _         & " contiene: " & lngNumRec & " record"     Set rstProva = tdfProva.OpenRecordset(dbOpenDynaset)     rstProva.Filter = "DataNascita > #1/1/60#"     Set rstFiltrato = rstProva.OpenRecordset()     rstFiltrato.MoveLast     MsgBox "Di questi: " & rstFiltrato.RecordCount _         & " sono nati dopo il 1/1/1960" End Sub

In questa routine viene dichiarata una variabile oggetto TableDef con il nome tdfProva, che viene successivamente assegnata all’oggetto TableDef Impiegati. Con questa assegnazione, la variabile tdfProva rappresenta la tabella Impiegati, che quindi viene aperta. L’oggetto TableDef ha la proprietà RecordCount, quindi è possibile visualizzare direttamente il suo valore, senza passare per il metodo OpenRecordset. Ma gli oggetti TableDef non hanno la proprietà Filter.Volendo, quindi, filtrare la tabella dobbiamo prima creare un recordset di tipo dynaset basato sull’oggetto TableDef e poi un secondo recordset che filtra il primo. Eseguiamo su questo secondo recordset il metodo MoveLast e otteniamo il conteggio dei record della tabella Impiegati che hanno un valore maggiore di 1/1/1960 nel campo DataNascita.

Figura 9.8  Le due finestre di messaggio generate dalla routine dimostrativa.

Lavorare con i DAO   315

Metodi e proprietà dell’oggetto Recordset I due semplici esempi del paragrafo precedente hanno fatto appena intravedere la potenza e la flessibilità dell’oggetto Recordset, in particolar modo rispetto agli altri oggetti DAO che consentono di accedere a tabelle (TableDef) e a query esistenti (QueryDef). I metodi e le proprietà dell’oggetto Recordset sono molto più numerosi di quelli disponibili per gli oggetti TableDef e QueryDef, il che consente di eseguire praticamente qualunque tipo di operazione possa essere necessaria per elaborare i record di un database e ricavare informazioni dai dati che vi sono contenuti. La Tabella 9.2 riepiloga le operazioni che si possono eseguire con i metodi dell’oggetto Recordset. È importante aver chiaro il significato di alcuni termini usati nella Tabella 9.2. Tabella 9.2  I metodi dell’oggetto Recordset. Metodo

Descrizione Aggiunge un nuovo record al Recordset Cancel Annulla l’esecuzione di un metodo CancelUpdate Annulla tutti gli aggiornamenti in sospeso Clone Crea un duplicato dell’oggetto Recordset che si riferisce all’oggetto Recordset originale. Close Chiude l’oggetto Recordset aperto. Metodo comune agli altri oggetti DAO. CopyQueryDef Restituisce un oggetto QueryDef che è una copia dell’oggetto QueryDef utilizzato per creare l’oggetto Recordset. Delete Elimina il record corrente in un oggetto Recordset. Edit Copia in un buffer di transito il record corrente da un oggetto Recordset per consentirne le modifiche. FillCache Riempie tutta o una parte di una cache locale di un oggetto Recordset che contiene dati provenienti da un’origine dati ODBC connessa al database. FindFirst Individua il primo record in un oggetto Recordset che soddisfa i criteri specificati e lo fa diventare il record corrente. FindLast Individua l’ultimo record in un oggetto Recordset che soddisfa i criteri specificati e lo fa diventare il record corrente. FindNext Individua il record successivo in un oggetto Recordset che soddisfa i criteri specificati e lo fa diventare il record corrente. FindPrevious Individua il record precedente in un oggetto Recordset che soddisfa i criteri specificati e lo fa diventare il record corrente. GetRows Reperisce più righe da un oggetto Recordset. Move Sposta la posizione del record corrente in un oggetto Recordset. MoveFirst Esegue lo spostamento sul primo record di un oggetto Recordset specificato e rende tale record il record corrente. MoveLast Esegue lo spostamento sull’ultimo record di un oggetto Recordset specificato e rende tale record il record corrente. MoveNext Esegue lo spostamento sul record successivo di un oggetto Recordset specificato e rende tale record il record corrente. MovePrevious Esegue lo spostamento sul record precedente di un oggetto Recordset specificato e rende tale record il record corrente. NextRecordset Solo per le aree di lavoro ODBCDirect. OpenRecordset Crea un nuovo oggetto Recordset e lo accoda all’insieme Recordset. Requery Aggiorna i dati in un oggetto Recordset eseguendo di nuovo la query su cui è basato l’oggetto. (continua) AddNew

316  Capitolo 9 Tabella 9.2  I metodi dell’oggetto Recordset. (segue) Metodo Seek Update

Descrizione Individua in un oggetto Recordset di tipo tabella indicizzato il record che soddisfa i criteri specificati per l’indice corrente e rende tale record il record corrente. Salva il contenuto del buffer di transito in un oggetto Recordset aggiornabile.

Record corrente Questo termine si riferisce al record in un oggetto Recordset che può essere utilizzato per modificare o esaminare dati. In un oggetto Recordset un solo record può essere quello corrente; tuttavia, un oggetto Recordset può non avere un record corrente. Il record corrente non è definito, per esempio, dopo che un record di un oggetto Recordset di tipo dynaset è stato eliminato oppure quando un oggetto Recordset non contiene record. In questo caso, eventuali operazioni da programma che fanno riferimento al record corrente causano un errore.

Buffer di transito Spazio di memoria creato e gestito dal motore ACE per contenervi un record che viene aperto per essere modificato. Il buffer di transito interviene nei seguenti casi: •• il metodo Edit copia il record corrente nel buffer di transito; •• il metodo AddNew cancella il buffer per un nuovo record e imposta i valori predefiniti; •• il metodo Update salva i dati dal buffer di transito nel database, sostituendo il record corrente o inserendo il nuovo record. Gli enunciati VBA che utilizzano metodi con i quali si reimposta o si sposta il puntatore del record corrente, eliminano il buffer di transito. Per esempio, se si utilizza il metodo MoveNext o si modifica la proprietà Index di una tabella, il contenuto del buffer di transito viene eliminato.

Area di lavoro ODBCDirect Area di lavoro che utilizza un sistema di oggetti chiamato ODBCDirect per accedere direttamente a un’origine dati ODBC, ignorando il motore per database Microsoft ACE. In questo libro non parliamo di ODBCDirect, perché è uno strumento reso obsoleto dal sistema di oggetti ADO e come tale non è più supportato da Access 2010. Ai 24 metodi riepilogati nella Tabella 9.2 si aggiungono 33 proprietà, che sono elencate nella Tabella 9.3. Tabella 9.3  Le proprietà dell’oggetto Recordset. Proprietà AbsolutePosition BatchCollisionCount BatchCollisions BatchSize

Descrizione Imposta o restituisce il numero di record relativo del record corrente di un oggetto Recordset. Solo per le aree di lavoro ODBCDirect. Solo per le aree di lavoro ODBCDirect. Solo per le aree di lavoro ODBCDirect.

Lavorare con i DAO   317

Proprietà BOF Bookmark Bookmarkable CacheSize CacheStart

Connection DateCreated EditMode EOF Fields Filter Index LastModified LastUpdated LockEdits Name NoMatch PercentPosition

RecordCount

RecordsetType RecordStatus Restartable Sort StillExecuting Transactions

Descrizione Restituisce un valore che indica se la posizione del record corrente precede il primo record in un oggetto Recordset. Imposta o restituisce un segnalibro che identifica in modo univoco il record corrente in un oggetto Recordset. Restituisce un valore che indica se l’oggetto Recordset supporta i segnalibri che è possibile impostare con la proprietà Bookmark. Imposta o restituisce un valore che specifica il numero di record reperiti da un’origine dati ODBC da collocare localmente nella memoria cache. Imposta o restituisce un valore che specifica il segnalibro del primo record in un oggetto Recordset di tipo dynaset contenente dati di un’origine dati ODBC da collocare localmente nella memoria cache. Solo per le aree di lavoro ODBCDirect. Restituisce la data e l’ora di creazione di un oggetto o di una tabella base se l’oggetto Recordset è di tipo tabella. Restituisce un valore che indica lo stato di modifica del record corrente. Restituisce un valore che indica se la posizione del record corrente è successiva all’ultimo record in un oggetto Recordset. Restituisce un insieme Fields che rappresenta tutti gli oggetti Field archiviati per l’oggetto specificato Imposta o restituisce un valore che determina i record inclusi in un oggetto Recordset aperto successivamente. Imposta o restituisce un valore che indica il nome dell’oggetto Index corrente in un oggetto Recordset di tipo tabella. Restituisce un segnalibro che indica il record aggiunto o modificato più di recente. Restituisce la data e l’ora dell’ultima modifica apportata a un oggetto o a una tabella base se si tratta di un oggetto Recordset di tipo tabella. Imposta o restituisce un valore che indica il blocco attivo durante una modifica. Imposta o restituisce un nome definito dall’utente per un oggetto Recordset. Indica se un determinato record è stato trovato utilizzando il metodo Seek o uno dei metodi Find. Imposta o restituisce un valore che indica la posizione approssimativa del record corrente nell’oggetto Recordset in percentuale rispetto ai record nel Recordset. Restituisce il numero di record ai quali si è avuto accesso in un oggetto Recordset o il numero totale dei record in un oggetto Recordset di tipo tabella o TableDef. Imposta o restituisce un valore che indica quale tipo di Recordset viene reso disponibile per una maschera. Solo per le aree di lavoro ODBCDirect. Restituisce un valore che indica se un oggetto Recordset supporta il metodo Requery. Imposta o restituisce il criterio di ordinamento per i record contenuti in un oggetto Recordset. Solo per le aree di lavoro ODBCDirect. Restituisce un valore che indica se un oggetto supporta le transazioni. (continua)

318  Capitolo 9 Tabella 9.3  Le proprietà dell’oggetto Recordset. (segue) Proprietà Updatable UpdateOptions ValidationRule ValidationText

Descrizione Restituisce un valore che indica se è possibile modificare l’oggetto Recordset. Solo per le aree di lavoro ODBCDirect. Imposta o restituisce un valore che convalida i dati in un campo quando tale campo viene modificato o aggiunto a una tabella. Imposta o restituisce un valore per il testo del messaggio visualizzato dall’applicazione se il valore di un oggetto Field non soddisfa la regola di convalida specificata dall’impostazione della proprietà ValidationRule.

Qualche chiarimento sui termini della Tabella 9.3.

Segnalibro Si chiama Segnalibro o Bookmark una stringa binaria, creata da Access in memoria, che identifica il record corrente. Se si assegna il valore di Bookmark a una variabile e ci si sposta su un altro record, si può tornare istantaneamente al record marcato con quel segnalibro impostando la proprietà Bookmark su quella variabile.

Valori restituiti da EditMode Il valore restituito da questa proprietà è individuato dalle seguenti costanti DAO: Costante dbEditNone DbEditInProgress dbEditAdd

Descrizione Nessuna modifica in corso. È stato richiamato il metodo Edit e il record corrente si trova nel buffer di transito. È stato richiamato il metodo AddNew e il record corrente nel buffer di transito è un record nuovo che non è stato salvato nel database.

Blocco/Bloccato Condizione che rende di sola lettura una pagina di dati, un oggetto Recordset o un oggetto Database per tutti gli utenti tranne per quello che vi sta attualmente immettendo dati.

Transazione Il termine indica una serie di modifiche apportate ai dati impostate in modo da costituire un’unica operazione. Si ricorre alle transazioni quando è necessario garantire che tutte le modifiche vengano apportate per intero; se una delle modifiche non va a buon fine per una qualsiasi ragione, gestendole mediante una transazione si possono annullare anche quelle precedenti, evitando di lasciare il database in uno stato di modifica parziale. Abbiamo riepilogato nelle tabelle 9.2 e 9.3 i metodi e le proprietà dell’oggetto Recordset per far capire quanto questo oggetto sia ricco di potenziali funzionalità e per orientare i lettori a trovare rapidamente il metodo o la proprietà più adatti per ottenere un determinato risultato con una routine VBA che utilizza l’oggetto Recordset.

Lavorare con i DAO   319

Non è il caso di presentare qui una descrizione puntuale di tutti i metodi e di tutte le proprietà di questo oggetto, magari con un esempio per ciascuno: porterebbe via molte pagine, sarebbe noiosissima e anche ridondante, perché queste informazioni sono tutte disponibili nella Guida di Access, che spiega la sintassi da usare con ciascuno dei metodi e delle proprietà. La proverbiale oscurità di questa fonte di informazioni non dovrebbe essere un ostacolo, quando si hanno chiari i concetti di base presentati finora. Invece di disperderci in un labirinto di minuziosi particolari, torniamo al nostro tema di fondo per vedere, con l’aiuto di qualche esempio, il ruolo che gli oggetti DAO possono avere quando si creano applicazioni professionali con Access.

Gli oggetti DAO nelle applicazioni Come dice il loro nome, i Data Access Objects sono oggetti che il motore ACE per database mette a disposizione di Access e del VBA per accedere ai dati. Questa definizione lascerebbe pensare che con i DAO si possa soltanto accedere a dati disponibili, che dovrebbero in qualche modo esistere già per conto loro. In realtà, con i DAO si può accedere ai dati non soltanto per leggerli, ma anche per modificarli e, se è necessario, per crearne di nuovi. Pur imponendo una serie di vincoli non banali nella formulazione degli enunciati, i DAO permettono di usare il VBA per risolvere qualsiasi esigenza applicativa. Proviamo a esaminare un paio di casi esemplificativi, molto vicini alla realtà gestionale delle imprese.

Il backup dei dati essenziali Tutti gli utenti di sistemi informatici sanno quanto sia importante creare con frequenza e sistematicità copie dei dati con i quali si lavora abitualmente. Chi non lo sa lo impara ben presto a sue spese, perché il problema non è mettersi al riparo nel caso che succeda qualcosa ai dati, ma prepararsi per quando succederà. I computer sono macchine intrinsecamente inaffidabili e delicate, basta pochissimo per provocarne l’arresto, con conseguente perdita di almeno una parte dei dati sui quali si stava lavorando appena un momento prima. Non bisogna inoltre dimenticare che l’unico componente in continuo movimento in un computer è proprio il disco rigido, quello che contiene dati e programmi e, come si sa, in una macchina tutto quello che si muove è soggetto a logoramenti e a rotture. Il backup dei dati, ovvero la creazione di copie di sicurezza dei file, è un’attività di importanza fondamentale per garantire la qualità di qualunque applicazione gestionale. Un’eccellente applicazione database, realizzata nel miglior modo possibile, per gestire gli ordini dei clienti si trasforma in un’atroce beffa quando si scopre che non è possibile fatturare le merci spedite perché le tabelle del database sono andate perse e non si dispone di una copia di backup recente dalla quale ripristinare le informazioni che servono. Nei sistemi gestionali evoluti, dove decine o centinaia di personal computer sono collegati in rete fra loro e attestati come client su un server centrale di adeguata potenza, le operazioni di backup vengono normalmente eseguite dallo staff informatico, che attiva sistematicamente opportune procedure di copia dei file comuni che risiedono sul server o sui server, garantendo in questo modo alla comunità degli utenti che non vi saranno arresti dovuti a perdite di dati, salvo i tempi tecnici per eventuali ripristini.

320  Capitolo 9

Diversa è la situazione delle piccole o medie utenze, che sono quelle più propense a utilizzare Access per la gestione dei loro dati. Molto spesso l’applicazione critica, quella sulla quale si basa tutto il lavoro, sta per conto suo su una sola macchina, alla quale sono collegati – eventualmente in rete paritetica – pochi altri computer, che accedono all’applicazione per acquisire informazioni o per aggiornare tabelle. È raro che in organizzazioni del genere esista uno staff addetto alla gestione dei computer e alla protezione del patrimonio informativo. Manca, quindi, uno specialista informatico che abbia la responsabilità del backup proprio in quelle organizzazioni che hanno maggiormente bisogno di mettersi al riparo creando opportune copie di sicurezza dei loro dati. Chi crea applicazioni destinate a operare in contesti del genere, poco professionalizzati dal punto di vista informatico, deve quindi farsi carico anche della protezione dei dati e predisporre opportune funzionalità di backup all’interno delle applicazioni che realizza, affidandole poi a utenti/operatori impegnati in altri mestieri: avvocati, medici o commercialisti. I suoi utenti devono poter creare copie di sicurezza dei loro dati senza uscire dall’applicazione, al limite senza neppure accorgersi che lo stanno facendo. Sulla scorta di queste considerazioni preliminari, che aiutano a definire l’esigenza applicativa, proviamo a vedere in che modo si possono creare funzionalità di backup del contenuto informativo di un’applicazione database.

Che cosa salvare, come e dove In un’applicazione database si distinguono nettamente due gruppi di componenti: i dati e l’interfaccia utente. L’utente/operatore non è abilitato a modificare gli oggetti che appartengono a questo secondo gruppo: è l’utente/realizzatore che li ha creati a occuparsi della loro manutenzione, intervenendo di tanto in tanto, su richiesta del committente, per aggiungere maschere, semplificare o arricchire quelle esistenti, aggiungere o modificare report e così via. Per queste ragioni, non è il caso di fare copie di sicurezza di maschere e report, perché la loro versione più aggiornata e corretta sarà sempre gelosamente custodita dall’utente/realizzatore che ha creato l’applicazione. E se così non fosse, bisognerà provvedere con urgenza a sostituire l’applicazione e a trovarsi un nuovo realizzatore, professionalmente adeguato. Diverso è il caso delle tabelle e delle query: il loro contenuto si modifica in continuazione, con l’aggiunta e l’eliminazione di record, la modifica del contenuto dei singoli campi e così via. La struttura delle query è anch’essa, come quella di maschere e report, fuori della portata dell’utente/operatore, ma il loro risultato varia in funzione delle modifiche che vengono apportate alle tabelle sulle quali si basano le query. In sintesi, ciò che deve essere salvaguardato sono le tabelle e le query. E con loro, ovviamente, tutte le informazioni che le rendono funzionali per l’applicazione: le relazioni fra le tabelle, gli indici e le proprietà di tabelle e query. Stabilito che cosa salvare, bisogna decidere dove va salvato. La regola, in questi casi, è quella delle uova e del paniere. Come suggerisce la saggezza popolare, è bene tenere separate le cose che potrebbero rompersi. Non sarà il caso di creare una copia dei dati sullo stesso disco in cui risiede l’applicazione: andrà messa su un disco diverso, meglio se di un altro computer. Dal momento che l’obiettivo da raggiungere è la creazione completamente automatica di una copia di sicurezza delle tabelle e delle query, con le loro informazioni comple-

Lavorare con i DAO   321

mentari, stabiliamo che l’operazione deve essere accessibile all’utente/operatore nel più semplice dei modi: l’applicazione sarà dotata di un pannello comandi, che contiene fra gli altri anche un pulsante di comando per uscire da Access; la routine evento associata al clic su quel pulsante attiva la routine di backup, che crea – in una specifica cartella di una unità disco in rete predefinita – un nuovo file database, copiandovi tutte le tabelle e le query del database attivo. Terminata questa operazione, se ne dà segnalazione all’operatore, procedendo poi con la chiusura di Access.

Che cosa serve Per ottenere il risultato che abbiamo schematizzato nel paragrafo precedente, occorre sviluppare una serie di routine VBA e creare almeno un paio di maschere con i loro moduli di classe. In particolare, serviranno: •• una maschera che agisca da pannello comandi, con un pulsante per chiudere Access; •• una maschera associata al clic su quel pulsante di comando, per avvertire l’operatore che sarà eseguita la procedura di backup di tabelle e query; •• opportune routine evento per le maschere; •• varie routine in un modulo standard per l’esecuzione delle copie. Presentiamo qui di seguito una possibile versione delle routine necessarie che, come sempre succede in programmazione, si potrebbero benissimo costruire diversamente per ottenere gli stessi risultati. Quello che qui ci interessa è sfruttare lo spunto di un’esigenza applicativa reale (la creazione di copie di sicurezza dei dati) per riepilogare tutta la tematica dei DAO, utilizzando insiemi, oggetti, proprietà e metodi già descritti nei paragrafi precedenti e altri che verranno presentati e spiegati nel momento in cui si utilizzeranno. La procedura è formata da una routine principale, CopiaDatabase, che chiama varie routine Sub, secondo lo schema della Tabella 9.4. Tabella 9.4  Le routine Sub utilizzate per copiare il database. CopiaDatabase CopiaTabelle CopiaQuery CopiaDati CopiaRelazioni CopiaIndici CopiaProprietà CopiaCampi

Crea il database di destinazione e chiama le routine di supporto per eseguire la copia Copia le strutture delle tabelle, a eccezione delle tabelle di sistema Copia direttamente le query nel nuovo database Copia i contenuti di tutti i record in ciascuna tabella, campo per campo Copia le relazioni fra tutte le tabelle Copia gli indici di ciascuna tabella Copia le proprietà di ciascun oggetto tabella, indice, campo e query Copia gli insiemi Fields degli oggetti tabelle, indici e relazioni.

Creare l’oggetto Database Il listato che segue contiene la routine principale, CopiaDatabase, che utilizza due parametri: il nome del database di origine (quello corrente) e quello del database di destinazione. Questi parametri vengono forniti dalla maschera di attivazione. Sub CopiaDatabase(strFileOrigine As String, strFileDest As String)     ' Crea una copia del database corrente usando i DAO

322  Capitolo 9     Dim dbsOrig As DAO.Database, dbsDest As DAO.Database     On Error GoTo errEsisteGià     Set dbsOrig = CurrentDb     Set dbsDest = DBEngine.CreateDatabase(strFileDest, _                  dbLangGeneral, dbVersion120)     CopiaTabelle dbsOrig, dbsDest     CopiaQuery dbsOrig, dbsDest     CopiaDati dbsOrig, dbsDest     CopiaRelazioni dbsOrig, dbsDest     dbsDest.Close     dbsOrig.Close     Exit Sub      errEsisteGià:     If Err = 3204 Then         MsgBox "Non posso copiare su un database che esiste già!"     Else         MsgBox "Errore: " & Error$     End If End Sub

La routine CopiaDatabase assegna le variabili oggetto dbsOrig e dbsDest ai database di origine e di destinazione che sono stati specificati come suoi argomenti e successivamente usa il metodo CreateDatabase dell’oggetto Workspace per creare nell’area di lavoro indicata (che può essere quella predefinita, cioè la prima dell’oggetto DBEngine) un nuovo database, utilizzando il nome completo di percorso che è stato fornito come argomento strFileDest. Il metodo CreateDatabase dell’oggetto Workspace ha la seguente sintassi: Set database = arealavoro.CreateDatabase (nome, internazionali, opzioni)

dove database è un’espressione che identifica il database da creare, arealavoro è il Workspace attivo, nome è il nome da assegnare al database, internazionali è una costante che specifica il tipo di ordinamento da usare per il database creato (normalmente si usa la costante dbLangGeneral, che vale per i criteri di ordinamento inglese, tedesco, francese, portoghese, spagnolo e italiano) e opzioni è una costante o una combinazione di costanti che permette di indicare se si vuole che il database sia cifrato e per quale versione di Access deve essere generato. Nell’esempio abbiamo impostato opzioni su dbVersion120, indicando così che il nuovo database dovrà essere creato nel formato di file di Access 2007, quindi con estensione .accdb; sono disponibili altre costanti per impostare la creazione in uno dei formati precedenti di Access (per esempio, con dbVersion40 si ottiene un nuovo database nel formato di Access 2003, cioè un file con estensione .mdb). La routine è protetta da un controllo di errore. Sulla gestione degli errori finora si è detto poco, perché l’argomento è trattato estesamente nel prossimo Capitolo 11. Per il momento può bastare sapere che, in caso di errore, la routine si arresta e va a verificare se il numero che identifica l’errore è il 3204, cioè “Database già esistente”. In questo caso, viene aperta una finestra di messaggio più esplicativa, altrimenti viene semplicemente visualizzato il messaggio di errore standard. Se la creazione del database è avvenuta senza errori, vengono chiamate una dopo l’altra le quattro routine CopiaTabelle, CopiaQuery, CopiaDati e CopiaRelazioni, indicando per ciascuna gli stessi due parametri, dbsOrig, dbsDest, cioè il file di origine e quello di destinazione.

Lavorare con i DAO   323

Creare le strutture delle tabelle La prima routine di supporto, CopiaTabelle, si presenta come nel listato seguente: Sub CopiaTabelle(dbsOrig As DAO.Database, dbsDest As DAO.Database)     Dim tbfOrigine As DAO.TableDef, tbfDest As DAO.TableDef     For Each tbfOrigine In dbsOrig.TableDefs         If (tbfOrigine.Attributes And dbSystemObject) Then         Else             Set tbfDest = dbsDest.CreateTableDef(tbfOrigine.Name, _                 tbfOrigine.Attributes, tbfOrigine.SourceTableName, _                 tbfOrigine.Connect)             If tbfOrigine.Connect = "" Then                 CopiaCampi tbfOrigine, tbfDest                 CopiaIndici tbfOrigine.Indexes, tbfDest             End If             CopiaProprietà tbfOrigine, tbfDest             dbsDest.TableDefs.Append tbfDest         End If     Next End Sub CopiaTabelle esegue un ciclo For Each basato sull’insieme TableDefs del database di origine e

copia una dopo l’altra nel database di destinazione appena creato la struttura di ciascuna tabella d’origine. Il ciclo For Each è soggetto a due vincoli, gestiti con costrutti If...Then. Col primo, si scartano le tabelle di sistema, che non vanno trasferite nel nuovo database, dal momento che vi vengono create automaticamente da Access. Per distinguere le tabelle di sistema, in questo caso utilizziamo un confronto più elegante di quello sui primi quattro caratteri del nome di una tabella che abbiamo utilizzato negli esempi di conteggio delle tabelle all’inizio del capitolo. Qui il controllo si fa sull’attributo identificato dalla costante DAO dbSystemObject, che permette di individuare gli oggetti di sistema. Il risultato è comunque lo stesso. Il secondo controllo eseguito nel ciclo che crea le tabelle si accerta che queste non provengano da un altro database, cioè non siano tabelle collegate. Quest’ultimo controllo si basa sulla proprietà Connect di ciascuna tabella. Se questa proprietà non è esplicitata, viene richiamata nel ciclo For Each la routine CopiaCampi in riferimento a ciascuna tabella e subito dopo la routine CopiaIndici. Al termine del ciclo che ha copiato campi e indici di ogni tabella, viene eseguita la routine CopiaProprietà, sempre riferita alla singola tabella, quindi la tabella viene accodata al database di destinazione usando il metodo Append dell’insieme TableDefs sul quale la routine CopiaTabelle sta lavorando. Il metodo CreateTableDef utilizzato in questa routine ha la seguente sintassi: Set deftabella = database.CreateTableDef (nome, attributi, origine, conness)

dove conness è una stringa che specifica la modalità di connessione di una tabella collegata. Il metodo CreateTableDef crea un’istanza di un oggetto TableDef nell’insieme TableDefs al quale si riferisce, ma lo aggiunge fisicamente all’insieme TableDefs soltanto quando si

324  Capitolo 9

usa il metodo Append. Prima di attivare questo metodo, vengono chiamate le due routine CopiaCampi e CopiaIndici, che hanno il seguente aspetto: Sub CopiaCampi(objOrigine As Object, objDest As Object)     Dim fldOrigine As Field, fldDest As Field     For Each fldOrigine In objOrigine.Fields         If TypeName(objDest) = "TableDef" Then             Set fldDest = objDest.CreateField(fldOrigine.Name, _                 fldOrigine.Type, fldOrigine.Size)         Else             Set fldDest = objDest.CreateField(fldOrigine.Name)         End If         CopiaProprietà fldOrigine, fldDest         objDest.Fields.Append fldDest     Next     Exit Sub End Sub

Anche CopiaCampi utilizza un ciclo For Each, basato sui campi di ciascuna tabella del database di origine, per eseguire il metodo CreateField, che è un metodo dell’oggetto TableDef. La sua sintassi è la seguente: Set campo = oggetto.CreateField (nome, tipo, dimensione)

Il metodo crea un campo nell’oggetto TableDef al quale si riferisce con nome, tipo di dati e dimensione come specificati nei parametri del metodo stesso. Si noti che non si fa riferimento al contenuto: il metodo CreateField, anche se utilizzato con i parametri di una tabella esistente, come in questo caso, crea solo un campo vuoto, senza dati. La routine chiama, per ogni campo creato con il metodo CreateField, la stessa routine di appoggio CopiaProprietà che viene usata anche dalla routine CreaTabelle. La routine CopiaProprietà si presenta in questo modo: Sub CopiaProprietà(objOrigine As Object, objDest As Object)     Dim prpProp As DAO.Property     For Each prpProp In objOrigine.Properties      On Error Resume Next         objDest.Properties(prpProp.Name) = prpProp.Value     Next End Sub

Dal momento che CopiaProprietà e CopiaCampi vengono richiamate da più routine, le variabili che utilizzano sono sempre origine e destinazione, ma sono dichiarate come generiche variabili oggetto e non come TableDef o QueryDef. Dopo l’esecuzione della routine CopiaCampi (e di CopiaProprietà, richiamata dall’interno di CopiaCampi) il controllo della routine torna a CopiaTabelle, dove viene attivata la routine CopiaIndici, che ha questa struttura: Sub CopiaIndici(idxsOrigine As DAO.Indexes, objDest As Object)     Dim idxOrigine As DAO.Index, idxDest As DAO.Index     Dim propOrigine As DAO.Property

Lavorare con i DAO   325     For Each idxOrigine In idxsOrigine         Set idxDest = objDest.CreateIndex(idxOrigine.Name)         CopiaProprietà idxOrigine, idxDest         CopiaCampi idxOrigine, idxDest         objDest.Indexes.Append idxDest     Next End Sub

Il primo parametro è l’insieme Indexes del database d’origine, il secondo è una generica variabile oggetto. Anche qui si utilizza un ciclo For Each, impostato in questo caso sull’insieme Indexes del database d’origine. Ogni singolo oggetto Index che sta in questo insieme viene copiato nell’insieme Indexes del database di destinazione, mediante il metodo CreateIndex dell’oggetto TableDef. Come si può notare, anche questa routine chiama le routine CopiaProprietà e CopiaCampi, in riferimento a ciascun oggetto Index che copia. Gli indici, infatti, hanno almeno un campo e possono essere di tipo primario, univoco, obbligatorio e così via. Esauriti i cicli per campi, e indici, il controllo torna alla routine CopiaTabella, che esegue per gli oggetti TableDef la routine CopiaProprietà, quindi restituisce il controllo alla routine principale, che chiama CopiaQuery. Se interrompessimo l’esecuzione a questo punto e andassimo ad aprire il database creato da CopiaDatabase, potremmo constatare che questo contiene tutte le tabella con i loro indici, ma esistono soltanto le strutture: i campi sono senza dati. Sono infatti stati creati, all’interno dei loro rispettivi insiemi, tanti oggetti TableDef, Field e Index quanti ve ne sono nel database d’origine. Per mettere i dati in questi oggetti bisognerà ricorrere ai metodi dell’oggetto Recordset, come vedremo fra poco.

Creare le query La routine principale CopiaDatabase chiama subito dopo la routine di supporto CopiaQuery. Per eseguire questa operazione non è necessario procedere prima alla creazione degli oggetti QueryDef e poi al loro riempimento con dati effettivi. La creazione di un oggetto QueryDef accoda automaticamente la query al database, senza che sia necessario ricorrere a un metodo Append o richiamare i metodi di Recordset. La copia delle query esistenti avviene con questa routine: Sub CopiaQuery(dbOrigine As DAO.Database, dbDest As DAO.Database)     ' Copia le query, escludendo quelle     ' con "~sq_" nella parte iniziale del nome     Dim qryOrigine As QueryDef, qryDest As QueryDef     Dim strTemp As String     For Each qryOrigine In dbOrigine.QueryDefs         strTemp = qryOrigine.Name         If Left$(strTemp, 4) «~sq_» Then             Set qryDest = dbDest.CreateQueryDef(qryOrigine.Name, _                 qryOrigine.SQL)             CopiaProprietà qryOrigine, qryDest         End If     Next End Sub

326  Capitolo 9

Il metodo usato è CreateQueryDef, che ha questo schema sintattico: Set querydef = oggetto.CreateQueryDef (nome, testosql)

dove testosql è l’enunciato SQL che definisce la query. Nell’insieme QueryDefs del database d’origine potrebbero trovarsi alcuni oggetti QueryDef il cui nome inizia con i caratteri “~sq_”. Sono componenti di sistema, non sempre presenti in un database e comunque non visibili dall’interfaccia grafica, che vengono esclusi dalla copia in base a un test sui primi quattro caratteri del loro nome interno.

Copiare i dati Tutte le strutture adesso sono state copiate nel nuovo database. Resta la parte più delicata e probabilmente più lunga da eseguire: il travaso dei dati dai campi delle tabelle d’origine a quelli delle tabelle di destinazione.Approfittiamo di questa occasione per presentare un importante strumento disponibile in Access per eseguire operazioni di aggiornamento di tabelle: la transazione. Una transazione è un’operazione con la quale si eseguono in una volta sola più modifiche su una o più tabelle, con la certezza che tutte le modifiche vengano eseguite correttamente oppure che non ne venga eseguita alcuna. Le transazioni sono un meccanismo per salvaguardare l’integrità dei dati. Per esempio, una serie di movimenti contabili comporta sempre due aggiornamenti per ciascun movimento: si addebita un conto e se ne accredita un altro. Se, per avventura, qualche aggiornamento non va a buon fine, per un inconveniente hardware o software che si verifica durante la modifica dei dati, le tabelle toccate dalle modifiche perdono la loro integrità e non rispecchiano più correttamente la situazione contabile: meglio, quindi, annullare tutte le modifiche, piuttosto di eseguirle soltanto parzialmente. Il motore ACE mette a disposizione dell’oggetto Workspace e dell’oggetto DBEngine, tre metodi da utilizzare per creare transazioni. Si tratta dei metodi BeginTrans, CommitTrans e RollBack. Richiamando opportunamente questi metodi, prima di eseguire una serie di modifiche in blocco, si mette il sistema in condizione di confermare tutte le modifiche (CommitTrans) oppure di annullarle (RollBack) se qualche modifica inclusa nel blocco della transazione non è andata a buon fine. Vediamo concretamente come operano questi metodi nella routine CopiaDati. Sub CopiaDati(dbsOrig As DAO.Database, dbsDest As DAO.Database)     Dim tbfOrigine As DAO.TableDef, rstDest As DAO.Recordset, _         rstOrigine As DAO.Recordset                  'Prepara la transazione     Dim wspTransact As Workspace     Dim fldOrigine As DAO.Field     Set wspTransact = DBEngine.Workspaces(0)              'Inizia la transazione     wspTransact.BeginTrans     On Error GoTo errRollback         

Lavorare con i DAO   327     For Each tbfOrigine In dbsOrig.TableDefs         If (tbfOrigine.Attributes And dbSystemObject) Or _             (tbfOrigine.Connect "") Then         'Esclude le tabelle di sistema e quelle allegate         Else         'Apre sulla tabella un recordset forward only             Set rstOrigine = dbsOrig.OpenRecordset(tbfOrigine.Name, _                 dbOpenTable, dbForwardOnly)             If Not rstOrigine.EOF Then                 Set rstDest = dbsDest.OpenRecordset(tbfOrigine.Name, _                     dbOpenDynaset, dbAppendOnly)                 Do While Not rstOrigine.EOF                     rstDest.AddNew                     For Each fldOrigine In rstOrigine.Fields                         rstDest(fldOrigine.Name) = fldOrigine.Value                     Next                     rstDest.Update                     rstOrigine.MoveNext                 Loop                 rstDest.Close             End If         rstOrigine.Close         End If     Next              ' Impegna la transazione     wspTransact.CommitTrans     Exit Sub          errRollback:     MsgBox "Errore:" & Error$     ' Annulla l'intera transazione se c'è stato errore     wspTransact.Rollback End Sub

Tra gli enunciati iniziali troviamo la dichiarazione della variabile oggetto wspTransact, che viene poi assegnata all’oggetto Workspace. Mediante questa variabile si attiva successivamente il metodo BeginTrans, che è specifico soltanto degli insiemi Workspaces o dell’oggetto DBEngine. Dopo l’invocazione di BeginTrans inizia il ciclo di copia dei contenuti di tutti i campi di tutte le tabelle dal database di origine a quello di destinazione. Il ciclo non ha niente di particolare, come possiamo vedere leggendo il listato: all’interno di un unico costrutto For Each si utilizza il metodo OpenRecordset su ciascuna tabella, in modo da creare un oggetto Recordset di tipo forward-only (non serve poter tornare indietro sul recordset, visto che va copiato integralmente dall’inizio alla fine in un colpo solo). Il recordset così creato viene poi accodato, record per record, alla corrispondente tabella di destinazione, usando il metodo AddNew all’interno di un ciclo Do While. Questo metodo aggiunge un nuovo record a una tabella, ma l’aggiunta diventa effettiva soltanto dopo l’esecuzione del metodo Update, che aggiorna fisicamente la tabella di destinazione, poi la tabella di origine viene fatta avanzare di un nuovo record mediante il metodo MoveNext e si eseguono di nuovo i metodi AddNew e Update per il record successivo. Tutte le operazioni di copia e avanzamento dei record vengono controllate mediante opportuni enunciati If...Then.

328  Capitolo 9

Una volta completati il ciclo For Each esterno e il ciclo Do While al suo interno, viene invocato il metodo CommitTrans, che impegna le modifiche, che erano state predisposte in zone protette della memoria, ma non ancora trasferite su disco. Se si verifica un errore durante l’esecuzione del ciclo, l’enunciato On Error GoTo errRollback

posto subito dopo la chiamata del metodo BeginTrans inibisce l’esecuzione del metodo CommitTrans e chiama invece il metodo RollBack, annullando tutte le modifiche e lasciando integro il database di destinazione, che non contiene dati parzialmente aggiornati, ma soltanto le strutture vuote create dalle routine precedenti.

Copiare le relazioni Nessun database Access degno di questo nome è senza relazioni fra tabelle. Copiati i dati, restano da copiare ancora le relazioni fra le tabelle. Siccome le relazioni prevedono in molti casi il vincolo dell’integrità referenziale, è necessario definirle nel nuovo database soltanto dopo aver copiato i dati. Infatti, la copia dei dati dalle tabelle d’origine a quelle di destinazione avviene senza un ordine logico, per cui può capitare che vengano copiati prima i dati di una tabella che sta dal lato molti di una relazione uno a molti e poi quelli della sua tabella master. Se la copia dei dati avvenisse dopo che sono state copiate le relazioni, il meccanismo di controllo dell’integrità referenziale potrebbe accorgersi che sono stati immessi dati non correlati, dando quindi una segnalazione di errore: è quanto basta per far scattare il metodo RollBack della transazione e annullare la copia dei dati. La routine per copiare le relazioni è piuttosto semplice: Sub CopiaRelazioni(dbsOrig As DAO.Database, dbsDest As DAO.Database)     Dim relOrigine As DAO.Relation, relDest As DAO.Relation         For Each relOrigine In dbsOrig.Relations             Set relDest = dbsDest.CreateRelation("Nuova" & relOrigine.Name, _                 relOrigine.Table, relOrigine.ForeignTable, _                 relOrigine.Attributes)             CopiaCampi relOrigine, relDest             dbsDest.Relations.Append relDest         Next End Sub

La routine usa il metodo CreateRelation dell’oggetto Database, che crea un oggetto Relation e ha questa sintassi: Set relazione = database.CreateRelation (nome, tabella, tabellaesterna, attributi)

Gli argomenti sono intuitivi: nome è il nome da dare all’oggetto Relation, tabella è il nome della tabella che sta a sinistra del join e che ha la chiave primaria, tabellaesterna è il nome della tabella che sta a destra del join e ha la chiave esterna, attributi sono le caratteristiche della relazione, vale a dire uno a molti, con vincolo di integrità referenziale eccetera. Il metodo CreateRelation viene eseguito con un enunciato di assegnazione che lo attiva in ciclo mediante un costrutto For Each basato sugli oggetti Relation del database d’origine. C’è un piccolo ma importante particolare da tener presente. Il parametro nome del me-

Lavorare con i DAO   329

todo CreateRelation utilizza un nome di sistema interno, che deve essere differenziato in qualche modo quando si copia la stessa relazione nel database di destinazione. Per evitare un errore di run-time, bisogna aggiungere un elemento qualunque al nome di destinazione, come si fa nell’enunciato che segue: Set relDest = dbsDest.CreateRelation("Nuova" & relOrigine.Name, _     relOrigine.Table, relOrigine.ForeignTable, _     relOrigine.Attributes)

Dopo aver creato le relazioni, la routine chiama la routine di appoggio CopiaCampi e infine applica il metodo Append per accodare le relazioni complete dei loro campi all’insieme Relations del database di destinazione. La Figura 9.9 mostra graficamente il flusso delle chiamate fra le varie routine.

CopiaDatabase

CopiaTabelle

CopiaCampi CopiaProprietà

CopiaIndici CopiaCampi

CopiaProprietà

CopiaProprietà

CopiaQuery CopiaProprietà

CopiaRelazioni CopiaCampi

CopiaDati

Figura 9.9  L’ordine col quale si susseguono le chiamate fra le otto routine per il backup.

330  Capitolo 9

La maschera di attuazione La procedura che abbiamo illustrato nei paragrafi precedenti può essere attivata da una maschera, che si presenta automaticamente all’operatore quando seleziona nel pannello comandi dell’applicazione il pulsante che chiude Access. La maschera così richiamata potrebbe presentare tre opzioni all’utente: 1. eseguire il backup del database sul quale ha lavorato fino a quel momento, 2. chiudere Access senza creare una copia di backup, 3. tornare a lavorare con l’applicazione. La prima opzione è quella predefinita, ma è necessario offrire anche le altre due, perché l’applicazione potrebbe essere stata aperta brevemente, giusto per leggere un dato o stampare un report, quindi senza modificare il database: l’esecuzione del backup non si giustificherebbe e, se il database fosse di grandi dimensioni, potrebbe durare parecchio, provocando un’inutile perdita di tempo. Inoltre, bisogna dare all’operatore la possibilità di ripensarci e di non chiudere affatto Access né di fare il backup, ma di tornare a lavorare sull’applicazione. La Figura 9.10 mostra un esempio di maschera molto semplice per gestire le tre operazioni appena descritte.

Figura 9.10  Un esempio di maschera per attivare il backup.

Il modulo di classe di questa maschera potrebbe contenere le dichiarazioni e le routine evento elencate nel listato che segue. Public strFileOrigine As String, strFileDestinazione As String Private Sub Form_Open(Cancel As Integer)     strFileOrigine = CurrentDb.Name End Sub Private Sub cmdAnnulla_Click()     DoCmd.Close End Sub Private Sub cmdEsci_Click()     DoCmd.Quit acQuitSaveAll End Sub Private Sub cmdBackup_Click()     strFileDestinazione = "\\servermike\discodnt\backup\"

Lavorare con i DAO   331     Dim strOggi As String     Dim dtmOggi As Date     dtmOggi = Date     strOggi = CStr(dtmOggi)     strOggi = Left$(strOggi, 2) & "-" & Mid$(strOggi, 4, 2) _         & "-" & Right$(strOggi, 2)     strFileDestinazione = strFileDestinazione & " Salvato il " & strOggi     'Visualizza il puntatore clessidra     'per segnalare che c'è attesa     DoCmd.Hourglass True     'Chiama la routine principale     CopiaDatabase strFileOrigine, strFileDestinazione     'Ripristina il puntatore normale e     'segnala la fine dell'operazione     DoCmd.Hourglass False     MsgBox "Backup concluso correttamente" End Sub

Il primo enunciato dichiara due variabili pubbliche di tipo String, strFileOrigine e strFileDestinazione. Dichiarate in questo modo, le variabili – destinate palesemente a contenere i nomi del file di origine e di quello di destinazione – sono visibili sia alle routine evento della maschera, sia alle routine esecutive, che stanno in un modulo standard. La prima routine è associata all’evento Su apertura della maschera. Contiene un solo enunciato, che assegna alla stringa strFileOrigine il nome, completo di percorso, del database corrente: strFileOrigine = CurrentDb.Name

Le due routine evento successive sono associate all’evento Su clic dei pulsanti di comando cmdEsci e cmdAnnulla. Un clic su cmdEsci (quello che ha la dicitura “Chiude senza backup”) fa chiudere Access, salvando automaticamente le eventuali modifiche fatte: DoCmd.Quit acSave

Un clic sul pulsante di comando cmdAnnulla, che ha la dicitura “Annulla chiusura”, chiude semplicemente la maschera: DoCmd.Close

La routine evento più complessa è quella associata al clic sul primo pulsante di comando, chiamato cmdBackup e individuato dalla dicitura “Esegue backup”. Nel primo enunciato della routine: strFileDestinazione = "\\servermike\discodnt\backup\"

si assegna alla variabile pubblica strFileDestinazione il percorso del file database da creare per il backup. Come si può rilevare dalla struttura di questo nome, il backup viene eseguito in una posizione prestabilita (la cartella \backup) di una unità a disco di rete (riconoscibile per la doppia barra inversa all’inizio del percorso).

332  Capitolo 9

Naturalmente il percorso non basta, occorre specificare anche un nome per il file database da creare. Questo nome si ottiene con gli enunciati che vengono immediatamente dopo, che hanno lo scopo di generare un nome diverso ogni volta che si fa il backup, nell’ipotesi che questa operazione non venga fatta più di una volta al giorno. Il nome viene costruito concatenando una stringa descrittiva con la data del sistema, opportunamente convertita in stringa, per cui, nell’ipotesi che il backup venga eseguito il 24 settembre 2011, il file Access di destinazione si chiamerà “Salvato il 24-09-11” e la stringa strFileDestinazione avrà questo contenuto: \\servermike\discodnt\backup\Salvato il 24-09-11

L’estensione del file (.accdb o .mdb) sarà generata automaticamente sulla base dell’opzione dbVersion utilizzata come argomento finale del metodo CreateDatabase (si veda sopra, la routine CopiaDatabase). Creata la stringa che identifica il file di destinazione, l’enunciato successivo non fa altro che chiamare la routine principale, CopiaDatabase, comunicandole i due parametri di cui ha bisogno: da quel momento si avvia in automatico la successione di chiamate di subroutine che abbiamo descritto prima e che è rappresentata graficamente nella Figura 9.9. L’operazione avviata con la chiamata di CopiaDatabase può durare un tempo indeterminato, da qualche secondo a qualche minuto, in funzione della quantità di dati da copiare e delle velocità dei collegamenti. Nel frattempo la maschera Backup controllato resta piantata sullo schermo, immobile e insensibile a eventuali tentativi di chiusura da parte dell’operatore. Per evitare che l’operatore si innervosisca o tema che il computer si sia bloccato e, preso dal panico, decida di far ripartire la macchina col risultato di perdersi definitivamente tutta l’applicazione, bisogna dargli qualche informazione rassicurante. Nella routine evento che stiamo commentando si ricorre a una segnalazione canonica di Windows, la trasformazione del puntatore del mouse in clessidra, per segnalare che è in corso un’operazione che potrebbe richiedere un po’ di tempo. Questa segnalazione viene data usando due istanze dell’oggetto DoCmd: la prima attiva la clessidra e la seconda, eseguita al termine del ciclo di backup, ripristina l’aspetto normale del puntatore, segnalando che ora è possibile agire sugli altri due pulsanti di comando, scegliendo di uscire da Access o di tornare all’applicazione. Per maggior chiarezza, viene visualizzata anche una finestra di messaggio che conferma la buona riuscita del backup.

Quel che resta da fare Questa procedura per eseguire in modo automatico il backup di un file database non può certo considerarsi un capolavoro di tecnica di programmazione e men che meno un esempio luminoso di professionalità nello sviluppo applicativo. Il suo scopo è semplicemente quello di dimostrare come sia possibile eseguire operazioni di una certa complessità usando esclusivamente il VBA e i DAO, senza ricorrere mai, neppure una volta, all’interfaccia utente. Parecchio resta ancora da fare per dare dignità di applicazione a questo che si potrebbe considerare un dignitoso semilavorato. Innanzitutto, vanno approfonditi con l’utente/operatore alcuni aspetti dell’esigenza applicativa e del modo in cui può essere risolta. Per esempio, la maschera di attuazione Backup controllato assegna automaticamente il nome del database corrente come file d’origine, mentre per quello del database di destinazione

Lavorare con i DAO   333

dà per scontato che esista una connessione in rete con un server, che su un disco specifico del server esista una cartella chiamata Backup, nella quale verrà creata la copia di backup con un nome predefinito, che cambia automaticamente col cambiare della data di sistema. Tutto questo potrebbe non andar bene all’utente, per molte ragioni, che vanno valutate e fatte proprie dall’utente/realizzatore, se vuol dare un buon servizio al suo committente. Potrebbe convenire, per esempio, lasciare libertà di designare di volta in volta un percorso e un nome per il file di destinazione, invece di imporre un percorso e un nome predefiniti. Oppure il committente potrebbe avere l’esigenza di eseguire il backup di database diversi da quello corrente. Un opportuno collaudo generale fatto con gli utenti finali potrebbe inoltre far capire che la segnalazione di “Lavori in corso, attendere” che viene data sobriamente facendo assumere l’aspetto della clessidra al puntatore del mouse non è sufficiente: gli operatori si innervosiscono e hanno la sensazione che il computer si sia bloccato. L’approfondimento dell’esigenza applicativa potrebbe portare a rifare la maschera di attuazione, modificandone la struttura, aggiungendo caselle di testo per immettere nomi di database d’origine e/o di destinazione e completando la segnalazione data dalla clessidra con una finestra di messaggio o magari visualizzando una barra di avanzamento, per segnalare graficamente il progressivo svolgersi delle operazioni di copia. Nel modificare la maschera di attuazione, quello che conta è produrre alla fine due stringhe formalmente e sostanzialmente corrette che, date come parametri alla routine CopiaDatabase, consentano l’esecuzione senza intoppi di tutte le chiamate di subroutine necessarie.

Ripristinare dal database di backup Come dovrà comportarsi l’utente/operatore quando avrà bisogno di ripristinare la sua applicazione utilizzando il database di backup? Dovrà chiedere l’aiuto di chi ha sviluppato per lui l’applicazione. Per questa persona sarà questione di pochi minuti, usando la sua copia originale dell’applicazione, crearne una nuova completa di tutti gli oggetti di interfaccia utente (che non sono stati salvati nel database di backup) e recuperando i dati aggiornati dalle tabelle e dalle query salvate con l’ultimo backup. Ma non potrebbe farlo direttamente l’utente/operatore? Potrebbe, ma è meglio di no: una regola non scritta, ma non per questo meno rispettata, del mondo applicativo stabilisce che il buon funzionamento di un’applicazione è responsabilità di chi l’ha sviluppata. Se si rovina per una qualsiasi ragione, la responsabilità resta sua se è lui a recuperarla: se la recupera l’utente finale, l’applicazione non è più di chi l’ha sviluppata e rimane tristemente orfana, perché l’utente finale non la sa gestire e il realizzatore non può più riconoscerla come una sua creatura.

Trovare un dato Chiunque abbia un po’ di dimestichezza con Access può aprire un file database, aprire una tabella in visualizzazione Foglio dati e cercare un dato che gli serve scorrendo le righe dei record. Se la tabella è dotata di una maschera, la ricerca è ancora più facile: si apre la maschera e si agisce sui pulsanti di spostamento, detti anche di navigazione, esaminando i record a mano a mano che compaiono, alla ricerca del dato.

334  Capitolo 9 NOTA In Access 2007 e 2010 al piede delle maschere e delle tabelle quando si aprono in visualizzazione Maschera o Foglio dati, c’è una casella con la dicitura Cerca, come quella che vediamo qui sotto:

Digitando in quella casella, al posto della dicitura Cerca, una stringa di caratteri si ottiene lo spostamento della maschera (o del selettore dei record, nel caso di una tabella) sul record che contiene in un qualunque campo la stringa di caratteri appena digitata. È una funzionalità comoda e veloce, ma poco selettiva, perché la ricerca dei caratteri digitati viene eseguita su tutti i campi e non su un campo specifico.

Fin tanto che la tabella contiene qualche decina di record, l’esplorazione manuale è lo strumento più semplice e tutto sommato più veloce. Il problema nasce quando i dati da esplorare sono migliaia o decine di migliaia e diventa ancora più impegnativo quando quello che si cerca non è completamente noto: il cognome di una persona che si chiama Rossi o forse Russo o magari Rossini o, chi sa, De Rossi. In questi casi si può utilizzare un comodo strumento disponibile nell’interfaccia grafica, attivabile facendo clic sul pulsante Trova (quello con l’icona del binocolo) nella scheda Home della barra multifunzione. La finestra di dialogo che compare si presenta come nella Figura 9.11 e si procede in questo modo: •• si fa clic nel campo della tabella o nel controllo della maschera in cui dovrebbe trovarsi l’informazione che interessa; •• si scrive il testo da cercare nella casella Trova; •• si sceglie una modalità di ricerca fra quelle disponibili: esplorare un campo o tutti i campi per trovarne uno che contenga esattamente il testo da cercare e soltanto quello, oppure accontentarsi di trovare un campo che al suo interno contenga la stringa cercata; si può indicare di tener conto oppure no delle maiuscole e delle minuscole ed è possibile specificare di prendere in considerazione la formattazione (per esempio, trovare la data del 24 settembre 2008 solo nel formato 24-Set-08 e non nel formato 24/9/08); •• un clic sul pulsante Trova successivo avvia la ricerca. Tutto questo va molto bene quando si lavora con l’interfaccia grafica, ma come ottenere lo stesso risultato in un’applicazione? Bisogna utilizzare, naturalmente, qualche routine VBA e i DAO.Vediamo in che modo si può soddisfare da programma questa elementare, ma diffusissima esigenza applicativa: trovare rapidamente un record sulla scorta di un frammento d’informazione che dovrebbe trovarsi in un campo determinato.

I controlli di attuazione Anche in questo caso le routine che stiamo per descrivere non rappresentano l’unico modo per ottenere il risultato che interessa, ma servono soprattutto per dimostrare concretamente l’utilizzo di alcuni oggetti e metodi DAO mediante VBA.

Lavorare con i DAO   335

Figura 9.11  La finestra di dialogo Trova e sostituisci: nella scheda Trova si definiscono i criteri della ricerca.

Vogliamo costruire un’applicazione embrionale che permetta di trovare l’indirizzo di una persona in base a qualche elemento del suo cognome: avremo quindi bisogno di una tabella con nomi e indirizzi e di una maschera che ne visualizzi i record. Supponendo di avere già a disposizione la tabella con nomi e indirizzi, costruiamo una semplice maschera che visualizzi in altrettanti controlli casella di testo tutti i campi di ciascun record. Chiameremo Cognome la casella di testo che visualizza il campo col cognome. Aggiungiamo un piè di pagina alla maschera e vi inseriamo una casella di testo e tre pulsanti di comando. Attribuiamo i seguenti nomi ai quattro controlli: Casella di testo Pulsante di comando 1 Pulsante di comando 2 Pulsante di comando 3

Cercare Trova Ancora Chiudi

Salviamo la maschera dandole il nome frmIndirizzi. I controlli avranno queste funzioni: 1. nella casella di testo Cercare si scrive la parte di cognome sulla quale basare la ricerca; 2. un clic sul pulsante di comando Trova avvia la ricerca, posizionando la maschera sul primo dei record che soddisfa il criterio espresso nella casella di testo Cercare; 3. una finestra di messaggio avverte se non esistono record che soddisfano il criterio di ricerca; 4. se è stato trovato almeno un record è possibile che ve ne siano altri: un clic sul pulsante di comando Ancora avvia di nuovo la ricerca, presentando un record ogni volta che trova una corrispondenza; 5. esauriti i record che soddisfano il criterio di ricerca, un ulteriore clic sul pulsante di comando Ancora fa uscire una finestra di messaggio per avvertire che non ve ne sono altri; 6. un clic sul pulsante di comando Chiudi fa chiudere la maschera. All’apertura della maschera frmIndirizzi la casella di testo per immettere gli elementi del cognome da cercare porta una scritta esplicativa, ottenuta scrivendo “Cognome da

336  Capitolo 9

cercare” nella casella della proprietà Valore predefinito. La dicitura va immessa fra doppie virgolette per segnalare che si tratta di una stringa, diversamente verrebbe interpretata come un nome di variabile e si avrebbe una segnalazione di errore. Sarà opportuno utilizzare, almeno per la casella di testo Cercare, la proprietà Testo descrizione controllo, in modo che, quando l’utente vi appoggia sopra il puntatore del mouse, compaia una scritta che spiega finalità e modalità d’uso della casella stessa. Il testo della descrizione potrebbe essere il seguente: Digitare almeno una parte del cognome da cercare, usando * per eventuali caratteri intermedi da ignorare. Per scrivere più comodamene questo testo (o uno diverso) nella casella della proprietà Testo descrizione controllo, si può ampliarla col comando Zoom (disponibile facendo un clic destro sulla casella). Per avere un testo disposto su più righe, si preme la combinazione di tasti Ctrl+Invio nel punto in cui si vuole andare a capo. Niente impedisce di aggiungere un testo esplicativo anche per i pulsanti di comando, ma le loro diciture dovrebbero bastare. La maschera frmIndirizzi, una volta completata con i controlli che abbiamo descritto, potrebbe presentarsi come quella che si vede nella Figura 9.12.

Figura 9.12  La maschera frmIndirizzi pronta per l’uso.

Il modulo associato alla maschera Esaurita la parte preliminare, si tratta di scrivere nel modulo di classe della maschera le routine evento per i tre pulsanti di comando.

Lavorare con i DAO   337

Per il pulsante Chiudi può bastare il collaudato oggetto DoCmd, associato al metodo Close: Private Sub Chiudi_Click()         DoCmd.Close End Sub

I due pulsanti Trova e Ancora fanno sostanzialmente lo stesso lavoro, con qualche leggera variante. In casi del genere, è opportuno mettere nelle routine evento soltanto enunciati specifici di ciascun pulsante, mentre l’operazione comune può essere eseguita con una funzione o con una routine. Nel nostro caso è preferibile usare una routine, perché non interessa acquisire un valore di ritorno da una funzione. Occorrono quindi: 1. una routine esecutiva, che esplori i record, cercando nel campo Cognome di ciascuno la stringa che è stata scritta nella casella di testo Cercare; 2. una routine evento per ciascuno dei due pulsanti di comando Trova e Ancora. Alcune variabili sono utilizzate sia dalle routine evento sia dalla routine esecutiva, per cui le dichiariamo nella sezione Dichiarazioni del modulo di classe, in questo modo: Public Public Public Public

rstMaschera As DAO.Recordset strCriterio As String strTrovami As String strChiamante As String

La routine per l’evento Su clic del pulsante Trova potrebbe essere scritta nel modo seguente: Private Sub Trova_Click()         strTrovami = Me.Cercare.Value         strChiamante = Me.Trova.Name         If IsNull(Cercare.Value) Or Cercare.Value = "Cognome da cercare" Then             MsgBox ("Non posso trovare quello che non hai scritto!"), vbOKOnly             Exit Sub         Else             Esplora strTrovami, strChiamante         End If End Sub

I primi due enunciati assegnano due variabili già dichiarate come Public in testa al modulo: •• strTrovami contiene la stringa che si trova nella casella di testo Cercare •• strChiamante contiene il nome del pulsante di comando Trova Si noti l’uso della variabile oggetto Me, che identifica la maschera nella quale si trovano i due controlli Cercare e Trova. Seguono gli enunciati esecutivi, che sono due, in alternativa, gestiti con un costrutto If...Then...Else. I casi possono essere soltanto due: 1. la casella di testo Cercare è vuota oppure contiene il testo predefinito “Cognome da cercare”; 2. la casella di testo Cercare contiene una stringa che si suppone sia quella da utilizzare per la ricerca nel campo Cognome.

338  Capitolo 9

Nel primo caso, viene emessa una finestra di messaggio, segnalando che non c’è niente da cercare; nel secondo caso, viene chiamata la routine esecutiva Esplora, fornendole come parametri la stringa da cercare (strTrovami) e il nome del pulsante di comando (strChiamante). La routine evento per il pulsante di comando Ancora è perfettamente uguale a quella del pulsante di comando Trova: Private Sub Ancora_Click()         strTrovami = Me.Cercare.Value         strChiamante = Me.Ancora.Name         If IsNull(Cercare.Value) Or Cercare.Value = "Cognome da cercare" Then             MsgBox ("Non posso trovare quello che non hai scritto!"), vbOKOnly             Exit Sub         Else             Esplora strTrovami, strChiamante         End If End Sub

L’unica differenza sta nel fatto che viene assegnato alla variabile stringa strChiamante il nome del pulsante di comando Ancora. Fin qui, niente di speciale, sono tutti enunciati elementari, che abbiamo già passato in rassegna nel Capitolo 7. La parte interessante, e istruttiva, sta nella routine Esplora, quella che viene chiamata dalle due routine evento. Il compito di Esplora è quello di andare a cercare nel campo Cognome dei record della tabella sulla quale è aperta la maschera frmIndirizzi una stringa di caratteri uguale a quella che è stata scritta nella casella di testo Cercare. Per dare un senso a tutta questa operazione, la ricerca eseguita con la routine Esplora deve emulare, quanto a flessibilità, la ricerca che si può eseguire manualmente dall’interfaccia grafica attraverso la finestra di dialogo Trova.Vogliamo, quindi, che l’operatore possa trovare tutti i Rossi, Russo o Rossini digitando semplicemente r*ss, dove il carattere asterisco sta a indicare che in quella posizione si accetta qualunque carattere o gruppo di caratteri, per cui verrebbero reperiti anche Rimessa o Raissa. Ecco la routine Esplora. Sub Esplora(Trovalo As String, Chiamante As String)     strTrovami = Trovalo     strCriterio = "Cognome Like '*" & strTrovami & "*'"     Set rstMaschera = Forms![Indirizzi].RecordsetClone     Select Case Chiamante         Case "Trova"         rstMaschera.MoveFirst 'cerca dal primo         rstMaschera.FindFirst strCriterio ' ' ' '

Se trova una corrispondenza, sposta la maschera su quel record, se non lo trova, lo segnala. Per spostare la maschera attiva il metodo Bookmark del recordset

        If Not rstMaschera.NoMatch Then             Forms![Indirizzi].Bookmark = rstMaschera.Bookmark         Else

Lavorare con i DAO   339             MsgBox ("Non c'è"), vbOKOnly         End If ' Trovato o non trovato, torna al chiamante TrovaPrimo_Exit:     rstMaschera.Close     On Error GoTo 0     Exit Sub TrovaPrimo_Err:     MsgBox "Errore" & Err & ": " & Error$, "Trova primo"     Resume TrovaPrimo_Exit     Case "Ancora"         rstMaschera.Bookmark = Forms![Indirizzi].Bookmark         rstMaschera.FindNext strCriterio ' ' ' '

Sposta la maschera sul record usando la proprietà Bookmark che viene utilizzata per eventuali richieste di ricerca successive Se dopo il Bookmark non vi sono altri record corrispondenti lo segnala ed esce

        If Not rstMaschera.NoMatch Then             Forms![Indirizzi].Bookmark = rstMaschera.Bookmark         Else             MsgBox ("Non ve ne sono altri"), vbOKOnly     End If TrovaSuccessivo_Exit:     rstMaschera.Close     On Error GoTo 0     Exit Sub

La routine utilizza due parametri, chiamati Trovalo e Chiamante, che corrispondono alle due variabili stringa strTrovami e strChiamante che le vengono inviate dalle routine evento al momento della chiamata. Effettua successivamente due assegnazioni. La prima, molto semplice, fa corrispondere la variabile strTrovami al parametro Trovalo: strTrovami = Trovalo

La seconda assegnazione è un po’ più complessa. Alla variabile strCriterio viene assegnata una stringa composta da vari elementi, che è importante distinguere bene. L’enunciato ha questa forma: strCriterio = "Cognome Like ' *" & strTrovami & "*' "

Bisogna fare estrema attenzione al gioco delle virgolette semplici e di quelle doppie, perché, se non sono disposte esattamente nel modo qui indicato, la ricerca del record non darebbe mai alcun risultato. In sostanza, l’assegnazione prende il contenuto della casella di testo Cercare e vi mette in testa e in coda un carattere asterisco.

340  Capitolo 9

Fa poi precedere e seguire i due caratteri asterisco da un carattere virgoletta semplice e concatena la stringa così ottenuta a una stringa predefinita che dice Cognome Like, per cui, se il valore di strTrovami fosse r*ss, la stringa assegnata a strCriterio conterrebbe: Cognome Like ' *r*ss* '

Il risultato dell’assegnazione è un criterio di ricerca da utilizzare con uno dei metodi Find dell’oggetto Recordset. Il criterio dice: trova nel campo Cognome una stringa come (Like) quella che segue, con un numero di caratteri qualunque al posto di ciascun asterisco. Vedo una mano alzarsi in fondo all’aula. Come dice? “I metodi Find sono dell’oggetto Recordset, ma qui stiamo lavorando su una maschera, che non è un oggetto DAO, ma un oggetto Access”. Ha perfettamente ragione e la ringrazio. E aggiungo, per il piacere di avere l’ultima parola, che l’oggetto Form di Access non ha alcun metodo che ne consenta l’esplorazione, mentre l’oggetto Recordset ne ha addirittura due: i metodi Seek e Find. Come è possibile, allora, effettuare una ricerca in un campo mentre ci si trova su una maschera? Sfruttando una proprietà delle maschere che si chiama RecordsetClone e che fa idealmente da ponte fra gli oggetti Access e i DAO. Con la proprietà RecordsetClone si può far riferimento all’oggetto Recordset sottostante a una maschera, vale a dire quello specificato dalla proprietà Origine record della maschera. Di conseguenza, avendo dichiarato come tipo Recordset la variabile rstMaschera, possiamo assegnarla con l’enunciato Set rstMaschera = Forms![frmIndirizzi].RecordsetClone

in modo che rstMaschera rappresenti a tutti gli effetti il recordset della tabella tblIndirizzi, quella sulla quale agisce la maschera frmIndirizzi. In termini pratici, con RecordsetClone si crea una copia (un clone, appunto) dei record associati alla della maschera, e su questa copia si possono utilizzare tutti i metodi e le proprietà dell’oggetto Recordset, spostandosi in avanti o all’indietro, modificandolo o chiudendolo, senza che la maschera ne risenta. Fatta questa operazione, il resto viene da sé. Dopo aver assegnato le variabili, la routine Esplora sviluppa il suo lavoro con due gruppi di enunciati esecutivi, uno per trovare la prima occorrenza del record che contiene la stringa da cercare e l’altro per trovare le successive. Le due operazioni di ricerca sono gestite con un costrutto Select Case, basato sul valore del parametro Chiamante. Se Chiamante identifica la routine dell’evento Su clic del pulsante Trova, col metodo MoveFirst si porta sul primo elemento del RecordsetClone e da lì attiva il metodo FindFirst, che ha la seguente sintassi: recordset.FindFirst criteri

dove criteri è un’espressione o una stringa che specifica i criteri da utilizzare per la ricerca. Nel nostro caso, criteri è la stringa strCriterio che abbiamo descritto (ed è stata assegnata) prima. Se il metodo FindFirst non trova almeno un record che corrisponde a criteri, il recordset restituisce un valore True per la proprietà NoMatch, indicando che è arrivato fino in fondo senza trovare nulla.

Lavorare con i DAO   341

Il costrutto If...Then che viene subito dopo l’enunciato rstMaschera.FindFirst strCriterio

verifica il valore di NoMatch e, se non è True, mette un segnalibro sul record che è stato trovato per primo, usando la proprietà Bookmark dell’oggetto Recordset. Contestualmente, nello stesso enunciato, il segnalibro del RecordsetClone viene assegnato al segnalibro della maschera: Forms![Indirizzi].Bookmark = rstMaschera.Bookmark

Risultato: la maschera ora visualizza il record trovato nel RecordsetClone e non quello sul quale era posizionata. Se la proprietà NoMatch è True, con la clausola Else dello stesso costrutto If...Then si attiva una finestra di messaggio per informare l’utente che nessun record contiene nel campo Cognome la stringa che ha digitato nella casella di testo Cercare. Al termine del costrutto If...Then si chiude la prima clausola Case e il controllo ritorna alla routine che l’ha chiamata. La seconda clausola Case – quella innescata quando il parametro Chiamante corrisponde alla routine dell’evento Su clic del pulsante Ancora – fa lo stesso lavoro che abbiamo descritto per la prima clausola Case, soltanto che utilizza il metodo FindNext invece di FindFirst, cioè esegue la ricerca subito dopo la posizione che era stata marcata con un Bookmark. A ogni clic sul pulsante di comando Ancora riprende la ricerca, si fa lo scambio dei segnalibri e si visualizza il record trovato, fino a quando la proprietà NoMatch diventa True. A questo punto, una finestra di messaggio segnala che non vi sono più altre occorrenze della stringa cercata nel campo Cognome. Un’interessante alternativa al meccanismo illustrato in questo esempio consiste nel far ricorso al metodo BuildCriteria, che è uno dei metodi dell’oggetto Application. Con questo metodo si impostano i criteri per creare un filtro, criteri che si possono applicare a una maschera o a un foglio dati di una tabella. Proviamo a impostare una maschera simile a quella dell’esempio precedente, basata sulla stessa tabella di indirizzi. Nel piè di pagina della maschera inseriamo una casella di testo che chiamiamo DaCercare e inseriamo come proprietà Testo descrizione controllo la dicitura “Digitare alcune lettere del cognome e premere Invio”. A lato di questa casella di testo collochiamo un pulsante di comando che chiamiamo Tutti. La Figura 9.13 riproduce la nuova maschera dimostrativa. La routine evento associata all’aggiornamento della casella di testo DaCercare è la seguente: Private Sub DaCercare_AfterUpdate()     Dim strInput, strFiltro As String     If Not IsNull(Me.DaCercare) Then     'Verifica che la casella di testo Cercare non sia vuota, quindi         'prende il suo contenuto e lo inserisce nella     ' variabile strInput facendolo precedere     ' e seguire da un asterisco         strInput = "*" & Me.DaCercare.Value & "*"     End If     

342  Capitolo 9     'Utilizza il metodo BuildCriteria per costruire il criterio di filtro     strFiltro = BuildCriteria("Cognome", dbText, strInput)          'Assegna il filtro alla proprietà "Filtro" della maschera     Me.Filter = strFiltro          ' Rende attivo il filtro; adesso la maschera visualizza i record filtrati.     Me.FilterOn = True End Sub

Figura 9.13  La maschera Indirizzi modificata per una selezione in base a un filtro.

I commenti spiegano quello che accade. Tutto ruota intorno all’esecuzione del metodo BuildCriteria, che ha la seguente sintassi: applicazione. BuildCriteria(campo, tipocampo, espressione)

dove applicazione è facoltativo e si riferisce comunque all’oggetto Application (Access, nel nostro caso); campo è il campo della tabella sul quale si applica il criterio; tipocampo è una costante intrinseca DAO che specifica il tipo di dato del campo ed espressione è il criterio di confronto da utilizzare per filtrare i dati. Le costanti utilizzabili per tipocampo sono le seguenti: Costante dbBoolean dbByte dbCurrency

Descrizione Sì/no Numerico Byte Valuta

Lavorare con i DAO   343

dbDate dbDouble dbInteger dbLong dbLongBinary dbMemo dbSingle dbText

Data/Ora Numerico Precisione doppia Numerico Intero Numerico Intero lungo OggettoOLE Memo Numerico Precisione singola Testo

Dopo che l’operatore ha digitato nella casella di testo DaCercare qualche carattere del cognome che vuole cercare e ha premuto Invio, il testo viene racchiuso fra due asterischi e impostato come argomento espressione di BuildCriteria, che già contiene Cognome come argomento campo e dbText come argomento tipocampo. L’esecuzione del metodo BuildCriteria, come lascia supporre il suo nome, genera un criterio di selezione, che viene successivamente attribuito alla proprietà Filtro della maschera. Il fatto che una maschera abbia un valore impostato nella sua proprietà Filtro di per sé non ha alcuna conseguenza. Diventa rilevante se si attiva la proprietà FilterOn, il che equivale a eseguire il comando “Applica filtro” dall’interfaccia grafica. Se i caratteri immessi in input si trovano in qualche punto del campo Cognome di qualche record, questi record vengono filtrati e gli altri nascosti. L’operatore può quindi scorrere i record filtrati ed esaminarli. Per eliminare il filtro, un clic sul pulsante di comando Tutti (che porta la dicitura “Tutti i cognomi”) attiva la seguente routine evento: Private Sub Tutti_Click()     'Elimina il filtro     DoCmd.ShowAllRecords     'Svuota la casella di testo     Me.DaCercare.Value = ""     'Rende attivo il criterio di ordinamento predisposto     'nella proprietà "Ordina per" della maschera Me.OrderByOn = True End Sub

Questa routine evento elimina il filtro, visualizzando tutti i record; svuota la casella di testo DaCercare per consentire l’eventuale immissione di un nuovo criterio di filtro e dispone nell’ordine predefinito i record della maschera. Non per falsa modestia, ma in tutta sincerità, le routine che abbiamo appena descritto non sono niente di eccezionale, si potrebbero ottenere gli stessi risultati con routine più raffinate, ma già queste possono costituire un primo nucleo di un’applicazione di un certo interesse.

Oggetti Access e oggetti DAO Gli esempi che abbiamo descritto nel paragrafo precedente avevano lo scopo di mostrare qualche caso concreto di uso combinato di oggetti DAO e oggetti Access. Le maschere, i report e i controlli che questi possono contenere sono oggetti Access, simili strutturalmente ai DAO, in quanto fanno parte di insiemi e hanno metodi e proprietà.

344  Capitolo 9

Esiste, però, una differenza fondamentale fra le due famiglie: si può lavorare con VBA anche su DAO che si trovano in un database non aperto (o addirittura in un database non Access), mentre si può utilizzare il VBA soltanto sugli oggetti Access del database corrente. Inoltre, gran parte dei metodi e delle proprietà degli oggetti Access sono accessibili con VBA soltanto se gli oggetti sono aperti, oltre a trovarsi nel database corrente. Analogamente ai vari metodi Create disponibili per i DAO e che abbiamo visto all’opera nel paragrafo “Il backup dei dati essenziali”, esistono funzioni (non metodi) Create che restituiscono oggetti Access. Le riepiloghiamo nella Tabella 9.5. Tabella 9.5  Funzioni per creare oggetti Access. Funzione CreateForm CreateControl CreateReport CreateReportControl CreateGroupLevel

Descrizione Restituisce un oggetto Form Restituisce un oggetto Control su una maschera specificata e aperta in visualizzazione Struttura Restituisce un oggetto Report Restituisce un oggetto Control su un report specificato e aperto in visualizzazione Struttura Permette di creare un nuovo livello di gruppo in un report specificato e aperto in visualizzazione Struttura

L’esempio di codice che segue mostra come sia possibile creare una maschera con VBA. Sub NuovaMaschera()     Dim frmNuovaMaschera As Form, strMaschera As String     ' Crea nuova maschera.     Set frmNuovaMaschera = CreateForm     ' Imposta proprietà della maschera.     With frmNuovaMaschera         .RecordSource = "Prodotti"         .Caption = "Maschera prodotti"         .ScrollBars = 0         .NavigationButtons = True         ' Acquisisce il nome generico assegnato alla nuova maschera         strMaschera = .Name     End With     ' Salva la maschera e la chiude.     DoCmd.Save acForm, strMaschera     DoCmd.Close acForm, strMaschera     ' Cambia il nome generico della nuova maschera appena creata     DoCmd.CopyObject , "Maschera prodotti", acForm, strMaschera     DoCmd.DeleteObject acForm, strMaschera End Sub

La routine si apre usando la funzione CreateForm in riferimento a una variabile dichiarata come tipo oggetto Form. Con un costrutto With...End With si impostano alcune proprietà per la nuova maschera. Nello stesso costrutto, l’enunciato strMaschera = .Name

Lavorare con i DAO   345

ha il compito di acquisire (invece di impostare) la proprietà Name della nuova maschera. Questo nome non è noto a priori, perché cambia ogni volta che si usa la funzione CreateForm, che attribuisce un nome generico alla maschera nel momento in cui la crea. Dopo il costrutto With...End With, due enunciati attivano: •• prima il metodo Save dell’oggetto DoCmd per salvare la maschera col suo nome generico; •• poi il metodo Close per chiuderla. I due enunciati conclusivi ricorrono ancora all’oggetto DoCmd per assegnare alla nuova maschera un nome definito dall’utente (“Maschera prodotti”, in questo esempio). L’operazione si fa con due passaggi: 1. col metodo CopyObject si fa una copia della maschera, dandole il nome scelto dall’utente; 2. col metodo DeleteObject si elimina la maschera che era stata salvata col nome generico, lasciando nel database soltanto la maschera col nome definito dall’utente. È necessario ricorrere a questo doppio passaggio, perché la proprietà Name di una maschera è modificabile soltanto dalla finestra Database, mentre da VBA è accessibile soltanto in lettura, quindi non c’è modo di cambiare questa proprietà da programma se non aggirando il vincolo con la creazione di una copia col nome desiderato. La maschera ottenuta con la routine che abbiamo appena descritto è associata alla tabella Prodotti, ma non visualizza nulla, perché non abbiamo creato i necessari controlli (caselle di testo) associati ai singoli campi dei record della tabella: per farlo avremmo dovuto utilizzare la funzione CreateControl, ma non valeva la pena di fare tutto quel lavoro, visto che non intendevamo utilizzare la maschera, ma semplicemente mostrare come se ne può costruire una con VBA. Sebbene sia possibile, sia pure con qualche equilibrismo, creare e manipolare oggetti database Access con il VBA, è relativamente inconsueto ricorrere a tecniche di programmazione per farlo: in un’applicazione professionale è poco probabile che occorra far nascere automaticamente una maschera o un report oppure qualche controllo nuovo su maschere o report già definiti nell’applicazione. Esiste, tuttavia, un’area di sviluppo applicativo nella quale si fa ampio utilizzo delle tecniche di programmazione per generare oggetti database Access, ed è quella della creazione di Composizioni guidate, ma si tratta di un contesto che va al di là degli argomenti che abbiamo deciso di trattare in questo libro.

Capitolo 10

Lavorare con gli ADO

Come sappiamo dalla breve panoramica degli oggetti in Access presentata nel Capitolo 5, per accedere ai dati Access 95 e 97 si affidavano ai servizi del Microsoft Jet Database Engine e ai suoi Data Access Objects (DAO). Anche se, a partire da Access 2000, per le stesse funzionalità si sono resi disponibili gli ActiveX Data Objects, in sigla ADO, i DAO hanno continuato a essere supportati nelle versioni successive di Access, con la sola differenza che in Access 2010 le funzionalità dei DAO sono assicurate da uno strumento chiamato Access Database Engine (ACE), che fa esattamente le stesse cose del motore Jet 4.0. Microsoft punta molto sugli ADO, che sono disponibili non soltanto nelle versioni di Access dalla 2000 in poi, ma in tutta la gamma dei prodotti software per lo sviluppo offerti sul mercato da Microsoft. Per qualche anno, diciamo dal 2000 al 2008, Microsoft ha fatto sapere di avere l’intenzione di abbandonare progressivamente i DAO per fare degli ADO la struttura portante di tutti i suoi prodotti, dagli applicativi tipo Office ai linguaggi di programmazione qualiVisual C++,Visual Basic .NET e tutto il resto. In tempi più recenti, il primato degli ADO sui DAO non sembra più essere l’indirizzo strategico prevalente, almeno per quanto riguarda Access, che nella versione 2010 utilizza per impostazione predefinita i DAO, messi a disposizione dal motore ACE direttamente integrato in Windows, mentre per utilizzare gli ADO, sempre in Access 2010, è necessario predisporre il loro richiamo nelle Referenze dell’Editor di Visual Basic (si veda qui avanti, il paragrafo “Utilizzare gli ADO”).

In questo capitolo •

L’universo dei dati e i dati universali



Utilizzare gli ADO



Come ottenere gli stessi risultati con DAO e con ADO



Gli ADO e il futuro di Access

348  Capitolo 10

L’universo dei dati e i dati universali Tutte le comunità hanno i loro miti e quella degli informatici non è da meno delle altre. Con una differenza, che talvolta i suoi miti si trasformano in realtà. Bill Gates aveva solo diciotto anni quando cominciò a favoleggiare di un computer su ogni scrivania, ma quello che sembrava allora soltanto un suo mito personale è diventato realtà in poco più di vent’anni. Un altro obiettivo mitico appassiona da sempre gli informatici tutti, e non solo il fondatore della Microsoft Corporation: mettere a punto un meccanismo software che consenta di accedere a tutti i dati possibili, comunque siano organizzati. Il mito ha un nome, deliberatamente ambiguo: si chiama Universal Data Access, come a dire accesso universale ai dati e, al tempo stesso, accesso ai dati universali. Intorno all’idea di Universal Data Access lavorano con impegno migliaia di persone, non soltanto in Microsoft, animate non da una passione filosofica ispirata a Gottfried W. Leibniz o a Raimondo Lullo, ma spinte da una forza potentissima: quella del denaro. È infatti convinzione di tutte le persone impegnate in questa impresa che la società di software che arriverà per prima a risolvere in modo pratico il problema dell’Universal Data Access dominerà incontrastata tutto lo sterminato mondo dei dati, che va dai grandi mainframe ai piccoli computer palmari, passando per i personal computer, le macchine dipartimentali, i telefoni cellulari e i satelliti che irradiano filmati in forma di flussi digitali. Sintetizzato al massimo, il piano della Microsoft per la realizzazione dell’Universal Data Access si presenta come nello schema che si vede nella Figura 10.1.

Figura 10.1  Il progetto Microsoft per l’Universal Data Access.

Lavorare con gli ADO    349

I tre elementi rappresentati con un riquadro ombreggiato nella figura formano oggi il nucleo dell’Universal Data Access. Interagendo fra loro, possono già consentire a qualsiasi applicazione o anche a un browser, di accedere a dati delle più diverse tipologie. La collocazione degli ADO nello schema spiega l’importanza che Microsoft attribuisce a questi strumenti. Dal momento che in questo libro ci occupiamo di Access e non di Universal Data Access, ci concentreremo sugli ADO, limitandoci a spiegare come sono fatti e come si possono utilizzare.

Il modello degli ADO Il modello DAO fornisce tutti i possibili servizi per l’accesso ai dati. In questa ecumenicità vi sono un elemento di forza e uno di debolezza. Con i DAO si può fare tutto (forza), ma per eseguire anche un’operazione semplicissima bisogna dispiegare ogni volta l’intero assortimento dei DAO (debolezza). Muovendo da questa elementare considerazione, i progettisti software di Microsoft hanno deciso di scomporre le funzionalità DAO in tre famiglie distinte, che insieme formano gli ADO. Abbiamo così tre modelli a oggetti, che si chiamano: •• Microsoft ActiveX Data Objects (ADO in senso stretto), •• Microsoft ADO Extensions for DDL and Security (ADOX), •• Microsoft Jet and Replication Objects (JRO). L’idea di base è quella di consentire l’uso di un solo strumento per volta, più leggero e meno ingombrante, per ottenere i risultati specifici che si vogliono avere da un’applicazione. Una prima conseguenza di questa impostazione è che i tre modelli – ADO, ADOX e JRO – sono più snelli e lineari rispetto al poderoso e intricato modello DAO. Come si può vedere dalla Figura 10.2, per esempio, gli ADO sono articolati in soli sei oggetti di base. Specializzati per la sola manipolazione dei dati, gli oggetti ADO derivano gerarchicamente dall’oggetto Connection, che definisce una sessione utente con un’origine dei dati. In DAO, invece, è l’oggetto Workspace che definisce la sessione per un utente, mentre l’origine dei dati è identificata dall’oggetto Database. Da Connection dipendono gli oggetti ADO Command e Recordset. Il primo, Command, è all’incirca l’equivalente dell’oggetto QueryDef, perché, alla pari di questo oggetto DAO, consente l’esecuzione di enunciati SQL su una fonte di dati. Analogamente, l’oggetto Recordset – sia in DAO sia in ADO – serve per visualizzare il contenuto di una tabella o i risultati di un enunciato SQL. L’oggetto Record è sostanzialmente lo stesso nei due modelli. Le cose si complicano alquanto, come del resto è inevitabile, quando si passa agli ADOX, attraverso i quali si definiscono le strutture dei dati (Data Definition Language: DDL) e si impostano le condizioni per la sicurezza. (Figura 10.3).

350  Capitolo 10

Figura 10.2  Il modello a oggetti ADO.

Figura 10.3  Il modello a oggetti ADOX.

Lavorare con gli ADO    351

Al vertice della gerarchia troviamo l’oggetto Catalog, che è il contenitore degli insiemi per la definizione dei dati (Tables, Procedures e Views) e degli insiemi per la sicurezza (Users e Groups). Nei DAO, queste funzionalità sono suddivise fra l’oggetto Database, che è il contenitore degli insiemi per la definizione dei dati, e l’oggetto Workspace, che contiene gli insiemi per la sicurezza. Inoltre, mentre un Workspace DAO può contenere più insiemi Databases, ciascun oggetto Catalog è associato a un solo oggetto Connection ADO. Il che semplifica parecchio le cose, se solo ci si pensa un po’. Sia gli oggetti ADO sia gli ADOX dispongono di un insieme standard denominato Properties, che contiene oggetti Property (Figura 10.4).

Figura 10.4  Gli ADO e gli ADOX hanno anche insiemi Properties.

L’ultimo componente della famiglia è il JRO, che serve per gestire le repliche dei database Access. Diversamente dagli altri due, è limitato ai soli database Access come origine dei dati e qui non ne parliamo, perché le repliche non sono più gestite da Access 2010.

Utilizzare gli ADO In quanto software, gli oggetti DAO e ADO si materializzano in file che appartengono alla categoria delle object library, chiamate librerie di oggetti nel gergo informatico ita-

352  Capitolo 10

liano. Nel momento in cui in un file Access si crea un modulo nuovo o se ne apre uno esistente, vengono predisposti alcuni riferimenti a determinate librerie di oggetti, che servono per utilizzare gli oggetti con codice di programma. Questa impostazione si può esaminare ed eventualmente modificare scegliendo il comando Riferimenti dal menu Strumenti quando ci si trova nell’Editor di Visual Basic. La finestra di dialogo Riferimenti contiene una casella di riepilogo intitolata Riferimenti disponibili, nella quale sono elencate tutte le possibili funzionalità, complementari o di base, che sono presenti sotto forma di file di libreria (estensioni .dll, .olb e altre) nel computer sul quale si lavora. Le librerie sono identificate in questa casella con un nome esplicativo, più o meno lungo, a lato del quale c’è una casella di controllo: se la si spunta, il riferimento diventa attivo e la libreria può essere utilizzata dai moduli VBA della sessione Access in corso. Quando un elemento è selezionato nella casella di riepilogo Riferimenti disponibili, compare in un riquadro in basso il nome effettivo del file, col suo percorso. In Access 2010 la finestra di dialogo Riferimenti dell’Editor di Visual Basic presenta normalmente come attivi i primi quattro riferimenti che si possono vedere nella Figura 10.5.

Figura 10.5  I riferimenti alle librerie di oggetti in Access 2010.

Per impostazione predefinita, nelle versioni di Access 2000/2002/2003 è attivato soltanto il riferimento alla libreria di oggetti ADO, per cui, per usare i DAO, bisogna attivare anche il riferimento alla Microsoft DAO 3.6 Object Library altrimenti al primo richiamo a un qualunque oggetto DAO si riceve una brusca segnalazione di errore di compilazione, prima ancora di poter eseguire la routine (Figura 10.6).

Lavorare con gli ADO    353

Figura 10.6  Se una libreria di oggetti non è stata attivata nei Riferimenti si verificano errori di compilazione.

In Access 2010, invece, la situazione di partenza è stata ribaltata: sono i DAO a essere attivati per impostazione predefinita quando si crea un nuovo modulo per sviluppare routine VBA, per cui si riceve lo stesso avviso di errore di compilazione se si cerca di eseguire una routine che utilizza gli ADO senza aver prima attivato nell’elenco Riferimenti disponibili la voce indicata col nome Microsoft ActiveX Data Objects 2.8 Library, che è la libreria di oggetti principale degli ADO. Si può ottenere con una routine VBA un elenco delle librerie di tipi attivate, facendo riferimento all’insieme References che appartiene all’oggetto Application: ecco un esempio. Sub StatoRiferimenti()     Dim ref As Reference, lngConteggio As Long     lngConteggio = Application.References.Count     Debug.Print "Riferimenti attivati: ", lngConteggio     'Percorre l'insieme References ed enumera i riferimenti.     For Each ref In References         ' Controlla la proprietà IsBroken.         If ref.IsBroken = False Then             Debug.Print "Nome: ", ref.Name             Debug.Print "Percorso: ", ref.FullPath             Debug.Print "Versione: ", ref.Major & "." & ref.Minor             Debug.Print "Essenziale: ", ref.BuiltIn         Else             Debug.Print "GUID dei riferimenti interrotti:"             Debug.Print ref.Guid         End If         Debug.Print     Next ref End Sub

L’insieme References contiene oggetti Reference, che corrispondono ai riferimenti attivati nella finestra di dialogo Riferimenti. La routine imposta una variabile chiamata ref assegnandole il tipo Reference, in modo da poter far riferimento agli oggetti contenuti in References. Imposta anche una variabile lngConteggio sul tipo dati Long, per acquisire il valore numerico che estrae dalla proprietà Count dell’insieme References e quindi stampa nella finestra Immediata dell’Editor di Visual Basic il risultato ottenuto, insieme con una scritta descrittiva.

354  Capitolo 10

Successivamente, con un ciclo For Each percorre tutti gli oggetti Reference contenuti nell’insieme References, chiedendosi per ciascuno se sia impostata la sua proprietà IsBroken: questa proprietà, come il suo nome lascia capire, è impostata su True se il riferimento per una qualche ragione è difettoso, cioè rimanda a un file di libreria che non è presente nel computer; se le cose stanno così, si limita a stampare il codice GUID del riferimento interrotto. Se il riferimento non è interrotto, ricava le sue proprietà Name, FullPath, Major, Minor e BuiltIn, stampando nella finestra Immediata un elenco che potrebbe presentarsi come quello che vediamo qui di seguito: Riferimenti attivati: 6 Nome: VBA Percorso: C:\PROGRA~1\COMMON~1\MICROS~1\VBA\VBA7\VBE7.DLL Versione: 4.1 Categoria: Libreria tipi Essenziale: Vero Nome: Percorso: Versione: Categoria: Essenziale:

Access C:\Program Files\Microsoft Office\Office14\MSACC.OLB 9.0 Libreria tipi Vero

Nome: Percorso: Versione: Categoria: Essenziale:

stdole C:\Windows\system32\stdole2.tlb 2.0 Libreria tipi Falso

Nome: Percorso: Versione: Categoria: Essenziale:

DAO C:\Program Files\Common Files\Microsoft Shared\OFFICE14\ACEDAO.DLL 12.0 Libreria tipi Falso

Nome: Percorso: Versione: Categoria: Essenziale:

ADODB C:\Program Files\Common Files\System\ado\msado28.tlb 2.8 Libreria tipi Falso

Nome: Percorso: Versione: Categoria: Essenziale:

ADOX C:\Program Files\Common Files\System\ado\msadox.dll 6.0 Libreria tipi Falso

La routine presenta in italiano i valori di alcune proprietà di ciascun oggetto Reference, rendendo Name con Nome, FullPath con Percorso, Major e Minor con Versione. La proprietà Built-In può assumere due valori, True se l’oggetto è essenziale per il corretto funzionamento di Access e False se non è essenziale. Come dimostra il risultato della routine dimostrativa, per utilizzare le funzionalità VBA di Access 2010 sono indispensabili soltanto due librerie dei tipi (mentre nelle versioni precedenti ne occorrevano almeno tre).

Lavorare con gli ADO    355

Primo trovato, primo servito La finestra di dialogo Riferimenti contiene due pulsanti di comando, contraddistinti uno da una freccia che punta verso l’alto e l’altro da una freccia che punta verso il basso. In mezzo c’è la dicitura Priorità. Quando una libreria è selezionata nella casella di riepilogo Riferimenti disponibili, agendo su quei pulsanti si può spostare il riferimento verso l’alto o verso il basso per modificare l’ordine col quale vi accedono le routine VBA. Questo spostamento non risponde a esigenze estetiche, ma serve a stabilire la priorità del riferimento. Dal momento che molti oggetti DAO e ADO hanno lo stesso nome, se il codice di programma che utilizza l’oggetto Recordset, per esempio, trova prima il riferimento ADO, considera che si tratti di un Recordset ADO, se trova prima il riferimento DAO lo interpreta come un Recordset DAO. Per non perdere tempo a inseguire messaggi di errore di compilazione apparentemente inspiegabili, conviene sempre qualificare i riferimenti, quando si sviluppano routine VBA usando sia ADO sia DAO, per cui, invece di dichiarare: Dim rst As Recordset

è opportuno, a seconda dei casi, essere più espliciti e dichiarare: Dim rstADO As ADODB.Recordset Dim rstDAO As DAO.Recordset

tenendo presente che la parola chiave per identificare gli ADO è ADODB. Gli esperti americani di Access, che hanno un nome per ogni cosa, chiamano questo modo di operare disambiguation. Tremiamo al pensiero di come potrebbero rendere questo termine certi traduttori italiani.

Fornitori e consumatori di origini dei dati Nel contesto generale dal quale sono nati gli ADO, quello dell’Universal Data Access, si indica col nome generico di origine (o fonte) dei dati (in inglese Data Source) qualunque aggregato di dati comunque accessibile su un computer. A una Data Source si accede mediante un meccanismo attivo, un programma di sistema o un’applicazione, che fa da tramite e per questa ragione si chiama fornitore (in inglese Provider). L’applicazione che utilizza i servizi di un Provider per accedere a una Data Source prende il nome di consumatore o Consumer. Talvolta viene detta anche client. Si veda la Figura 10.7. Dal punto di vista ADO, un database Access è una Data Source, che può essere fornita dal provider Microsoft Jet OLE DB 4.0, se il file database ha il formato delle versioni di Access che precedono la 2007 (quindi estensione .mdb) o dal provider Microsoft Office 12.0 Access Database Engine OLE DB, quando si lavora con file dell’ultima versione di Access, quindi con l’estensione .accdb. Un riferimento esplicito al nome del provider deve comparire negli enunciati VBA usati per lavorare con gli ADO, rendendo certi enunciati più prolissi di quelli equivalenti, un po’ più compatti, che si è soliti utilizzare quando si lavora con i DAO. Niente di drammatico, basta solo farci la mano.

356  Capitolo 10

Figura 10.7  I ruoli per l’accesso ai dati.

La stringa di connessione Per poter lavorare con VBA sugli ADO dobbiamo in primo luogo far loro sapere con quale database devono connettersi. Questa informazione viene data impostando un valore per la proprietà ConnectionString del capostipite degli ADO, che si chiama Connection. Se una routine VBA deve lavorare sul database corrente, la connessione si imposta in questo modo: Dim cnn As ADODB.Connection Set cnn = CurrentProject.Connection

Se, invece, vogliamo lavorare con VBA su un database Access diverso da quello corrente, per esempio un database chiamato Clienti.accdb che sta nella cartella \Esempi\Gestione del disco C, dovremo trasmettere questa informazione all’oggetto Connection, operazione che si può fare in molti modi diversi, il più semplice dei quali è il seguente: Dim cnn As ADODB.Connection Set cnn = New ADODB.Connection cnn.ConnectionString = Provider= _     "Microsoft.ACE.OLE DB.12.0= C:\Esempi\Gestione\Clienti.accdb;"

Lavorare con gli ADO    357

Il valore che col terzo enunciato si assegna alla proprietà ConnectionString si chiama stringa di connessione ed è articolato in un certo numero di coppie proprietà=valore, separate da un punto e virgola. Queste coppie di stringhe possono essere molto numerose (da venti a trenta, in alcuni casi), ma, per fortuna, per lavorare mediante gli ADO su un database Access basta indicarne soltanto due nella stringa di connessione e precisamente: Provider=Microsoft.ACE.OLEDB.12.0

che informa gli ADO che il Provider dei dati è il motore per database Microsoft Office 12.0 Access Database Engine e Data Source= C:\Esempi\Gestione\Clienti.accdb;

che indica il nome, completo di percorso, della Data Source, cioè del database in formato Access 2007 al quale si intende accedere. Se, nello stesso percorso, avessimo un file database in una versione precedente di Access, chiamato quindi Clienti.mdb, la stringa di connessione dovrà essere definita in questo modo: cnn.ConnectionString = Provider= _     "Microsoft.Jet 4.0= C:\Esempi\Gestione\Clienti.mdb;"

Sulla base della prima coppia di valori Provider=nomeprovider;

viene generata automaticamente la stringa di connessione vera e propria, che contiene una serie di coppie proprietà=valore, ciascuna impostata su un valore predefinito. Qui di seguito vediamo l’imponente lista contenuta nella proprietà ConnectionString dell’oggetto Connection impostato nell’esempio che stiamo esaminando. Provider=Microsoft.ACE.OLEDB.12.0; User ID=Admin; Data Source= C:\Esempi\Gestione\Clienti.accdb; Mode=Share Deny None; Jet OLEDB:System database=""; Jet OLEDB:Registry Path=""; Jet OLEDB:Database Password=""; Jet OLEDB:Engine Type=6; Jet OLEDB:Database Locking Mode=1; Jet OLEDB:Global Partial Bulk Ops=2; Jet OLEDB:Global Bulk Transactions=1; Jet OLEDB:New Database Password=""; Jet OLEDB:Create System Database=False; Jet OLEDB:Encrypt Database=False; Jet OLEDB:Don't Copy Locale on Compact=False; Jet OLEDB:Compact Without Replica Repair=False; Jet OLEDB:SFP=False; Jet OLEDB:Support Complex Data=False;

358  Capitolo 10

È disponibile un meccanismo semi automatico per generare una stringa di connessione da Windows, senza passare per Access. Si può poi richiamare da una routine VBA la stringa generata in questo modo, tenendola a disposizione per tutte le routine che potrebbero averne bisogno. Il meccanismo funziona in questo modo: 1. si apre Esplora risorse di Windows e si fa clic col pulsante destro del mouse in una zona libera della cartella nella quale si vuole creare la stringa di connessione; 2. dal menu contestuale che si apre si sceglie il comando Nuovo e successivamente Documento di testo; 3. si attribuisce al nuovo documento un titolo facile da ricordare, per esempio ConnessioneClienti, e si sostituisce l’estensione predefinita .txt con l’estensione .udl; 4. si risponde Sì alla finestra di messaggio di Windows che chiede conferma del cambio di estensione e si ottiene un file vuoto che si chiama, nell’esempio, ConnessioneClienti. udl ed è contraddistinto da un’icona particolare, diversa da quella normalmente associata ai file di testo; 5. si fa un doppio clic sul nuovo file, provocando l’apertura di una finestra di dialogo articolata in tre schede come quella che si vede nella Figura 10.8.

Figura 10.8  La finestra di dialogo Proprietà di Data Link.

Nella scheda Provider della finestra di dialogo Proprietà di Data Link compare un elenco di tutti i fornitori di dati disponibili sul computer col quale si lavora (questo elenco,

Lavorare con gli ADO    359

ovviamente, varia da macchina a macchina, in funzione del software applicativo e di sistema che vi è installato). Se si intende lavorare con un database Access 2007, si seleziona la voce Microsoft Office 12.0 Access Database Engine OLE DB Provider e quindi si fa clic sul pulsante Avanti, che porta in primo piano la scheda Connessione della finestra di dialogo. In questa scheda una casella di testo associata alla dicitura Inserire l’origine dati e il relativo percorso serve per contenere il nome, completo di percorso, del file Access 2007 da connettere. È meglio lasciare invariate le altre opzioni che compaiono predefinite, mentre può essere opportuno premere il pulsante Verifica connessione: dopo qualche istante, si viene gratificati dalla comparsa della finestra di messaggio che vediamo nella Figura 10.9, che conferma la correttezza della connessione.

Figura 10.9  Nella scheda Connessione si imposta la connessione e se ne può verificare il buon funzionamento.

A questo punto la connessione necessaria per lavorare con gli ADO è già pronta: volendo, si possono aprire le altre due schede – Avanzate e Tutte le proprietà – della finestra di dialogo Proprietà di Data Link per aggiungere, eliminare o modificare le proprietà disponibili per quella connessione.

360  Capitolo 10

Il nome del file ConnessioneClienti.udl può essere utilizzato in una routine VBA per definire la stringa di connessione di un oggetto Connection in questo modo: Dim cnn As ADODB.Connection Set cnn = New ADODB.Connection Dim strConnessione As String cnn.ConnectionString = "File Name=" _     & "C:\Esempi\ConnessioneClienti.udl"

Per effetto dell’assegnazione effettuata nell’ultimo enunciato, la proprietà nella stringa di connessione diventa:

Data Source

Data Source=C:\Esempi\Gestione\Clienti.accdb

cioè assume il valore che è stato impostato nella scheda Connessione della finestra di dialogo Proprietà di Data Link (si veda sopra, la Figura 10.9), per cui un successivo enunciato cnn.Open

senza ulteriori indicazioni, fa aprire il database Clienti.accdb dalla cartella Esempi\Gestione del disco C. Se in Windows si torna ad aprire il file ConnessioneClienti.udl facendo così comparire la finestra di dialogo Proprietà di Data Link e si modifica la connessione contenuta nel file, riferendola, per esempio, al database Fornitori.accdb, la stessa routine VBA aprirà il database Fornitori.accdb, senza che si debba modificare il codice VBA. Prestare particolare attenzione nel digitare le parole chiave che formano la prima parte della stringa concatenata con la quale si designa il contenuto della stringa di connessione: bisogna scrivere esattamente "File Name="

senza inserire spazi prima e dopo il segno di uguale e usando correttamente maiuscole e minuscole, altrimenti si generano misteriosi e apparentemente incomprensibili errori di run-time. Nella seconda parte della stringa concatenata è inoltre necessario specificare l’intero percorso del file .udl, altrimenti il VBA lo va a cercare nel percorso del database corrente e, non trovandolo, segnala anche in questo caso un errore di run-time.

Come ottenere gli stessi risultati con DAO e con ADO Il resto del capitolo è dedicato a una rassegna degli oggetti ADO e ADOX. Per non ridurre tutto a un’unica, gigantesca e terrificante tabella, abbiamo creato una serie di esempi a coppie, per dimostrare in che modo si possa ottenere lo stesso risultato con i DAO e con gli ADO. Per poter utilizzare gli esempi con Access 2010 bisogna predisporre una cartella \Esempi nel disco C, creare in questa cartella un nuovo file Access, chiamandolo Nwin.accdb, e quindi importarvi tutti gli oggetti contenuti nel vecchio file dimostrativo Northwind.mdb (che è disponibile nel CD-ROM allegato al libro).

Lavorare con gli ADO    361

Gli esempi sono raggruppati secondo le operazioni che capita più spesso di dover eseguire con un’applicazione gestionale. Ciascun esempio dimostra se e in che modo gli oggetti, i metodi e le proprietà ADO si differenziano dagli equivalenti DAO. Non tutto è specificato, però: per avere tutti i particolari si dovrà consultare la Guida di Access, che risulterà, speriamo, meno impenetrabile del solito dopo la lettura delle nostre spiegazioni.

Aprire un database La prima operazione che normalmente si deve eseguire all’avvio di un’applicazione è l’apertura di un database. Ecco due esempi per ottenere lo stesso risultato:

Routine DAO Sub DAOApreDatabaseJet()     Dim db As DAO.Database     Set db = DBEngine.OpenDatabase ("C:\Esempi\Nwin.accdb")     db.Close End Sub

Routine ADO Sub ADOApreDatabaseJet()     Dim cnn As New ADODB.Connection     cnn.Open "Provider=Microsoft.ACE.OLEDB.12.0;" _         & "Data Source=C:\Esempi\Nwin.accdb;"     cnn.Close End Sub

La differenza più marcata fra i due esempi di codice è l’uso dell’oggetto ADO Connection invece dell’oggetto DAO DBEngine. Mentre, però, la routine DAO produce lo stesso risultato anche se si omette il riferimento iniziale a DBEngine (che è implicito nella dichiarazione della variabile db come DAO.Database), nel caso della routine ADO il mancato riferimento a una connessione esplicitamente dichiarata genera un errore di run-time. Quando si prevede di utilizzare più volte la stessa stringa di connessione per più routine VBA in uno stesso modulo standard, può essere opportuno impostarla come costante pubblica di tipo String, nel modo seguente: Public Const Connessione As String = _"Provider=Microsoft.ACE.OLEDB.12.0;" _     & "Data Source=C:\Esempi\Nwin.accdb;"

La routine precedente potrebbe quindi essere scritta in questo modo : Sub ADOApreDatabaseJet1()     Dim cnn As ADODB.Connection     Set cnn = New ADODB.Connection     cnn.Open Connessione     cnn.Close End Sub

362  Capitolo 10

Per impostazione predefinita, un database viene aperto, sia con ADO sia con DAO, in modalità condivisa e aggiornabile (lettura/scrittura). Quando fosse necessario porre un vincolo di sola lettura, pur mantenendo la condivisione, si possono costruire routine di questo tipo:

Routine DAO Sub DAOApreDatabaseJetSolaLettura()     Dim db As DAO.Database     ' Apre in modalità condivisa e di sola lettura.     Set db = OpenDatabase("C:\Esempi\Nwin.accdb", False, True)     db.Close End Sub

Routine ADO Sub ADOApreDatabaseSolaLettura()     Dim cnn As New ADODB.Connection     ' Apre in modalità condivisa e di sola lettura     cnn.Mode = adModeRead     cnn.Open Connessione     cnn.Close End Sub

In DAO, il secondo e il terzo parametro del metodo OpenDatabase indicano rispettivamente l’accesso in modalità esclusiva e in sola lettura. Con ADO si usa la proprietà Mode dell’oggetto Connection, impostandola sulla costante intrinseca ADO adModeRead, che indica la sola lettura. L’apertura avviene di norma in modalità condivisa, a meno che venga espressamente dichiarata la costante adModeShareExclusive, per impostare l’accesso esclusivo. Nei capitoli precedenti abbiamo usato molto spesso negli esempi la funzione CurrentDb per indicare un riferimento al database attualmente aperto. Questa funzione esiste soltanto nei DAO, mentre per gli ADO si deve operare a livello dell’oggetto Connection. Ecco la differenza:

Routine DAO Sub DAORiferimentoAlDatabaseCorrente()     Dim db As DAO.Database     Set db = CurrentDb() End Sub

Routine ADO Sub ADORiferimentoAlDatabaseCorrente()     Dim cnn As ADODB.Connection     Set cnn = CurrentProject.Connection End Sub

Lavorare con gli ADO    363

In ADO si deve impostare la connessione sulla proprietà Connection dell’oggetto CurrentProject, che identifica (manco a dirlo) il progetto (ovvero il database) corrente.

Reperire e modificare dati Il grosso del lavoro sui dati si esegue, con i DAO come con gli ADO, mediante l’oggetto Recordset, che rappresenta, in entrambi i contesti, un insieme di record contenuti in una tabella oppure ottenuti con una query. L’oggetto Recordset contiene un insieme Fields, formato da oggetti Field, ciascuno dei quali rappresenta una singola colonna di dati nel Recordset. Per aprire un Recordset ADO si può usare: •• il metodo Execute dell’oggetto Connection, •• il metodo Execute dell’oggetto Command, •• il metodo Open dell’oggetto Recordset. Gli oggetti Recordset ADO aperti col metodo Execute sono sempre di tipo forward-only e di sola lettura, equivalgono ai Recordset DAO di tipo snapshot. Per poter scorrere nelle due direzioni un recordset e modificarne i dati, bisogna aprirlo col metodo Open. Questo metodo prevede tre parametri, chiamati CursorType, Options e LockType, con i quali si stabilisce quale tipo di oggetto Recordset si ottiene. Si ricorderà dal Capitolo 9 che la sintassi di OpenRecordset per i DAO è analoga: OpenRecordset (tipo, opzioni, lockedits). La Tabella 10.1 elenca le corrispondenze fra i parametri del metodo OpenRecordset DAO e quelli del metodo Open ADO. Tabella 10.1  Corrispondenze fra parametri di OpenRecordset e Open. Tipo di Recordset DAO

Proprietà o parametri dei Recordset ADO

dbOpenDynaset dbOpenSnapshot dbOpenForwardOnly dbOpenTable

CursorType=adOpenKeyset CursorType=adOpenStatic CursorType=adOpenForwardOnly CursorType=adOpenKeyset, Options=adCmdTableDirect

Valori per le opzioni del Recordset DAO

Proprietà dei Recordset ADO

dbAppendOnly dbSQLPassThrough dbSeeChanges dbDenyWrite dbDenyRead dbInconsistent dbConsistent

Properties("Append-Only Rowset”) Properties("Jet OLEDB:ODBC Pass-Through Statement”) Non esiste equivalente. Non esiste equivalente. Non esiste equivalente. Properties("Jet OLEDB:Inconsistent”) = True Properties("Jet OLEDB:Inconsistent”) = False

Valori di LockType DAO

Valori di LockType ADO

dbReadOnly dbPessimistic dbOptimistic

adLockReadOnly adLockPessimistic adLockOptimistic

364  Capitolo 10

Vediamo due esempi di routine per aprire un Recordset di tipo forward-only, in sola lettura, per stampare nella finestra Immediata i valori di ciascun campo di un record.

Routine DAO Sub DAOApreRecordset()     Dim db As DAO.Database     Dim rst As DAO.Recordset     Dim fld As DAO.Field     'Apre il database     Set db = OpenDatabase("C:\Esempi\Nwin.accdb")     ' Apre il Recordset     Set rst = db.OpenRecordset("Select * from Clienti where Zona" & _         " = 'SP'", dbOpenForwardOnly, dbReadOnly)     ' Stampa nella finestra Immediata i valori dei campi nel primo record     For Each fld In rst.Fields         Debug.Print fld.Value & vbCrLf     Next     'Chiude il recordset     rst.Close End Sub

Routine ADO Sub ADOApreRecordset()     Dim cnn As New ADODB.Connection     Dim rst As New ADODB.Recordset     Dim fld As ADODB.Field     ' Apre la connessione     cnn.Open Connessione     ' Apre il recordset forward-only, in sola lettura     rst.Open "Select * from Clienti where Zona = 'SP'", cnn, _         adOpenForwardOnly, adLockReadOnly     ' Stampa nella finestra Immediata i valori dei campi nel primo record     For Each fld In rst.Fields         Debug.Print fld.Value & vbCrLf     Next     'Chiude il recordset     rst.Close End Sub

Come sappiamo dal Capitolo 9, nel momento in cui nascono, i Recordset DAO di tipo dynaset, snapshot o forward-only presentano soltanto il primo record reperito; le informazioni che si possono ricavare con l’oggetto Field sono quelle contenute nel record corrente, quindi, in entrambi gli esempi, lo stesso ciclo For Each fa stampare nella finestra Immediata tutti i campi del primo record ottenuto con il metodo OpenRecordset (DAO) oppure Open (ADO). Gli oggetti Recordset hanno una proprietà che indica in quale punto si trovano rispetto ai record (la loro posizione corrente). La posizione può essere subito prima del primo record (BOF), subito dopo l’ultimo record (EOF) o su un record specifico. In DAO come in ADO si hanno a disposizione vari metodi per spostarsi da un record all’altro. Per visua-

Lavorare con gli ADO    365

lizzare tutti i record contenuti nel recordset bisogna leggerli uno dopo l’altro, utilizzando il metodo MoveNext fino a quando la proprietà EOF del recordset diventa True. Ecco due esempi che eseguono questa operazione di scorrimento completo.

Routine DAO Sub DAOMoveNext()     Dim db As DAO.Database     Dim rst As DAO.Recordset     Dim fld As DAO.Field     'Apre il database     Set db = OpenDatabase("C:\Esempi\Nwin.accdb")     'Apre un recordset     Set rst = db.OpenRecordset("SELECT * FROM Clienti WHERE Zona" & _         " = 'SP'", dbOpenForwardOnly, dbReadOnly)     ' Stampa nella finestra Immediata i valori di tutti i campi di tutti i record     While Not rst.EOF         For Each fld In rst.Fields             Debug.Print fld.Value & ";";         Next         Debug.Print         rst.MoveNext     Wend     'Chiude il recordset     rst.Close End Sub

Routine ADO Sub ADOMoveNext()     Dim cnn As New ADODB.Connection     Dim rst As New ADODB.Recordset     Dim fld As ADODB.Field     ' Apre la connessione     cnn.Open Connessione     ' Apre un recordset di tipo forward-only e in sola lettura     rst.Open "SELECT * FROM Clienti WHERE Zona = 'SP'", cnn, _         adOpenForwardOnly, adLockReadOnly     ' Stampa nella finestra Immediata i valori di tutti i campi di tutti i record     While Not rst.EOF         For Each fld In rst.Fields             Debug.Print fld.Value & ";";         Next         Debug.Print         rst.MoveNext     Wend          'In alternativa al ciclo While...Wend si può usare     'il metodo GetString dell'oggetto Recordset, specificando     'una costante per il formato, in questo modo:     '    Debug.Print rst.GetString(adClipString, , ";")

366  Capitolo 10     'Chiude il recordset     rst.Close End Sub

Come si può vedere, a parte le ormai note differenze nelle dichiarazioni e assegnazioni iniziali, il nucleo esecutivo formato dal ciclo While...Wend è identico nelle due versioni DAO e ADO.

Il metodo GetString In ADO si ha a disposizione un interessante metodo per l’oggetto Recordset, che si chiama GetString. Questo metodo restituisce l’intero contenuto del Recordset in un’unica stringa e in colpo solo, per cui si può fare a meno del ciclo While...Wend, sostituendolo con un unico enunciato, come il seguente, col quale si ottiene lo stesso risultato: Debug.Print rst.GetString(adClipString, , ";")

La sintassi del metodo consente di specificare un parametro di formato che è costituito da una costante, adClipString, seguita da tre valori facoltativi: il numero delle righe da formattare (che può essere inferiore a quelle contenute nel recordset), il carattere da utilizzare per separare le colonne e quello per separare le righe. Se non si specifica il numero delle righe vengono acquisite e formattate tutte quelle contenute nel Recordset; se non si specifica un delimitatore delle righe viene usato il comando di a capo e se non si indica il delimitatore dei campi viene usato il carattere di tabulazione.

Per individuare la posizione di un record specifico entro un Recordset si può esaminare la proprietà AbsolutePosition, disponibile in entrambi i contesti.Vediamo gli esempi.

Routine DAO Sub DAOTrovaPosizioneCorrente()     Dim db As DAO.Database     Dim rst As DAO.Recordset     ' Apre il database     Set db = OpenDatabase("C:\Esempi\Nwin.accdb")     ' Apre il recordset     Set rst = db.OpenRecordset("SELECT * FROM Clienti", dbOpenDynaset)     ' Stampa la posizione assoluta     Debug.Print rst.AbsolutePosition     ' Si sposta sull'ultimo record     rst.MoveLast     ' Stampa la posizione assoluta     Debug.Print rst.AbsolutePosition     ' Chiude il recordset     rst.Close End Sub

Lavorare con gli ADO    367

Routine ADO Sub ADOTrovaPosizioneCorrente()     Dim cnn As New ADODB.Connection     Dim rst As New ADODB.Recordset     ' Apre la connessione     cnn.Open Connessione     ' Apre il recordset     rst.CursorLocation = adUseClient     rst.Open «SELECT * FROM Clienti», cnn, adOpenKeyset, _         adLockOptimistic, adCmdText     ' Stampa la posizione assoluta     Debug.Print rst.AbsolutePosition     ' Si sposta sull'ultimo record     rst.MoveLast     ' Stampa la posizione assoluta     Debug.Print rst.AbsolutePosition     ' Chiude il recordset     rst.Close End Sub

I due esempi di codice sono sostanzialmente uguali per la parte esecutiva. C’è da tener presente, però, che in DAO la proprietà AbsolutePosition è a base zero, mentre in ADO è a base uno; quindi, nella prima routine si otterrà 0 come indicatore di posizione del primo record e la seconda routine darà 1 per lo stesso valore; analogamente, l’ultimo record in un Recordset DAO che contiene 91 record avrà la proprietà impostata su 90, mentre nello stesso Recordset ADO la proprietà è impostata su 91. In DAO, inoltre, esiste una proprietà chiamata PercentPosition, che restituisce un valore percentuale che indica la posizione del record entro il Recordset attuale. Questa proprietà non è disponibile in ADO.

Cursori Nel contesto ADO e in quello più ampio di OLE DB si usa molto un termine che può creare sconcerto, se non se ne ha chiaro il significato. Il termine è cursor, che in italiano viene tradotto con cursore. Non ha nulla a che fare col cursore grafico, il segnale luminoso e intermittente che indica sul monitor il punto di inserimento di un carattere. Si tratta, invece, di un termine gergale, che indica sinteticamente sia un insieme di dati, un recordset, sia una particolare modalità di scorrimento e acquisizione dei dati stessi. I cursori si possono creare su una macchina server, su una macchina client o su entrambe. Inoltre, su una stessa macchina, soprattutto se è un server, è possibile creare più cursori. È compito di chi sviluppa un’applicazione database scegliere quale cursore usare, nella misura in cui ciò gli è consentito dagli strumenti di sviluppo di cui dispone. La proprietà CursorType dell’oggetto Recordset ADO permette di stabilire il modo in cui opera il cursore: scorrimento solo in avanti, nelle due direzioni, con estrazione dinamica o statica dei record. La proprietà CursorLocation serve per impostare il cursore da utilizzare: locale, sulla macchina client; server, sulla macchina che ha il ruolo di provider dei dati. Nell’esempio di routine ADOTrovaPosizioneCorrente, la proprietà CursorLocation è impostata sulla costante ADO adUseClient, il che vuol dire che si utilizza il cursore della macchina locale.

368  Capitolo 10

Trovare un record L’oggetto Recordset dispone, per i DAO come per gli ADO, di due metodi per trovare record che corrispondono a criteri specifici: Find e Seek. Mentre il primo si può utilizzare su qualunque recordset, il metodo Seek può agire soltanto su un oggetto Recordset di tipo tabella, aperto su una tabella provvista di un indice. Ecco un esempio di uso del metodo Find.

Routine DAO Sub DAOTrovaRecord()     Dim db As DAO.Database     Dim rst As DAO.Recordset     ' Apre il database     Set db = OpenDatabase("C:\Esempi\Nwin.accdb")     ' Apre il Recordset     Set rst = db.OpenRecordset("Clienti", dbOpenDynaset)     ' Trova il primo cliente il cui paese sia la Spagna     rst.FindFirst "Paese = 'Spagna'"        ',
View more...

Comments

Copyright ©2017 KUPDF Inc.
SUPPORT KUPDF