Ir para o conteúdo

ProgressBar - Barras de progresso desmistificadas

Introdução

Estão onde quer que existam processos demorados. No sistema operativo, se copiarmos ficheiros, lá está uma barra de progresso. Vai queimar um CD ou DVD? Prepare-se para se encontrar com barras de progresso. Vai jogar? Muito provavelmente vai encontrar barras de progresso ao passar de níveis. As barras de progresso são um elemento visual precioso para um feedback ao utilizador. Não só fica com a impressão de que o processo não está parado, mas também fica com uma noção global do tempo que o processo pode demorar.

O básico

As barras de progresso podem surgir das mais variadas formas e comunicarem progresso das maneiras mais curiosas, mas todas partilham do mesmo princípio: representam a totalidade de um determinado processo e o valor apresentado representa a posição do progresso na totalidade desse processo. Ou seja, todas as barras de progresso compreendem um alcance de 0 (zero) até 1 (unidade). Se o nosso processo consistir no processamento de 4587 ficheiros, o número 4587 representa a totalidade do processo, ou seja, representa a unidade (4587/4587=1). Senão vejamos:

Número de ficheiros processados Número total de ficheiros Progresso
0 4587 0
500 4587 0,1
1500 4587 0,33
2750 4587 0,6
4000 4587 0,87
4587 4587 1

Com isto, é fácil determinar uma percentagem para o progresso de qualquer processo quantificável. Se multiplicarmos o progresso na unidade por 100, obtemos a dita percentagem do processo, que é habitualmente o valor que se usa para comunicar progresso numa barra de progresso.

Progresso A multiplicar por Equivale a
0 100 0%
0,1 100 10%
0,33 100 33%
0,6 100 60%
0,87 100 87%
1 100 100%

Com este tipo de noções, é possível construírmos uma barra de progresso visual, no Visual Basic.

Uma barra de progresso básica

Para a construcção de uma barra de progresso básica, vamos precisar de noções básicas de matemática e de alguma imaginação na utilização de controlos. Deixo, em baixo, um bloco de código devidamente comentado para a construção de uma barra de progresso.

'Copie e substitua todo o código do form
'Lembre-se que o exemplo está preparado para criar todos os controlos em runtime

'Começamos por importar bibliotecas necessárias
Imports System.Drawing
Imports System.Drawing.Drawing2D

