Lavorare con gli "Oggetti" (ActiveX e non) sul Foglio di Lavoro. - dal 04/09/04 pagina vista: volte

Articolo correlato: "Identificare una Forma (Shapes) e lavorarci."

Aggiornamento al 23/03/05

Questo articolo prosegue e integra l'articolo "Shapes (forme) - lavorare con gli" presente nella sezione vba del sito padre (http://ennius.interfree.it), che invito a leggere per le informazioni ivi contenute, e che integrano ciò che non verrà detto in questa pagina.

Sappiamo che Excel possiede, come dire, una doppia personalità : può lavorare come Foglio di calcolo, con tutte le sue funzioni, formule, opzioni, ecc., ed integra anche le potenzialità del VisualBasic, il suo VBA. Il VisualBasic è un linguaggio di programmazione orientato agli "oggetti". E' possibile cioè usare degli "Strumenti di lavoro" come CommandButton, TextBox, UserForm, Label, ComboBox, Image, ecc.ecc., che altro non sono che "oggetti" già pronti, che possiamo utilizzare inserendoli nei nostri lavori. Questi "oggetti" appartengono ai controlli ActiveX, e che possiedono delle "proprietà" su cui è possibile intervenire modificandone le impostazioni. Possiedono inoltre degli "Eventi", che ci consentono di "pilotare" l'esecuzione di istruzioni in codice (o macro che dir si voglia). Tutti questi "Controlli ActiveX" si differenziano notevolmente dai "Controlli classici"  tipici di Excel (per esempio i controlli presenti nella finestrina Moduli) non solo per la maniera di usarli, ma anche nel modo in cui Excel li identifica, e quindi nel modo in cui noi dovremo usare le istruzioni per interreagire con tutti questi "Controlli" diversi tra loro.

L'esercizio su cui ci "alleneremo" è quello di vedere come "dialogare" con questi "oggetti" una volta inseriti sul foglio di lavoro. Iniziamo col vedere le differenze di come Excel reagisce all'inserimento di due controlli simili, ma uno proveniente dai controlli classici, ed uno proveniente dai controlli ActiveX :

  • CommandButton preso da Moduli ("controllo classico"). Se attiviamo il registratore d macro, ed inseriamo il commandbutton sul foglio, vedremo questo codice compilato da Excel:

  • ActiveSheet.Buttons.Add(195, 73.5, 89.25, 21).Select

  • viene usato il Metodo Add (aggiungi) del controllo Buttons, e vengono impostate le coordinate per la dimensione e il suo posizionamento sul foglio di lavoro. Suggerisco ai meno esperti, di inserire manualmente il pulsante senza ricorrere ad istruzioni come quella sopra. Cito questi passaggi solo per far notare le differenze; infatti per un

  • CommandButton preso da Strumenti di controllo (o Casella degli strumenti, per versioni Excel precedenti) ("controllo ActiveX"). Se attiviamo il registratore d macro, ed inseriamo il commandbutton sul foglio, vedremo questo codice compilato da Excel:

  • ActiveSheet.OLEObjects.Add(ClassType:="Forms.CommandButton.1", Link:=False _
    , DisplayAsIcon:=False, Left:=242.25, Top:=111.75, Width:=114.75, _
    Height:=26.25).Select

  • la differenza sostanziale sta in quel OLEObjects, che ci evidenzia che il commandbutton aggiunto sul foglio (Add) appartiene agli Oggetti OLE, che sono oggetti a collegamento dinamico (ActiveX), ma più semplicemente: sono oggetti "prestati" ad Excel. Questa abitudine, di farsi prestare oggetti dal VisualBasic, Excel la ripaga con la necessità di un maggior numero di istruzioni per riconoscere un controllo ActiveX inserito su un foglio di lavoro (diverso sarebbe se il cmdbutton fosse stato inserito su UserForm, ma non ne parliamo in questa sede)

L'esempio che seguiremo sarà quello di poter cambiare il nome che appare sul controllo, per poter abbinare istruzioni diverse a secondo il nome scelto. Questo nome potremo scriverlo in una cella (es. la A2), oppure selezionarlo da una ListBox.

Vediamo come "identificare" questi "oggetti", e chiamiamo ora i due commandbutton : "button" (da Moduli) e "cmdbtn" (ActiveX) (per semplificare),  e verifichiamo le differenze:

  • intanto, per poter modificare il button, è necessario selezionarlo:

  • ActiveSheet.Shapes("Button 1").Select

  • e di seguito fare seguire le istruzioni per la modifica del testo:

  • ActiveSheet.Shapes("Button 1").Select
    Selection.Characters.Text = [A2].Value
    [A2].Select

