ProgressBar.            (23/07/03 - Aggiornamento 10/02/04)

Alcune richieste, anche passate, avevano come argomento, la realizzazione di una "barra di avanzamento" (ProgressBar) che avvisasse l'utente che una macro era in esecuzione. Infatti a volte si può lamentare una lentezza nell'esecuzione di procedure, tale da far pensare che il computer non stia lavorando, Una barra di avanzamento consentirebbe, con la sua progressione, di "visualizzare" che in effetti "qualcosa" sta succedendo. Premesso che alcune routine sono mal costruite, ed impiegano più tempo del dovuto a terminare, premesso che alcuni computer sono dotati di scarse risorse Hardware (poca RAM, dischi lenti, processori obsoleti) esistono tuttavia lentezze causate dalla gran quantità di dati che devono essere elaborati, da qui l'esigenza di avere un "segnale" che tutto sta procedendo.

Bisogna capire come prima cosa, che qualsiasi ProgressBar per funzionare, deve poter essere collegata ad una variabile che sia un vettore del numero di iterazioni che si stanno eseguendo. Solo con questa impostazione sarà possibile verificare in tempo reale (o quasi ) l'avanzamento dell'esecuzione di una macro, e le istruzioni per l'avvio della ProgressBar, andranno posizionate opportunamente per evitare che lo scorrimento della barra di avanzamento non parta, o parta dopo, o addirittura interferisca con l'esecuzione stessa (della procedura) rallentandola ulteriormente.

La ProgressBar più semplice e veloce da ottenere (ATTENZIONE: questa soluzione NON vale per la versione 97 di Excel), è quella di usare la "BARRA DI STATO". Vediamo come ottenerla:

dal menù Visualizza, selezionare la voce "Barra di stato", vedi foto:

e in fondo alla pagina, comparirà una barra in più, con all'estrema sinistra, la parola "Pronto" :

Questa operazione può comunque essere evitata, perchè la "Barra di stato" può venire chiamata da un'istruzione che integreremo nella nostra macro, e che imposterà anche il testo che apparirà al posto di "Pronto", indicando peraltro il valore che indica lo "stato di avanzamento", ottenendo l'effetto di tranquillizzare chi aspetta che la macro termini. Nella foto sotto vediamo un'istantanea dei  numeri che scorrono, indicando l'avanzamento:

Vediamo quindi come sfruttare le istruzioni, presentando un esempio nel quale, riempiremo tutte le celle che vanno dalla cella A1 alla J500, con numeri casuali. Questa la macro, che potrete copiare e provare sul vostro computer :

Sub Test()
[A1:J500].ClearContents
'nel range previsto come esempio, puliamo le celle
Dim r  
'dimensioniamo la variabile "r" per indicare le righe
Dim c  
'dimensioniamo la variabile "c" per indicare le colonne
For r = 1 To 500
 'indi si inizializza il ciclo che conterà le righe da 1 fino a 500
For c = 1 To 10 
 ' e le colonne fino alla colonna 10 (la J)
Randomize
 'Randomize serve a reinizializzare il numero iniziale casuale, diverso dal 'primo ottenuto con Rnd, successivo

Cells(r, c) = Int(Rnd * 150)
'poi assegniamo un numero casuale compreso tra zero e 150 'alla cella, riga r, colonna c, in quel momento reperita con i cicli For..Next sopra
Cells(r, c).Select 
 'selezioniamo la cella in quel momento "spazzolata" solo per creare un 'effetto visivo delle celle che vengono di volta in volta individuata. In questo modo si può 'controllare l'efficacia dell'avanzamento routine
Counter = r * c
'con la variabile "counter" (riga per colonna) otteniamo il numero di celle 'in ogni momento "contate" dai cicli For..Next e rappresenta il valore che verrà mostrato 'nella "StatusBar", indicando quindi la progressione delle operazioni.
DoEvents
 'istruzione necessaria perchè lascia l'esecuzione delle istruzioni "libere" di 'proseguire, senza "bloccarne" l'esecuzione.

'sotto: si attiva la "barra di stato" e si imposta il testo che vedremo, seguito dal valore 'rappresentato da "COUNTER".
Application.StatusBar = "Procedura in esecuzione : " & Counter

'indi si procede con Next a scorrere prima le righe, e a seguire le colonne
Next c
Next r
[A1].Select
'a fine ciclo si seleziona la cella A1 per ritornare a inizio foglio
Application.StatusBar = False 
'questa istruzione cancella il testo dalla barra di stato
End Sub

se non si fosse utilizzata l'ultima istruzione (Application.StatusBar = False), ma questa:

Application.StatusBar = "Procedura completata : " & Counter 

questo sarebbe ciò che vedremo nella barra di stato:

il tutto crea un buon effetto di macro in esecuzione e termine.

Bisogna fare attenzione al posizionamento delle istruzioni per l'avvio della StatusBar, si corre il rischio che per vedere una progressione di avanzamento, si rallenti in maniera notevole l'esecuzione della macro peggiorando il risultato finale in termini di tempo impiegato. Ognuno dovrà valutare le proprie necessità in funzione delle proprie istruzioni. Non chiedetemi aiuto in questo caso, ognuno dovrà capire come usare l'esempio proposto, e risolvere da solo.

Un modo meno problematico e comunque di effetto, è quello di usare una UserForm con una Label, dove sfrutteremo la Caption della Label per gestire due messaggi: Inizio Procedura, e Fine Procedura. Se avviamo l'UserForm ad inizio macro, usando DoEvents, lasceremo la prosecuzione delle istruzioni fino alla fine, e indipendentemente dal tempo impiegato, otterremo di avvisare l'utente che la procedura è in esecuzione, e alla fine che è terminata, senza interferire con i tempi già lunghi richiesti dalle vostre procedure. Vediamo come lavorare:

  • Impostare la proprietà ShowModal della UserForm a False. Questo consente di spostare il focus sul foglio lasciandolo libero di aggiornarsi. Condizione necessaria per l'aggiornamento o lo scorrimento del foglio, dipende ovviamente dalle istruzioni contenute nelle vostre procedure. Questa impostazione crea un problema su Excel 97 che si rifiuta, dando errore, di mostrare una UserForm con ShowModal impostato a False.

Usiamo l'esempio dei numeri casuali usato sopra, ma con le istruzioni per inizializzare la UserForm:

Sub Test3()
[A1:J500].ClearContents
'sotto: attiviamo la userform e nella Label facciamo apparire la scritta "PROCEDURA IN 'CORSO", usando la proprietà Caption della Label (etichetta)
UserForm1.Show
UserForm1.Label1.Caption = "PROCEDURA IN CORSO"
DoEvents '
Non ci scordiamo di trasferire il controllo ad altri processi, quelli sotto:
Dim r
Dim c
For r = 1 To 500
For c = 1 To 10
Randomize

Cells(r, c) = Int(Rnd * 150)
Cells(r, c).Select
Counter = r * c
'DoEvents

Next c
Next r
[A1].Select
'alla fine della procedura, modifichiamo il testo nella Label sull'userform
UserForm1.Label1.Caption = "FINE PROCEDURA"
End Sub

L'efficacia è garantita, questa l'immagine iniziale all'avvio della macro:

e questa l'immagine a fine procedura.

 

Un'altra ProgressBar  molto più efficace come effetto visivo, che comporta un pò più di lavoro per ottenerla, la propongo nell'esempio sotto. Premetto che l'idea non è mia, ma trovata in Internet, e adattata da me per l'occasione. Viene sfruttata anche in questo caso una UserForm, ma l'effetto visivo dello stato di avanzamento simula il funzionamento di una reale ProgressBar. Anche questa procedura non è attuabile con la versione 97 di Excel o precedenti. Ma vediamo cosa serve:

  • una UserForm la cui proprietà ShowModal va impostata a False.

  • una Cornice (Frame) (da inserire nella Userform), la cui proprietà SpecialEffect va impostata a 2-fmSpecialEffectSunken per creare l'effetto incavato.

  • un'etichetta (Label) (da inserire all'interno del Frame) la cui proprietà SpecialEffect va impostata a 1-fmSpecialEffectRaised per creare l'effetto rilievo.

  • la proprietà BackColor della Label andrà impostata ad un colore rosso (o blù o altro colore)

Per poter usare questo esempio adattandola alle proprie necessità è utile evidenziare come lavora la sottoesposta macro:

il nocciolo di tutto il lavoro è la selezione di ogni cella letta nei cicli For...Next e a questo provvede l'istruzione Cells(r, c).Select  se non si fa la selezione lo scorrimento delle istruzioni sarà tanto veloce da vanificare l'effetto progressbar. Viceversa se usassimo un Ciclo For Each...Next e si selezionasse ogni cella letta le istruzioni si pianterebbero alla prima celle letta e per proseguire dovremmo annullare la procedura; se non selezioniamo una cella la procedura finisce senza l'effetto voluto, è quindi indispensabile usare un doppio ciclo For Next che ci consente di proseguire generando l'effetto dell'avanzamento; ovviamente manca un vettore numerico che serva da indice alla lunghezza della Label usata, e per questo usiamo una variabile contatore che si incrementa di una unità ad ogni cella letta, in questo caso la variabile Counter farà avanzare la lunghezza della Label.

