Mostrando o Progresso de Macros Demoradas

É importante que um programa dê feedback visual ao usuário a respeito da execução de um processo longo. Por exemplo, durante a realização de cálculos demorados, o Excel mostra ao usuário o progresso dos cálculos:

Se você desenvolve macros que executam cálculos e análises demoradas, é recomendável criar um feedback visual semelhante. Esta página ensina como mostrar ao usuário o progresso de uma tarefa na barra de status do Excel durante a execução de uma macro.

Introdução


Numa pasta de trabalho (vá até o final da página para baixá-la), há uma planilha com uma tabela como a mostrada abaixo:

Essa planilha tem aproximadamente 20.000 linhas e nela há uma macro que preenche os valores de impostos das colunas vizinhas, de acordo com critérios que não possuem relevância aqui.

O processo, como todo, é demorado. Então, criei uma barra de progresso que é mostrada na barra de status do Excel durante a execução do processo:

Código


O código completo, que deve estar num módulo regular, é mostrado abaixo:

Option Explicit
 
Private Sub DemoStatusBar()
    Dim i As Long
    Dim iStep As Long
    Dim iValue As Double
    Dim TotalSteps As Long
    
    wsData.Range("C3:F20000").ClearContents
    
    'Configurar qual o máximo da barra de progresso
    'Regra geral: [Maior valor] - [Menor valor] + 1
    TotalSteps = 20000 - 3 + 1
    
    For i = 3 To 20000
         iValue = wsData.Cells(i, "B").Value2
         wsData.Cells(i, "C") = iValue * WorksheetFunction.RandBetween(2, 5) / 100
         wsData.Cells(i, "D") = iValue * WorksheetFunction.RandBetween(7, 10) / 100
         wsData.Cells(i, "E") = iValue * WorksheetFunction.RandBetween(12, 15) / 100
         wsData.Cells(i, "F") = iValue * WorksheetFunction.RandBetween(17, 20) / 100
         
         'Só queremos que a execução entre neste bloco de código algumas vezes
         If i Mod 100 = 0 Then
            'Como o primeiro passo se dá quando i = 3, devemos ajustar o valor do passo
            'subtraindo-se 2
            iStep = i - 2
            SetProgressBar iStep, TotalSteps
            'Use DoEvents para desafogar os processos pendentes do Excel e não permitir travá-lo
            DoEvents
         End If
    Next i
    
    'Volte a barra de status do Excel para o normal:
    Application.StatusBar = False
End Sub
 
Sub SetProgressBar(pStep As Long, pTotalSteps As Long)
    Const MAX_WIDTH As Long = 40
    
    Dim ProgressBar As String
    Dim CompletedProgress As String
    Dim MissingProgress As String
    
    CompletedProgress = WorksheetFunction.Rept("|", pStep / pTotalSteps * MAX_WIDTH)
    MissingProgress = WorksheetFunction.Rept(" ", MAX_WIDTH - Len(CompletedProgress))
    ProgressBar = CompletedProgress & MissingProgress
    ProgressBar = "[ " & ProgressBar & " ]"
    
    ProgressBar = ProgressBar & " " & Format(pStep / pTotalSteps, "0%")
    
    Application.StatusBar = ProgressBar
End Sub

Para definir visualmente o progresso de uma barra precisamente, é necessário saber qual é o passo que um determinado processo se encontra e quantos passos são necessários. O número total de passos pode ser calculado, normalmente, através da pseudofórmula:

TotalPassos = ValorFinal - ValorInicial + 1

Note que ValorInicial não corresponde, necessariamente, ao valor inicial que a variável de controle assume no laço. No nosso laço, i começa em 3 e vai até 20000. Então, temos um total de 20000 - 3 + 1 = 19998 passos no total e a variável iStep vai, consequentemente, de 1 a 19998.

Para definir a barra de progresso num passo, criei o subprocedimento SetProgressBar. Ela monta uma cadeia de texto que calcula quantos | (pipe) e espaços em branco devem ser mostrados de acordo com o valor de pStep em relação a pTotalSteps nas variáveis CompletedProgress e MissingProgress, respectivamente. Por fim, concatena essas variáveis, concatena o percentual do progresso e atribui o valor da cadeia de texto na barra de status do Excel.

No laço, a condição If i Mod 100 = 0 Then faz com que seu bloco de código seja executado apenas quando i for um múltiplo de 100. Pelo fato do laço rodar várias vezes (19998, para ser mais exato), é desnecessário atualizar a barra de progresso todas as vezes porque, caso contrário, o desempenho da rotina seria prejudicado. Limitar de 100 em 100, mesmo que arbitrariamente, é uma boa prática.

Da mesma forma, a instrução DoEvents não precisa ser executada todas as 19998 vezes. Aproveitei o bloco de código do teste condicional para inserí-lo também. Cabe a você decidir se ela fica fora do subprocedimento de atualização da barra de progresso ou não.

Ao final da rotina, atribui-se Application.StatusBar = False para que a barra de status do Excel volte ao normal.

Download


Para fazer download da pasta de trabalho usada neste artigo, clique aqui.