notiamo che il "button" viene considerato uno Shapes (Forme) e per assegnare uno specifico testo, una volta selezionato, si ricorre al metodo Characters di cui Text rappresenta il testo. E' inoltre necessario ricorrere all'istruzione  [A2].Select per "deselezionare" il button e renderlo cliccabile.

  • per il cmdbtn invece si agisce direttamente identificando il controllo senza necessità di selezionarlo, e modificando la sua proprietà Caption :

  • ActiveSheet.OLEObjects("CommandButton1").Object.Caption = [A2].Value

  • decisamente più rapido, e col vantaggio di poter disporre, in "modalità progettazione", di altre "proprietà" del cmdbtn oltre che dei suoi "eventi" per attivare macro.

Con gli stessi concetti potremo, per entrambi, modificare altre "condizioni" nei nostri 2 "controlli", come il colore di fondo dei commandbutton o il tipo di font per esempio, ma la differenza sostanziale consiste nel modo in cui si possono collegare a istruzioni da mandare in esecuzione, cioè come si possono associare alle macro. Vediamo una tabellina che riepiloga queste possibilità per i due tipi di commandbutton:

tipo commandbutton

Eventi propri

Macro su Modulo

metodo

buttons

NO SI da "Associa Macro" scegliere il nome della macro

cmdbtn

SI SI inserire le istruzioni nell'evento scelto, es. evento Click, oppure nell'evento scrivere il nome di una macro esistente

  quindi il buttons può associarsi solo a macro su moduli

Ma ritorniamo al nostro esercizio: abbiamo detto che vorremmo sostituire il testo che appare sul commandbutton, in modo da poter eseguire istruzioni diverse, ognuna legata al testo che sarà sul commandbutton, e di voler scegliere, per sostituire il testo, se farlo legando il testo al contenuto di una cella del foglio di lavoro, oppure ad un testo contenuto in una ListBox. Per questo tipo di istruzione, nel primo caso,  presupponendo che potremo variare in nome nella cella, sfrutteremo l'evento Change del foglio di lavoro, e vediamo l'istruzione vista per tutti e due i commandbutton:

  • Private Sub Worksheet_Change(ByVal Target As Range)

  • 'istruzione per il CommandButton ActiveX (cmdbtn)
    ActiveSheet.OLEObjects("CommandButton1").Object.Caption = Range("A2").Value

  • 'istruzione per il CommandButton "classico" (buttons)
    ActiveSheet.Shapes("Button 1").Select
    Selection.Characters.Text = [A2].Value
    [A2].Select
    End Sub

Ad ogni variazione di un nome in A2 verranno modificate le scritte sui pulsanti. Per la variante ad un nome da scrivere in una cella, useremo una ListBox. Premetto subito che useremo solo la ListBox presa da "Strumenti di controllo" in quanto quella presa da Moduli opera diversamente sia come restituzione della voce selezionata (restituisce nella cella collegata il numero indice anzichè il valore o testo selezionatovi), sia perchè non dispone di "eventi". E' infatti con l'evento Click (o Change) della ListBox, che ci affideremo alla sua proprietà Text per modificare la scritta sui due commandbutton, questa l'istruzione vista per tutti e due

  • Private Sub ListBox1_Click()

  • 'istruzione per il CommandButton ActiveX (cmdbtn)
    ActiveSheet.OLEObjects("CommandButton1").Object.Caption = ListBox1.Text
    'istruzione per il CommandButton "classico" (buttons)
    ActiveSheet.Shapes("Button 1").Select
    Selection.Characters.Text = ListBox1.Text
    [A2].Select
    End Sub

Ricordo inoltre che per identificare dei Controlli ActiveX posti sul foglio, si segue un indice progressivo assegnato da Excel, che parte da 1 a seguire, PER OGNI TIPO DI CONTROLLO, quindi se si inseriscono due commandbutton, si identificheranno come CommandButton1, CommandButton2, se a seguire si inserisce una Listbox questa si identifica come ListBox1, ecc, cioè l'indice è legato al tipo di controllo.

Se invece si lavorasse con strumenti classici (da Moduli), l'indice è progressivo indipendentemente dal tipo di controllo, quindi mettendo in sequenza due commandbutton ed una ListBox, si avrebbe Button 1, Button 2, List Box 3 e via così, ogni nuovo Shapes incrementa l'indice, anche se poi si cancella.

Sarà possibile inserire qualunque oggetto tra i disponibili (e sono tanti), e, aiutandosi col registratore di macro, scoprire come vengono identificati quando si selezionano, e memorizzare l'informazione.