Public Class Form1
    'Instanciamos um controlo Button para ser o nosso botão de início de processamento
    Private WithEvents Botao As New Button With {.Text = "Processar", .Location = New Point(0, 50)}

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

        'Preparamos o form para que a barra fique visível
        Me.Width = 300
        Me.Height = 110
        'E adicionamos o botão à form
        Me.Controls.Add(Botao)

    End Sub

    Private Sub CarregaBotao(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Botao.Click
        'Instanciamos um novo bitmap, com as dimensões do painel que faz de barra de progresso
        'Este bitmap vai ser usado como sua imagem de fundo e por isso é importante ter as mesmas dimensões
        Dim Progresso As New Bitmap(250, 40)

        'Instanciamos o nosso painel, que é no fundo a nossa barra de progresso
        Dim BarraProgresso As New Panel
        'E alteramos as propriedades para a colocar no sítio certo e com as dimensões certas
        With BarraProgresso
            .Width = 250
            .Height = 40
            .Location = New Point(0, 0)
            .BackgroundImage = Progresso
        End With
        'Por fim, adicionamos o controlo à form
        Me.Controls.Add(BarraProgresso)

        'Instanciamos a classe graphics para se desenhar directamente no bitmap que é agora o fundo do painel
        Dim G As Graphics = Graphics.FromImage(Progresso)

        'Criamos uma simmulação de processo demorado, como um ciclo for de 1 a 500 e um atraso de 100 msecs por ciclo.
        For i As Integer = 1 To 500
            Threading.Thread.Sleep(100)
            'Calculamos o novo comprimento do rectângulo colorido (a barra de progresso)
            Dim ProgWid As Integer = Math.Round((i * 250) / 500, 0)
            G.Clear(Color.White)
            'Criamos um brush gradiente, só para ser mais bonito
            Dim LGF As New LinearGradientBrush(New Point(0, 0), New Point(ProgWid + 1, 0), Color.LightCyan, Color.Yellow)

            'Desenhamos o rectângulo com o gradiente e na posição pré-calculada
            G.FillRectangle(LGF, New Rectangle(0, 0, ProgWid, 40))

            'Para desenhar o valor da percentagem, comecemos por instanciar uma nova fonte
            Dim F As New Font("Arial", 14, FontStyle.Bold)

            'De seguida efectuamos os cálculos necessários para apresentar o valor em percentagem
            Dim ProgLabel As String = CStr(Math.Round((ProgWid / 250) * 100, 0)) & "%"
            'Medimos o seu tamanho final, essencial para entrar o valor na barra
            Dim Tam As SizeF = G.MeasureString(ProgLabel, F)

            'E desenhamos o valor por cima da barra, com os cálculos de posição que o façam ficar ao centro
            G.DrawString(ProgLabel, F, Brushes.Black, (250 / 2) - (Tam.Width / 2), (40 / 2) - (Tam.Height / 2))

            'Refrescamos a barra, para que possa ser actualizada a cada ciclo
            BarraProgresso.Refresh()
            'E por fim, forçamos a aplicação a fazer o que tem a fazer, mesmo que isso signifique perda de performance
            'Isto é importante para que a aplicação não fique sem responder durante o processo
            My.Application.DoEvents()
        Next
    End Sub
End Class

Precisamos mesmo de a construír nós mesmos?

Claro que não. Estamos a falar da .Net Framework. Nada foi deixado ao acaso. Temos à nossa inteira disposição uma barra de progresso perfeitamente integrada com os temas do sistema operativo e ainda capaz de realizar as operações de unidade internamente, ficando à nossa responsabilidade referir apenas o valor mínimo, máximo e o actual progresso do processo. Deixo, em baixo, um bloco de código devidamente comentado para a utilização de uma barra de progresso no exemplo anterior.

'Copie e substitua todo o código do form
'Lembre-se que o exemplo está preparado para criar todos os controlos em runtime

Public Class Form1
    'Instanciamos um controlo Button para ser o nosso botão de início de processamento
    Private WithEvents Botao As New Button With {.Text = "Processar", .Location = New Point(0, 50)}
    'Instanciamos uma barra de progresso e inicializamos o seu máximo e mínimo, localização e tamanho
    Private WithEvents PB As New ProgressBar With {.Maximum = "500", .Minimum = 0, .Location = New Point(0, 0), .Size = New Point(250, 40)}

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

        'Preparamos o form para que a barra fique visível
        Me.Width = 300
        Me.Height = 110
        'E adicionamos o botão e progressbar à form
        Me.Controls.Add(Botao)
        Me.Controls.Add(PB)

    End Sub

    Private Sub CarregaBotao(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Botao.Click
        'Criamos uma simmulação de processo demorado, como um ciclo for de 1 a 500 e um atraso de 100 msecs por ciclo.
        For i As Integer = 1 To 500
            Threading.Thread.Sleep(100)
            'E dentro do ciclo basta reportar o progresso, através da propriedade VALUE
            PB.Value = i
            'E por fim, forçamos a aplicação a fazer o que tem a fazer, mesmo que isso signifique perda de performance
            'Isto é importante para que a aplicação não fique sem responder durante o processo
            My.Application.DoEvents()
        Next
    End Sub
End Class

Ideia: Alterando o "Style" para "Blocks", ao invés do padrão "Continuous", a barra comportar-se-á como uma barra de progresso normal, mas a barra colorida é apresentada em forma de blocos.

Progresso faseado

Por vezes é necessário ilustrar o progresso de um processo de fases. Poderia resolver o problema, dividindo a totalidade por o número de fases, e acrescentar ao progresso o valor resultante. Por exemplo, num processo de 4 fases, 100 a dividir por 4 é igual a 25, e 25 é o valor que teria de aumentar ao progresso por cada fase ultrapassada. Estes cálculos são completamente desnecessários uma vez que o controlo possuí formas de controlar processos faseados.

'Copie e substitua todo o código do form
'Lembre-se que o exemplo está preparado para criar todos os controlos em runtime

Public Class Form1
    'Instanciamos um controlo Button para ser o nosso botão de início de processamento
    Private WithEvents Botao As New Button With {.Text = "Próxima fase", .Location = New Point(0, 50)}
    'Instanciamos uma barra de progresso e inicializamos o seu step e mínimo, localização e tamanho
    Private WithEvents PB As New ProgressBar With {.Step = "25", .Maximum = 100, .Location = New Point(0, 0), .Size = New Point(250, 40)}

    'A propriedade STEP é o valor que representa uma fase completa do máximo.
    'Se o máximo é 100 e o step é 25, o processo terá 4 fases.

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

        'Preparamos o form para que a barra fique visível
        Me.Width = 300
        Me.Height = 110
        'E adicionamos o botão e progressbar à form
        Me.Controls.Add(Botao)
        Me.Controls.Add(PB)

    End Sub

    Private Sub CarregaBotao(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Botao.Click
        'Preenche a barra de progresso até ao valor equivalente à próxima fase
        PB.PerformStep()
    End Sub
End Class

Outros tipos de visualização

Para além do progresso comunicado, existem por vezes processos que não produzem qualquer tipo de relatório de progresso ou que não é possível determinar o seu término. Para estes, o controlo possuí o Marquee. O marquee produz uma animação na barra de progresso, tornando-a numa barra de espera. Como o movimento do marquee passa a ideia de processamento, em processos de término indefinido é importante dar a entender ao utilizador que a aplicação não está parada.

'Copie e substitua todo o código do form
'Lembre-se que o exemplo está preparado para criar todos os controlos em runtime

Public Class Form1
    'Instanciamos um controlo Button para ser o nosso botão de início de processamento
    Private WithEvents Botao As New Button With {.Text = "Esperar", .Location = New Point(0, 50)}
    'Instanciamos uma barra de progresso e inicializamos a sua localização e tamanho
    Private WithEvents PB As New ProgressBar With {.Location = New Point(0, 0), .Size = New Point(250, 40)}

    'Uma barra de espera não precisa de configuração a nível de máximos e mínimos.

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

        'Preparamos o form para que a barra fique visível
        Me.Width = 300
        Me.Height = 110
        'E adicionamos o botão e progressbar à form
        Me.Controls.Add(Botao)
        Me.Controls.Add(PB)

    End Sub

    Private Sub CarregaBotao(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Botao.Click
        'Configuramos a velocidade de efeito
        'Este valor está em milisegundos.
        PB.MarqueeAnimationSpeed = 1
        'Ditamos que o estilo é Marquee, o que causa a barra a entrar num modo como que se fosse um modo de espera
        'por um processo cujo término é impossível de determinar, tal como esperar por acção humana
        PB.Style = ProgressBarStyle.Marquee
    End Sub
End Class

Ideia: Os exemplos anteriores utilizam o With. Poderá saber mais sobre o With através do artigo O With.

Conclusão

As barras de progresso são controlos essenciais no que toca a comunicação de progressos com o utilizador. Existem muitas situações onde aplicá-las e podem inclusivamente servir outros propósitos que não os de relatar progresso. Se combinarmos as barras de progresso com backgroundworkers, produzimos uma tarefa a trabalhar num thread diferente do thread da interface, mas capaz de comunicar progresso de uma thread para a outra.