Il VBA (Visual Basic for Applications) relativo alle varie applicazioni Office quali Excel, Word, Access, etc. è uno strumento decisamente utile per coloro i quali intendono "espandere" le già ampie possibilità offerte da questi software, implementando da soli caratteristiche non previste dall'applicazione originale o creando, ad esempio, una maschera per il data-entry che faccia da front-end per il proprio documento.
Proprio riguardo l'aspetto del data-entry, chi è abituato a lavorare in Visual Basic 6, utilizzando il VBA potrebbe trovarsi ad operare in un ambiente un po' più stretto considerato che in quest'ultimo mancano strumenti importanti che renderebbero agevole la gestione di molti controlli dello stesso genere. Uno di questi è l'array di controlli.
Un array è definito come una serie di variabili identificate da uno stesso nome al quale è possibile riferirsi utilizzando un numero ovvero un indice.
Analogamente, un array di controlli, è una serie di controlli dello stesso genere che condividono il nome. Anche in questo caso ogni controllo è raggiungibile attraverso un indice numerico.
La cosa che rende gli Array di Controlli decisamente utili è l'esposizione degli eventi in maniera comune a tutti i suoi elementi e le Routines di evento sono in grado di restituire un indice che identifica il controllo che ha scatenato l'evento stesso.
In Visual Basic 6 è molto semplice creare e gestire un Control Array, poichè questi vengono gestiti direttamente dall'ambiente di sviluppo. Non si può dire alltrettanto per il VBA dove, se vogliamo un array di controlli, dobbiamo crearcelo e gestircelo da soli.
In VBA, quando si dispongono i controlli sul Form, essi vengono a far parte della collection Controls che è possibile ciclare per effettuare delle operazioni su più controlli, anche di genere diverso, a prescindere dal loro nome.
Questo è un breve esempio per dimostrare come è possibile azzerare il contenuto di tutti i TextBox presenti su un Form utilizzando la collection Controls:
Sub AzzeraTxt(frm As UserForm) Dim ctl As Control For Each ctl In frm.Controls If TypeOf ctl Is MSForms.TextBox Then ctl.Text = "" End If Next ctl End Sub
Se si inserisce la suddetta routine in un Modulo, sarà possibile richiamarla da qualsiasi UserForm semplicemente scrivendo:
AzzeraTxt Me
Purtroppo, se è vero che tramite la collection Controls abbiamo accesso ad una serie di controlli pur se non indicizzati e con nomi differenti è altrettanto vero che non possiamo che ricevere singolarmente gli eventi relativi a ciascuno dei controlli presenti sul Form. Questo ci costringe a dover scrivere in maniera ripetitiva del codice per inserirlo negli eventi relativi a tutti i controlli che ci interessa gestire.
Una maniera interessante di far fronte al problema è l'utilizzo delle Classi.
Utilizzando una Classe si possono gestire attraverso un solo oggetto molti aspetti relativi a più oggetti facenti parte di uno stesso insieme. All'interno di una Classe si possono definire Proprietà, Metodi ma soprattutto Eventi, il che è proprio ciò che ci interessa, considerato che potremo rilasciare degli eventi comuni a tutti i controlli gestiti dalla Classe stessa.
Per fare in modo di avere la maggiore flessibilità possibile ho pensato di realizzare 2 Classi.
La prima rappresenta l'oggetto vero e proprio, ovvero l'Item della Array di controlli che andremo a gestire, mentre la seconda si occuperà di fare da "tramite" tra la gestione dei singoli Item disposti in una Collection e la nostra applicazione.
La prima delle due Classi è molto semplice e non fa altro che definire gli oggetti relativi alle varie tipologie di controlli che si desiderano gestire (TextBox, CheckBox, Images, etc...) ed il rilascio degli eventi peculiari per ogni controllo.
In considerazione del fatto che la classe può essere istanziata più volte per ottenere più Array di controlli, si è manifestato il problema di identificare la Classe "madre" alla quale restituire la chiamata dell'evento. Per ovviare a ciò è bastato utilizzare un ulteriore oggetto "CallerObject" che in pratica permette ad ogni Item di "sapere" a quale Classe appartiene.
Ecco il codice della classe cMatrixItem:
Public WithEvents itmTextBox As MSForms.TextBox Public WithEvents itmImage As MSForms.Image Public WithEvents itmCheckBox As MSForms.CheckBox Public CallerObject As cCtlMatrix Private Sub itmCheckBox_Click() CallerObject.ItemClick itmCheckBox End Sub Private Sub itmImage_Click() CallerObject.ItemClick itmImage End Sub Private Sub itmTextBox_Change() CallerObject.ItemChange itmTextBox End Sub
Come si può notare gli eventi definiti sono davvero pochi come pure i tipi di oggetti gestiti ma, per analogia, è immediata l'aggiunta sia di ulteriori tipi di controlli che di ulteriori eventi ad essi relativi.
Andiamo adesso a progettare la Classe che ospiterà la Collection vera e propria.
Definiamo prima di tutto gli oggetti che la classe dovrà gestire: gli Item e la Collection.
Dim itm As cMatrixItem Dim cControls As Collection
L'oggetto itm è dichiarato come cMatrixItem e servirà per tipizzare correttamente l'oggetto da aggiungere alla Collection.
Quindi definiamo gli Eventi che la Classe sarà in grado di rilasciare.
Come detto prima, gli eventi qui dichiarati a scopo esemplificativo sono pochi, ma è possibile arricchire, semplicemente dichiarandoli, la lista di eventi che la Classe sarà in grado di rilasciare in risposta alle azioni compiute sui controlli gestiti dalla Collection.
Public Event Click(item As Object) Public Event Change(item As Object)
Nel nostro caso, al posto di restituire esclusivamente un indice, nelle Routines di Evento forniremo un riferimento all'oggetto che l'evento lo ha generato.
Restituire solo l'indice servirebbe a poco visto che i controlli in VBA non sono comunque indicizzabili, rendendo di conseguenza difficile l'identificazione del controllo stesso in relazione all'evento scatenato.
I moduli di Classe possiedono un evento che viene scatenato quanto esse vengono istanziate nel codice che le utilizza: l'evento Class_Initialize().
Questo possiamo sfruttarlo per "dare vita" alla nuova Collection di controlli:
Private Sub Class_Initialize() Set cControls = New Collection End Sub
Occupiamoci di definire adesso un metodo Add attraverso il quale, da codice, potremo aggiungere alla Collection i controlli che ci interessa gestire come Array
Public Sub Add(ByVal actItm As Object) Set itm = New cMatrixItem Set itm.CallerObject = Me If TypeOf actItm Is MSForms.TextBox Then Set itm.itmTextBox = actItm ElseIf TypeOf actItm Is MSForms.Image Then Set itm.itmImage = actItm ElseIf TypeOf actItm Is MSForms.CheckBox Then Set itm.itmCheckBox = actItm End If cControls.Add itm Set itm = Nothing End Sub
Nel metodo appena definito si può notare che il controllo è visto in maniera generica ma viene fatta una distinzione sul suo tipo tramite l'If...Then. Questo avviene perchè non tutti i controlli possiedono le stesse proprietà o i medesimi eventi, anche se molti condividono entrambi.
Anche in questo caso gli oggetti considerati sono solo 3, ma la possibilità di espandere il range di tipologie è sempre valida e fattibile in maniera immediata.
Continuiamo lo sviluppo della Classe aggiungendo 2 Proprietà in sola lettura: Count e ItemCollection per ottenere rispettivamente il numero di elementi già presenti nell'Array e il riferimento ad uno dei controlli dell'Array attraverso un Indice numerico.
Public Property Get Count() As Single Count = cControls.Count End Property Public Property Get ItemCollection(ByVal Index As Single) As Object Dim tmpObject As Object Set tmpObject = cControls(Index) With tmpObject If Not .itmCheckBox Is Nothing Then Set ItemCollection = .itmCheckBox If Not .itmImage Is Nothing Then Set ItemCollection = .itmImage If Not .itmTextBox Is Nothing Then Set ItemCollection = .itmTextBox End With End Property
Ricordo che anche nella ItemCollection è possibile aggiungere altri tipi di controlli analogamente a come già fatto per le 3 tipologie in esame.
Passiamo adesso a definire le routines che rilasceranno gli eventi veri e propri attraverso la loro invocazione (da parte dell'Item) dei metodi del CallerObject
Friend Sub ItemClick(item As Object) RaiseEvent Click(item) End Sub Friend Sub ItemChange(item As Object) RaiseEvent Change(item) End Sub
Inutile dire che anche in questo ennesimo caso si possono aggiungere tutti gli eventi necessari relativi ai vari controlli.
Ovviamente bisogna badare al fatto che molti controlli possono condividere eventi dello stesso genere, come nel caso dei controlli Image e CheckBox che condividono l'evento Click. In questi casi basterà dichiarare una sola volta la Routine ItemClick.
Questo è il codice completo della Classe cCtlMatrix:
Dim itm As cMatrixItem Dim cControls As Collection Public Event Click(item As Object) Public Event Change(item As Object) Private Sub Class_Initialize() Set cControls = New Collection End Sub Public Sub Add(ByVal actItm As Object) Set itm = New cMatrixItem Set itm.CallerObject = Me If TypeOf actItm Is MSForms.TextBox Then Set itm.itmTextBox = actItm ElseIf TypeOf actItm Is MSForms.Image Then Set itm.itmImage = actItm ElseIf TypeOf actItm Is MSForms.CheckBox Then Set itm.itmCheckBox = actItm End If cControls.Add itm Set itm = Nothing End Sub Public Property Get Count() As Single Count = cControls.Count End Property Public Property Get ItemCollection(ByVal Index As Single) As Object Dim tmpObject As Object Set tmpObject = cControls(Index) With tmpObject If Not .itmCheckBox Is Nothing Then Set ItemCollection = .itmCheckBox If Not .itmImage Is Nothing Then Set ItemCollection = .itmImage If Not .itmTextBox Is Nothing Then Set ItemCollection = .itmTextBox End With End Property Friend Sub ItemClick(item As Object) RaiseEvent Click(item) End Sub Friend Sub ItemChange(item As Object) RaiseEvent Change(item) End Sub
La progettazione delle Classi è terminata. Non rimane che creare un semplice progetto di prova per valutare la correttezza del comportamento.
Aggiungiamo un nuovo UserForm e disponiamo su di esso alcuni controlli Image, delle CheckBox, qualche TextBox, un CommandButton e una Label. Lasciamo pure i nomi di default, come in figura.

Usiamo questo codice per il Form:
Dim WithEvents TextBoxArray As cCtlMatrix Dim WithEvents ImageArray As cCtlMatrix Dim WithEvents CheckArray As cCtlMatrix Private Sub UserForm_Initialize() Dim ctl As Control Set TextBoxArray = New cCtlMatrix For Each ctl In Me.Controls If TypeOf ctl Is MSForms.TextBox Then TextBoxArray.Add ctl End If Next ctl Set ImageArray = New cCtlMatrix For Each ctl In Me.Controls If TypeOf ctl Is MSForms.Image Then ImageArray.Add ctl End If Next ctl Set CheckArray = New cCtlMatrix For Each ctl In Me.Controls If TypeOf ctl Is MSForms.CheckBox Then CheckArray.Add ctl End If Next ctl End Sub Private Sub CheckArray_Click(item As Object) Label1.Caption = item.Name & ": " & item.Value End Sub Private Sub Command1_Click() For k = 1 To CheckArray.Count CheckArray.ItemCollection(k).Value = True Next End Sub Private Sub TextBoxArray_Change(item As Object) Label1.Caption = item.Name & ": " & item.Text End Sub Private Sub ImageArray_Click(item As Object) Label1.Caption = item.Name End Sub
Per dubbi o chiarimenti non esitate a contattarmi.
E' possibile scaricare il file di Excel con il progetto completo da qui:


























Commenti
Guida bene Fatta, lettura scorrevole e soprattutto codice che funziona!!!Per me sei il numero 1
Grazie
GianCasa
RSS feed dei commenti di questo post.