Aggiornamento al 23/03/05

Esiste tuttavia un problemino: Excel considera comunque genericamente Shapes tutti gli "oggetti" inseriti su un foglio, siano essi provenienti dai "Controlli ActiveX" (gli OLEObjects), sia dai "controlli classici" (provenienti da "Moduli" o dalle Forme (Disegno, etc)) , ne può conseguire una certa confusione quando, dovendo riferirci ad un "Oggetto" (inserito) per agire su di esso, (o ad un "gruppo di Oggetti" inseriti), dovremo usare la giusta sintassi : Shapes o OLEObjects ? Molto dipenderà dall'azione che intendiamo compiere sull' "oggetto", ma procediamo con ordine; intanto, per verificare che per Excel sono tutti Shapes, possiamo inserire in un foglio "oggetti" diversi, ad esempio con questa sequenza :

  1. 5 CheckBox ActiveX (caselle di controllo) ed ogni CheckBox verrà identificata dal nome seguito da un indice progressivo che partirà da 1 : CheckBox1, CheckBox2, CheckBox3, ecc.
  2. 2 pulsanti ottenuti dalla casella Disegno, usando la Forma Rettangolo, che verranno identificati come "Rettangle" seguiti da un numero indice che non partirà da1 (ricordo che per gli Shapes il numero indice che identifica ogni Shape è un numero progressivo dipendente dal numero di "Controlli" inseriti in precedenza sullo stesso foglio, qualunque sia il "tipo di controllo"), e quindi saranno "Rettangle 6" e "Rettangle 7".
  3. 2 ToggleButton ActiveX (pulsante interruttore) i quali, pur essendo ActiveX, appartengono ad una "Classe" (tipo) diverso dai CheckBox, saranno identificati come ToggleButton1 e ToggleButton2..
  4. 1 pulsante Ovale, ottenuto dalla casella Disegno, usando la Forma Ovale, che verrà identificato come "Oval 10"; si 10, perchè di fatto è il decimo "oggetto" inserito, in quanto anche i precedenti ToggleButton (il cui loro numero indice è inizializzato da 1) rappresentano gli Shapes 8 e 9.

