Capitolo d`esempio - Mondadori Informatica

C A PI TO LO 7
Automazione di un'applicazione
tramite Visual Basic
Perché non sono utilizzate le macro . . . . . . . . . . . . . . . . 335
Assistenza per l'immissione dei dati . . . . . . . . . . . . . . . . 337
Convalida di dati complessi . . . . . . . . . . . . . . . . . . . . . . . 356
Controllo dell'ordine di tabulazione su una
maschera a più pagine . . . . . . . . . . . . . . . . . . . . . . . . . . . . 365
Collegamento ai dati correlati in un'altra maschera o
report . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 382
Automazione di attività complesse . . . . . . . . . . . . . . . . . 390
Automazione dei report . . . . . . . . . . . . . . . . . . . . . . . . . . 399
Richiamo di macro per i dati con nome . . . . . . . . . . . . . 408
Automazione della selezione dei dati . . . . . . . . . . . . . . . 367
O
ra che hai appreso i fondamenti dell'utilizzo di Microsoft Visual Basic, è tempo
di mettere in pratica queste conoscenze. In questo capitolo imparerai a creare il
codice Visual Basic necessario per automatizzare molte attività comuni.
Nei database Conrad Systems Contacts, Housing Reservations e Wedding List puoi trovare
decine di esempi di automazione. Ogni volta che, esplorando i database, vedi qualcosa
d'interessante, apri la maschera o il report in visualizzazione Struttura, e osserva il codice
Visual Basic della maschera o del report. Questo capitolo spiega in modo approfondito
alcuni degli esempi più interessanti di questi database.
Nota
Puoi trovare il codice discusso in questo capitolo nelle applicazioni Conrad Systems
Contacts (Contacts.accdb), Housing Reservations (Housing.accdb) e Wedding List
(WeddingList.accdb).
Perché non sono utilizzate le macro
Anche se puoi utilizzarle per automatizzare le applicazioni, le macro hanno dei limiti. Come
avrai notato esaminando l'elenco degli eventi disponibili nel capitolo 1, "Elaborazione
degli eventi", molti eventi richiedono o restituiscono parametri che possono essere
passati o letti da una routine Visual Basic, ma non da una macro. Come hai appreso nei
capitoli 2, "Automazione di un'applicazione client tramite macro", e 3, "Automazione di
un'applicazione Web tramite macro", le funzioni di debug per le macro non sono affidabili
quanto quelle di Visual Basic.
335
336
Capitolo 7
Automazione di un'applicazione tramite Visual Basic
Quando utilizzare le macro
Utilizza le macro nell'applicazione in ognuna delle seguenti circostanze:
Capitolo 7
●
Per gestire un database Web.
●
L'applicazione è composta solo da poche maschere e da pochi report.
●
Devi creare una semplice applicazione automatizzata utilizzando solo azioni macro
attendibili, in modo che possa essere eseguita in un ambiente non attendibile.
●
L'applicazione potrebbe essere utilizzata da utenti che non hanno familiarità
con Visual Basic, che desiderano comprendere com'è costruita l'applicazione e
possibilmente modificarla o arricchirla.
●
Stai sviluppando un prototipo di applicazione e desideri automatizzare velocemente
alcune funzionalità per far vedere la struttura del progetto. Una volta che conosci
Visual Basic, automatizzare un'applicazione dimostrativa è molto semplice utilizzando
le routine evento.
●
Non hai bisogno di valutare o impostare i parametri passati da alcuni eventi, quali
DopoConfermaEliminazione, Applicazionefiltro, PrimaDiConfermaEliminazione, Errore,
Filtro, TastoGiù, TastoSu, PulsanteMouseGiù, MouseSpostato, PulsanteMouseSu,
NonInElenco e Aggiornato.
●
Non hai bisogno di aprire recordset o altri oggetti o di lavorare con essi.
Quando utilizzare Visual Basic
Anche se le macro dell'interfaccia utente possono essere utili quando lavori con le
maschere Web, numerose attività non possono essere eseguite con le macro e altre
possono essere implementate meglio per mezzo di una routine Visual Basic. Utilizza una
routine Visual Basic invece di una macro in ognuna delle seguenti circostanze:
●
Serve una gestione degli errori complessa nell'applicazione.
●
Desideri definire una nuova funzione.
●
Devi gestire alcuni eventi che passano parametri o accettano valori di ritorno (a parte
l'evento di annullamento).
●
Devi creare nuovi oggetti (tabelle, query, maschere o report) nel database, dal codice
dell'applicazione.
●
L'applicazione deve interagire con un altro programma basato su Windows tramite
automazione ActiveX.
337
●
Desideri essere in grado di chiamare direttamente le funzioni Windows API
(Application Programming Interface).
●
Desideri definire codice applicativo comune a più applicazioni, in una libreria.
●
Desideri essere in grado di aprire un recordset e di lavorare con i suoi dati, record per
record.
●
Devi utilizzare alcuni programmi di utilità nativi del sistema di gestione del database
relazionale che gestisce le tue tabelle collegate (quali le routine Microsoft SQL Server
o le funzionalità di definizione dei dati).
●
Desideri le massime prestazioni nell'applicazione. Dato che sono compilati, durante
l'esecuzione i moduli risultano leggermente più veloci delle macro. Probabilmente
noterai la differenza solo sui processori più lenti.
●
Stai creando un'applicazione complicata, della quale sarà difficile eseguire il debug.
Assistenza per l'immissione dei dati
Per garantire che l'utente dell'applicazione immetta dati corretti, puoi utilizzare macro per i
dati e definire valori predefiniti, maschere di input e regole di convalida. Ma cosa puoi fare
se i valori predefiniti provengono da una tabella correlata? Come puoi assistere un utente
che deve immettere un valore che non si trova nell'origine riga di una casella combinata?
Come puoi rendere più leggibile il testo visualizzato? Esiste un modo per semplificare
all'utente l'acquisizione delle date e degli orari? E come puoi aiutare l'utente a modificare
file immagine collegati? Puoi trovare le risposte a queste domande nelle sezioni seguenti.
Immissione di dati correlati
La tabella tblContactProducts nel database Conrad Systems Contacts (Contacts.accdb)
contiene un campo SoldPrice che riporta il prezzo effettivo del prodotto al momento
della vendita. La tabella tblProducts contiene un campo UnitPrice in cui è presente il
prezzo di vendita normale del prodotto. Quando l'utente lavora nella maschera Contacts
(frmContacts) e desidera vendere un nuovo prodotto, non vuoi che debba ricercare il
prezzo corrente del prodotto prima di immetterlo nel record.
Come probabilmente sai, puoi creare una maschera con sottomaschere nidificate a due
livelli per modificare i contatti, la società predefinita per ciascun contatto e i prodotti
venduti a quella società e registrati per il contatto corrente. Tuttavia, se apri frmContacts
nel database di esempio Contacts.accdb e fai clic sulla scheda Products come mostrato
nella figura 7-1, puoi notare che questa apparentemente non mostra alcun dato aziendale
di collegamento tra contatti e prodotti venduti. La sottomaschera usata per visualizzare
i prodotti di contatto non è nidificata all'interno di un'altra sottomaschera che mostra le
società per il contatto attuale. Di nuovo, l'utente non dovrebbe cercare l'ID della società
Capitolo 7
Assistenza per l'immissione dei dati
338
Capitolo 7
Automazione di un'applicazione tramite Visual Basic
predefinita per il contatto corrente, prima di vendere un prodotto. Nota che nella figura 7-1
siamo passati al quarto record di contatto.
Capitolo 7
Figura 7-1 La vendita di un prodotto a un contatto comporta l'inserimento del prezzo e della
società predefinita.
Una casella combinata nella sottomaschera (fsubContactProducts) aiuta l'utente a scegliere
il prodotto da vendere. Parte del segreto per impostare automaticamente il prezzo (il
campo SoldPrice nella tabella tblContactProducts) si trova nella query dell'origine riga per
la casella combinata qlkpProductsForContacts, come mostrato nella figura 7-2.
Figura 7-2 La query qlkpProductsForContacts è l'origine riga della casella combinata Product in
fsubContactProducts.
339
Certamente ti serve il campo ProductID per il nuovo record nella tabella
tblContactProducts. Visualizzare il campo ProductName nella casella combinata è più
significativo che mostrare il valore di ProductID e, come puoi vedere nella figura 7-1,
l'elenco della casella combinata mostra anche il campo CategoryDescription e l'indicazione
se il prodotto è una versione di prova. Ma perché abbiamo incluso le colonne UnitPrice,
TrialExpire e PreRequisite nella griglia di struttura della query?
Come puoi intuire, per recuperare uno di questi campi dalla riga corrente della casella
combinata, puoi fare riferimento alla proprietà Column della casella combinata. Più
avanti in questo capitolo, nella sezione "Convalida di dati complessi" a pagina 356, vedrai
che l'altro codice presente nella maschera utilizza i campi aggiuntivi per garantire che
il contatto disponga già di eventuali prodotti indicati come prerequisiti. Puoi vedere la
semplice riga di codice che copia il campo UnitPrice aprendo il modulo Visual Basic nella
maschera fsubContactProducts. Passa al riquadro di spostamento, seleziona la maschera
fsubContactProducts, fai clic con il pulsante destro del mouse sulla maschera, fai clic su
Visualizzazione Struttura nel menu e infine fai clic sul pulsante Visualizza codice nel gruppo
Strumenti della scheda Struttura. Nella finestra Codice di Visual Basic Editor (VBE) scorri
finché non trovi la routine cmbProductID_AfterUpdate. Il codice è il seguente:
Private Sub cmbProductID_AfterUpdate()
‘ Grab the default price from the hidden 5th column
Me.SoldPrice = Me.cmbProductID.Column(4)
End Sub
Per prelevare la colonna desiderata è utilizzato un numero d'indice. L'indice parte da zero;
puoi fare riferimento alla quinta colonna nella query (UnitPrice), chiedendo la proprietà
Column(4) della casella combinata. Il codice utilizza l'oggetto Me per fare riferimento
all'oggetto maschera dove il codice è in esecuzione. Pertanto, ogni volta che prelevi un
prodotto differente si verifica l'evento DopoAggiornamento per la casella combinata
ProductID, e questo codice assegna automaticamente un valore al prezzo correlato. Chiudi
la maschera fSubContactProducts prima di procedere con la sezione successiva.
Se apri la maschera frmContacts in visualizzazione Struttura, selezioni la maschera
fsubContactProducts sulla scheda Products, ed esamini le proprietà Collega campi
secondari e Collega campi master, puoi notare che le due maschere sono collegate
sul campo ContactID. La tabella tblContactProducts tuttavia richiede anche il
campo CompanyID nella sua chiave primaria. Il codice nel modulo per la maschera
fsubContactProducts gestisce il prelevamento dell'ID della società predefinita per il contatto
corrente, e quindi non occorre una sottomaschera intermediaria, che confonderebbe la
struttura della maschera. Se hai ancora il modulo per la maschera fsubContactProducts
aperto nella finestra VBE, puoi trovare il codice nella routine Form_BeforeInsert: Il codice è
il seguente:
Capitolo 7
Assistenza per l'immissione dei dati
340
Capitolo 7
Automazione di un'applicazione tramite Visual Basic
Capitolo 7
Private Sub Form_BeforeInsert(Cancel As Integer)
Dim varCompanyID As Variant
‘ First, disallow insert if nothing in outer form
If IsNothing(Me.Parent.ContactID) Then
MsgBox “You must define the contact information on a new row before
“attempting to sell a product”, vbCritical, gstrAppTitle
Cancel = True
Exit Sub
End If
‘ Try to lookup this contact’s Company ID
varCompanyID = DLookup(“CompanyID”, “qryContactDefaultCompany”, _
“(ContactID = “ & Me.Parent.ContactID.Value & “)”)
If IsNothing(varCompanyID) Then
‘ If not found, then disallow product sale
MsgBox “You cannot sell a product to a Contact that does not have a
“related Company that is marked as the default for this Contact.”
“ Press Esc to clear your edits and click on the Companies tab “
“to define the default Company for this Contact.”, vbCritical, _
gstrAppTitle
Cancel = True
Else
‘ Assign the company ID behind the scenes
Me.CompanyID = varCompanyID
End If
End Sub
“ & _
“ & _
& _
& _
Questa routine viene eseguita quando l'utente imposta un valore su una nuova riga nella
sottomaschera.
Per prima cosa, la routine verifica che la maschera esterna contenga un ContactID valido.
Fatto ciò, il codice utilizza la funzione di dominio DLookup per tentare di recuperare
l'identificativo predefinito della società per il contatto attuale.
La query include un filtro per ritornare solo le righe della tabella tblCompanyContacts per
le quali il campo DefaultForContact è True. Se la funzione ritorna un valore valido, il codice
imposta automaticamente il campo CompanyID richiesto.
Se non riesce a trovare un CompanyID, il codice utilizza l'istruzione MsgBox per avvisare
l'utente dell'errore.
Nota
La funzione IsNothing, che vedi utilizzata nel codice di tutte le applicazioni di esempio,
non è una funzione Visual Basic predefinita. Questa funzione verifica se il valore che le
viene passato contiene una stringa di lunghezza nulla o un valore Null o zero. Questa
funzione è disponibile nel modulo modUtility standard di tutti i database di esempio.
Assistenza per l'immissione dei dati
L'utilità delle funzioni di dominio
Abbastanza frequentemente nel codice, in una query oppure nell'origine controllo
di una maschera o di un report, potresti dover cercare un singolo valore da una delle
tabelle o delle query del database. Anche se è possibile definire e aprire un recordset
nel codice, Access fornisce un insieme di funzioni, chiamate funzioni di dominio, che
possono fornire il valore che ti serve con una singola chiamata di funzione. Le funzioni
disponibili sono le seguenti:
Nome di
funzione
Descrizione
DFirst, DLast
Ritorna un valore casuale dal dominio specificato (la tabel la o la
query che è l'origine record)
DLookup
Cerca un valore nel dominio specificato
DMax
Ritorna il valore massimo (Max) nel dominio specificato
DMin
Ritorna il valore minimo (Min) nel dominio specificato
DStDev, DstDevP Ritorna la deviazione standard di un campione della popo lazione
o della popolazione del dominio specificato
DSum
Ritorna la somma di un'espressione da un dominio
DVar, DVarP
Ritorna la varianza di un campione della popolazione o della
popolazione del dominio specificato
La sintassi per chiamare una funzione di dominio è la seguente:
<function name>(<field expression>, <domain name>
> [, <criteria>
> ])
dove
<nome funzione> è il nome di una delle funzioni elencate in precedenza
<espressione campo> è un letterale o il nome di una variabile di tipo stringa
contenente il nome di un campo o un'espressione che utilizza i campi del dominio
specificato
<nome dominio> è un letterale o il nome di una variabile di tipo stringa contenente il
nome di una tabella o di una query nel database
<criteri> è un letterale o il nome di una variabile di tipo stringa contenente
un'espressione di confronto booleana usata per filtrare i record nel dominio
Quando una funzione di dominio non trova alcun record, il valore ritornato è Null;
dovresti dunque assegnare sempre il risultato a una variabile di tipo Variant. Quando
costruisci un'espressione per i criteri, devi racchiudere i letterali di tipo stringa tra
virgolette e i letterali di tipo data/ora tra i caratteri #. Se utilizzi le doppie virgolette per
delimitare la stringa dei criteri, usa le virgolette semplici per i letterali all'interno della
stringa e viceversa. Per trovare il valore minimo tra i codici postali, per i contatti il cui
tipo è customer e la data di nascita è anteriore al primo gennaio 1970, digita
DMin("WorkPostalCode", "tblContacts", "[ContactType] = 'customer'
And Format([BirthDate], 'mm/dd/yyyy') < #01/01/1970#")
Capitolo 7
APPROFONDIMENTO
AP
PPROFONDIMENTO
341
342
Capitolo 7
Automazione di un'applicazione tramite Visual Basic
Gestione dell'evento NonInElenco
Capitolo 7
In quasi tutte le maschere di immissione dati, devi offrire all'utente un modo per impostare
la chiave esterna del record modificato sul lato molti di una relazione, per puntare al
record corretto del lato uno, ad esempio per impostare il campo ProductID della tabella
tblContactProducts, quando viene venduto un prodotto nella scheda Products della
maschera frmContacts. Ma cosa succede se l'utente deve creare un nuovo prodotto?
L'utente deve prima aprire la maschera per la modifica dei prodotti, per creare il nuovo
prodotto, prima di venderlo? La risposta è assolutamente no; devi comporre il codice
nell'evento NonInElenco della casella combinata, per gestire i nuovi valori e fornire un
modo per creare nuove righe nella tabella tblProducts.
La figura 7-3 mostra cosa accade quando l'utente cerca di digitare il nome di un prodotto
che non è già presente nella tabella tblProducts. In questo caso il cliente desidera
acquistare un prodotto con supporto di due anni invece del prodotto con supporto di un
anno, già disponibile. Puoi vedere che qualcosa ha intercettato il nuovo nome di prodotto,
per confermare l'aggiunta del nuovo prodotto da parte dell'utente.
Figura 7-3 Quando immetti un prodotto che non è definito nel database, l'applicazione chiede
se desideri aggiungere il nuovo prodotto.
Per iniziare, la casella combinata è stata definita con la proprietà Solo in elenco impostata
su Sì. Quindi è stata definita una routine evento per gestire l'evento NotInList della casella
combinata; è il codice di questo evento che chiede all'utente se desidera aggiungere un
prodotto. Se l'utente fa clic su Sì per confermare l'aggiunta del prodotto, la routine evento
apre la maschera frmProductAdd in modalità Dialog, per consentire all'utente di immettere
i nuovi dati, come mostrato nella figura 7-4. L'apertura di una maschera in modalità Dialog
forza l'utente a rispondere prima che l'applicazione riprenda l'esecuzione. Il codice che apre
questa maschera passa il nome del prodotto immesso e il tipo del prodotto selezionato
343
dall'utente, prima di immettere un nuovo nome di prodotto. L'utente può compilare il
prezzo e altri dettagli. Se l'utente fa clic su Cancel il record non viene salvato e la maschera
viene chiusa. Se l'utente fa clic su Save, il record del nuovo prodotto viene salvato e la
maschera viene chiusa per consentire al codice della routine evento NonInElenco di
proseguire.
Figura 7-4
La maschera frmProductAdd ti permette di definire i dettagli del nuovo prodotto.
Per vedere come funziona, apri la maschera fsubContactProducts nella visualizzazione
Struttura, fai clic sulla casella combinata cmbProductID, trova la proprietà evento
NonInElenco nella finestra delle proprietà e fai clic sul pulsante Genera per aprire il codice.
Ti abbiamo fatto selezionare la casella combinata dalla finestra delle proprietà perché
il controllo casella di testo ProductName si sovrappone al controllo casella combinata
cmbProductID sulla griglia di progettazione della maschera. Il codice per la routine è il
seguente.
Private Sub cmbProductID_NotInList(NewData As String, Response As Integer)
Dim strType As String, strWhere As String
‘ User has typed in a product name that doesn’t exist
strType = NewData
‘ Set up the test predicate
strWhere = “[ProductName] = “”” & strType & “”””
‘ Ask if they want to add this product
If vbYes = MsgBox(“Product “ & NewData & “ is not defined. “ & _
“Do you want to add this Product?”, vbYesNo + vbQuestion + _
vbDefaultButton2, gstrAppTitle) Then
‘ Yup. Open the product add form and pass it the new name
‘ - and the pre-selected Category
DoCmd.OpenForm “frmProductAdd”, DataMode:=acFormAdd, _
WindowMode:=acDialog, _
OpenArgs:=strType & “;” & Me.cmbCategoryDescription
‘ Verify that the product really got added
If IsNull(DLookup(“ProductID”, “tblProducts”, strWhere)) Then
‘ Nope.
MsgBox “You failed to add a Product that matched what you entered.” & _
“ Please try again.”, vbInformation, gstrAppTitle
‘ Tell Access to continue - we trapped the error
Response = acDataErrContinue
Else
‘ Product added OK - tell Access so that combo gets requeried
Response = acDataErrAdded
Capitolo 7
Assistenza per l'immissione dei dati
344
Capitolo 7
Automazione di un'applicazione tramite Visual Basic
Capitolo 7
End If
Else
‘ Don’t want to add - let Access display normal error
Response = acDataErrDisplay
End If
End Sub
Access passa due parametri alla routine evento NotInList. Il primo parametro, NewData,
contiene la stringa che hai immesso nella casella combinata. Puoi impostare il valore del
secondo parametro, Response, prima di uscire dalla subroutine per indicare ad Access
ciò che desideri fare. Poiché non è possibile accedere a questi parametri in una macro, è
evidente che per poter gestire correttamente l'evento è necessaria una routine Visual Basic.
La routine crea la stringa dei criteri, usata per verificare che l'utente abbia salvato il
prodotto, quindi utilizza la funzione MsgBox per chiedere se l'utente desidera aggiungere
al database il prodotto visualizzato nella figura 7-3. Se hai mai letto l'argomento della
Guida in linea relativo alla funzione MsgBox, sai che il secondo parametro è un numero che
è la somma di tutte le opzioni desiderate. Fortunatamente Visual Basic fornisce costanti
con nome per queste opzioni, e quindi non devi ricordare i codici numerici. In questo caso
la routine richiede la visualizzazione di un'icona punto interrogativo (vbQuestion) e dei
pulsanti Sì e No (vbYesNo); essa specifica inoltre che il pulsante predefinito è il secondo
pulsante (vbDefaultButton2), il pulsante No, nel caso in cui l'utente prema velocemente
Invio dopo aver visto il messaggio.
Se l'utente fa clic su Sì nella finestra messaggio, la routine utilizza DoCmd.OpenForm
per aprire la maschera frmProductAdd in modalità Dialog, e passa ad essa il nome del
prodotto immesso e il tipo del prodotto selezionato, impostando la proprietà OpenArgs
della maschera. Nota l'uso della sintassi con parametri con nome nella chiamata a DoCmd.
OpenForm per semplificare l'impostazione dei parametri desiderati. Devi aprire la maschera
in modalità Dialog. In caso contrario, il codice continua a essere eseguito durante l'apertura
della maschera. Ogni volta che viene aperta una finestra di dialogo, l'esecuzione del codice
Visual Basic si interrompe fino alla chiusura della finestra di dialogo, che in questo caso è
critica poiché il record deve essere salvato o annullato prima di poter proseguire con altri
test.
Dopo la chiusura della maschera frmProductAdd, l'istruzione successiva chiama la
funzione DLookup per verificare che il prodotto sia stato realmente aggiunto al database.
Se il codice non riesce a trovare un nuovo nome di prodotto corrispondente (l'utente
ha cambiato il nome del prodotto nella maschera di aggiunta oppure ha fatto clic su
Cancel), utilizza l'istruzione MsgBox per informare l'utente del problema e imposta un
valore di ritorno nel parametro Response, per indicare ad Access che il valore non è stato
aggiunto, ma che Access può proseguire senza visualizzare il proprio messaggio di errore
(acDataErrContinue).
Se il nome del nuovo prodotto esiste (il che significa che l'utente ha fatto clic su Save nella
maschera frmProductAdd), il codice indica ad Access che il nuovo prodotto ora esiste
(acDataErrAdded).
345
Access interroga nuovamente la casella combinata e cerca una nuova corrispondenza.
Infine, se l'utente fa clic su No nella finestra messaggio della figura 7-3, la routine imposta
Response su acDataErrDisplay, per indicare ad Access di visualizzare il normale messaggio
di errore.
L'altra parte di codice critica è nell'evento Load per la maschera frmProductAdd: Il codice è
il seguente:
Private Sub Form_Load()
Dim intI As Integer
If Not IsNothing(Me.OpenArgs) Then
‘ If called from “not in list”, Openargs should have
‘
Product Name; Category Description
‘ Look for the semi-colon separating the two
intI = InStr(Me.OpenArgs, “;”)
‘ If not found, then all we have is a product name
If intI = 0 Then
Me.ProductName = Me.OpenArgs
Else
‘ If called from fsubContactProducts,
‘ .. have category only
If intI > 1 Then
‘ Have a product name - grab it
Me.ProductName = Left(Me.OpenArgs, intI - 1)
End If
Me.CategoryDescription = Mid(Me.OpenArgs, intI + 1)
‘ lock the category
Me.CategoryDescription.Locked = True
Me.CategoryDescription.Enabled = False
‘ .. and clear the tool tip
Me.CategoryDescription.ControlTipText = “”
End If
End If
End Sub
Come ricorderai, la routine evento NotInList della casella combinata cmbProductID
passa al metodo OpenForm la stringa originale immessa dall'utente e il tipo di prodotto
selezionato (il campo CategoryDescription) come parametro OpenArgs. Viene così
impostata la proprietà OpenArgs della maschera che viene aperta; la proprietà OpenArgs
dovrebbe contenere il nome del nuovo prodotto, un punto e virgola e il tipo del prodotto
selezionato. La routine Form_Load analizza il nome e il tipo del prodotto per mezzo della
funzione InStr, per cercare il punto e virgola (la funzione InStr ritorna l'offset nella stringa
specificata nel primo parametro, corrispondente al punto nel quale si trova la stringa
specificata nel secondo parametro; ritorna 0, se non trova la stringa cercata); il codice
utilizza poi i due valori trovati per impostare i campi ProductName e CategoryDescription.
Quando il codice trova una descrizione di categoria, blocca quella casella combinata, in
modo che l'utente non possa cambiarla in qualcosa di diverso da ciò che era selezionato
sulla riga del nuovo prodotto nella maschera originale.
Capitolo 7
Assistenza per l'immissione dei dati
346
Capitolo 7
Automazione di un'applicazione tramite Visual Basic
Controllo automatico di un indirizzo di posta elettronica
Capitolo 7
Uno dei modi più facili per immettere un collegamento ipertestuale consiste nell'utilizzare
la funzionalità Inserisci collegamento ipertestuale. Tuttavia puoi anche digitare l'indirizzo
del collegamento ipertestuale direttamente nel campo in un foglio dati o in una maschera.
Ricorda che un campo collegamento ipertestuale può contenere fino a quattro parti: testo
visualizzato, indirizzo del collegamento ipertestuale, segnalibro e testo della descrizione
comando. Se un utente si limita a immettere un indirizzo di posta elettronica in un campo
collegamento ipertestuale, Access 2010 riconosce il formato, aggiunge il protocollo mailto:
e utilizza ciò che l'utente digita come testo visualizzato. Se per esempio l'utente immette
[email protected]
Access memorizza nel campo collegamento ipertestuale
[email protected]#mailto:[email protected]#
Invece di ripetere l'indirizzo di posta elettronica come testo visualizzato, il risultato
potrebbe avere un aspetto migliore se il testo mostrato fosse il nome della persona. Una
delle maschere che contengono un indirizzo di posta elettronica è la maschera frmContacts
nell'applicazione Conrad Systems Contacts. Puoi trovare il codice che esamina e cerca di
correggere l'indirizzo nella routine evento AfterUpdate (dell'evento DopoAggiornamento)
per la casella di testo EmailName. Se l'utente immette un protocollo valido diverso da
http:// o mailto:, questo codice non lo altera. Il codice è il seguente:
Private Sub EmailName_AfterUpdate()
‘ If you just type in an email name: [email protected]
‘ Access changes it to: [email protected]#mailto:[email protected]#
‘ This code replaces the display field with the user name
Dim intI As Integer
‘ Don’t do anything if email is empty
If IsNothing(Me.EmailName) Then Exit Sub
‘ Fix up http:// if it’s there
‘ This was an old bug in 2003 and earlier, but fixed in Access 2007
Me.EmailName = Replace(Me.EmailName, “http://”, “mailto:”)
‘ Now look for the first “#” that delimits the hyperlink display name
intI = InStr(Me.EmailName, “#”)
‘ And put the person name there instead if found
If intI > 0 Then
Me.EmailName = (Me.FirstName + “ “) & Me.LastName & _
Mid(Me.EmailName, intI)
End If
End Sub
Se l'utente deseleziona la casella di testo EmailName, il codice non esegue alcuna
operazione. Se invece la casella di testo contiene una stringa, il codice utilizza la funzione
Replace per cercare un indirizzo http:// non corretto e sostituirlo con l'identificatore di
protocollo mailto: corretto. Come sai, un campo collegamento ipertestuale può contenere
testo che viene visualizzato al posto del collegamento ipertestuale, un delimitatore di
carattere # e l'effettivo indirizzo del collegamento ipertestuale. Il codice utilizza la funzione
347
InStr per verificare la presenza del delimitatore. La funzione InStr restituisce l'offset nella
stringa specificata nel primo parametro, corrispondente al punto in cui si trova la stringa
specificata nel secondo parametro. Se trova il delimitatore, il codice sostituisce il contenuto
del campo con il nome e il cognome della persona come testo da visualizzare, seguito
dal testo che comincia con il delimitatore # . La funzione Mid chiamata senza specificare
la lunghezza (il terzo parametro facoltativo) restituisce tutti i caratteri che cominciano
all'offset specificato.
Nota
In Access 2003 e versioni precedenti, quando si digitava un indirizzo di posta
elettronica senza il prefisso del protocollo mailto: in un campo collegamento
ipertestuale, Access memorizzava erroneamente il collegamento ipertestuale con il
prefisso di protocollo http://.
/ Questo problema è stato corretto in Access 2007, ma il
codice precedente corregge il problema se lo utilizzi nelle versioni precedenti.
Utilizzo di un calendario grafico
Puoi fornire una maschera di input per aiutare l'utente a immettere correttamente le date
e le ore, tuttavia una maschera di input può essere inadeguata, per esempio se richiede
che l'utente digiti sempre il mese con due cifre. Una maschera di input può anche essere in
conflitto con qualsiasi valore predefinito che potresti voler assegnare. È perciò preferibile
che l'utente possa scegliere la data da un calendario grafico.
Access 2010 fornisce la nuova proprietà Visualizza selezione data per le caselle di testo.
Puoi impostare questa proprietà a Per le date per indurre Access a visualizzare un'icona
calendario accanto al controllo quando contiene un valore di data/ora e ha lo stato attivo.
L'utente può fare clic sul pulsante per far apparire un calendario grafico in cui selezionare
un valore di data. Visualizza selezione data non è disponibile per i controlli diversi dalle
caselle di testo e il selettore data consente all'utente di immettere soltanto la data e non
la data e l'ora. Poiché nel selettore data sei anche limitato a spostarti di un mese alla volta
usando i tasti freccia sinistro e destro, per immettere una data che dista di molti mesi dalla
data corrente, ad esempio una data di nascita, devi fare clic molte volte sui tasti freccia per
raggiungere il mese e l'anno corretti.
Entrambe le applicazioni di esempio Conrad Systems Contacts e Housing Reservations
offrono maschere e codice calendario di esempio che puoi utilizzare per impostare
un valore di data/ora in qualsiasi controllo. Queste due applicazioni contengono una
maschera calendario chiamata frmCalendar che impiega codice Visual Basic per trascinare il
calendario in una maschera usando un gruppo opzioni e controlli pulsante interruttore. La
maschera calendario fornisce un'opzione che permette di immettere l'ora e selezionare la
data.
Capitolo 7
Assistenza per l'immissione dei dati
348
Capitolo 7
Automazione di un'applicazione tramite Visual Basic
RISOLUZIONE DEI PROBLEMI
Perché Access non imposta il mio valore definito per un campo di data/ora?
Capitolo 7
Hai anche definito la proprietà Maschera di input? Se è così, il problema è questo. Un
campo Data/ora è effettivamente un numero in virgola mobile ma Access converte
e visualizza sempre il valore carattere nei fogli dati delle tabelle e delle query e
nelle maschere e report. Quando definisci la proprietà Maschera di input, qualsiasi
impostazione Valore predefinito deve corrispondere alle restrizioni imposte dalla
maschera di input. Se il valore viola le restrizioni, Access non utilizzerà il valore
predefinito. Quando assegni un valore predefinito a un campo Data/ora, utilizzi
tipicamente una delle funzioni incorporate Date e Now. Queste funzioni restituiscono
un valore in virgola mobile di data/ora valido che probabilmente non corrisponderà
alle restrizioni della maschera di input. Per ottenere che Access utilizzi il valore
predefinito, devi formattarlo in modo che corrisponda alla maschera di input. Ad
esempio, se la tua maschera di input è 00/90/0000\ 00:00, devi impostare la proprietà
Valore predefinito del campo o controllo su =Format(Now(), "gg/mm/aaaa hh:nn").
Questo induce Access a restituire un valore stringa come predefinito che corrisponde
alla maschera di input.
Questa funzionalità grafica è disponibile nelle applicazioni di esempio ogni qualvolta
vedi un piccolo pulsante di comando vicino a una campo di tipo data o data/ora su una
maschera. Fai clic sul pulsante per aprire il calendario e impostare il valore. Un controllo che
utilizza una maschera calendario personalizzata è il controllo ContactDateTime della scheda
Events della maschera frmContacts. Puoi vedere il calendario aperto nella figura 7-5.
Figura 7-5 Fai clic sul pulsante di comando del calendario accanto al controllo ContactDateTime
nella scheda Events della maschera frmContacts per aprire una maschera grafica in cui
selezionare la data e immettere l'ora.
Il codice nell'evento Clic di questo pulsante di comando chiama una funzione pubblica
per aprire la maschera e passare ad essa il controllo correlato che dovrebbe ricevere
il valore della data risultante. Questo codice è disponibile nel modulo della maschera
fsubContactEvents.
Private Sub cmdContactTimeCal_Click()
Dim varReturn As Variant
‘ Clicked the calendar icon asking for graphical help
‘ Put the focus on the control to be updated
Me.ContactDateTime.SetFocus
‘ Call the get a date/time function
varReturn = GetDate(Me.ContactDateTime, False)
End Sub
APPROFONDIMENTO
AP
PPROFONDIMENTO
Utilizzare i tasti di scelta rapida per passare
a routine e funzioni
Se evidenzi la chiamata di funzione (GetDate nell'esempio precedente) e premi
Maiusc+F2, Access passa direttamente a quella funzione nel modulo modCalendar.
Quando l'utente fa clic sul pulsante di comando, Access lo attiva. Il codice attiva
nuovamente il campo data e chiama la funzione pubblica dove avviene l'azione effettiva.
Puoi trovare il codice per la funzione GetDate nel modulo modCalendar.
Option Compare Database
Option Explicit
Public Function GetDate(ctl As control, _
Optional intDateOnly As Integer = 0) As Integer
‘----------------------------------------------------------‘ Inputs: A Control object containing a date/time value
‘
Optional “date only” (no time value) flag
‘ Outputs: Sets the Control to the value returned by frmCalendar
‘ Created By: JLV 09/05/01
‘ Last Revised: JLV 09/05/01
‘----------------------------------------------------------Dim varDateTime As Variant
Dim strDateTime As String
Dim frm As Form
‘ Error trap just in case
On Error GoTo Error_Date
‘ First, validate the kind of control passed
Select Case ctl.ControlType
‘ Text box, combo box, and list box are OK
Case acTextBox, acListBox, acComboBox
Case Else
GetDate = False
Exit Function
End Select
‘ If the control has no value
If IsNothing(ctl.Value) Then
349
Capitolo 7
Assistenza per l'immissione dei dati
350
Capitolo 7
Automazione di un'applicazione tramite Visual Basic
Capitolo 7
If intDateOnly Then
‘ Set default date
varDateTime = Date
Else
‘ .. or default date and time
varDateTime = Now
End If
Else
‘ Otherwise, pick up the current value
varDateTime = ctl.Value
‘ Make sure it’s a date/time
If vbDate <> varType(varDateTime) Then
GetDate = False
Exit Function
End If
End If
‘ Turn the date and time into a string
‘ to pass to the form
strDateTime = Format(varDateTime, “mm/dd/yyyy hh:nn”)
‘ Make sure we don’t have an old copy of
‘ frmCalendar hanging around
If IsFormLoaded(“frmCalendar”) Then
DoCmd.Close acForm, “frmCalendar”
End If
‘ Open the calendar as a dialog so this code waits,
‘ and pass the date/time value
DoCmd.OpenForm “frmCalendar”, WindowMode:=acDialog, _
OpenArgs:=strDateTime & “,” & intDateOnly
‘ If the form is gone, user canceled the update
If Not IsFormLoaded(“frmCalendar”) Then Exit Function
‘ Get a pointer to the now-hidden form
Set frm = Forms!frmCalendar
‘ Grab the date part off the hidden text box
strDateTime = Format(frm.ctlCalendar.Value, “dd-mmm-yyyy”)
If Not intDateOnly Then
‘ If looking for date and time,
‘ also grab the hour and minute
strDateTime = strDateTime & “ “ & frm.txtHour & _
“:” & frm.txtMinute
End If
‘ Stuff the returned value back in the caller’s control
ctl.Value = DateValue(strDateTime) + TimeValue(strDateTime)
‘ Close the calendar form to clean up
DoCmd.Close acForm, “frmCalendar”
GetDate = True
Exit_Date:
Exit Function
Error_Date:
‘ This code is pretty simple and does check for
‘ a usable control type,
‘ .. so this should never happen.
‘ But if it does, log it...
ErrorLog “GetDate”, Err, Error
GetDate = False
Resume Exit_Date
End Function
351
La funzione imposta innanzitutto la gestione degli errori che provoca l'esecuzione del
codice all'etichetta Error_Date se qualcosa va male. La funzione accetta gli argomenti
ctlToUpdate e intDateOnly. Access utilizza l'argomento ctlToUpdate per esaminare il tipo di
controllo e controllare il valore passato alla funzione. Access utilizza l'argomento facoltativo
intDateOnly per indicare se il controllo richiede data e ora o solo la data. La funzione
verifica se il controllo passato è una casella di testo, una casella combinata o una casella
di riepilogo. Se il controllo coincide con uno di questi tipi, la funzione procede, altrimenti
si interrompe. La funzione controlla quindi il valore del controllo passato. Se nel controllo
non è presente alcun valore, Access assegna alla variabile varDateTime la data corrente
o la data e l'ora correnti, a seconda del valore presente nell'argomento intDateOnly. Se il
controllo ha un valore, la funzione assegna tale valore attuale alla variabile e verifica anche
che il valore corrisponda a una data e un'ora valide. Fatto ciò, la funzione converte il valore
di tipo Variant in una stringa e verifica se la maschera frmCalendar è aperta. Se trova la
maschera calendario aperta, la funzione la chiude e la riapre in modalità Dialog. Nella
chiamata OpenForm a frmCalendar, la funzione passa il valore stringa di data e ora e il
valore intDateOnly come parametri di OpenArgs.
Una volta che l'utente ha fatto clic sul pulsanti Save o Cancel nella maschera frmCalendar,
questa funzione continua a rimanere in esecuzione. Se l'utente annulla l'aggiornamento
facendo clic sul pulsante di comando cmdCancel nella maschera frmCalendar, la funzione
si interrompe. Se l'utente fa clic sul pulsante di comando cmdSave nella maschera
frmCalendar, la funzione prende le informazioni di data e ora selezionate dalla maschera,
imposta il valore del controllo chiamante con data e ora, chiude la maschera frmCalendar e
si interrompe.
Gli ultimi segmenti di codice che fanno funzionare l'esempio si trovano nel modulo della
maschera frmCalendar. Il codice nell'evento Load della maschera è elencato di seguito:
Private Sub Form_Load()
‘ Establish an initial value for the date
If IsNothing(Me.OpenArgs) Then
varDate = Date
Else
‘ Should have date, time, and “DateOnly”
‘ indicator in OpenArgs:
‘
mm/dd/yyyy hh:mm,-1
varDate = Left(Me.OpenArgs, 10)
Me.txtHour = Mid(Me.OpenArgs, 12, 2)
Me.txtMinute = Mid(Me.OpenArgs, 15, 2)
‘ If “date only”
If Right(Me.OpenArgs, 2) = “-1” Then
‘ Hide some stuff
Me.txtHour.Visible = False
Me.txtMinute.Visible = False
Me.lblColon.Visible = False
Me.lblTimeInstruct.Visible = False
Me.SetFocus
‘ .. and resize my window
DoCmd.MoveSize , , , 4295
End If
Capitolo 7
Assistenza per l'immissione dei dati
352
Capitolo 7
Automazione di un'applicazione tramite Visual Basic
Capitolo 7
End If
‘ Initialize the month selector
Me.cmbMonth = Month(varDate)
‘ Initialize the year selector
Me.cmbYear = Year(varDate)
‘ Call the common calendar draw routine
SetDays
‘ Place the date/time value in a hidden control ‘ The calling routine fetches it from here
Me.ctlCalendar = varDate
‘ Highlight the correct day box in the calendar
Me.optCalendar = Day(varDate)
End Sub
Per prima cosa, il codice controlla alla maschera vengono passati parametri OpenArgs.
In caso contrario, il codice assegna la data corrente a una variabile chiamata varDate.
Se i parametri OpenArgs vengono passati, il codice esamina gli elementi OpenArgs alla
ricerca della possibile parte relativa a data e ora. Se la variabile facoltativa intDateOnly è
vera (il controllo richiede solo un valore di tipo data, non una data e un'ora), la maschera
si restringe per nascondere quelle caselle di testo. Poiché il campo data/ora dell'evento
richiede un valore di ora, il parametro è False, perciò dovresti essere in grado di vedere le
caselle di testo dell'ora e dei minuti. La parte finale del codice imposta il calendario in base
a elementi di controllo, in modo da farlo corrispondere al valore già presente nel controllo
o alla data e all'ora di sistema. Come puoi notare, il codice richiama una routine SetDays
inclusa nel modulo classe della maschera per impostarne i vari controlli.
Quando il codice di impostazione è terminato, la maschera aspetta fino a quando l'utente
non immette un valore e fa clic su Save, oppure decide di non modificare il valore e fa clic
su Cancel. Il codice delle due routine che rispondono ai pulsanti di comando è il seguente:
Public Sub cmdCancel_Click()
‘ Closing doesn’t pass the value back
DoCmd.Close acForm, Me.Name
End Sub
Private Sub cmdSave_Click()
‘ Hiding this dialog lets the calling code in GetDate continue
Me.Visible = False
End Sub
Se l'utente fa clic sul pulsante Cancel (cmdCancel_Click) la maschera viene chiusa senza
modificare nessun valore nel controllo passato ad essa. Il codice che salva il valore
selezionato dall'utente in un calendario grafico si trova nel modulo GetDate. Per salvare il
valore, l'evento clic del pulsante di comando cmdSave deve semplicemente nascondere la
maschera frmCalendar.
Assistenza per l'immissione dei dati
353
RISOLUZIONE DEI PROBLEMI
In Access 2010, Microsoft ha rimosso il controllo Calendario di ActiveX fornito con
molte delle precedenti versioni di Access. Per visualizzare un calendario grafico
agli utenti che devono selezionare una data, devi utilizzare il nuovo controllo
incorporato Selezione data fornito con Access, oppure creare una maschera calendario
personalizzata usando Visual Basic, come abbiamo fatto nelle applicazioni di esempio
Conrad Systems Contacts e Housing Reservations.
Lavorare con foto collegate
Benché in un'applicazione Access sia possibile memorizzare e visualizzare fotografie in
modo sicuro usando il tipo di dati Oggetto OLE, se l'applicazione ha bisogno di gestire
centinaia o migliaia di foto puoi facilmente eccedere il limite di dimensione di 2 gigabyte
dei file .accdb In alternativa puoi utilizzare il tipo di dati Allegato per archiviare le tue
fotografie più efficientemente, ma anche in questo caso è possibile incorrere nelle
limitazioni relative alle dimensioni dei file. Il metodo alternativo è memorizzare le immagini
come file e salvare i percorsi d'immagine come campi di testo nelle tabelle.
La buona notizia è che il controllo Immagine di Access 2010 ti permette di specificare la
proprietà Origine controllo. Quando questa proprietà punta a un campo contenente una
posizione di cartella e file come stringa di testo, il controllo Immagine carica direttamente
la foto da quella posizione. Tu devi comunque fornire caratteristiche nelle maschere per
aiutare l'utente a modificare facilmente le informazioni sulla posizione del file.
Il database Housing Reservations (Housing.accdb) è progettato in modo da utilizzare
questa funzionalità. Apri il database di esempio Housing.accdb, quindi apri la maschera
frmEmployeesPlain, come mostrato nella figura 7-6. L'immagine del dipendente che
vedi nelle maschere frmEmployees e frmEmployeesPlain viene recuperata dal controllo
Immagine attraverso il percorso memorizzato nel campo Photo della tabella.
Capitolo 7
Perché in Access 2010 non è presente il Controllo Calendario di ActiveX?
354
Capitolo 7
Automazione di un'applicazione tramite Visual Basic
Capitolo 7
Figura 7-6 Il controllo Immagine carica la foto nella maschera Employees da un percorso.
Nota che l'utente non può vedere il contenuto del campo Photo che include le
informazioni sul percorso d'immagine. Abbiamo comunque fornito due pulsanti di
comando per facilitare all'utente la modifica e l'eliminazione delle informazioni sul percorso
d'immagine.
Eliminazione e aggiornamento di un percorso d'immagine
Cancellare un nome di file salvato nel record è la parte semplice, perciò vediamola per
prima. Dietro il pulsante Delete che puoi vedere sulla maschera frmEmployeesPlain è
presente il codice che segue:
Private Sub cmdDelete_Click()
‘ User asked to remove the picture
‘ Clear photo
Me.txtPhoto = Null
‘ Set the message
Me.lblMsg.Caption = “Click Add to create a photo for this employee.”
‘ Put focus in a safe place
Me.FirstName.SetFocus
End Sub
Quando l'utente fa clic sul pulsante di comando che chiede di eliminare la foto, il codice
imposta il percorso della foto a Null e mostra l'etichetta informativa. Impostando il campo
Photo su Null si induce il controllo Immagine a eliminare l'immagine. Poiché lo sfondo del
controllo Immagine è trasparente, lascia vedere il controllo Etichetta nascosto dietro che
mostra un messaggio informativo.
La parte difficile è fornire all'utente un modo per immettere un percorso d'immagine
affinché possa aggiungere o aggiornare un'immagine in un record. Nonostante sia
possibile utilizzare la funzione InputBox per chiedere il percorso all'utente, è molto più
professionale richiamare la finestra di dialogo Apri file di Windows per fare in modo che
l'utente possa accedere all'immagine desiderata utilizzando strumenti familiari. La cattiva
355
notizia è che chiamare qualsiasi routine in Windows è complicato e di solito richiede
di impostare strutture di parametri e una speciale dichiarazione di funzione esterna.
La buona notizia è che Microsoft Office 2010 include lo speciale oggetto FileDialog
che semplifica notevolmente la procedura. Per facilitare l'uso di questo oggetto, devi
aggiungere un riferimento alla libreria Microsoft Office: dalla finestra Visual Basic Editor,
seleziona Riferimenti nel menu Strumenti e accertati che sia selezionata l'opzione Microsoft
Office 14.0 Object Library. Dopo averlo fatto, puoi includere codice utilizzando l'oggetto
FileDialog per caricare un percorso d'immagine. Puoi trovare il codice che segue dietro
l'evento Click del pulsante Add (cmdAdd) della maschera frmEmployeesPlain:
Private Sub cmdAdd_Click()
‘ User asked to add a new photo
Dim strPath As String
‘ Grab a copy of the Office file dialog
With Application.FileDialog(msoFileDialogFilePicker)
‘ Select only one file
.AllowMultiSelect = False
‘ Set the dialog title
.Title = “Locate the Employee picture file”
‘ Set the button caption
.ButtonName = “Choose”
‘ Make sure the filter list is clear
.Filters.Clear
‘ Add two filters
.Filters.Add “JPEGs”, “*.jpg”
.Filters.Add “Bitmaps”, “*.bmp”
‘ Set the filter index to 2
.FilterIndex = 2
‘ Set the initial path name
.InitialFileName = CurrentProject.Path & “\Pictures”
‘ Show files as thumbnails
.InitialView = msoFileDialogViewThumbnail
‘ Show the dialog and test the return
If .Show = 0 Then
‘ Didn’t pick a file - bail
Exit Sub
End If
‘ Should be only one filename - grab it
strPath = Trim(.SelectedItems(1))
‘ Set an error trap
On Error Resume Next
‘ Set the image
Me.txtPhoto = strPath
‘ Set the message in case Image control couldn’t find it
Me.lblMsg.Caption = “Failed to load the picture you selected.” & _
“ Click Add to try again.”
End With
‘ Put focus in a safe place
Me.FirstName.SetFocus
End Sub
Capitolo 7
Assistenza per l'immissione dei dati
356
Capitolo 7
Automazione di un'applicazione tramite Visual Basic
Capitolo 7
Il codice definisce un puntatore all'oggetto FileDialog per mezzo di un'istruzione With,
imposta le varie proprietà dell'oggetto, comprese le estensioni di file consentite e il
percorso iniziale, quindi utilizza il metodo Show per visualizzare la finestra di dialogo Apri
file.
Impostando il campo Photo si induce il controllo Immagine a caricare la nuova immagine,
ma il codice imposta anche il messaggio nascosto dietro il controllo Immagine nel caso il
controllo Immagine incontri un problema caricando il file.
Convalida di dati complessi
Anche se puoi certamente trarre vantaggio dalla proprietà Input Mask e dalle proprietà
Validation Rule di campo e di tabella, la tua applicazione spesso possiede regole applicative
aggiuntive che puoi far rispettare solo aggiungendo codice alle maschere fornite per
modificare i dati.
Gli esempi seguenti mostrano come molte delle regole aziendali nelle applicazioni Conrad
Systems Contacts e Housing Reservations sono fatte rispettare con codice Visual Basic.
Verifica di possibili nomi duplicati
Quando progetti una tabella, in generale devi cercare di identificare alcune combinazioni
di campi che siano uniche nell'insieme di tutti i record da utilizzare come chiave primaria.
Quando tuttavia crei una tabella per memorizzare alcune informazioni sulle persone,
solitamente crei un codice artificiale univoco come chiave primaria della tabella, poiché
dovresti combinare molti campi per assicurare un valore univoco.
Infatti anche costruendo una chiave primaria dal nome, dal cognome, dall'indirizzo, dal
codice postale e dal numero telefonico, non si può essere certi dell'univocità della chiave
all'interno di tutte le righe.
L'utilizzo di una chiave primaria artificiale non significa rinunciare a identificare le righe
potenzialmente duplicate. Il codice nella maschera frmContacts dell'applicazione Conrad
Systems Contacts verifica il cognome immesso dall'utente per un nuovo record, e visualizza
un messaggio di avvertimento se trova nomi simili.
Se ad esempio l'utente crea un nuovo record e immette un cognome tipo "Viescas"
(presumendo che il record di John sia ancora presente nella tabella), il codice dell'evento
BeforeUpdate nella maschera scopre il nome simile e visualizza il messaggio d'avvertimento
della figura 7-7.
357
Capitolo 7
Convalida di dati complessi
Figura 7-7 L'applicazione segnala un nome potenzialmente duplicato nell'applicazione Conrad
Systems Contacts.
Il codice cerca potenziali duplicati confrontando i codici Soundex dei cognomi. La formula
per generare un codice Soundex per un nome è stata creata da U.S. National Archives and
Records Administration (NARA). Vengono esaminate le lettere in base al suono e viene
prodotto un codice di quattro caratteri; quando i codici dei due nomi corrispondono,
è probabile che i nomi siano molto simili e producano lo stesso suono. Pertanto, grazie
all'impiego di Soundex, il codice di verifica degli errori non solo trova contatti esistenti
con esattamente lo stesso cognome, ma anche altri contatti il cui nome potrebbe essere lo
stesso pur non essendo stato scritto correttamente.
Access 2010 fornisce una funzione Soundex predefinita (SQL Server sì), ma è facile creare
una semplice routine Visual Basic per generare il codice per un nome. Puoi trovare una
funzione Soundex nel modulo modUtility, in entrambi i database Conrad Systems Contacts
e Housing Reservations. Puoi trovare il codice che controlla un nome potenzialmente
duplicato nella routine evento BeforeUpdate della maschera frmContacts: Il codice è il
seguente:
Private Sub Form_BeforeUpdate(Cancel As Integer)
Dim rst As DAO.Recordset, strNames As String
‘ If on a new row,
If (Me.NewRecord = True) Then
‘ Check for similar name
If Not IsNothing(Me.LastName) Then
‘ Open a recordset to look for similar names
Set rst = CurrentDb.OpenRecordset(“SELECT LastName, FirstName FROM “ & _
“tblContacts WHERE Soundex([LastName]) = ‘” & _
Soundex(Me.LastName) & “’”)
358
Capitolo 7
Automazione di un'applicazione tramite Visual Basic
Capitolo 7
‘ If got some similar names, collect them for the message
Do Until rst.EOF
strNames = strNames & rst!LastName & “, “ & rst!FirstName & vbCrLf
rst.MoveNext
Loop
‘ Done with the recordset
rst.Close
Set rst = Nothing
‘ See if we got some similar names
If Len(strNames) > 0 Then
‘ Yup, issue warning
If vbNo = MsgBox(“CSD Contacts found contacts with similar “ & _
“last names already saved in the database: “ & vbCrLf & vbCrLf & _
strNames & vbCrLf & _
“Are you sure this contact is not a duplicate?”, _
vbQuestion + vbYesNo + vbDefaultButton2, gstrAppTitle) Then
‘ Cancel the save
Cancel = True
End If
End If
End If
End If
End Sub
Il codice esegue la verifica solo quando l'utente sta per salvare una nuova riga. Apre
un recordset per prelevare tutti gli altri record dei contatti dove il codice Soundex del
cognome corrisponde al cognome che sta per essere salvato; include tutti i nomi che trova
nel messaggio di avvertimento, in modo che l'utente possa verificare se il nuovo contatto
è un duplicato. Se l'utente decide di non salvare il record, il codice imposta il parametro
Cancel a True, per indicare ad Access di non salvare il nuovo contatto.
Verifica dell'esistenza di record correlati in fase di
eliminazione di un record
Dovresti definire alcune relazioni tra le tabelle, e chiedere ad Access di far rispettare
l'integrità referenziale, per impedire il salvataggio di record non correlati oppure
l'eliminazione di un record che ha ancora record correlati in altre tabelle. Nella maggior
parte dei casi, non desideri attivare la funzionalità di eliminazione a cascata, per eliminare
automaticamente i record correlati. Ogni volta che l'utente cerca di eliminare un record
che possiede record dipendenti in altre tabelle, Access visualizza un messaggio con l'avviso
che il record non può essere eliminato o modificato, in quanto 'tblXYZ' contiene record
correlati.
Puoi eseguire le tue verifiche nel codice delle maschere, nell'evento Eliminazione (Delete),
e puoi inviare all'utente un messaggio che identifica più chiaramente il problema. Ecco per
esempio il codice della routine evento Delete della maschera frmContacts nell'applicazione
Conrad Systems Contacts:
359
Private Sub Form_Delete(Cancel As Integer)
Dim db As DAO.Database, qd As DAO.QueryDef, rst As DAO.Recordset
Dim varRelate As Variant
‘ Check for related child rows
‘ Get a pointer to this database
Set db = CurrentDb
‘ Open the test query
Set qd = db.QueryDefs(“qryCheckRelateContact”)
‘ Set the contact parameter
qd!ContactNo = Me.ContactID
‘ Open a recordset on the related rows
Set rst = qd.OpenRecordset()
‘ If we got rows, then can’t delete
If Not rst.EOF Then
varRelate = Null
‘ Loop to build the informative error message
rst.MoveFirst
Do Until rst.EOF
‘ Grab all the table names
varRelate = (varRelate + “, “) & rst!TableName
rst.MoveNext
Loop
MsgBox “You cannot delete this Contact because you have “ & _
“related rows in “ & _
varRelate & _
“. Delete these records first, and then delete the Contact.”, _
vbOKOnly + vbCritical, gstrAppTitle
‘ close all objects
rst.Close
qd.Close
Set rst = Nothing
Set qd = Nothing
Set db = Nothing
‘ Cancel the delete
Cancel = True
Exit Sub
End If
‘ No related rows - clean up objects
rst.Close
qd.Close
Set rst = Nothing
Set qd = Nothing
Set db = Nothing
‘ No related rows, so OK to ask if they want to delete!
If vbNo = MsgBox(“Are you sure you want to delete Contact “ & _
Me.txtFullName & “?”, _
vbQuestion + vbYesNo + vbDefaultButton2, gstrAppTitle) Then
Cancel = True
End If
End Sub
Il codice utilizza una speciale query di unione con parametri, qryCheckRelateContact,
che cerca di prelevare le righe correlate da tblCompanyContacts, tblCompanies (il campo
ReferredBy), tblContactEvents e tblContactProducts, e ritorna i nomi delle tabelle che
Capitolo 7
Convalida di dati complessi
360
Capitolo 7
Automazione di un'applicazione tramite Visual Basic
Capitolo 7
hanno righe correlate. Quando il codice trova delle righe ritornate dalla query, formatta
un messaggio contenente i nomi più significativi per l'utente, e include tutte le tabelle che
l'utente deve deselezionare per essere in grado di eliminare il contatto. Il messaggio di
errore standard di Access elenca solo la prima tabella correlata trovata da Access. Anche
quando il controllo di record correlati è negativo, il codice offre all'utente la possibilità di
decidere di non eliminare il contatto.
Verifica dei prerequisiti
In alcune applicazioni ha senso salvare un certo tipo di record, solo se esistono alcuni
record prerequisiti. In un'applicazione per l'iscrizione scolastica per esempio, l'utente
potrebbe verificare che la persona che si sta iscrivendo ha completato con successo i corsi
prerequisiti. Nell'applicazione Conrad Systems Contacts non ha senso vendere il supporto
per un prodotto che il contatto non possiede. Non è possibile chiedere ad Access di
eseguire questo tipo di verifica in una regola di convalida, pertanto devi scrivere il codice o
creare una macro che facciano rispettare questa regola aziendale.
La figura 7-8 mostra il messaggio che l'utente vede quando cerca di vendere il supporto
per un prodotto che il contatto non possiede. Questo messaggio appare anche se l'utente
tenta di vendere lo speciale upgrade al prodotto Multi-user a un contatto che non possiede
il prodotto Single-user indicato come prerequisito.
Figura 7-8 Lo speciale codice della regola aziendale non ti consente di vendere un prodotto con
un prerequisito mancante.
Il codice che fa rispettare questa regola aziendale è nella routine evento BeforeUpdate della
maschera fsubContactProducts: Il codice è il seguente:
361
Private Sub Form_BeforeUpdate(Cancel As Integer)
Dim lngPreReq As Long, strPreReqName As String
‘ Check for prerequisite
If Not IsNothing(Me.cmbProductID.Column(6)) Then
‘ Try to lookup the prerequisite for the contact
lngPreReq = CLng(Me.cmbProductID.Column(6))
If IsNull(DLookup(“ProductID”, “tblContactProducts”, _
“ProductID = “ & lngPreReq & “ And ContactID = “ & _
Me.Parent.ContactID)) Then
‘ Get the name of the prerequisite
strPreReqName = DLookup(“ProductName”, “tblProducts”, _
“ProductID = “ & lngPreReq)
‘ Display error
MsgBox “This contact must own prerequisite product “ & strPreReqName & _
“ before you can sell this product.”, vbCritical, gstrAppTitle
‘ Cancel the edit
Cancel = True
End If
End If
End Sub
La query che fornisce l'origine riga per la casella combinata cmbProductID include ogni
ProductID prerequisito nella sua settima colonna, come mostrato nella figura 7-2. Quando
il codice trova un prerequisito, utilizza la funzione DLookup per verificare che il contatto
corrente possieda già il prodotto richiesto. In caso contrario il codice cerca il nome del
prodotto, lo include in un messaggio di errore visualizzato all'utente, e non consente il
salvataggio del prodotto impostando il parametro Cancel a True. In questo modo viene
fatta rispettare la regola aziendale e viene indicata chiaramente all'utente l'azione correttiva
necessaria.
Mantenimento di uno speciale valore univoco
Quando due soggetti sono legati da una relazione molti a molti nel database, devi
definire una tabella di collegamento per creare la relazione. Per ulteriori informazioni sulla
progettazione di tabelle a supporto di una relazione molti a molti, consulta l'articolo 1,
"Progettazione di un'applicazione di database". Spesso aggiungerai alcuni campi nella
tabella di collegamento, per rendere più chiara la relazione tra una riga in una della tabelle
correlate e la riga corrispondente nell'altra. La figura 7-9 mostra la tabella che definisce il
collegamento tra società e contatti nell'applicazione Conrad Systems Contacts.
Capitolo 7
Convalida di dati complessi
362
Capitolo 7
Automazione di un'applicazione tramite Visual Basic
Capitolo 7
Figura 7-9 La tabella tblCompanyContacts definisce la relazione molti a molti tra società e
contatti.
Due speciali campi sì/no in questa tabella identificano quale società è la predefinita per
un contatto e quale contatto è il predefinito per una società. Un contatto non può avere
due o più società predefinite; analogamente, non ha senso per una società avere più di un
contatto predefinito. Per verificare questo tipo di vincolo speciale di valore univoco, devi
aggiungere alcune regole aziendali nel codice delle maschere che fornisci agli utenti per
modificare questi dati.
Puoi trovare il codice che assicura che vi sia solo una società predefinita per ciascun
contatto nel codice della maschera fsubContactCompanies dell'applicazione Conrad
Systems Contacts (Contacts.accdb). Il codice è nella routine evento BeforeUpdate per il
controllo DefaultForContact della maschera: Il codice è il seguente:
Private Sub DefaultForContact_BeforeUpdate(Cancel As Integer)
‘ Disallow update if there’s no Company ID yet
If IsNothing(Me.CompanyID) Then
MsgBox “You must select a Company / Organization before” & _
“ you can set Default.”, _
vbCritical, gstrAppTitle
Cancel = True
Exit Sub
End If
‘ Make sure there’s only one default
‘ Check only if setting Default = True
If (Me.DefaultForContact = True) Then
‘ Try to lookup another contact set Default
If Not IsNothing(DLookup(“ContactID”, “tblCompanyContacts”, _
“ContactID = “ & Me.Parent.ContactID & _
“ AND CompanyID <> “ & Me.CompanyID & _
“ AND DefaultForContact = True”)) Then
‘ ooops...
MsgBox “You have designated another Company as the” & _
363
“ Default for this Contact.” & _
“ You must remove that designation before you” & _
“ can mark this Company as the Default.”, _
vbCritical, gstrAppTitle
Cancel = True
End If
End If
End Sub
Per prima cosa, il codice verifica che l'utente abbia scelto una società per questo record.
Le proprietà Collega campi secondari e Collega campi master del controllo sottomaschera
forniscono il ContactID. Se l'utente sta cercando di contrassegnare questa società come la
società predefinita per il contatto, il codice utilizza la funzione DLookup per vedere se esiste
un altro record (nella tabella tblCompanyContacts per il contatto corrente) contrassegnato
come predefinito. Se trova un tale record duplicato, avvisa l'utente e imposta il parametro
Cancel a True per impedire il salvataggio della modifica al controllo. Troverai codice simile
nella maschera fsubCompanyContactst, che assicura che solo un contatto sia la chiave
primaria per una società.
Verifica dell'esistenza di dati sovrapposti
Quando crei un'applicazione che mantiene traccia della programmazione di eventi o
prenotazioni che possono estendersi per un periodo di tempo, devi molto probabilmente
assicurare che un nuovo evento o prenotazione non si sovrapponga a uno esistente. Questa
operazione può essere complicata, specialmente nel caso in cui i record contengono date
e/o ore, iniziali e finali.
L'applicazione Housing Reservations (Housing.accdb) deve assicurare che un impiegato
non immetta una richiesta di prenotazione che si sovrappone a un'altra. Per vedere come
funziona, apri il database e poi apri la maschera frmSplash per avviare l'applicazione. Scegli
il nome di un dipendente qualunque dalla casella combinata nella finestra di dialogo
Sign-on (ad esempio Jack Richins), digita password come password e fai clic sul pulsante
Sign On. Nel pannello comandi principale, fai clic sul pulsante Reservation Requests. Se
viene visualizzata la finestra di dialogo Edit Reservation Requests (poiché sei connesso
come manager), fai clic sul pulsante Edit All.
La maschera Reservation Requests non ti consente di immettere una data di prenotazione
relativa al passato. Fai clic nella nuova riga vuota nell'elenco delle richieste di prenotazione,
immetti una richiesta di prenotazione per la settimana prossima della durata di alcuni
giorni e salva la riga. Ricorda che, per scegliere le date desiderate, puoi fare clic sui pulsanti
Calendar visualizzati accanto ai campi data quando è attivo il campo. Immetti un'altra
richiesta che si sovrappone alla prenotazione appena creata e cerca di salvare la riga;
dovresti vedere un messaggio di avvertimento simile a quello della figura 7-10.
Capitolo 7
Convalida di dati complessi
364
Capitolo 7
Automazione di un'applicazione tramite Visual Basic
Capitolo 7
Figura 7-10 L'applicazione Housing Reservations visualizza un avvertimento quando cerchi di
salvare una richiesta di prenotazione che si sovrappone a una esistente.
Se fai clic su No, il codice annulla il salvataggio e puoi correggere il record. Come puoi
notare, l'applicazione consente anche di fare clic su Sì per salvare il duplicato, poiché il
dipendente potrebbe volere prenotare intenzionalmente due o più stanze in date che si
sovrappongono. Il codice che esegue questo controllo nell'evento BeforeUpdate della
maschera fsubReservationRequests è il seguente (nota che questo codice si trova vicino alla
fine del codice di evento BeforeUpdate):
Dim varNum As Variant
‘ Check for overlap with existing request
‘ Try to grab RequestID - will be Null on unsaved row
varNum = Me.RequestID
If IsNull(varNum) Then varNum = 0 ‘ Set dummy value
If Not IsNull(DLookup(“RequestID”, “tblReservationRequests”, _
“(EmployeeNumber = “ & _
Me.Parent.EmployeeNumber & “) AND (CheckInDate < #” & _
Format(Me.CheckOutDate, “mm/dd/yyyy”) & _
“#) AND (CheckOutDate > #” & _
Format(Me.CheckInDate, “mm/dd/yyyy”) & “#) AND (RequestID <> “ & _
varNum & “)”)) Then
If vbNo = MsgBox(“You already have a room request “ & _
“that overlaps the dates you have “ & _
“requested. Are you sure you want to make this request?”, _
vbQuestion + vbYesNo + vbDefaultButton2, gstrAppTitle) Then
Cancel = True
Exit Sub
End If
End If
Il codice utilizza la funzione DLookup per vedere se esiste un'altra prenotazione (ma con
un differente RequestID) per lo stesso impiegato, con date che si sovrappongono. I criteri
richiedono che ogni record abbia una data di check-in anteriore alla data di check-out
365
richiesta (un dipendente può legittimamente lasciare una stanza e quindi registrarsi di
nuovo nella stessa data) e una data di check-out successiva alla data di check-in richiesta.
Potresti essere tentato di creare criteri più complessi, che verificano tutte le combinazioni
di prenotazioni che si sovrappongono con l'inizio o con la fine del periodo richiesto, che si
estendono sull'intero periodo richiesto oppure che sono contenute interamente all'interno
del periodo richiesto, ma i due semplici test sono sufficienti.
Controllo dell'ordine di tabulazione su una maschera a
più pagine
Puoi creare una maschera a più pagine come metodo per gestire la visualizzazione di dati
che non possono essere visualizzati tutti insieme sullo schermo del computer. Puoi inoltre
controllare l'ordine di tabulazione sulla maschera, impostando la proprietà Sequenza
della maschera a Pagina corrente. Uno svantaggio di questo approccio è l'impossibilità
di utilizzare i tasti Tab o Maiusc+Tab per passare ad altre pagine o record. A tal fine,
devi infatti utilizzare i tasti PagSu e PagGiù o i selettori di record. Per ripristinare questa
funzionalità, puoi impostare la proprietà Sequenza su Tutti i record, ma alcuni strani fatti
possono verificarsi se non aggiungi il codice per gestire l'allineamento della pagina.
Per vedere cosa accade, apri la maschera frmXmplContactsPages del database Conrad
Systems Contacts (Contacts.accdb) nel riquadro di spostamento. Premi PagGiù per spostarti
sul campo Home Address dell'ultimo contatto; fatto ciò, premi una volta Maiusc+Tab. Lo
schermo dovrebbe essere simile a quello della figura 7-11.
Figura 7-11 Quando premi Maiusc+Tab nel campo Home Address in frmXmplContactsPages, la
pagina della maschera non viene allineata correttamente.
Se lasci la proprietà Sequenza impostata a Tutti i record o a Record corrente, la tabulazione
oltre i limiti della pagina causa un cattivo allineamento, a meno che non aggiungi
codice specifico per gestire l'allineamento stesso. Ciò che accade è che Access scorre la
visualizzazione della maschera solo per mostrare il controllo sul quale sei posizionato dopo
Capitolo 7
Controllo dell'ordine di tabulazione su una maschera a più pagine
366
Capitolo 7
Automazione di un'applicazione tramite Visual Basic
Capitolo 7
aver premuto il tasto Tab (in questo esempio ti sei posizionato sulla casella di testo Notes).
Apri la maschera frmContactsPages che contiene il codice per risolvere questo problema e
prova lo stesso esercizio. Dovresti scoprire che Maiusc+Tab ti posiziona sul campo Notes,
ma la maschera scorre per mostrare l'intera prima pagina.
Per consentire la tabulazione oltre il limite di una pagina, mantenendo un corretto
allineamento della pagina, sono necessarie alcune routine nell'evento Invio (Enter) per il
primo e l'ultimo controllo che può ricevere l'attivazione, su ciascuna pagina. Se esamini il
codice della maschera frmContactsPages, troverai queste quattro routine:
Private Sub ContactID_Enter()
‘ If tabbing forward into this field from previous record
‘ align page 1
Me.GoToPage 1
End Sub
Private Sub HomeAddress_Enter()
‘ If tabbing forward into this field, align page 2
Me.GoToPage 2
End Sub
Private Sub Notes_Enter()
On Error Resume Next
‘ If tabbing backward into the last control on page 1, align it
Me.GoToPage 1
End Sub
Private Sub Photo_Enter()
On Error Resume Next
‘ If tabbing backward into the last control on page 2, align it
Me.GoToPage 2
End Sub
Si potrebbe obiettare che questo è uno dei segmenti di codice più semplice nei database
di esempio, ma questa attenzione per il dettaglio renderà gli utenti della tua applicazione
molto felici.
Nota
Il codice viene eseguito anche quando premi Maiusc+Tab nei controlli ContactID e
HomeAddress, quando passi ai controlli Notes o Photo, oppure quando fai clic in
qualunque controllo. Access si rende conto che la maschera è già sulla pagina richiesta
in ciascun caso, e quindi non fa niente.
Automazione della selezione dei dati
367
Una delle attività più comuni da automatizzare in un'applicazione di database è il filtro dei
dati. Quando un database contiene migliaia di record, gli utenti in genere devono lavorare
con pochi record alla volta; se le maschere di modifica visualizzano sempre tutti i record,
le prestazioni possono soffrirne enormemente. È buona norma consentire all'utente di
specificare facilmente un sottoinsieme dei record; in questa sezione sono esaminati quattro
metodi per farlo.
Utilizzo di una casella di riepilogo a scelta multipla
In Windows e in Access lavori sempre con le caselle di riepilogo. L'elenco di file in Esplora
risorse e il riquadro di spostamento di Access 2010 sono caselle di riepilogo, come lo è
anche l'elenco delle proprietà in qualsiasi scheda della finestra delle proprietà. Nell'elenco
della finestra delle proprietà, puoi selezionare una sola proprietà alla volta. se fai clic su
un'altra proprietà, la precedente viene deselezionata. Questa è una casella di riepilogo
semplice. In Esplora risorse puoi selezionare un file, puoi selezionare più file non contigui,
tenendo premuto il tasto Ctrl mentre fai clic, oppure puoi selezionare un intervallo di file,
tenendo premuto il tasto Maiusc mentre fai clic; questa è una casella di riepilogo a scelta
multipla.
Supponi di utilizzare l'applicazione Conrad Systems Contacts (Contacts.accdb) e di essere
interessato a esaminare i dettagli di molti contatti, ma di voler molto raramente esaminare
l'intero elenco. Avvia l'applicazione aprendo la maschera frmSplash, seleziona John Viescas
come User Name, fai clic su Sign On (non è necessaria alcuna password) Fai clic sul pulsante
Contacts nella maschera del pannello comandi principale; fatto ciò, l'applicazione apre la
maschera Select Contacts (frmContactList). Come mostrato nella figura 7-12, la maschera
frmContactList contiene una casella di riepilogo a scelta multipla.
Nota
Se l'opzione Don't Show Contact List è selezionata nel profilo utente di John, non
vedrai la finestra di dialogo Select Contacts. Se la maschera Contacts viene aperta
quando fai clic sul pulsante Contacts nel pannello comandi principale, chiudila e fai clic
sul pulsante Users. Deselezona l'opzione Clear the Don't Show Contact List nel profilo
di John, salva il record, e chiudi la maschera. Dovresti ora vedere la finestra di dialogo
Select Contacts, quando fai clic sul pulsante Contacts sul pannello comandi principale.
Capitolo 7
Automazione della selezione dei dati
368
Capitolo 7
Automazione di un'applicazione tramite Visual Basic
Capitolo 7
Figura 7-12 Puoi selezionare più record di contatti da modificare nella maschera frmContactList.
In questa casella di riepilogo i contatti sono mostrati in ordine alfabetico per cognome,
e l'elenco è associato al campo ContactID della tabella sottostante. Puoi modificare ogni
singolo contatto semplicemente facendo doppio clic sul nome della persona. Puoi spostare
l'evidenziazione in su o in giù, per mezzo dei tasti freccia. Puoi anche digitare la prima
lettera del cognome di un contatto per saltare al contatto successivo il cui cognome
comincia con quella lettera. Puoi tenere premuto il tasto Maiusc e utilizzare i tasti freccia
per estendere la selezione a più nomi. Puoi infine tenere premuto il tasto Maiusc, o il tasto
Ctrl, e utilizzare il mouse per selezionare più nomi.
La figura 7-12 mostra tre contatti selezionati per mezzo del tasto Ctrl e del mouse. Quando
fai clic su Edit, viene aperta la maschera frmContacts con solo i record che hai selezionato.
La didascalia a destra della casella del numero di record indica che sono disponibili tre
record e che il recordset è filtrato, come mostrato nella figura 7-13.
369
Capitolo 7
Automazione della selezione dei dati
Figura 7-13 Dopo aver selezionato i record che desideri modificare nella maschera
frmContactList, l'applicazione apre la maschera frmContacts e visualizza solo quei record.
Per capire come funziona, devi esaminare la struttura della maschera frmContactList. Fai
clic su Exit sul pannello comandi principale, per tornare al riquadro di spostamento. Se
l'applicazione si offre di creare un backup, fai clic su Sì nella finestra messaggio "Are you
sure you want to exit?", quindi fai clic su No. Seleziona frmContactList e apri la maschera
nella visualizzazione Struttura, come mostrato nella figura 7-14. Fai clic sul controllo
casella di riepilogo e apri la Finestra delle proprietà per vedere come è definita la casella di
riepilogo. La casella di riepilogo utilizza due colonne dalla query qlkpContacts, nasconde
ContactID (la chiave primaria che consente una ricerca veloce) nella prima colonna
e visualizza il nome del contatto nella seconda colonna. L'aspetto più importante di
questa casella di riepilogo è che la sua proprietà Selezione multipla è impostata a Estesa.
L'impostazione estesa offre le funzionalità complete Ctrl+Clic o Maiusc+Clic che vedi
nella maggior parte delle caselle di riepilogo in Windows. Il valore predefinito per questa
proprietà è Nessuna, che consente di selezionare solo un valore alla volta. Puoi impostarla
su Semplice se desideri selezionare o deselezionare più valori per mezzo del mouse o della
barra spaziatrice.
370
Capitolo 7
Automazione di un'applicazione tramite Visual Basic
Capitolo 7
Figura 7-14 La casella di riepilogo a scelta multipla nella maschera frmContactList ha la
proprietà Selezione multipla impostata su Estesa.
Se scorri le proprietà sulla scheda Evento, troverai una routine evento definita per l'evento
Su doppio clic. Il codice per questa routine evento, chiamata quando fai doppio clic su un
elemento nella casella di riepilogo, esegue solo la routine cmdSome_Click. Fai clic destro
sul pulsante di comando cmdSome (quello la cui didascalia dice Edit), e seleziona Genera
evento nel menu di scelta rapida, per accedere alla routine cmdSome_Click che esegue
tutto il lavoro.
Private Sub cmdSome_Click()
Dim strWhere As String, varItem As Variant
‘ Request to edit items selected in the list box
‘ If no items selected, then nothing to do
If Me!lstCName.ItemsSelected.Count = 0 Then Exit Sub
‘ Loop through the items selected collection
For Each varItem In Me!lstCName.ItemsSelected
‘ Grab the ContactID column for each selected item
strWhere = strWhere & Me!lstCName.Column(0, varItem) & “,”
Next varItem
‘ Throw away the extra comma on the “IN” string
strWhere = Left$(strWhere, Len(strWhere) - 1)
‘ Open the contacts form filtered on the selected contacts
strWhere = “[ContactID] IN (“ & strWhere & “) And (Inactive = False)”
DoCmd.OpenForm FormName:=”frmContacts”, WhereCondition:=strWhere
DoCmd.Close acForm, Me.Name
End Sub
Quando imposti la proprietà Selezione multipla di una casella di riepilogo a qualcosa di
diverso da Nessuna, puoi esaminare l'insieme ItemsSelected del controllo, per determinare
cosa è stato selezionato. Per prima cosa, il codice Visual Basic della routine cmdSome_Click
controlla la proprietà Count della raccolta ItemsSelected del controllo per stabilire se è
stato selezionato qualcosa. Se il valore di Count è 0, non ci sono operazioni di eseguire e la
routine si interrompe.
371
L'insieme ItemsSelected è composto da valori di tipo variant, ciascuno dei quali fornisce un
indice a un elemento evidenziato nella casella di riepilogo. Il ciclo For Each chiede a Visual
Basic di ciclare su tutti i valori variant disponibili nell'insieme, uno alla volta. All'interno del
ciclo il codice utilizza il valore del tipo variant per recuperare il ContactID dall'elenco. Le
caselle di riepilogo hanno anche una proprietà Column; puoi fare riferimento a tutti i valori
nell'elenco utilizzando un'istruzione quale
Me.ListBoxName.Column(ColumnNum, RowNum)
dove NomeCasellaRiepilogo è il nome del controllo casella di riepilogo, NumColonna è il
numero di colonna relativo (la prima colonna è 0, la seconda è 1, e così via) e NumRiga
è il numero di riga relativo (anch'esso comincia da 0). I valori di tipo variant nell'insieme
ItemsSelected ritornano il numero di riga relativo. Questo codice Visual Basic utilizza la
colonna 0 e i valori nell'insieme ItemsSelected per aggiungere tutti i ContactID selezionati
a una variabile di tipo stringa, separati da virgole. Un elenco di valori separati dal punto e
virgola è ideale per una clausola IN.
Dopo aver recuperato tutti i ContactID, l'istruzione successiva rimuove la virgola finale dalla
stringa. La clausola Where finale include un ulteriore criterio, per visualizzare solo i contatti
attivi. Il comando DoCmd.OpenForm utilizza la stringa risultante, per creare una clausola
filtro all'apertura della maschera. Infine, la macro chiude la maschera CityInformation. (Me.
Name è il nome della maschera attuale.)
Utilizzo di una query in base a maschera personalizzata
Supponi di voler eseguire una ricerca più complessa sulla maschera frmContacts,
utilizzando criteri quali il tipo di contatto, la società o i prodotti disponibili, invece che
semplicemente il nome del contatto. Potresti insegnare ai tuoi utenti come utilizzare la
funzionalità Filtro in base a maschera, per creare la ricerca, oppure potresti utilizzare Filtro
in base a maschera per costruire facilmente criteri OR multipli su semplici test. Ma se
desideri per esempio trovare tutti i contatti che possiedono l'edizione Single User oppure
che hai contattato entro certe date, non vi è modo di costruire questa richiesta utilizzando
le funzionalità di filtro standard. Infatti, quando definisci un filtro per una sottomaschera,
come ad esempio la sottomaschera Events della maschera frmContacts tramite la
funzionalità Filtro in base a maschera, filtri solo le righe della sottomaschera. Così facendo
non puoi trovare i contatti che hanno solo una riga di sottomaschera corrispondente.
L'unica soluzione è dunque quella di fornire una query in base a maschera personalizzata,
che fornisce le opzioni per eseguire una ricerca su tutti i campi importanti, e poi creare
la clausola Where per risolvere il problema della ricerca utilizzando il codice Visual Basic.
Apri l'applicazione Conrad Systems Contacts Se sei tornato al riquadro di spostamento,
puoi avviare l'applicazione aprendo frmSplash. connettiti, fai clic sul pulsante Contacts
sul pannello comandi principale e poi fai clic sul pulsante Search, nella finestra di dialogo
Select Contacts. A questo punto dovresti vedere la maschera fdlgContactSearch, come
mostrato nella figura 7-15.
Capitolo 7
Automazione della selezione dei dati
372
Capitolo 7
Automazione di un'applicazione tramite Visual Basic
Capitolo 7
Figura 7-15 Per eseguire una ricerca complessa, puoi progettare una query in base a maschera
personalizzata.
Prova a selezionare i contatti il cui cognome comincia con la lettera M e che hai contattato
tra il 1° settembre 2010 e il 15 dicembre 2010 e che possiedono il prodotto BO$$ Single
User (dall'elenco a discesa Owns Product). Facendo clic sul pulsante Search, dovresti vedere
la maschera frmContacts aperta con due contatti visualizzati.
Per capire come funziona, devi esplorare la struttura della maschera fdlgContactSearch.
Passa al riquadro di spostamento (premendo F11) e apri la maschera nella visualizzazione
Struttura. Dovresti vedere una finestra simile a quella mostrata nella figura 7-16. Nota
che la maschera non è collegata ad alcuna origine record. i controlli non devono essere
associati in modo da poter accettare qualunque criterio immesso dall'utente.
Figura 7-16 Quando osservi la maschera fdlgContactSearch nella visualizzazione Struttura, puoi
vedere che non possiede un'origine record.
Automazione della selezione dei dati
373
Private Sub cmdSearch_Click()
Dim varWhere As Variant, varDateSearch As Variant
Dim rst As DAO.Recordset
‘ Initialize to Null
varWhere = Null
varDateSearch = Null
‘ First, validate the dates
‘ If there’s something in Contact Date From
If Not IsNothing(Me.txtContactFrom) Then
‘ First, make sure it’s a valid date
If Not IsDate(Format(Me.txtContactFrom, “mm/dd/yyyy”)) Then
‘ Nope, warn them and bail
MsgBox “The value in Contact From is not a valid date.”, _
vbCritical, gstrAppTitle
Exit Sub
End If
‘ Now see if they specified a “to” date
If Not IsNothing(Me.txtContactTo) Then
‘ First, make sure it’s a valid date
If Not IsDate(Format(Me.txtContactTo, “mm/dd/yyyy”)) Then
‘ Nope, warn them and bail
MsgBox “The value in Contact To is not a valid date.”, _
vbCritical, gstrAppTitle
Exit Sub
End If
‘ Got two dates, now make sure “to” is >= “from”
If Format(Me.txtContactTo, “mm/dd/yyyy”) < _
Format(Me.txtContactFrom, “mm/dd/yyyy”) Then
MsgBox “Contact To date must be greater than “ & _
“or equal to Contact From date.”, _
vbCritical, gstrAppTitle
Exit Sub
End If
End If
Else
‘ No “from” but did they specify a “to”?
If Not IsNothing(Me.txtContactTo) Then
‘ Make sure it’s a valid date
If Not IsDate(Format(Me.txtContactTo, “mm/dd/yyyy”)) Then
‘ Nope, warn them and bail
MsgBox “The value in Contact To is not a valid date.”, _
vbCritical, gstrAppTitle
Exit Sub
End If
End If
End If
‘ If there’s something in Follow-up Date From
If Not IsNothing(Me.txtFollowUpFrom) Then
‘ First, make sure it’s a valid date
If Not IsDate(Format(Me.txtFollowUpFrom, “mm/dd/yyyy”)) Then
‘ Nope, warn them and bail
MsgBox “The value in Follow-up From is not a valid date.”, _
Capitolo 7
Il grosso del lavoro avviene quando fai clic sul pulsante Search. Il codice della routine
evento per l'evento Clic del pulsante Search è il seguente.
374
Capitolo 7
Automazione di un'applicazione tramite Visual Basic
Capitolo 7
vbCritical, gstrAppTitle
Exit Sub
End If
‘ Now see if they specified a “to” date
If Not IsNothing(Me.txtFollowUpTo) Then
‘ First, make sure it’s a valid date
If Not IsDate(Format(Me.txtFollowUpTo, “mm/dd/yyyy”)) Then
‘ Nope, warn them and bail
MsgBox “The value in Follow-up To is not a valid date.”, _
vbCritical, gstrAppTitle
Exit Sub
End If
‘ Got two dates, now make sure “to” is >= “from”
If Format(Me.txtFollowUpTo, “mm/dd/yyyy”) < _
Format(Me.txtFollowUpFrom, “mm/dd/yyyy”) Then
MsgBox “Follow-up To date must be greater than “ & _
“or equal to Follow-up From date.”, _
vbCritical, gstrAppTitle
Exit Sub
End If
End If
Else
‘ No “from” but did they specify a “to”?
If Not IsNothing(Me.txtFollowUpTo) Then
‘ Make sure it’s a valid date
If Not IsDate(Format(Me.txtFollowUpTo, “mm/dd/yyyy”)) Then
‘ Nope, warn them and bail
MsgBox “The value in Follow-up To is not a valid date.”, _
vbCritical, gstrAppTitle
Exit Sub
End If
End If
End If
‘ OK, start building the filter
‘ If specified a contact type value
If Not IsNothing(Me.cmbContactType) Then
‘ .. build the predicate
varWhere = “(ContactType.Value = ‘” & Me.cmbContactType & “’)”
End If
‘ Do Last Name next
If Not IsNothing(Me.txtLastName) Then
‘ .. build the predicate
‘ Note: taking advantage of Null propagation
‘ so we don’t have to test for any previous predicate
varWhere = (varWhere + “ AND “) & “([LastName] LIKE ‘” & _
Me.txtLastName & “*’)”
End If
‘ Do First Name next
If Not IsNothing(Me.txtFirstName) Then
‘ .. build the predicate
varWhere = (varWhere + “ AND “) & “([FirstName] LIKE ‘” & _
Me.txtFirstName & “*’)”
End If
‘ Do Company next
If Not IsNothing(Me.cmbCompanyID) Then
‘ .. build the predicate
‘ Must use a subquery here because the value is in a linking table...
varWhere = (varWhere + “ AND “) & _
“([ContactID] IN (SELECT ContactID FROM tblCompanyContacts “ & _
“WHERE tblCompanyContacts.CompanyID = “ & Me.cmbCompanyID & “))”
End If
‘ Do City next
If Not IsNothing(Me.txtCity) Then
‘ .. build the predicate
‘ Test for both Work and Home city
varWhere = (varWhere + “ AND “) & “(([WorkCity] LIKE ‘” & _
Me.txtCity & “*’)” & _
“ OR ([HomeCity] LIKE ‘” & Me.txtCity & “*’))”
End If
‘ Do State next
If Not IsNothing(Me.txtState) Then
‘ .. build the predicate
‘ Test for both Work and Home state
varWhere = (varWhere + “ AND “) & “(([WorkStateOrProvince] LIKE ‘” & _
Me.txtState & “*’)” & _
“ OR ([HomeStateOrProvince] LIKE ‘” & Me.txtState & “*’))”
End If
‘ Do Contact date(s) next -- this is a toughie
‘
because we want to end up with one filter on the subquery table
‘
for both Contact Date range and FollowUp Date range
‘ Check Contact From first
If Not IsNothing(Me.txtContactFrom) Then
‘ .. build the predicate
varDateSearch = “tblContactEvents.ContactDateTime >= #” & _
Format(Me.txtContactFrom, “mm/dd/yyyy”) & “#”
End If
‘ Now do Contact To
If Not IsNothing(Me.txtContactTo) Then
‘ .. add to the predicate, but add one because ContactDateTime includes
‘ a date AND a time
varDateSearch = (varDateSearch + “ AND “) & _
“tblContactEvents.ContactDateTime < #” & _
CDate(Format(Me.txtContactTo, “mm/dd/yyyy”)) + 1 & “#”
End If
‘ Now do Follow-up From
If Not IsNothing(Me.txtFollowUpFrom) Then
‘ .. add to the predicate
varDateSearch = (varDateSearch + “ AND “) & _
“tblContactEvents.ContactFollowUpDate >= #” & _
Format(Me.txtFollowUpFrom, “mm/dd/yyyy”) & “#”
End If
‘ Finally, do Follow-up To
If Not IsNothing(Me.txtFollowUpTo) Then
‘ .. add to the predicate
varDateSearch = (varDateSearch + “ AND “) & _
“tblContactEvents.ContactFollowUpDate <= #” & _
Format(Me.txtFollowUpTo, “mm/dd/yyyy”) & “#”
End If
‘ Did we build any date filter?
If Not IsNothing(varDateSearch) Then
375
Capitolo 7
Automazione della selezione dei dati
376
Capitolo 7
Automazione di un'applicazione tramite Visual Basic
Capitolo 7
‘ OK, add to the overall filter
‘ Must use a subquery here because the value is in a linking table...
varWhere = (varWhere + “ AND “) & _
“([ContactID] IN (SELECT ContactID FROM tblContactEvents “ & _
“WHERE “ & varDateSearch & “))”
End If
‘ Do Product
If Not IsNothing(Me.cmbProductID) Then
‘ .. build the predicate
‘ Must use a subquery here because the value is in a linking table...
varWhere = (varWhere + “ AND “) & _
“([ContactID] IN (SELECT ContactID FROM tblContactProducts “ & _
“WHERE tblContactProducts.ProductID = “ & Me.cmbProductID & “))”
End If
‘ Finally, do the Inactive check box
If (Me.chkInactive = False) Then
‘ Build a filter to exclude inactive contacts
varWhere = (varWhere + “ AND “) & “(Inactive = False)”
End If
‘ Check to see that we built a filter
If IsNothing(varWhere) Then
MsgBox “You must enter at least one search criteria.”, _
vbInformation, gstrAppTitle
Exit Sub
End If
‘ Open a recordset to see if any rows returned with this filter
Set rst = CurrentDb.OpenRecordset(“SELECT * FROM tblContacts “ & _
“WHERE “ & varWhere)
‘ See if found none
If rst.RecordCount = 0 Then
MsgBox “No Contacts meet your criteria.”, vbInformation, gstrAppTitle
‘ Clean up recordset
rst.Close
Set rst = Nothing
Exit Sub
End If
‘ Hide me to fix later focus problems
Me.Visible = False
‘ Move to last to find out how many
rst.MoveLast
‘ If 5 or less or frmContacts already open,
If (rst.RecordCount < 6) Or IsFormLoaded(“frmContacts”) Then
‘ Open Contacts filtered
‘ Note: if form already open, this just applies the filter
DoCmd.OpenForm “frmContacts”, WhereCondition:=varWhere
‘ Make sure focus is on contacts
Forms!frmContacts.SetFocus
Else
‘ Ask if they want to see a summary list first
If vbYes = MsgBox(“Your search found “ & rst.RecordCount & _
“ contacts. “ & _
“Do you want to see a summary list first?”, _
vbQuestion + vbYesNo, gstrAppTitle) Then
‘ Show the summary
DoCmd.OpenForm “frmContactSummary”, WhereCondition:=varWhere
377
‘ Make sure focus is on contact summary
Forms!frmContactSummary.SetFocus
Else
‘ Show the full contacts info filtered
DoCmd.OpenForm “frmContacts”, WhereCondition:=varWhere
‘ Make sure focus is on contacts
Forms!frmContacts.SetFocus
End If
End If
‘ Done
DoCmd.Close acForm, Me.Name
‘ Clean up recordset
rst.Close
Set rst = Nothing
End Sub
La prima parte della routine convalida i valori From e To delle date di contatto e follow-up.
Se vi sono date non valide, oppure se una data From è maggiore della corrispondente data
To, il codice visualizza un appropriato messaggio di avvertimento e si interrompe.
I successivi segmenti di codice creano una stringa WHERE, esaminando i controlli non
associati, uno alla volta. Se il campo corrispondente è una stringa, il codice crea un
test utilizzando il predicato LIKE, in modo che qualunque cosa l'utente immetta possa
corrispondere a qualunque parte del campo nella tabella sottostante; non tutti i campi sono
però stringhe. Quando la funzione aggiunge una clausola durante la creazione della stringa
WHERE, inserisce la parola chiave AND tra le clausole, se esistono già altre clausole. Poiché
la variabile contenente la clausola WHERE è di tipo Variant inizializzata a Null, il codice può
utilizzare una concatenazione con l'operatore + per aggiungere facoltativamente la parola
chiave AND. Nota che poiché il campo ContactType è un campo a valori multipli, il codice
cerca in modo specifico la proprietà Valore del campo.
L'origine record per la maschera frmContacts non include direttamente le informazioni sugli
eventi o sui prodotti del contatto, e quindi la routine deve creare un predicato utilizzando
una sottoquery, se chiedi una ricerca basata sulla data Contact, sulla data Follow-up oppure
sul prodotto. Nel caso della data Contact o Follow-up, il codice crea una stringa di filtro
(varDateSearch), poiché entrambi i campi sono nella stessa tabella (tblContactEvents). Se
richiedi la verifica di un intervallo di date, il codice crea i criteri utilizzando una sottoquery,
che trova il ContactID dai record della tabella tblContactEvents che rientrano nell'intervallo.
Nel caso di una ricerca per prodotto, il codice crea i criteri utilizzando una sottoquery,
che trova il ContactID dai record della tabella tblContactProducts, che corrispondono
al prodotto che hai selezionato. Se non selezioni la casella di controllo Include Inactive
Contacts, il codice aggiunge un test per includere solo i record attivi.
Dopo aver esaminato tutti i possibili valori del filtro che l'utente potrebbe aver immesso, il
codice verifica se c'è qualcosa nella stringa del filtro (varWhere). Non serve a niente aprire
la maschera senza un filtro, e quindi il codice visualizza un messaggio ed esce, lasciando la
maschera aperta per consentire all'utente di riprovare.
Capitolo 7
Automazione della selezione dei dati
378
Capitolo 7
Automazione di un'applicazione tramite Visual Basic
Capitolo 7
La parte finale della routine crea un semplice recordset sulla tabella tblContacts, utilizzato
nelle maschere frmContacts e frmContactSummary, applicando la clausola WHERE creata
dal codice nella prima parte della routine. Se non trova nessun record, utilizza la funzione
MsgBox per informare l'utente, e poi lascia all'utente la possibilità di riprovare.
Quando apri la prima volta un oggetto Recordset nel codice, la sua proprietà RecordCount
è 0, se il recordset è vuoto, ed è maggiore di 0, se il recordset contiene record. La proprietà
RecordCount di un oggetto Recordset indica il numero di righe visitate e non il numero
di righe nel recordset; pertanto, se trova delle righe, la routine passa all'ultima riga del
recordset temporaneo per ottenere un conteggio accurato. Quando il conteggio di
record è maggiore di 5 e la maschera frmContacts non è già aperta, la routine utilizza
la funzione MsgBox per offrire all'utente la possibilità di vedere un riepilogo dei record
che si trovano nella maschera frmContactSummary, oppure di visualizzare i record che si
trovano direttamente nella maschera frmContacts (come notato in precedenza, entrambe
le maschere utilizzano la stessa origine record, e dunque il codice può applicare il filtro da
esso creato, all'apertura di entrambe le maschere). Esamineremo il funzionamento della
maschera frmContactSummary nella prossima sezione.
Selezione da un elenco di riepilogo
Se più di cinque righe soddisfano i criteri immessi, l'utente deve fare una scelta, come
hai visto nella routine cmdSearch_Click nella sezione precedente. Per esaminare questa
caratteristica in maggior dettaglio, assicurati che la maschera frmContacts non sia aperta, e
ricerca i contatti con Contact Type uguale a Customer, nella maschera fdlgContactSearch. Il
risultato dovrebbe essere simile a quello della figura 7-17, in cui vi sono 30 contatti di tipo
Customer.
Figura 7-17 Questa finestra messaggio appare quando la routine cmdSearch_Click restituisce più
di cinque righe.
Se fai clic su Sì, la routine cmdSearch_Click apre la maschera Contact Search Summary
(frmContactSummary), come mostrato nella figura 7-18. Puoi posizionarti su qualunque
riga e attivarla (assicurati che l'indicatore di selettore di riga stia puntando a quella riga),
quindi puoi fare clic sul pulsante View Details per aprire la maschera frmContacts e
visualizzare i dettagli per il contatto selezionato. Questo è un modo molto efficiente per
aiutare l'utente a restringere una ricerca a un particolare contatto.
379
Capitolo 7
Automazione della selezione dei dati
Figura 7-18 Puoi selezionare uno specifico contatto dalla maschera di riepilogo di una ricerca.
Puoi inoltre fare doppio clic sul campo Contact ID o sul campo Name, per vedere i dettagli
relativi a quel contatto. Poiché questo elenco è già filtrato in base ai criteri specificati nella
maschera fdlgContactSearch, il codice che risponde alla tua richiesta crea un semplice filtro
su Contact ID per rendere più efficiente l'apertura della maschera frmContacts. Il codice di
questa maschera, che risponde alla tua richiesta nell'evento Click del pulsante di comando
Details, è il seguente:
Private Sub Details_Click()
Dim strFilter As String
‘ They asked for details (or double-clicked one of the controls)
‘ Set up the filter
strFilter = “(ContactID = “ & Me.ContactID & “)”
‘ Open contacts filtered on the current row
DoCmd.OpenForm FormName:=”frmContacts”, WhereCondition:=strFilter
‘ Close me
DoCmd.Close acForm, Me.Name
‘ Put focus on contacts
Forms!frmContacts.SetFocus
End Sub
Filtro di un elenco con un altro elenco
Come puoi aver notato durante le modifiche ai prodotti nella scheda Products della
maschera frmContacts (figura 7-1), puoi prima scegliere un tipo di prodotto per restringere
l'elenco dei prodotti, per poi scegliere il prodotto desiderato. Nell'applicazione di esempio
ci sono solo undici prodotti, e quindi restringere la scelta dei prodotti non è molto utile, ma
puoi immaginare come una funzionalità come questa sarebbe assolutamente necessaria in
un'applicazione con migliaia di prodotti disponibili per la vendita.
Il segreto è dato dal fatto che l'origine riga per la casella combinata Product è una query
con parametri, che filtra i prodotti in base al tipo di prodotto scelto. Quando utilizzi questa
380
Capitolo 7
Automazione di un'applicazione tramite Visual Basic
Capitolo 7
tecnica in una maschera in visualizzazione Maschera singola, tutto ciò che devi fare è
rieseguire la query sulla casella combinata filtrata (in questo caso la casella combinata
Product), quando l'utente passa a un nuovo record (nell'evento Corrente della maschera), e
rieseguire la query, quando l'utente sceglie un differente valore nella casella combinata che
fornisce il valore del filtro (nell'evento DopoAggiornamento della casella combinata che
fornisce il valore del filtro).
L'utilizzo di questa tecnica per una maschera in visualizzazione Maschera continua è
tuttavia notevolmente più complesso. Anche se puoi vedere più righe nella visualizzazione
Maschera continua, sulla maschera c'è in realtà una sola copia di ciascun controllo. Se ripeti
la query sulla casella combinata Product ogni volta che ti sposti in una nuova riga, il nome
del prodotto visualizzato nelle altre righe che hanno un differente tipo di prodotto appare
vuoto. Quando il valore in una riga non corrisponde a un valore nell'elenco, ottieni un
risultato vuoto, non il valore reale del campo.
Per risolvere questo problema devi includere il nome da visualizzare nel recordset per la
maschera, quindi sovrapporre attentamente ciascuna casella combinata con una casella di
testo che visualizzi sempre il valore corretto indipendentemente dal filtro. Per vedere come
abbiamo fatto, puoi aprire la maschera fsubContactProducts in visualizzazione Struttura.
La figura 7-19 mostra la maschera con le due caselle di testo che si sovrappongono
(CategoryDescription e ProductName) estratte dalle caselle combinate sottostanti
(Unbound e ProductID).
Figura 7-19 Puoi risolvere un problema di visualizzazione di una casella combinata filtrata
mediante la sovrapposizione di caselle di testo.
L'origine controllo della casella combinata Product è in realtà il campo ProductID, ma la
casella combinata visualizza il campo ProductName. La casella combinata Product Type
inoltre non è associata a nessun campo (non esiste un campo CategoryDescription nella
tabella tblContactProducts), ma visualizza il campo CategoryDescription dalla tabella di
ricerca. Per far funzionare tutto devi includere i campi ProductName e CategoryDescription
nell'origine record per questa maschera. Queste caselle di testo devono visualizzare i valori
sovrapposti senza tuttavia essere aggiornate dall'utente. Esse hanno la proprietà Bloccato
impostata su Sì per impedire l'aggiornamento, e la proprietà Seleziona con tabulazione
impostata su No per fare in modo che l'utente possa premere il tasto Tab per posizionarsi
sulle caselle combinate sottostanti e non su queste caselle di testo. La figura 7-20 mostra la
query qryContactProducts, che è l'origine per questa maschera.
381
Capitolo 7
Automazione della selezione dei dati
Figura 7-20 La query qryContactProducts fornisce i campi ProductName e CategoryDescription
necessari da una tabella correlata, in modo da permettere di visualizzare i valori.
Perché tutto funzioni, molte routine evento assicurano che vengano attivati i campi
necessari e che venga ripetuta la query sulla casella combinata filtrata Product. Il codice
della maschera fsubContactProducts che svolge questo compito è il seguente:
Private Sub CategoryDescription_GotFocus()
‘ We have some tricky “overlay” text boxes here that
‘ shouldn’t get the focus. Move focus to the underlying
‘ combo box if that happens.
Me.cmbCategoryDescription.SetFocus
End Sub
Private Sub cmbCategoryDescription_AfterUpdate()
‘ If they pick a new Category, then requery the
‘ product list that’s filtered on category
Me.cmbProductID.Requery
‘ Set the Product to the first row in the new list
Me.cmbProductID = Me.cmbProductID.ItemData(0)
‘ .. and signal Product after update.
cmbProductID_AfterUpdate
End Sub
Private Sub Form_Current()
‘ If we have a valid Category Description on this row...
If Not IsNothing(Me.CategoryDescription) Then
‘ Then make sure the unbound combo is in sync.
Me.cmbCategoryDescription = Me.CategoryDescription
End If
‘ Requery the product list to match the current category
Me.cmbProductID.Requery
If (Me.Invoiced = True) Then
Me.cmbProductID.Locked = True
Me.cmbCategoryDescription.Locked = True
Me.DateSold.Locked = True
Me.SoldPrice.Locked = True
Me.RegistrationCode.Locked = True
382
Capitolo 7
Automazione di un'applicazione tramite Visual Basic
Else
Capitolo 7
Me.cmbProductID.Locked = False
Me.cmbCategoryDescription.Locked = False
Me.DateSold.Locked = False
Me.SoldPrice.Locked = False
Me.RegistrationCode.Locked = False
End If
End Sub
Private Sub ProductName_GotFocus()
‘ We have some tricky “overlay” text boxes here that
‘ shouldn’t get the focus. Move focus to the underlying
‘ combo box if that happens.
Me.cmbProductID.SetFocus
End Sub
Come previsto, il codice ripete le query sulla casella combinata Product ogni volta che
selezioni una nuova categoria (cmbCategoryDescription_AfterUpdate) o ti sposti su una
nuova riga (Form_Current). Il codice mantiene inoltre sincronizzata la casella combinata
non associata quando passi da una riga all'altra, a condizione che il record sottostante
abbia una categoria valida. Ricorda che un nuovo record non ha una CategoryDescription
correlata fino a quando non scegli un ProductID, pertanto il codice non aggiorna la
casella combinata non associata su un nuovo record. Se infine cerchi di fare clic su
CategoryDescription o ProductName, il codice GotFocus ti sposta sulla casella combinata
sottostante. Perché non abbiamo semplicemente impostato la proprietà Abilitato a Sì,
per CategoryDescription e ProductName? Se lo facessimo, non potresti mai fare clic sulle
caselle combinate della categoria o del prodotto, poiché la casella di testo disabilitata,
sovrapposta, ti bloccherebbe.
Nota
Per vedere come appare la casella combinata filtrata senza la sovrapposizione
della casella di testo, crea una copia di backup di Contacts.accdb, apri la maschera
fsubContactProducts nella visualizzazione Struttura, sposta le caselle di testo Category
Description e Product Name in basso come mostrato nella figura 7-19 e infine salva la
maschera. Fatto ciò, apri la maschera frmContacts e fai clic sulla scheda Products.
Collegamento ai dati correlati in un'altra maschera o
report
Ora che sai come creare un filtro, per limitare i dati visti dall'utente, puoi probabilmente
supporre che l'utilizzo di un filtro rappresenti un buon metodo per aprire un'altra maschera,
o un altro report, che visualizza informazioni correlate al record corrente o all'insieme di
record filtrati nella maschera corrente. Questa sezione mostra come farlo per le maschere
e per i report; più avanti in questa sezione imparerai a utilizzare gli eventi nei moduli di
classe, per creare collegamenti sofisticati.
Collegamento ai dati correlati in un'altra maschera o report
383
La maschera frmContactSummary (vista nella figura 7-18) utilizza un semplice filtro
per eseguire un collegamento tra il record selezionato in quella maschera e i dettagli
completi nella maschera frmContacts. Puoi trovare codice simile nella maschera
fsubCompanyContacts, utilizzata come sottomaschera della maschera frmCompanies.
La figura 7-21 mostra la maschera frmCompanies e i pulsanti Edit This su quella
sottomaschera.
Figura 7-21 Puoi fornire un collegamento dalla maschera Companies/Organizations ai dettagli
riguardanti un particolare contatto.
Per vedere i dettagli per un particolare contatto, l'utente fa clic sul pulsante Edit This, sul
record del contatto scelto, e il codice apre la maschera frmContacts con quel contatto
visualizzato. Il codice di questo pulsante è il seguente:
Private Sub cmdEdit_Click()
‘ Open Contacts on the related record
DoCmd.OpenForm “frmContacts”, WhereCondition:=”ContactID = “ & Me.ContactID
End Sub
Il codice nell'evento Corrente della maschera impedisce all'utente di fare clic sul pulsante
quando si trova su un nuovo record che non ha un ContactID:
Private Sub Form_Current()
‘ Disable “edit this” if on a new row
Me.cmdEdit.Enabled = Not (Me.NewRecord)
End Sub
Se la proprietà Abilitato del pulsante è impostata a False il pulsante viene visualizzato
oscurato, e l'utente non può fare clic su esso.
Capitolo 7
Collegamento delle maschere per mezzo di un filtro
384
Capitolo 7
Automazione di un'applicazione tramite Visual Basic
Collegamento a un report per mezzo di un filtro
Capitolo 7
Esaminiamo ora l'utilizzo della tecnica del filtro per collegare informazioni correlate in un
report. Apri la maschera frmInvoices nell'applicazione Conrad Systems Contacts (Contacts.
accdb) e passa a una fattura che sembra interessante, ad esempio la numero 50. Fai clic
sul pulsante Print per aprire la maschera Print Invoices (fdlgInvoicePrintOptions), la quale
ti offre la possibilità di vedere la fattura corrente formattata in un report, visualizzare tutte
le fatture non stampate in un report, visualizzare solo le fatture non stampate per il cliente
corrente o stampare tutte le fatture al momento visualizzate nella maschera frmInvoices.
Se desideri filtrare le fatture da visualizzare, puoi utilizzare Search. Seleziona l'opzione
Current Invoice Only e fai nuovamente clic su Print per vedere la fattura in un report, come
mostrato nella figura 7-22. La figura mostra la sequenza che vedi dopo aver fatto clic sul
pulsante Print della maschera frmInvoices. La finestra di dialogo Print Invoices viene chiusa,
dopo l'apertura del report.
Figura 7-22 Puoi chiedere di stampare solo la fattura corrente nel database Conrad Systems
Contacts.
Il codice dell'evento Clic del pulsante Print nella maschera fdlgInvoicePrintOptions è il
seguente:
Private Sub cmdPrint_Click()
Dim strFilter As String, frm As Form
‘ Set an error trap
On Error GoTo cmdPrint_Error
‘ Get a pointer to the Invoices form
Set frm = Forms!frmInvoices
Select Case Me.optFilterType.Value
‘ Current Invoice
Case 1
‘ Set filter to open Invoice report for current invoice only
strFilter = “[InvoiceID] = “ & frm!InvoiceID
‘ All unprinted invoices
Case 2
‘ Set filter to open all unprinted invoices
strFilter = “[InvoicePrinted] = 0”
‘ Unprinted invoices for current company
Case 3
‘ Set filter to open unprinted invoices for current company
strFilter = “[CompanyID] = “ & frm!cmbCompanyID & _
“ AND [InvoicePrinted] = 0”
‘ Displayed invoices (if filter set on form)
Case 4
‘ Check for a filter on the form
If IsNothing(frm.Filter) Then
‘ Make sure they want to print all!
If vbNo = MsgBox(“Your selection will print all “ & _
“Invoices currently in the “ & _
“database. Are you sure you want to do this?”, _
vbQuestion + vbYesNo + vbDefaultButton2, _
gstrAppTitle) Then
Exit Sub
End If
‘ Set “do them all” filter
strFilter = “1 = 1”
Else
strFilter = frm.Filter
End If
End Select
‘ Hide me
Me.Visible = False
‘ Have a filter now. Open the report on that filter
DoCmd.OpenReport “rptInvoices”, acViewPreview, , strFilter
‘ Update the Print flag for selected invoices
CurrentDb.Execute “UPDATE tblInvoices SET InvoicePrinted = -1 WHERE “ & _
strFilter
‘ Refresh the form to show updated Printed status
frm.Refresh
‘ Execute the Current event on the form to make sure it is locked correctly
frm.Form_Current
cmdPrint_Exit:
‘ Clear the form object
Set frm = Nothing
‘ Done
DoCmd.Close acForm, Me.Name
Exit Sub
cmdPrint_Error:
‘ Got an error
385
Capitolo 7
Collegamento ai dati correlati in un'altra maschera o report
386
Capitolo 7
Automazione di un'applicazione tramite Visual Basic
Capitolo 7
‘ If Cancel, that means the filter produced no Invoices
If Err = errCancel Then
‘ Exit - report will display “no records” message
Resume cmdPrint_Exit
End If
‘ Got unknown error - display and log
MsgBox “Unexpected error while printing and updating print flags: “ & _
Err & “, “ & _
Error, vbCritical, gstrAppTitle
ErrorLog Me.Name & “_Print”, Err, Error
Resume cmdPrint_Exit
End Sub
Questa prima parte di questa routine imposta un riferimento alla maschera frmInvoices,
perché sia più facile utilizzare InvoiceID o CompanyID e fare riferimento alle proprietà e ai
metodi dell'oggetto della maschera. L'istruzione Select Case trova il pulsante di opzione
selezionato dall'utente nella maschera fdlgInvoicePrintOption e crea l'appropriato filtro
per il report. Nota che, se l'utente richiede la stampa di tutte le fatture attualmente
visualizzate nella maschera, il codice verifica innanzitutto l'esistenza di un filtro applicato
dall'utente sulla maschera frmInvoices e, se non trova nessun filtro, chiede se l'utente
desidera stampare tutte le fatture. Il codice utilizza il filtro da esso creato (o il filtro corrente
nella maschera frmInvoices) per aprire il report rptInvoices in Anteprima di stampa, quindi
esegue un'istruzione SQL UPDATE per contrassegnare tutte le fatture stampate dall'utente.
Se esamini il codice dell'evento Corrente della maschera frmInvoices, puoi notare che tutti i
controlli sono bloccati, in modo che l'utente non possa aggiornare una fattura che è stata
stampata.
Sincronizzazione di due maschere per mezzo di un evento di
classe
Talvolta è utile dare all'utente la possibilità di aprire una maschera popup, che mostra altri
dettagli relativi ad alcune informazioni visualizzate su un'altra maschera. Quando ti sposti
da una riga all'altra nella maschera principale, sarebbe bello se la maschera che visualizza le
informazioni aggiuntive rimanesse sincronizzata.
L'evento Corrente di una maschera naturalmente ti consente di sapere quando ti sposti su
una nuova riga. Nel database Wedding List creato con le macro (WeddingListMC.accdb), le
macro eseguono un'elaborata operazione di filtro, per mantenere la maschera popup che
visualizza le informazioni aggiuntive sulla città dell'invitato sincronizzata con la maschera
principale. Farlo con le macro rappresenta tuttavia il metodo più difficile!
L'applicazione Wedding List principale si trova nel database WeddingList.accdb, e utilizza
Visual Basic per fornire tutta l'automazione. Con Visual Basic siamo stati in grado di
dichiarare e di utilizzare un evento personalizzato nella maschera WeddingList, per
segnalare alla maschera CityInformation se è aperta e se risponde agli eventi. Nell'evento
Corrente della maschera WeddingList non dobbiamo preoccuparci se la maschera associata
è aperta; il codice segnala semplicemente l'evento e lascia che la maschera City Information
Collegamento ai dati correlati in un'altra maschera o report
387
Capitolo 7
si preoccupi di mantenersi sincronizzata con la maschera principale (l'utente può aprire la
maschera City Information in ogni istante, facendo clic sul pulsante City Info sulla maschera
Wedding List). Puoi vedere queste due maschere in azione nella figura 7-23.
Figura 7-23 La maschera CityInformation si apre sopra la maschera principale WeddingList per
visualizzare informazioni aggiuntive sulla città dell'invitato.
Ecco il codice del modulo di classe WeddingList che rende disponibile un evento per inviare
segnalazioni alla maschera CityInformation:
Option Compare Database
Option Explicit
‘ Event to signal we’ve moved to a new city
Public Event NewCity(varCityName As Variant)
‘ End of Declarations Section
Private Sub Form_Current()
On Error GoTo Form_Current_Err
‘ Signal the city form to move to this city
‘ and pass the city name to the event
RaiseEvent NewCity(Me!City)
Form_Current_Exit:
Exit Sub
Form_Current_Err:
MsgBox Error$
Resume Form_Current_Exit
End Sub
Private Sub cmdCity_Click()
On Error GoTo cmdCity_Click_Err
‘ If the city form is not open, open it
If Not IsFormLoaded(“CityInformation”) Then
388
Capitolo 7
Automazione di un'applicazione tramite Visual Basic
Capitolo 7
DoCmd.OpenForm “CityInformation”, acNormal, , , acFormReadOnly, acHidden
‘ Give the other form a chance to “hook” our event
DoEvents
End If
‘ Signal the form we just opened
RaiseEvent NewCity(Me!City)
cmdCity_Click_Exit:
Exit Sub
cmdCity_Click_Err:
MsgBox Error$
Resume cmdCity_Click_Exit
End Sub
Nella sezione Dichiarazioni del modulo abbiamo dichiarato una variabile di evento e ho
indicato che abbiamo intenzione di passare un parametro (il nome della città) nell'evento.
Nella routine evento Form_Current il codice utilizza RaiseEvent per passare il nome della
città corrente a ogni altro modulo in ascolto. Il codice non deve preoccuparsi se altri moduli
sono interessati a questo evento, ma segnala semplicemente l'evento quando appropriato,
e poi termina Questo modo di procedere è simile a quello di Access; quando una
maschera passa a un nuovo record, Access segnala l'evento Form_Current, ma non accade
nulla se non hai inserito il codice di risposta all'evento. La variabile passata è dichiarata
come tipo Variant, per gestire il caso in cui l'utente si sposta alla fine della nuova riga (in
questo caso il controllo City sarà Null). Un pulsante di comando (cmdCity) sulla maschera
WeddingList consente all'utente di aprire la maschera CityInformation. L'evento Clic di quel
pulsante apre la maschera nascosta e utilizza la funzione DoEvents per dare alla maschera
CityInformation la possibilità di essere aperta e di indicare che desidera ascoltare l'evento
NewCity sulla maschera WeddingList. Dopo aver aspettato che la maschera CityInformation
termini l'elaborazione, il codice genera l'evento per indicare a quella maschera la città nella
riga corrente.
La maschera CityInformation svolge tutto il lavoro (quando è aperta) per rispondere
all'evento segnalato dalla maschera WeddingList e si sposta sulla riga corretta. Il codice è il
seguente.
Option Compare Database
Option Explicit
Dim WithEvents frmWedding As Form_WeddingList
‘ End of the Declarations Section
Private Sub Form_Load()
On Error GoTo Form_Load_Err
‘ If the wedding list form is open
If IsLoaded(“WeddingList”) Then
‘ Then set to respond to the NewCity event
Set frmWedding = Forms!WeddingList
End If
Form_Load_Exit:
Exit Sub
Form_Load_Err:
MsgBox Error$
Resume Form_Load_Exit
Collegamento ai dati correlati in un'altra maschera o report
389
Private Sub frmWedding_NewCity(varCityName As Variant)
‘ The Wedding List form has asked us to move to a
‘ new city via the NewCity event
On Error Resume Next
If IsNothing(varCityName) Then
‘ Hide me if city name is empty
Me.Visible = False
Else
‘ Reveal me if there’s a city name, and go
‘ find it
Me.Visible = True
Me.Recordset.FindFirst “[CityName] = “”” & _
varCityName & “”””
End If
End Sub
Nella sezione Dichiarazioni puoi trovare una variabile oggetto chiamata frmWedding,
che ha un tipo di dati uguale al nome del modulo di classe della maschera WeddingList.
La parola chiave WithEvents indica che il codice in questo modulo di classe risponde agli
eventi segnalati da qualunque oggetto assegnato a questa variabile. Quando la maschera
viene aperta, la routine Form_Load controlla se la maschera WeddingList è aperta (nel caso
tu abbia aperto questa maschera dal riquadro di spostamento). Se la maschera WeddingList
è aperta, "aggancia" l'evento NewCity in quella maschera, assegnandolo alla variabile
frmWedding.
La routine frmWedding_NewCity risponde all'evento NewCity dell'oggetto frmWedding.
Una volta che il codice dell'evento Load definisce frmWedding come un puntatore alla
maschera WeddingList, questa routine viene eseguita ogni volta che il codice nel modulo di
classe per quella maschera segnala l'evento NewCity con RaiseEvent.
Il codice nella routine evento è molto semplice. Se il parametro CityName passato
dall'evento contiene un valore Null o una stringa di lunghezza zero, la routine nasconde
la maschera poiché non trova nulla da visualizzare. Se l'evento passa un nome di città
valido, la routine utilizza il metodo FindFirst dell'oggetto Recordset di questa maschera per
spostarsi alla città corretta.
Nota
In Access 2010, la proprietà Recordset di una maschera di un database Access (file
.accdb) restituisce un recordset DAO (Data Access Objects). Per questo motivo, per
individuare le righe nel recordset di una maschera dovresti utilizzare un metodo DAO
FindFirst, invece di un metodo ADO Find.
Capitolo 7
End Sub
390
Capitolo 7
Automazione di un'applicazione tramite Visual Basic
Automazione di attività complesse
Capitolo 7
Il codice Visual Basic più complesso esaminato finora in questo capitolo è la routine
per la creazione di una clausola di ricerca a partire dai dati immessi nella maschera
fdlgContactSearch. Tuttavia abbiamo solo cominciato a scalfire la superficie.
Attivazione di un'attività da una maschera correlata
Uno dei blocchi di codice più complessi nel database Conrad Systems Contacts è attivato
dalla maschera fsubContactEvents, che fa parte della maschera frmContacts. Dopo essersi
connesso correttamente, l'utente può aprire la maschera frmContacts, fare clic sulla scheda
Events e aggiungere un evento che indica la vendita di un prodotto. Non appena l'utente
salva il record, il codice della sottomaschera aggiunge automaticamente il prodotto al
contatto, come mostrato nella figura 7-24.
Figura 7-24 La registrazione dell'evento della vendita di un prodotto nella scheda Events
comporta la vendita automatica del prodotto al contatto.
Se esamini la struttura della maschera fsubContactEvents, puoi trovare alcune routine
evento che individuano la creazione di un evento di vendita da parte dell'utente e che
eseguono un comando SQL INSERT per creare la riga del prodotto correlato. Il codice è il
seguente:
Option Compare Database
Option Explicit
‘ Flag to indicate auto-add of a product if new event requires it
Dim intProductAdd As Integer
‘ Place to store Company Name on a product add
Dim varCoName As Variant
‘ End of the Declarations Section
Private Sub ContactEventTypeID_BeforeUpdate(Cancel As Integer)
‘ Did they pick an event that involves a software sale?
‘ NOTE: All columns in a combo box are TEXT
If Me.ContactEventTypeID.Column(4) = “-1” Then
‘ Try to lookup this contact’s Company Name
varCoName = DLookup(“CompanyName”, “qryContactDefaultCompany”, _
“ContactID = “ & Me.Parent.ContactID.Value)
‘ If not found, then disallow product sale
If IsNothing(varCoName) Then
MsgBox “You cannot sell a product to a Contact “ & _
“that does not have a “ & _
“related Company that is marked as the default for this Contact.” & _
“ Press Esc to clear your edits and click on the Companies tab “ & _
“to define the default Company for this Contact.”, _
vbCritical, gstrAppTitle
Cancel = True
End If
End If
End Sub
Private Sub Form_BeforeUpdate(Cancel As Integer)
‘ Did they pick an event that involves a software sale?
‘ NOTE: All columns in a combo box are TEXT
If Me.ContactEventTypeID.Column(4) = “-1” Then
‘ Do this only if on a new record or they changed the EventID value
If (Me.NewRecord) Or (Me.ContactEventTypeID <> _
Me.ContactEventTypeID.OldValue) Then
‘ Set the add product flag
‘- product added by AfterUpdate code for safety
intProductAdd = True
End If
End If
End Sub
Private Sub Form_AfterUpdate()
Dim strSQL As String, curPrice As Currency,
Dim lngProduct As Long, varCoID As Variant
Dim rst As DAO.Recordset, strPreReqName As String
‘ See if we need to auto-add a product
If (intProductAdd = True) Then
‘ Reset so we only do this once
intProductAdd = False
‘ Set an error trap
On Error GoTo Insert_Err
‘ Save the Product ID
lngProduct = Me.ContactEventTypeID.Column(5)
‘ Fetch the product record
Set rst = CurrentDb.OpenRecordset(“SELECT * FROM tblProducts “ & _
“WHERE ProductID = “ & lngProduct)
‘ Make sure we got a record
If rst.EOF Then
MsgBox “Could not find the product record for this sales event.” & _
“ Auto-create of “ & _
“product record for this contact has failed.”, _
vbCritical, gstrAppTitle
391
Capitolo 7
Automazione di attività complesse
392
Capitolo 7
Automazione di un'applicazione tramite Visual Basic
Capitolo 7
rst.Close
Set rst = Nothing
GoTo Insert_Exit
End If
‘ Check for prerequisite product
If Not IsNull(rst!PreRequisite) Then
‘ Make sure contact owns the prerequisite product
If IsNull(DLookup(“ProductID”, “tblContactProducts”, _
“ProductID = “ & rst!PreRequisite & “ And ContactID = “ & _
Me.Parent.ContactID)) Then
‘ Get the name of the prerequisite
strPreReqName = DLookup(“ProductName”, “tblProducts”, _
“ProductID = “ & rst!PreRequisite)
‘ Display error
MsgBox “This contact must own prerequisite product “ & _
strPreReqName & “ before you can sell this product.” & _
vbCrLf & vbCrLf & _
“Auto-create of product record for this contact has failed”, _
vbCritical, gstrAppTitle
‘ Bail
rst.Close
Set rst = Nothing
GoTo Insert_Exit
End If
End If
‘ Save the price
curPrice = rst!UnitPrice
‘ Done with the record - close it
rst.Close
Set rst = Nothing
‘ Now, find the default company for this contact
varCoID = DLookup(“CompanyID”, “qryContactDefaultCompany”, _
“ContactID = “ & Me.Parent.ContactID.Value)
‘ If not found, then disallow product sale
If IsNothing(varCoID) Then
MsgBox “You cannot sell a product to a Contact who does not have a “ & _
“related Company that is marked as the default for this Contact.”, _
vbCritical, gstrAppTitle
GoTo Insert_Exit
End If
‘ Set up the INSERT command
strSQL = “INSERT INTO tblContactProducts “ & _
“(CompanyID, ContactID, ProductID, DateSold, SoldPrice) “ & _
“VALUES(“ & varCoID & “, “ & Me.Parent.ContactID & “, “ & _
lngProduct & “, #” & _
DateValue(Format(Me.ContactDateTime, “mm/dd/yyyy”)) & “#, “ & _
curPrice & “)”
‘ Attempt to insert the Product row
CurrentDb.Execute strSQL, dbFailOnError
‘ Got a good add - inform the user
MsgBox “The product you sold with this event “ & _
“has been automatically added “ & _
“to the product list for this user. “ & _
“Click the Products tab to verify the price.”, _
vbInformation, gstrAppTitle
393
‘ Requery the other subform to get the new row there
Me.Parent.fsubContactProducts.Requery
End If
Insert_Exit:
Exit Sub
Insert_Err:
‘ Was error a duplicate row?
If Err = errDuplicate Then
MsgBox “CSD Contacts attempted to auto-add “ & _
“the product that you just indicated “ & _
“that you sold, but the Contact appears “ & _
“to already own this product. Be sure “ & _
“to verify that you haven’t tried to sell the same product twice.”, _
vbCritical, gstrAppTitle
Else
MsgBox “There was an error attempting to auto-add “ & _
“the product you just sold: “ & _
Err & “, “ & Error, vbCritical, gstrAppTitle
‘ Log the error
ErrorLog Me.Name & “_FormAfterUpdate”, Err, Error
End If
Resume Insert_Exit
End Sub
Nella sezione Dichiarazioni del modulo, puoi trovare due variabili utilizzate dalle routine
evento per passare le informazioni tra gli eventi. Se le dichiari all'interno di una delle
routine, le variabili possono essere utilizzate solamente dalla routine in questione. La
routine evento BeforeUpdate (dell'evento PrimaDiAggiornare) per il tipo di evento contatto
controlla se l'evento è la vendita di un prodotto (esaminando una delle colonne nascoste
nell'origine riga della casella combinata). Se l'utente sta cercando di registrare la vendita
di un prodotto e questo particolare contatto non ha una società predefinita, il codice
visualizza un messaggio di errore e non consente all'utente di salvare quel tipo di evento.
Ricorda che un record nella tabella tblContactProducts deve avere un CompanyID, come
pure un ContactID.
Quando l'utente cerca di salvare il record di un evento, nuovo o modificato, Access esegue
la routine evento BeforeUpdate della maschera; questo codice controlla di nuovo se il
record che sta per essere salvato è relativo alla vendita di un prodotto. Se tuttavia questo
non è un nuovo record oppure se l'utente sta salvando il vecchio record di un evento,
ma non ha modificato il tipo di evento, il codice esce, per non aggiungere due volte il
record di un prodotto (se questo è un record esistente e il tipo di evento non è cambiato,
questo codice probabilmente ha creato il record del prodotto del contatto, la prima volta
che l'utente ha salvato il record). Il codice potrebbe a questo punto inserire il record nella
tabella tblContactProducts, ma il record viene effettivamente salvato solo al termine
dell'evento PrimaDiAggiornare. Questo codice imposta quindi la variabile modulo per
indicare alla routine evento AfterUpdate (dell'evento DopoAggiornamento) della maschera
di eseguire quell'attività dopo che Access ha salvato il record modificato.
Capitolo 7
Automazione di attività complesse
394
Capitolo 7
Automazione di un'applicazione tramite Visual Basic
Capitolo 7
Dopo aver salvato il record di un evento, nuovo o modificato, Access esegue la routine
evento AfterUpdate della maschera. Se il codice in BeforeUpdate ha impostato la variabile
modulo intProductAdd a True, indicando che è necessario inserire un prodotto, questo
codice aggiunge il nuovo record; apre un recordset sulla tabella tblProducts per il prodotto
che è stato appena venduto, in modo da poter ottenere il prezzo del prodotto e verificare
l'esistenza di qualsiasi prodotto prerequisito; se il prodotto ha un prerequisito, ma questo
contatto non possiede il prodotto prerequisito, il codice visualizza un messaggio di errore
ed esce.
Anche se il codice precedente ha verificato l'esistenza di un CompanyID predefinito per
questo contatto, questo codice esegue nuovamente questo controllo, ed esce se non riesce
a trovare un CompanyID predefinito. Quando il codice ha completato tutti i controlli, e
possiede le informazioni necessarie relative al prezzo e al CompanyID, inserisce il nuovo
record nella tabella tblContactProducts mediante SQL. Nella parte inferiore della routine
puoi trovare il codice di gestione degli errori che controlla se l'inserimento ha causato un
errore di record duplicato.
Collegamento a un'attività correlata
Passa all'applicazione Housing Reservations (Housing.accdb) ed esamina il processo di
conferma di una camera per una richiesta di prenotazione. Avvia l'applicazione aprendo la
maschera frmSplash, quindi connettiti come amministratore (Conrad, Jeff, Richins, Jack S.,
Schare, Gary o John L. Viescas) digitando password come password. Nel pannello comandi
principale fai clic su Reservation Requests, e poi fai clic su View Unbooked nella finestra
di dialogo Edit Reservation Requests. Fatto ciò, vedrai la maschera Unbooked Requests
(frmUnbookedRequests), come mostrato nella figura 7-25.
Nota
La query che fornisce i record visualizzati nella maschera frmUnbookedRequests
comprende alcuni criteri che permettono di escludere tutte le richieste che hanno
una data di check-in anteriore alla data odierna. Come è facile intuire, non ha
senso confermare una richiesta di prenotazione per una data già trascorsa. L'ultima
data di check-in richiesta nel database originale è il venerdì 11 marzo 2011, perciò
probabilmente vedi un messaggio di errore quando cerchi di esaminare le richieste non
registrate. Puoi utilizzare la maschera zfrmLoadData per caricare nuove prenotazioni
e richieste più recenti nella query qryUnbookedRequests, in modo da non eliminare
le vecchie richieste ed essere in grado di vedere come funziona la maschera
frmUnbookedRequests.
395
Capitolo 7
Automazione di attività complesse
Figura 7-25 La maschera Unbooked Requests consente di vedere le richieste pendenti e di
avviare il processo di prenotazione.
Nella sezione "Collegamento ai dati correlati in un'altra maschera o report" a pagina
382, hai appreso una tecnica che permette di utilizzare un pulsante di comando per un
collegamento a un'attività correlata. L'attività principale nell'applicazione Housing
Reservations consiste nell'assegnare una camera e nel registrare una prenotazione per
le richieste pendenti. Quando fai clic su uno dei pulsanti Book sulla maschera Unbooked
Requests, il codice della maschera apre una maschera per mostrare le camere che
corrispondono alla richiesta e che non sono registrate per l'intervallo di tempo richiesto. Se
fai clic sulla richiesta da parte di Kirk DeGrasse per una camera con letto king-size dal 1°
dicembre 2010 al 19 dicembre 2010, vedrai l'elenco delle camere disponibili nella maschera
fdlgAvailableRooms, come mostrato nella figura 7-26.
396
Capitolo 7
Automazione di un'applicazione tramite Visual Basic
Capitolo 7
Figura 7-26 La maschera fdlgAvailableRooms elenca le camere disponibili in base alla richiesta
di prenotazione selezionata.
Il codice del pulsante Book della maschera frmUnbookedRequests è il seguente:
Private Sub cmdBook_Click()
‘ Make sure no changes are pending
If Me.Dirty Then Me.Dirty = False
‘ Open the available rooms form - hidden, dialog
‘ and check if any available
DoCmd.OpenForm “fdlgAvailableRooms”, _
WhereCondition:=”Smoking = “ & Me.Smoking, _
WindowMode:=acHidden
If Forms!fdlgAvailableRooms.RecordsetClone.RecordCount = 0 Then
MsgBox “There are no available rooms of this “ & _
“type for the dates requested.” & _
vbCrLf & vbCrLf & _
“You can change the Room Type or dates and try again.”, _
vbInformation, gstrAppTitle
DoCmd.Close acForm, “fdlgAvailableRooms”
Exit Sub
End If
‘ Show the available rooms
‘ - form will call our public sub to create the res.
Forms!fdlgAvailableRooms.Visible = True
End Sub
L'origine record della maschera fdlgAvailableRooms è una query con parametri, che
filtra le camere già registrate per le date specificate, e include le rimanenti camere che
corrispondono al tipo di camera richiesto. Il codice del pulsante Book aggiunge un filtro
per la richiesta di camere per fumatori o non fumatori, poiché questa informazione non
Automazione di attività complesse
397
Private Sub cmdPick_Click()
Dim intReturn As Integer
‘ Call the build a reservation proc in the calling form
intReturn = Form_frmUnbookedRequests.Bookit(Me.FacilityID, Me.RoomNumber, _
Me.DailyRate, Me.WeeklyRate)
If (intReturn = True) Then
MsgBox “Booked!”, vbExclamation, gstrAppTitle
Else
MsgBox “Room booking failed. Please try again.”, _
vbCritical, gstrAppTitle
End If
DoCmd.Close acForm, Me.Name
End Sub
Riesci a immaginare cosa avviene? In frmUnbookedRequests c'è una funzione pubblica
di nome Bookit richiamata da questo codice come metodo di quella maschera. Questa
funzione passa i campi FacilityID, RoomNumber, DailyRate e WeeklyRate per completare la
prenotazione. Il codice della funzione pubblica Bookit è il seguente:
Public Function Bookit(lngFacility As Long, lngRoom As Long, _
curDaily As Currency, curWeekly As Currency) As Integer
‘ Sub called as a method by fdlgAvailableRooms to book the selected room
‘ Caller passes in selected Facility, Room number, and rates
Dim db As DAO.Database, rstRes As DAO.Recordset
Dim varResNum As Variant, strSQL As String, intTrans As Integer
‘ Set error trap
On Error GoTo BookIt_Err
‘ Get a pointer to this database
Set db = CurrentDb
‘ Open the reservations table for insert
Set rstRes = db.OpenRecordset(“tblReservations”, _
dbOpenDynaset, dbAppendOnly)
‘ Start a transaction
BeginTrans
intTrans = True
‘ Get the next available reservation number
varResNum = DMax(“ReservationID”, “tblReservations”)
If IsNull(varResNum) Then varResNum = 0
varResNum = varResNum + 1
‘ Update the current row
strSQL = “UPDATE tblReservationRequests SET ReservationID = “ & _
varResNum & “ WHERE RequestID = “ & Me.RequestID
db.Execute strSQL, dbFailOnError
‘ Book it!
rstRes.AddNew
‘ Copy reservation ID
rstRes!ReservationID = varResNum
‘ Copy employee number
rstRes!EmployeeNumber = Me.EmployeeNumber
‘ Copy facility ID from the room we picked
rstRes!FacilityID = lngFacility
‘ .. and room number
Capitolo 7
è inclusa nel tipo di camera, ma è indicata per ciascuna camera disponibile. Il codice del
pulsante Pick This della maschera fdlgAvailableRooms è il seguente:
398
Capitolo 7
Automazione di un'applicazione tramite Visual Basic
Capitolo 7
rstRes!RoomNumber = lngRoom
‘ Set reservation date = today
rstRes!ReservationDate = Date
‘ Copy check-in, check-out, and notes
rstRes!CheckInDate = Me.CheckInDate
rstRes!CheckOutDate = Me.CheckOutDate
rstRes!Notes = Me.Notes
‘ Copy daily and weekly rates
rstRes!DailyRate = curDaily
rstRes!WeeklyRate = curWeekly
‘ Calculate the total charge
rstRes!TotalCharge = ((Int(Me.CheckOutDate - Me.CheckInDate) \ 7) * _
curWeekly) + _
((Int(Me.CheckOutDate - Me.CheckInDate) Mod 7) * _
curDaily)
‘ Save the Reservation Row
rstRes.Update
‘ Commit the transaction
CommitTrans
intTrans = False
‘ Clean up
rstRes.Close
Set rstRes = Nothing
Set db = Nothing
‘ Requery this form to remove the booked row
Me.Requery
‘ Return success
Bookit = True
BookIt_Exit:
Exit Function
BookIt_Err:
MsgBox “Unexpected Error: “ & Err & “, “ & Error, vbCritical, gstrAppTitle
ErrorLog Me.Name & “_Bookit”, Err, Error
Bookit = False
If (intTrans = True) Then Rollback
Resume BookIt_Exit
End Function
Ha senso avere il codice di registrazione nella maschera frmUnbookedRequests, poiché
la riga che il codice deve inserire nella tabella tblReservations richiede molti campi del
record di richiesta corrente (EmployeeNumber, CheckInDate, CheckOutDate e Notes). Il
codice avvia una transazione, poiché deve simultaneamente immettere un ReservationID
in entrambe le tabelle tblReservationRequests e tblReservations. Se fallisce, il codice
di gestione degli errori ripristina entrambi gli aggiornamenti. Il codice apre la tabella
tblReservations in modalità solo aggiunta, per rendere l'inserimento della nuova
prenotazione più efficiente.
Calcolo di un valore memorizzato
Se applichi le regole per la corretta progettazione delle tabelle, sai che memorizzare
un valore calcolato in una tabella in genere non è una buona idea, poiché è necessario
scrivere il codice che deve gestire tale valore. Talvolta tuttavia, in un database molto grosso,
399
è necessario calcolare e salvare un valore, per migliorare le prestazioni per le ricerche
e i report; l'applicazione Housing Reservations non è così grossa, ma potrebbe esserlo
nella vita reale. Abbiamo scelto di memorizzare il conto totale calcolato, per ciascuna
prenotazione, per mostrarti alcune delle azioni che devi eseguire per gestire un valore
come questo.
Gli utenti possono creare e modificare le richieste di prenotazione, ma la creazione dei
record delle prenotazioni, che contengono il valore calcolato, è controllata interamente
dal codice; la gestione del valore calcolato TotalCharge in questa applicazione è quindi
molto semplice. Hai già visto dove viene creato il record di una nuova prenotazione (nella
funzione pubblica Bookit, che si trova nella maschera frmUnbookedRequests). Il segmento
di codice che calcola il valore è il seguente:
‘ Calculate the total charge
rstRes!TotalCharge = ((Int(Me.CheckOutDate - Me.CheckInDate) \ 7) * _
curWeekly) + _
((Int(Me.CheckOutDate - Me.CheckInDate) Mod 7) * _
curDaily)
In molte applicazioni tuttavia non riuscirai a controllare così bene la modifica di un valore
calcolato. Devi considerare attentamente le conseguenze del salvataggio di un valore
calcolato nella tua tabella, e devi forse comporre il codice che un amministratore può
eseguire periodicamente, per verificare che ogni valore calcolato salvato sia sincronizzato
con gli altri campi utilizzati per eseguire il calcolo.
Nota
Access 2010 comprende il nuovo tipo di dati calcolato che puoi usare per memorizzare
i valori calcolati, ma in questa sezione vogliamo mostrarti come ottenere queste
funzionalità tramite Visual Basic. Devi tuttavia ricordare che, utilizzando Visual Basic per
memorizzare un valore calcolato, non puoi forzare l'esecuzione del calcolo quando un
utente modifica i dati direttamente in un foglio dati di una tabella o query. Se utilizzi
un tipo di dati calcolato, Access controlla il valore calcolato a livello di motore.
Automazione dei report
In un'applicazione tipica, probabilmente circa l'80 o il 90 percento del lavoro di scrittura
del codice riguarda le routine evento per le maschere. Ciò non significa che non esistano
numerose attività che possono essere automatizzare nei report. Questa sezione mostra
alcune delle possibilità.
Capitolo 7
Automazione dei report
400
Capitolo 7
Automazione di un'applicazione tramite Visual Basic
Gestione delle etichette postali
Capitolo 7
Hai mai desiderato creare un report di etichette postali, ed escogitare un metodo per
stampare le etichette selezionate su una pagina già parzialmente utilizzata? Puoi trovare
la risposta nell'applicazione Conrad Systems Contacts (Contacts.accdb). Supponi di volere
inviare un messaggio promozionale a tutti i contatti che possiedono il prodotto Single
User offrendo loro un upgrade a Multi-User. Apri il pannello comandi principale (frmMain),
fai clic su Contacts e poi fai clic su Search nella finestra popup Select Contacts. Esegui
una ricerca di tutti i contatti che possiedono il prodotto BO$$ Single User: nel database
originale dovresti trovarne otto. Fai clic su No quando l'applicazione ti chiede se desideri
vedere prima un elenco di riepilogo. Fai clic sul pulsante Print nella maschera frmContacts,
seleziona Avery 5163 (2"× 4"), richiedi che il report includa i contatti visualizzati (Displayed
Contacts) e indica che 3 etichette sono già state utilizzate sulla prima pagina. A questo
punto, il tuo schermo dovrebbe apparire simile alla figura 7-27.
Figura 7-27 Puoi richiedere etichette postali e specificare che alcune etichette sono già state
utilizzate sulla prima pagina.
Fai clic sul pulsante Print nella finestra di dialogo; dovresti vedere le etichette stampate, ma
con tre spazi vuoti in corrispondenza di quelle utilizzate, come mostrato nella figura 7-28.
401
Capitolo 7
Automazione dei report
Figura 7-28 Vengono stampate solo le etichette non utilizzate.
Puoi trovare del codice interessante nella routine evento AfterUpdate del gruppo di
opzioni per scegliere il tipo di report, nella maschera fdlgContactPrintOptions: Il codice è il
seguente:
Private Sub optReportType_AfterUpdate()
‘ Figure out whether to show the “used labels” combo
Select Case Me.optReportType
Case 1
‘ Show the used labels combo
Me.cmbUsedLabels.Visible = True
‘ Hide the number of days option group
Me.optDisplay.Visible = False
‘ up to 29 used labels on 5160
Me.cmbUsedLabels.RowSource = “0;1;2;3;4;5;6;7;8;9;10;11;12;13;” & _
“14;15;16;17;18;19;20;21;22;23;24;25;26;27;28;29”
Case 2
‘ Show the used labels combo
Me.cmbUsedLabels.Visible = True
‘ Hide the number of days option group
Me.optDisplay.Visible = False
‘ up to 9 used labels on 5163
Me.cmbUsedLabels.RowSource = “0;1;2;3;4;5;6;7;8;9”
Case 3, 4
‘ Don’t need the combo for Envelopes and contact list
Me.cmbUsedLabels.Visible = False
‘ .. or the number of days filter
Me.optDisplay.Visible = False
Case 5, 6
‘ Don’t need the used labels combo for contact events or products
Me.cmbUsedLabels.Visible = False
‘ Do need the day filter
Me.optDisplay.Visible = True
End Select
End Sub
402
Capitolo 7
Automazione di un'applicazione tramite Visual Basic
Capitolo 7
Puoi avere fino a 29 etichette usate quando stampi su un foglio Avery 5160 (1" x 2.625").
Puoi avere fino a 9 etichette usate quando stampi su un foglio Avery 5163 (2" x 4"). La
casella combinata che puoi utilizzare per indicare il numero di etichette usate ha un Value
List come tipo di origine riga, e quindi il codice imposta l'appropriato elenco in base al tipo
di etichetta scelto.
Tuttavia, il vero trucco che permette di lasciare spazi vuoti all'interno del report si
trova nella query qryRptContactLabels che rappresenta l'origine record del report
rptContactLabels5163. Nel database di esempio puoi trovare una tabella chiamata
ztblLabelSpace che contiene 30 record, ognuno dei quali ha un campo contenente i valori
da 1 a 30. Il codice SQL per la query qryRptContactLabels è il seguente:
PARAMETERS [Forms]![fdlgContactPrintOptions]![cmbUsedLabels] Long;
SELECT “” As Contact, “” As CompanyName, “” As Address, “” As CSZ,
Null As ContactID, “” As Zip, “” As LastName, “” As FirstName,
“” As ContactType, “” As WorkCity, “” As WorkStateOrProvince,
“” As HomeCity, “” As HomeStateOrProvince, 0 As Inactive
FROM ztblLabelSpace
WHERE ID <= [Forms]![fdlgContactPrintOptions]![cmbUsedLabels]
UNION ALL
SELECT ([tblContacts].[Title]+” “) & [tblContacts].[FirstName] & “ “ &
([tblContacts].[MiddleInit]+”. “) & [tblContacts].[LastName] &
(“, “+[tblContacts].[Suffix]) AS Contact,
Choose([tblContacts].[DefaultAddress], qryContactDefaultCompany.CompanyName,
Null) As CompanyName,
Choose([tblContacts].[DefaultAddress],[tblContacts].[WorkAddress],
[tblContacts].[HomeAddress]) AS Address,
Choose([tblContacts].[DefaultAddress],[tblContacts].[WorkCity] & “, “ &
[tblContacts].[WorkStateOrProvince] & “ “ & [tblContacts].[WorkPostalCode],
[tblContacts].[HomeCity] & “, “ & [tblContacts].[HomeStateOrProvince]
& “ “ & [tblContacts].[HomePostalCode]) AS CSZ,
tblContacts.ContactID,
Choose([tblContacts].[DefaultAddress],[tblContacts].[WorkPostalCode],
[tblContacts].[HomePostalCode]) AS Zip,
tblContacts.LastName, tblContacts.FirstName, tblContacts.ContactType,
tblContacts.WorkCity, tblContacts.WorkStateOrProvince, tblContacts.HomeCity,
tblContacts.HomeStateOrProvince, tblContacts.Inactive
FROM tblContacts
LEFT JOIN qryContactDefaultCompany
ON tblContacts.ContactID = qryContactDefaultCompany.ContactID;
La prima istruzione SELECT (fino a UNION ALL) crea colonne vuote fittizie per i campi
utilizzati dal report e impiega la tabella ztblLabelSpace e un filtro sulla casella combinata
nella maschera fdlgContactPrintOptions (figura 7-27) per restituire il numero corretto di
righe vuote. La query utilizza un'istruzione UNION con la query attuale che ritorna i dati del
contatto, per visualizzare le informazioni sul report.
Poiché questo report stampa un logo, la parte finale del codice evita che queste
informazioni appaiano sulle etichette vuote nel report rptContactLabels5163. Il codice è il
seguente:
403
Private Sub Detail_Format(Cancel As Integer, FormatCount As Integer)
‘ Don’t print the return logo if this is a “spacer” record
If IsNull(Me.ContactID) Then
Me.imgCSD.Visible = False
Else
Me.imgCSD.Visible = True
End If
End Sub
L'evento Formattazione (Format) della sezione Dettagli dipende dal fatto che ContactID
nelle righe vuote sia Null. Quando stampi una riga vuota per motivi di spaziatura, il codice
nasconde il logo.
Disegno su un report
Quando desideri disegnare un bordo intorno all'area di stampa di un report, talvolta devi
comporre il codice per chiedere ad Access di disegnare le linee o un bordo, dopo aver
posto i dati sulla pagina. Questo è vero specialmente se uno o più controlli sul report
possono espandersi, per accogliere una grossa quantità di dati.
Abbiamo utilizzato la Creazione guidata Report per creare il report rptContacts che utilizza
il formato Giustificato. Abbiamo inoltre personalizzato il report al termine della creazione
guidata. La creazione guidata ha creato un layout abbastanza decente, con un bordo intorno
a tutti i campi, ma la casella di testo per visualizzare le note non è abbastanza grande da
visualizzare il testo per tutti i contatti. La figura 7-29 mostra il report che visualizza il record di
John dal database. Come puoi vedere, le note su John sono troncate in basso.
Figura 7-29 Questo report utilizza un bordo intorno ai dati, ma una delle caselle di testo non è
abbastanza larga per visualizzare tutto il testo.
Capitolo 7
Automazione dei report
404
Capitolo 7
Automazione di un'applicazione tramite Visual Basic
Capitolo 7
È abbastanza semplice cambiare la proprietà Espandibile della casella di testo a Sì, per
consentire la sua espansione, ma il controllo rettangolo utilizzato per disegnare il bordo
intorno a tutto il testo non ha una proprietà Espandibile. La soluzione consiste nel
rimuovere il rettangolo e nell'utilizzare il metodo Line dell'oggetto report nell'evento
Formattazione del report della sezione Dettaglio. Il codice che puoi trovare in questa
routine evento nel report rptContactsExpandNotes è il seguente:
Private Sub Detail_Print(Cancel As Integer, PrintCount As Integer)
Dim sngX1 As Single, sngY1 As Single
Dim sngX2 As Single, sngY2 As Single, lngColor As Long
‘ Set coordinates
sngX1 = 120
sngY1 = 120
sngX2 = 9120
‘ Adjust the height if Notes has expanded
sngY2 = 7680 + (Me.Notes.Height - 2565)
‘ Draw the big box around the data
‘ Set width of the line to 8 pixels
Me.DrawWidth = 8
‘ Draw the rectangle around the expanded fields
Me.Line Step(sngX1, sngY1)-Step(sngX2, sngY2), RGB(0, 0, 197), B
End Sub
Il metodo Line accetta tre parametri:
1. Gli angoli superiore sinistro e inferiore destro della linea o del riquadro che desideri
disegnare, espressi in twip. Ci sono 1440 twip per pollice. Includi la parola chiave Step
per indicare che le coordinate sono relative alla posizione corrente del disegno, che
comincia sempre alla posizione 0,0. Quando utilizzi Step per la seconda coordinata,
fornisci i valori relativi a quelli che hai specificato nel primo insieme di coordinate.
2. Il colore che desideri utilizzare per disegnare la linea o il riquadro, espresso come
valore RGB (red-green-blue). Per questo parametro è utile la funzione RGB.
3. Un indicatore per richiedere una linea, un rettangolo o un rettangolo pieno. Se
non è specificato alcun indicatore, viene disegnata una linea. Includi la lettera B per
richiedere un rettangolo; aggiungi la lettera F per richiedere un rettangolo pieno.
Prima di chiamare il metodo Line, puoi impostare la proprietà DrawWidth del report, per
impostare la larghezza della linea (la larghezza predefinita è in pixel).
L'unica complicazione sono le coordinate. Sul report originale il rettangolo comincia a
0,0833 pollici da sinistra e dal margine superiore; moltiplicando dunque quel valore per
1440 twip per pollice, abbiamo ottenuto i valori di partenza di 120 dal margine superiore
e di 120 da sinistra. La larghezza del rettangolo deve essere di circa 6,3333 pollici (16 cm),
e quindi la coordinata relativa per l'angolo superiore destro è 6,3333 x 1440, oppure circa
9.120 twip. L'altezza del rettangolo deve essere almeno 5,3333 pollici (13,5 cm), o circa
7680 twip, e l'altezza deve essere adattata alla dimensione della casella di testo Notes, che
può espandersi. La casella di testo Notes è progettata per essere alta come minimo 1,7813
Automazione dei report
405
Se apri il report rptContactsExpandNotes e ti sposti sul record di John alla pagina 32, vedrai
che il rettangolo ora si espande correttamente, per adattarsi intorno alla casella di testo
Notes che si è ingrandita per visualizzare tutto il testo nel record di John . La figura 7-30
mostra il report con un rettangolo disegnato dal codice del report.
Figura 7-30 Il codice del report rptContactsExpandNotes disegna un rettangolo personalizzato
attorno al testo espanso.
APPROFONDIMENTO
AP
PPROFONDIMENTO
Esaminare altri rapporti che usano il
metodo Line
Per vedere esempi più interessanti di impiego del metodo Line per disegnare linee
dinamiche in un rapporto, puoi anche esaminare i rapporti client rptLaborPlan,
rptLaborPlanIndividual e rptWeeklyPostedSchedule nel database Web Back Office
Software System. Spesso, la comprensione di questi rapporti risulta più facile se utilizzi
il database BOSSDataCopy.accdb, poiché il database principale BOSS.accdb usa barre
multifunzione personalizzate.
Capitolo 7
pollici (4,5 cm), o 2.565 twip, e quindi sottraendo 2.565 dall'altezza attuale della casella di
testo Notes quando è formattata (anche la proprietà Altezza è espressa in twip) ottieni la
quantità che devi aggiungere all'altezza originale del rettangolo.
406
Capitolo 7
Automazione di un'applicazione tramite Visual Basic
Filtro dinamico di un report in fase di apertura
Capitolo 7
Per aprire un report filtrato e stampare specifici record, puoi utilizzare uno dei due seguenti
metodi:
●
Utilizza il parametro WhereCondition con il metodo DoCmd.OpenReport (in genere
nel codice di una routine evento di una maschera), per specificare un filtro.
●
Basa il report su una query con parametri che chieda all'utente i valori del filtro,
oppure che faccia riferimento ai valori in una maschera aperta.
In alcuni casi potresti progettare un report che intendi aprire da molti punti
dell'applicazione, e non puoi garantire che la maschera per fornire i valori del filtro sia
sempre aperta. In alternativa, puoi avere più report che richiedono gli stessi criteri di
filtro e non desideri dover progettare una maschera di filtro distinta per ciascun report.
Per risolvere questi problemi, puoi aggiungere al report il codice per aprire la sua finestra
di dialogo di filtro dalla routine evento Open del report. Torna all'applicazione Housing
Reservations (Housing.accdb), per esaminare un report che utilizza questa tecnica.
Nell'applicazione Housing Reservations, i report rptFacilityOccupancy e
rptFacilityRevenueChart dipendono da una singola maschera, fdlgReportDateRange, per
fornire una data iniziale e finale per il report. Per vedere il report rptFacilityOccupancy,
avvia l'applicazione aprendo la maschera frmSplash, connettiti come amministratore
(Conrad, Jeff, Richins, Jack S., Schare, Gary o John L. Viescas), fai clic sul pulsante Reports
nel pannello comandi principale, quindi sul pulsante Reservations nella categoria Facilities
nel pannello comandi Reports. Puoi anche aprire direttamente il report dal riquadro di
spostamento. Quando apri il report, puoi vedere una finestra di dialogo che richiede le date
desiderate, come mostrato nella figura 7-31.
Figura 7-31 Dal report che hai chiesto di visualizzare viene aperta una finestra di dialogo con
parametri.
407
A meno che tu non abbia ricaricato i dati di esempio, il database contiene prenotazioni dal
24 agosto 2010 al 22 marzo 2011, pertanto puoi stampare un report per ottobre, novembre
e dicembre. Immetti le date desiderate e fai clic su Go per vedere il report, come mostrato
nella figura 7-32.
Figura 7-32 Il report Facility Occupancy utilizza una finestra di dialogo di filtro condivisa per
consentirti di specificare un intervallo di date.
Il report ha una query con parametri nella sua origine record, e i parametri puntano alle
date From e To nella maschera fdlgReportDateRange mostrata nella figura 7-31. Il codice
del pulsante Reservations sul pannello comandi Reports apre tuttavia il report non filtrato;
è il codice nella routine evento Open del report che apre la finestra di dialogo, in modo che
la query nell'origine record possa trovare i parametri necessari. Il codice è il seguente:
Private Sub Report_Open(Cancel As Integer)
‘ Open the date range dialog
‘ .. report record source is filtered on this!
DoCmd.OpenForm “fdlgReportDateRange”, WindowMode:=acDialog
End Sub
Ciò funziona perché un oggetto Report non cerca di aprire l'origine record per il report
fino al termine dell'esecuzione del codice dell'evento Open. Puoi quindi inserire del codice
nell'evento Open per cambiare dinamicamente l'origine record del report o, come in
questo esempio, aprire una maschera in modalità Dialog per attendere fino a quando tale
maschera non viene chiusa o nascosta. Il codice della maschera fdlgReportDateRange è il
seguente:
Capitolo 7
Automazione dei report
408
Capitolo 7
Automazione di un'applicazione tramite Visual Basic
Capitolo 7
Private Sub Form_Load()
‘ If passed a parameter, reset defaults to last quarter
If Not IsNothing(Me.OpenArgs) Then
‘ Set the start default to first day of previous quarter
Me.txtFromDate.DefaultValue = “#” & _
DateSerial(Year(Date), ((Month(Date) - 1) \ 3) * 3 - 2, 1) & “#”
‘ Set the end default to last day of previous quarter
Me.txtToDate.DefaultValue = “#” & _
DateSerial(Year(Date), ((Month(Date) - 1) \ 3) * 3 + 1, 1) & “#”
End If
End Sub
Private Sub cmdGo_Click()
‘ Hide me so report can continue
Me.Visible = False
End Sub
Il codice nell'evento Caricamento (Load) della maschera controlla se il report che
sta aprendo la maschera ha passato un parametro nella proprietà OpenArgs. In caso
affermativo, il codice reimposta i valori predefiniti per le due caselle di testo delle date
sulle date iniziale e finale del trimestre precedente. Se esamini il codice del report
rptFacilityRevenueChart, puoi notare che questo report richiede valori predefiniti differenti,
ma è il codice nell'evento Clic del pulsante di comando Go che fa funzionare tutto. Il
codice della maschera risponde al tuo clic sul pulsante Go, per nascondere se stessa in
modo che il report possa proseguire; non può chiudere se stessa, poiché l'origine record
del report dipende dalle due date. Come notato in precedenza, nascondere questa
maschera, aperta in modalità Dialog, consente al codice nell'evento Apertura del report di
terminare, in modo tale che il report possa infine caricare la propria origine record. Come
potresti sospettare, nell'evento Chiusura (Close) del report si trova il codice per chiudere la
maschera dei parametri, quando chiudi il report o quando il report termina la stampa.
Richiamo di macro per i dati con nome
Access 2010 comprende ora macro per i dati che puoi utilizzare per collegare la logica al
livello dei dati. Quando colleghi le macro per i dati agli eventi tabella, Access esegue queste
macro solo in risposta agli eventi dati, come ad esempio l'inserimento di un nuovo record,
l'aggiornamento di valori in un record esistente o l'eliminazione di uno o più record. Puoi
anche creare una macro per i dati con nome collegata alla tabella stessa. Per molti aspetti,
una macro per i dati con nome è simile a una routine o a una funzione pubblica in un
modulo di codice standard: puoi richiamare una macro per i dati con nome da altre macro
e codice Visual Basic per eseguire le azioni definite nella macro per i dati e se desideri
restituire il risultato al richiedente.
Nel database Web Back Office Software System, abbiamo definito molte macro per i
dati con nome collegate alle tabelle Web, in modo tale da aiutare ad automatizzare
l'applicazione. Nella tabella tblErrorLog inclusa in questo database Web registriamo gli
errori dell'applicazione che si verificano quando gli utenti lavorano con il database nel
409
client Access. Collegata a questa tabella c'è una macro per i dati con nome chiamata
LogError. Questa macro per i dati con nome include sei parametri usati per rilevare i dati
da registrare in merito all'errore nella tabella. Il blocco di dati CreateRecord viene usato
per creare un nuovo record nella tabella tblErrorLog, quindi l'azione SetField permette di
passare i dati nei parametri ai campi appropriati.
Apri il database Web BOSSDataCopy.accdb per vedere come richiamare queste macro
per i dati con nome da Visual Basic. Se apri una maschera o un rapporto client in questo
database Web, puoi notare che ogni routine o funzione Visual Basic contiene un'etichetta
del gestore errori. Quando la routine o funzione rileva un errore, viene richiamata una
routine pubblica in un modulo standard per registrarne i dettagli. L'esempio di codice che
segue mostra la chiamata a livello di maschera e di report:
ErrorHandler "frmDailyLaborPlans", "cmdClearList_Click", Err.Number, Err.Description
Come puoi vedere, viene richiamata una routine pubblica chiamata ErrorHandler che
include quattro parametri: il nome dell'oggetto in cui si è verificato l'errore, la specifica
routine che ha generato l'errore, il numero di errore restituito da Access e la descrizione
dell'errore dell'applicazione. Per esaminare questa routine, apri il modulo modErrorTrap
nella visualizzazione Struttura dal riquadro di spostamento. Il codice nella routine pubblica
ErrorHandler in modErrorTrap è il seguente:
Public Sub ErrorHandler(strModule As String, _
strProcedure As String, _
lngErrorNumber As Long, _
strErrorString As String)
‘ For this procedure, continue on if any errors occur
On Error Resume Next
Dim strMessage As String
Dim datCurrentTime As Date
Dim strUser As String
‘ Current date and time
datCurrentTime = Now()
‘ If we can’t determine the current web user who
‘ triggered the error, then use the legacy CurrentUser
‘ function to log user name
If IsNull(CurrentWebUser(acWebUserName)) Then
strUser = CurrentUser()
Else
strUser = CurrentWebUser(acWebUserName)
End If
‘ The second action argument of DoCmd.SetParameter is an expression.
‘ If the second argument is a string,
‘ you need to escape the string in double quotes.
DoCmd.SetParameter “ParamModule”, “’” & strModule & “’”
DoCmd.SetParameter “ParamProcedure”, “’” & strProcedure & “’”
DoCmd.SetParameter “ParamErrorNumber”, lngErrorNumber
DoCmd.SetParameter “ParamErrorString”, “’” & strErrorString & “’”
DoCmd.SetParameter “ParamUser”, “’” & strUser & “’”
DoCmd.SetParameter “ParamTime”, “#” & datCurrentTime & “#”
‘ Run the named data macro to log this error event to the error table
DoCmd.RunDataMacro “tblErrorLog.LogError”
Capitolo 7
Richiamo di macro per i dati con nome
410
Capitolo 7
Automazione di un'applicazione tramite Visual Basic
Capitolo 7
‘ Build up a custom message to display to the user
strMessage = “There was an unexpected error in your application.” & vbCr & vbCr
strMessage = strMessage & “Error Details:” & vbCr
strMessage = strMessage & “Module : “ & strModule & vbCr
strMessage = strMessage & “Procedure : “ & strProcedure & vbCr
strMessage = strMessage & “Error : “ & lngErrorNumber & vbCr & vbCr
strMessage = strMessage & “Please notify Technical Support of this error.” _
& vbCr & vbCr
strMessage = strMessage & “Would you like to view this report in order to print?”
‘ View report if requested by user
If MsgBox(strMessage, vbCritical + vbYesNo + vbDefaultButton1, _
“BOSS - Unexpected Error”) = vbYes Then
DoCmd.OpenReport “rptErrorLog”, acViewPreview, , _
“ErrorTime=#” & datCurrentTime & “#”
End If
End Sub
Nella prima parte della routine, il codice recupera la data e l'ora correnti di sistema e il
nome dell'utente attualmente connesso da registrare nella tabella tblErrorLog. Fatto ciò, il
codice deve eseguire un metodo DoCmd.SetParameter per ogni parametro che abbiamo
definito nella macro per i dati. Il primo argomento dell'azione SetParameter è il nome
del parametro definito nella macro per i dati con nome, mentre il secondo argomento
dell'azione sono i dati da passare al parametro. Come puoi vedere nel codice, vengono
elencati i nomi di parametro e la variabile contenente i dati da registrare. Nota che, se i dati
che vengono passati sono una stringa, questa deve essere racchiusa tra virgolette. Una volta
che il codice ha impostato tutti i parametri, è possibile procedere a richiamare la macro
per i dati con nome usando il metodo DoCmd.RunDataMacro. Nell'argomento del metodo
RunDataMacro, devi inserire il nome della tabella, un punto e il nome della macro per i dati
con nome collegata alla tabella. Devi racchiudere l'argomento tra virgolette anche se i nomi
della tabella e della macro per i dati con nome non contengono spazi. L'ultima parte di
questo codice crea un messaggio personalizzato da mostrare all'utente e chiede se desidera
stampare il report degli errori. La routine di codice completata registra qualsiasi errore
dell'applicazione rilevato nel client Access richiamando la nostra macro per i dati con nome.
Quando richiami una macro per i dati con nome che restituisce dati al richiedente
usando ReturnVar, puoi usare il valore nella variabile ReturnVar nel codice Visual Basic. Ad
esempio, se desideri utilizzare Visual Basic per ottenere il numero della versione attuale
dell'applicazione BOSS e mostrare tale valore in una finestra di messaggio, puoi usare la
routine di esempio TestReturnVar nel modulo modErrorTrap:
Public Sub TestReturnVar()
‘ Set the parameter of ParamValue to Version
DoCmd.SetParameter “ParamValue”, “””Version”””
‘ Run the named data macro to get the current version of BOSS
DoCmd.RunDataMacro “tblSettings.GetCurrentValue”
‘ Display the current version of BOSS in a message box
‘ Use the ReturnVar called RVVersion in the message box
MsgBox “The current version number of BOSS is: “ & _
ReturnVars!RVVersion, vbInformation, “Version Number”
End Sub
411
Quando esegui questa routine di esempio, il codice esegue la macro per i dati con nome
GetCurrentValue collegata alla tabella Web tblSettings, passa il valore del parametro
appropriato da usare e visualizza quindi il valore restituito dalla macro per i dati nella
finestra di messaggio usando la raccolta ReturnVars. Nota che, a differenza della raccolta
TempVars, la raccolta ReturnVars non supporta alcun metodo Remove o RemoveAll per
eliminare o cancellare i valori memorizzati nella raccolta ReturnVars. Non è possibile
nemmeno impostare una variabile di ritorno usando Visual Basic; per fare ciò, devi infatti
utilizzare sempre l'azione sui dati SetReturnVar in una macro per i dati con nome collegata
a una tabella. Se imposti un ReturnVar in una macro per i dati con nome, Access conserva
tale valore nella raccolta ReturnVars fino a quando non esegui un'altra macro per i dati con
nome che imposti un altro ReturnVar, oppure fino alla chiusura del database. Nella raccolta
ReturnVars possono essere impostate fino a 255 variabili di ritorno, ma queste dovrebbero
quindi essere impostate in una chiamata a una macro per i dati con nome.
Come abbiamo visto in questo capitolo, Visual Basic è un linguaggio incredibilmente
potente, e le attività che puoi eseguire con esso sono limitate solo dalla tua immaginazione.
Nel capitolo 8, "Ultimi ritocchi", apprenderai come impostare le proprietà di avvio e creare
barre multifunzione personalizzate.
Capitolo 7
Richiamo di macro per i dati con nome