Ir para o conteúdo

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.