Potremmo inserire altri "oggetti", ma l'esempio sopra è sufficiente per renderci conto di quanto detto in questo articolo; dobbiamo quindi considerare che, (posta una situazione come l'esempio sopra (più tipi di "oggetti" sullo stesso foglio)), determinate istruzioni in alcuni casi non possono essere seguite, per esempio non possiamo usare la proprietà Name per riferirci agli oggetti, in cicli For Next che scorrano tutti gli oggetti cercando ad esempio solo i Rettangle per impostarne il colore di fondo, oppure istruzioni che basandosi su Shapes (d'altra parte sono tutti Shapes, no?) vogliano modificarne proprietà, come ad esempio selezionare o deselezionare le CheckBox presenti.(proprietà Value di un controllo CheckBox). Mi permetto di suggerire intanto una routine per identificare il nome e relativo numero indice di tutti gli Shapes presenti sul foglio, questa ci consente quindi di identificarli e poter usare istruzioni mirate. Vedremo poi alcune soluzioni ad alcuni problemini sull'argomento, problemi riscontrati da lettori del sito. La routine sotto è già presente nell'articolo citato ad inizio pagina, ma la riportiamo qui per comodità, con una modifica che potrà servire:

  • Sub DimmiNomeShape()
    Dim N As Integer
    x = ActiveSheet.Shapes.Count
    For N = 1 To x
    MsgBox ActiveSheet.Shapes(N).Name
    Next
    End Sub

Questa routine ci dirà il nome e il loro numero indice (come Shapes), e con una piccola modifica, potremo ottenere di farci dire solo i nomi degli Shapes Forme, oppure dagli Shapes ActiveX. Sfrutteremo la proprietà Type che restituisce 12 quando lo Shapes visitato nel ciclo appartiene agli "Oggetti ActiveX" (OLEObjects) , qualunque esso sia, sempre e soltanto 12, mentre per gli Shapes Forme, il numero restituito da Type varia a seconda la Forma utilizzata. Creando un'istruzione condizionale che, ad esempio, escluda i Type 12 (Type <> 12) , otterremo solo i nomi degli Shapes Forme:

  • Sub DimmiNomeTipoShape()
    Dim N As Integer
    x = ActiveSheet.Shapes.Count
    For N = 1 To x
    If ActiveSheet.Shapes(N).Type <> 12 Then
    MsgBox ActiveSheet.Shapes(N).Name & "-" & ActiveSheet.Shapes(N).Type
    End If
    Next
    End Sub

E' altrettanto vero che un'istruzione che identifichi lo Shapes attraverso un numero "Type", ci consentirà di agire solo su tutti gli Shapes di quel tipo, impostando le necessarie istruzioni.

Inoltre, in particolare per gli OLEObjects (gli Oggetti ActiveX), come abbiamo già visto nella prima parte di questo articolo, sia Excel che il suo VBA, usano definizioni che possono creare ulteriori confusioni: quando noi inseriamo uno di questi oggetti, ad esempio una CheckBox, nella barra della formula del foglio di lavoro vediamo comparire questa scritta:

=INCORPORA("Forms.CheckBox.1";"") , cioè l'istruzione di incorporare l'oggetto "CheckBox.1", ossia il nome viene scritto con un punto tra il nome dell'oggetto e il suo numero indice; lo stesso accade se compiliamo un'istruzione in vba per aggiungere (Add) ex novo uno stesso controllo :

ActiveSheet.OLEObjects.Add(ClassType:="Forms.CheckBox.1", Link:=False ecc. ecc

e questo modo di "chiamare" un oggetto crea confusione quando invece dovremo riferirci ad esso per far eseguire istruzioni; infatti le istruzioni per selezionarlo, modificarne una proprietà, ecc, usano il nome dell'oggetto ma senza il punto tra nome e numero indice, tuttalpiù uno spazio come nei nomi degli Shapes Forme. Avremo quindi necessità di usare la sintassi :

"CheckBox1" per una (prima) Casella di controllo, e per un rettagolo preso da Forme (Disegno), "Rectangle 1". Risulta quindi utile la macro appena vista per essere sicuri di come viene visto esattamente il nome dal vba.

Ed ora vediamo alcuni esempi di istruzioni che sono nate rispondendo ad alcune problematiche avanzate dai lettori del sito. Unica premessa, visto che agiremo su più "Oggetti" simili, useremo cicli For Next che agiscano sugli "Insiemi", cioè su tutti gli Oggetti appartenenti allo steso Tipo o Classe, che vengono quindi identificati dal loro nome e dal loro "numero indice".

- Problema : "Ho un piccolo problema per il quale chiedo una mano: ho inserito in un foglio una serie di caselle di controllo, vorrei creare una macro con la quale deselezionare tutte le caselle selezionate senza toccare quelle già vuote".
- Soluzione :

Basta creare un ciclo che scorra tutti gli oggetti presenti sul foglio, (di cui con "x" si contano quanti sono), e se le CheckBox hanno il Value = True (segno di spunta nella casella) , si imposta il Value a False.

Questa la routine:

x = ActiveSheet.OLEObjects.Count 'contiamo quante sono le CheckBox ed usiamo quindi la "x" come limite super.
For N = 1 To x
If ActiveSheet.OLEObjects("CheckBox" & N).Object.Value = True Then
ActiveSheet.OLEObjects("CheckBox" & N).Object.Value = False
End If
Next

Questa istruzione funziona bene se tutti gli "Oggetti ActiveX" appartengono alla stessa Classe, alle CheckBox, ma se sul foglio avremo inserito anche altri "Oggetti ActiveX" , come i ToggleButton o altri oggetti ActiveX, si genererà un errore; infatti con la variabile "x" che conta tutti gli OLEObjects presenti, e che rappresenta il limite superiore del ciclo For Next, vengono "letti" anche gli OleObjects che CheckBox non sono, generando l'errore per l'impossibilità di trovare l'OLEOBjects "CheckBox" seguito da un numero indice N (scorso nel ciclo) che sarà di un'altro OLEOjects. Allora useremo questa soluzione che ci consente di intervenire SOLO se l'OLEObjects letto sarà una CheckBox: sfruttiamo la proprietà Name per identificare il nome dell' OLEObjects letto, e usiamo la proprietà Value dell'oggetto Object dell'OLEOBjects :

x = ActiveSheet.OLEObjects.Count
For N = 1 To x
If ActiveSheet.OLEObjects(N).Name = "CheckBox" & N Then
ActiveSheet.OLEObjects(N).Object.Value = False '*
End If
Next

purtroppo questa storia di "oggetti" figli di "Oggetti", di proprietà degli uni e non degli altri ecc., è una caratteristica che sicuramente appesantisce la compilazione di istruzioni, ma purtroppo non ne possiamo fare a meno, ma semplificando, la riga richiamata dall'asterisco (*), si spiega così: sul foglio attivo, all'oggetto (Object) rappresentato, nell'insieme degli oggetti OLE (OLEOBJects), dall'oggetto OLE col numero indice (N), mi imposti la sua proprietà Value a False

Se le spiegazioni vi sembrano un tantino incasinate, scusate, ma meglio non mi vengono. Ecco un'altro esempio, che farà aumentare la confusione:

- Problema : "mi occorrerebbe nascondere diversi ToggleButton; ho trovato una routine che mi permette di nascondere TUTTI i controlli ActiveX, ma non so come fare per nascondere SOLO i pulsanti Togglebutton (dal n° 16 al n° 40)".

Soluzione: basta lavorare sul nome dell'oggetto ActiveX, usando un ciclo che scorra i numeri degli indici numerici dei loro nomi, quindi:

For m = 16 To 40  ' sono da nascondere dal 16° al 40° ToggleButton sul foglio ora attivo
ActiveSheet.Shapes("ToggleButton" & m).Visible = False
Next

Dicevamo confusione; infatti la routine sopra, pur riferendosi ad "OggettiActiveX" e quindi OLEOBjects, accetta l'identificazione attraverso il riconoscimento come Shapes (d'altra parte sono comunque anche Shapes per Excel), ed agisce senza generare errori anche se sul foglio fossero prsesenti altri Oggetti ActiveX. Il motivo è semplice : conosciamo esattamente da quale numero (indice) a quale altro numero (indice) riferirci, ed il ciclo scorrerà solo i ToggleButton "m", inoltre l'azione di rendere non visibile uno Shape è svolta da una proprietà (Visible) che gli ShapeRange possiedono, mentre non possiedono ad esempio una proprietà Value. Usate quindi la guida in linea per verificare le proprietà possedute dagli oggetti che inserite.

Proseguendo con esempi, simuliamo ora di voler intervenire solo su determinati OLEObjects di una stessa Classe, ad esempio delle CheckBox ActiveX. Supponiamo di avere inserito 10 CheckBox, e di voler agire solo su 5 di esse, e queste cinque non sono conseguenti, per cui i lori numeri indice sono sfalsati; non conviene usare tante istruzioni condizionali con If...Then ma risulta più pratico creare una matrice di questi "Controlli", indicando nella matrice i numeri indice delle CheckBox sulle quali vorremo agire, ed usare la variabile dell'Array come vettore del relativo numero indice. Agiremo quindi sulle CheckBox 3, 5, 7, 9

N = Array(3, 5, 7, 9)
For M = LBound(N) To UBound(N)
ActiveSheet.OLEObjects("CheckBox" & N(M)).Object.Value = False
Next

oppure, se avessimo voluto agire sulla loro visibilità, avremmo potuto usare la sintassi Shapes, così:

N = Array(3, 5, 7, 9)
For M = LBound(N) To UBound(N)
ActiveSheet.Shapes("CheckBox" & N(M)).Visible = False
Next

Questi ultimi due suggerimenti si possono diversificare aggiungendo ad esempio la condizione che se una certa cella conterrà un determinato valore (testo, data o numero) allora certe CheckBox saranno non visibili, altrimenti saranno visibili; uso la cella A1: se conterrà il valore 1, saranno visibili, altrimenti invisibili:

If [A1] = 1 Then
boleano = True
Else
boleano = False
End If
N = Array(3, 5, 7, 9)
For M = LBound(N) To UBound(N)
ActiveSheet.Shapes("CheckBox" & N(M)).Visible = boleano
Next

un' ultimo caso : se avremo molti Shapes presi ad esempio da Forme, Disegno "Rectangle", e vorremo colorare il fondo di rosso, useremo un Array indicando gli indici che ci interessano, e poi useremo la loro proprietà ForeColor per "riempirla" (Fill) di rosso: (da notare che la sintassi "Rectangle " vuole uno spazio prima del numero indice N(M))

N = Array(6, 8, 9, 15, 20) 'i numeri indice dei Rectangle che ci interessano
For M = LBound(N) To UBound(N)
ActiveSheet.Shapes("Rectangle " & N(M)).Fill.ForeColor.RGB = RGB(255, 0, 0)
Next

Questi pochi esempi spero che servano per districarsi un pò di più nel modo di interreagire con gli "oggetti", visto che su un foglio di lavoro si possono utilizzare veramente tanti tanti tipi Shapes (dai Commenti alle Forme (libere e non), dai Callout agli oggetti della finestra Moduli, da Immagini ai WordArt, dai diagrammi a.....ecc.ecc.).

Buon lavoro.

prelevato sul sito www.ennius.altervista.org