TheTruster's Box

  • Increase font size
  • Default font size
  • Decrease font size
Home Programmazione Articoli Array di Controlli in VBA di Excel

Array di Controlli in VBA di Excel

E-mail Stampa PDF

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: DOWNLOAD

 

HEADER  

 
+1 #1 Y-m-d H:i
Volevo ringraziarti!!! 8)

Guida bene Fatta, lettura scorrevole e soprattutto codice che funziona!!!Per me sei il numero 1

Grazie

GianCasa
 
 
0 #2 Y-m-d H:i
Ho necessià di accedere da Access VB ad alcune informazioni memorizzate tra le proprietà di un documento di Word (es. Titolo del documento o data di creazione ).
Mi puoi dare qualche suggerimento?

Grazie, Al.
 
 
0 #3 Y-m-d H:i
Splendido !
Mi ha risolto un problema abbastanza serio visto che Microsoft ha pensato bene di non supportare più il vecchio controllo calendario in Office 2010.
 
 
0 #4 TheTruster Y-m-d H:i
Grazie Luca,
Felice di esserti stato utile
 
 
0 #5 Franco Ostoich Y-m-d H:i
Ho molto apprezzato le chiare istruzioni ed ho usato il tutto con successo.
Non so però come fare con eventi quali DblClick o KeyPress che passano parametri (per es. KeyAscii)
Ancora grazie e cordiali saluti
 

Sondaggio

Cosa vorresti vedere di più su TheTruster's Box?
 

Utenti on-line

 216 visitatori online

MasterDrive.it



Aggiungi TheTruster's Box ai preferiti!


Questo sito utilizza cookie, anche di terze parti, per personalizzare i contenuti. Per informazioni o negare il consenso a tutti o ad alcuni cookie leggi la nostra Cookie Policy. Chiudendo questo banner, scorrendo questa pagina o cliccando su qualunque suo elemento acconsenti all'uso dei cookie. Per informazioni sui Cookies che usiamo e su come cancellarli, guarda la nostra Cookie Policy.

Accetto esplicitamente i Cookies di questo sito.

EU Cookie Directive Module Information