Implementação de um painel deslizante
Introdução
Este artigo descreve a lógica necessária para implementar um painel deslizante com efeito de desaceleração, situado na lateral esquerda. Os paineis deslizantes são soluções ideais para utilizar em aplicações onde se tenha de ter à disposição uma série de dados ou configurações em espaços pequenos. A utilização de, digamos dez, paineis deslizantes uns por cima dos outros aproveitam exactamente a mesma área útil da janela da sua aplicação do que apenas um.
Quando devo utilizar?
Sempre que sentir a necessidade de utilizar um controlo como o TabControl
, mas apenas precisa de um, dois ou três separadores e um acrescido valor em estética ou dinamismo, deverá implementar um painel deslizante.
Qual é a lógica da atenuação de velocidade?
A atenuação de velocidade é calculada multiplicando um valor inferior à unidade com a distância da posição actual até à posição final. Imaginando que a posição actual e a final se encontram a 100 pixels de distância, multiplicando os 100 pixels por 0.2, acrescentamos à posição inicial o resultado, 20. Com 20 deduzidos dos iniciais 100 ficamos com uma distância entre os dois pontos de 80 pixels. Multiplicando de novo a distância de 80 pixels por 0.2, deduzimos o resultado, 16, à distância, e assim sucessivamente até atingir a posição máxima. Desta forma o movimento vai atenuar quanto mais perto o ponto actual se encontrar do máximo.
Na prática...
Importante: A falta de explicação no corpo do artigo é compensada pelos comentários nos blocos de código.
Comecemos por instanciar tudo o que precisamos.
'o painel principal
Private Painel As New Panel
'o separador que fica visível
Private WithEvents PainelSep As New Panel
'o temporizador responsável por o deslize
Private WithEvents Deslizador As New Timer
Depois declaramos as variáveis necessárias para controlar o painel com facilidade.
'o valor da posição do painel quando recolhido
Private PosicaoRecolhida As Integer
'o valor da posição do painel quando extendido
Private PosicaoEstendida As Integer
'para determinar se o painel está aberto ou fechado
Private PainelAberto As Boolean = False
'a velocidade com que o painel se move
'(na verdade é a velocidade com que a distância restante se atenua)
Private Velocidade As Double = 0.1
Aviso: A variável Velocidade
é o valor pelo qual, a distância restante será multiplicada, de forma a que quanto mais pequena é a distância do ponto actual ao final, mais lento seja o movimento. Com isto, este valor deverá manter-se entre qualquer valor acima de 0 e abaixo de 1. O valor 1 causa um movimento instantâneo já que a distância não é atenuada.
De seguida vamos adicionar e preparar todos os controlos no arranque do Form
.
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
'definimos o tamanho da form
Me.Size = New Size(300, 300)
'definimos o tamanho dos paineis
Painel.Size = New Size(250, 250)
PainelSep.Size = New Size(32, 32)
'preparamos o painel separador para apresentar uma imagem
PainelSep.BackgroundImageLayout = ImageLayout.Zoom
PainelSep.BackgroundImage = Me.Icon.ToBitmap.Clone
'preparamos o painel principal para que o seu movimento se note
Painel.BackColor = Color.Aqua
Painel.BorderStyle = BorderStyle.FixedSingle
'definimos os valores de funcionamento inicial do temporizador
Deslizador.Interval = 1
Deslizador.Enabled = False
'definimos os valores das posições garantidas
PosicaoRecolhida = 0 - Painel.Width
PosicaoEstendida = 0
'adicionamos tudo ao form
Me.Controls.Add(Painel)
Me.Controls.Add(PainelSep)
'ajustamos as posições definitivas dos paineis
Painel.Top = 5
PainelSep.Top = 5
Painel.Left = 0 - Painel.Width
PainelSep.Left = Painel.Left + Painel.Width
End Sub
Nota: Foi usado o ícone da aplicação como imagem do separador para garantir que o exemplo não esteja dependente de uma imagem.
Ideia: Os painéis, bem como o temporizador podem ser adicionados na fase de desenho. No artigo, a ideia inicial é a abstracção para que quem o consulta possa ter o exemplo a funcionar antes de começar a estudar o código com minúcia. Muitas das propriedades definidas no Form_Load
podem e devem ser definidas na fase de desenho.
Como sabemos que o painel principal se move e o painel separador tem obrigatoriamente de segui-lo na mesma posição.
Private Sub RecolocarSeparador()
'posiciona o painel separador sempre na mesma posição, dependente da posição do painel principal
PainelSep.Left = Painel.Left + Painel.Width
End Sub
Sabemos também, que a interacção reside no separador. É a partir dele que se abre e fecha o painel. Preparamos o evento Click
para determinar se fecha ou abre o painel.
Private Sub PainelSep_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles PainelSep.Click
'se o painel estiver aberto, marca como fechado
If PainelAberto = True Then
'trocamos a posição do painel para fechado
PainelAberto = False
'mas se o painel estiver fechado, marca como aberto
ElseIf PainelAberto = False Then
'trocamos a posição do painel para aberto
PainelAberto = True
End If
'e por fim, activamos o temporizador para validar as novas ordens de posição
Deslizador.Enabled = True
End Sub
Ideia: Se necessário, é no evento Click que deve reordenar os painéis e trazê-los para a frente, utilizando .BringToFront
nos dois painéis. Desta forma nunca vão ficar por detrás dos componentes do form.
Para finalizar, a lógica do temporizador responsável pelo movimento.
Private Sub Deslizador_Tick(ByVal sender As Object, ByVal e As System.EventArgs) Handles Deslizador.Tick
'declaramos uma variável que possa lidar com valores decimais
Dim TempX As Double = Painel.Left
'se o painel estiver definido para aberto e residir em qualquer ponto antes do seu máximo
'significa que temos de abri-lo até ao máximo
If PainelAberto = True And Painel.Left < PosicaoEstendida Then
'calculamos o novo número para a posição, sem arredondar (e isto é importante)
TempX += Math.Ceiling((PosicaoEstendida - Painel.Left) * Math.Abs(Velocidade))
'se o valor calculado estiver para além do valor máximo...
If TempX >= PosicaoEstendida Then
'desligamos o temporizador para impedir mais movimento
Deslizador.Enabled = False
'...normalizamos o valor para o máximo
TempX = PosicaoEstendida
End If
'depois do cálculo estar feito e validado, atribuímos o novo valor à posição do painel
Painel.Left = TempX
'mandamos recolocar o separador, chamando o método
RecolocarSeparador()
End If
'se o painel estiver definido para fechado e residir em qualquer ponto depois do seu mínimo
'significa que temos de fechá-lo até ao mínimo
If PainelAberto = False And Painel.Left > PosicaoRecolhida Then
'calculamos o novo número para a posição, sem arredondar (e isto é importante)
TempX -= Math.Ceiling((Math.Abs(PosicaoRecolhida) - Math.Abs(Painel.Left)) * Math.Abs(Velocidade))
'se o valor calculado estiver para aquém do valor mínimo...
If TempX <= PosicaoRecolhida Then
'desligamos o temporizador para impedir mais movimento
Deslizador.Enabled = False
'...normalizamos o valor para o mínimo
TempX = PosicaoRecolhida
End If
'depois do cálculo estar feito e validado, atribuímos o novo valor à posição do painel
Painel.Left = TempX
'mandamos recolocar o separador, chamando o método
RecolocarSeparador()
End If
End Sub
O exemplo completo
Crie um novo projecto, e substitua todo o código do form de arranque por este:
Public Class Form1
'o painel principal
Private Painel As New Panel
'o separador que fica visível
Private WithEvents PainelSep As New Panel
'o temporizador responsável por o deslize
Private WithEvents Deslizador As New Timer
'o valor da posição do painel quando recolhido
Private PosicaoRecolhida As Integer
'o valor da posição do painel quando extendido
Private PosicaoEstendida As Integer
'para determinar se o painel está aberto ou fechado
Private PainelAberto As Boolean = False
'a velocidade com que o painel se move
'(na verdade é a velocidade com que a distância restante se atenua)
Private Velocidade As Double = 0.2
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
'definimos o tamanho da form
Me.Size = New Size(300, 300)
'definimos o tamanho dos paineis
Painel.Size = New Size(250, 250)
PainelSep.Size = New Size(32, 32)
'preparamos o painel separador para apresentar uma imagem
PainelSep.BackgroundImageLayout = ImageLayout.Zoom
PainelSep.BackgroundImage = Me.Icon.ToBitmap.Clone
'preparamos o painel principal para que o seu movimento se note
Painel.BackColor = Color.Aqua
Painel.BorderStyle = BorderStyle.FixedSingle
'definimos os valores de funcionamento inicial do temporizador
Deslizador.Interval = 1
Deslizador.Enabled = False
'definimos os valores das posições garantidas
PosicaoRecolhida = 0 - Painel.Width
PosicaoEstendida = 0
'adicionamos tudo ao form
Me.Controls.Add(Painel)
Me.Controls.Add(PainelSep)
'ajustamos as posições definitivas dos paineis
Painel.Top = 5
PainelSep.Top = 5
Painel.Left = 0 - Painel.Width
PainelSep.Left = Painel.Left + Painel.Width
End Sub
Private Sub RecolocarSeparador()
'posiciona o painel separador sempre na mesma posição, dependente da posição do painel principal
PainelSep.Left = Painel.Left + Painel.Width
End Sub
Private Sub PainelSep_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles PainelSep.Click
'se o painel estiver aberto, marca como fechado
If PainelAberto = True Then
'trocamos a posição do painel para fechado
PainelAberto = False
'mas se o painel estiver fechado, marca como aberto
ElseIf PainelAberto = False Then
'trocamos a posição do painel para aberto
PainelAberto = True
End If
'e por fim, activamos o temporizador para validar as novas ordens de posição
Deslizador.Enabled = True
End Sub
Private Sub Deslizador_Tick(ByVal sender As Object, ByVal e As System.EventArgs) Handles Deslizador.Tick
'declaramos uma variável que possa lidar com valores decimais
Dim TempX As Double = Painel.Left
'se o painel estiver definido para aberto e residir em qualquer ponto antes do seu máximo
'significa que temos de abri-lo até ao máximo
If PainelAberto = True And Painel.Left < PosicaoEstendida Then
'calculamos o novo número para a posição, sem arredondar (e isto é importante)
TempX += Math.Ceiling((PosicaoEstendida - Painel.Left) * Math.Abs(Velocidade))
'se o valor calculado estiver para além do valor máximo...
If TempX >= PosicaoEstendida Then
'desligamos o temporizador para impedir mais movimento
Deslizador.Enabled = False
'...normalizamos o valor para o máximo
TempX = PosicaoEstendida
End If
'depois do cálculo estar feito e validado, atribuímos o novo valor à posição do painel
Painel.Left = TempX
'mandamos recolocar o separador, chamando o método
RecolocarSeparador()
End If
'se o painel estiver definido para fechado e residir em qualquer ponto depois do seu mínimo
'significa que temos de fechá-lo até ao mínimo
If PainelAberto = False And Painel.Left > PosicaoRecolhida Then
'calculamos o novo número para a posição, sem arredondar (e isto é importante)
TempX -= Math.Ceiling((Math.Abs(PosicaoRecolhida) - Math.Abs(Painel.Left)) * Math.Abs(Velocidade))
'se o valor calculado estiver para aquém do valor mínimo...
If TempX <= PosicaoRecolhida Then
'desligamos o temporizador para impedir mais movimento
Deslizador.Enabled = False
'...normalizamos o valor para o mínimo
TempX = PosicaoRecolhida
End If
'depois do cálculo estar feito e validado, atribuímos o novo valor à posição do painel
Painel.Left = TempX
'mandamos recolocar o separador, chamando o método
RecolocarSeparador()
End If
End Sub
End Class
Importante: Esta implementação é válida apenas para posicionar um painel deslizante na lateral esquerda do form.