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.