ed ora vediamo la routine presentata sopra per generare numeri casuali, modificata con le istruzioni necessarie:

Sub TestBar()
[A1:J500].ClearContents
Dim r
Dim c
RowMax = 500
'assegniamo il valore 500 al numero massimo di righe(con RowMax)
ColMax = 10 
'assegniamo il valore 10 al numero massimo di colonne (con ColMax)

For r = 1 To 500
'inizializziamo i cicli come spiegato nella Sub Test
For c = 1 To 10

Randomize
Cells(r, c) = Int(Rnd * 150)
Cells(r, c).Select

'qui posizioneremo le nostre istruzioni per intervenire nella cella selezionata

Dim Counter As Integer 'dichiariamo la variabile Counter come Integer
Counter = Counter + 1
'incrementiamo di 1 il counter ad ogni cella scorsa

Next c 
'si passa alla cella della colonna successiva
Percent = Counter / (RowMax * ColMax) 
'con Percent prendiamo il valore della 'percentuale delle celle scorse, dividendo il valore di counter per 5000
UserForm1.Show
'chiamiamo la Userform

With UserForm1
' con la Userform impostiamo il formato (0%) della proprietà Caption del 'Frame del valore rappresentato da Percent
.Frame1.Caption = Format(Percent, "0%")
.Label1.Width = Percent * (.Frame1.Width - 10)
' questa è l'istruzione che imposta la 'lunghezza della Label, data appunto dal valore Percent moltiplicato la Lunghezza del 'Frame meno 10 pt. E' questo che provoca l'effetto avanzamento nella label, il cui fondo è 'stato impostato a rosso
End With 
'fine con

DoEvents
'istruzione importante che passa il controllo ad altri processi, passando con 'Next r alla riga successiva per ripetere tutto il ciclo
Next r


[A1].Select

End Sub

Due immagini relative alla Sub TestBar

 

Chiaramente la velocità dell'avanzamento è proporzionale al tempo di esecuzione delle istruzioni.

 

10/02/04 - Aggiornamento.

Quando invece non sia possibile stabilire il numero di iterazioni legate all'esecuzione di una procedura, per cui risulta problematico determinare il vettore del valore di incremento di una ProgressBar, ma si voglia comunque avvisare l'utente che una procedura, a volte molto lunga, è in corso, possiamo usare una cella del foglio di lavoro (magari determinandola a priori, o meglio lasciando una cella libera per questo scopo), dove far apparire la scritta : "Procedura in esecuzione". Evitiamo in questo modo di "trafficare" con UserForm, e saremo comunque avvisati che il computer sta lavorando.

L'idea, presentata dall'amico Falini Eliano ( falinieliano@virgilio.it ), è semplice, facilmente attuabile, e di sicura efficacia. Si pongono ad inizio e fine della procedura di cui vorremo avvisare, due semplici blocchi di istruzioni, il primo attiverà la cella che avremo destinato allo scopo, scrivendo una frase che ci avvisi, e magari evidenziamo di giallo la cella stessa, e ne evidenziamo i bordi; il secondo blocco invece ripristina la cella, eliminando la scritta, il colore e togliendo i bordi. Quando attiveremo la nostra procedura, verremo avvisati che la procedura è in esecuzione, e quando sarà terminata sparirà l'avviso e la formattazione della cella ritornerà normale.

Simuliamo la Cella B1 come quella da noi destinata, e vediamo i due blocchi di istruzioni:

  • Sub TuaMacro()
    With [B1]  
     'blocco iniziale nuove istruzioni
    .Value = "Procedura in esecuzione :"   
    'messaggio che appare in B1
    .Interior.ColorIndex = 6                         
    'si colora la cella di giallo
    .Borders.LineStyle = xlContinuous      
     'si impostano i bordi alla cella
    End With
    .....seguono le istruzioni da eseguire

    With [b1]   
     'blocco finale nuove istruzioni
    .Value = ""                                            
     'si pulisce la cella B1
    .Interior.ColorIndex = xlNone               
    'si toglie il colore giallo
    .Borders.LineStyle = xlNone                
     'si tolgono i bordi
    End With
    End Sub

Ovviamente potrete decidere se usare il grassetto per la cella, ed eventualmete un colore per il font.

Un grazie ad Eliano.

 

Buon lavoro.

prelevato sul sito http://ennius.interfree.it