0

I am trying to create an animated collapsible panel that consists of three different elements. Each element is created from a panel container that sits on a user control. It is made up of a header panel, a content panel and a footer panel (with the footer panel sitting inside the content panel):

enter image description here

Within each panel I override the draw event and do my own custom drawing. This includes rounding the corners, drawing a border, and filling the background (and drawing text in the cases of the header and footer).

The control also allows users to embed into the content panel at both design time and runtime.

When placed on a form, it looks exactly how I want it to, however, I cannot seem to animate the panel in a seamless 'smooth' transition. It is jerky, jittery and looks horrendous when expanding the panel (even with no content).

The way it should work is that when minimized, the content panel (including the footer panel) shrinks to only be the height of the header panel. The header panel then redraws itself to look different. Then when maximized, the panel basically does everything in reverse.

My animation code looks like such:

Dim m_Height As Integer = Me.Height
Dim m_HeaderHeight As Integer = 40
Dim m_FooterHeight As Integer = 35
Dim ShrinkStepSize As Integer = CInt((m_Height - m_HeaderHeight) / 10)
Dim ExpandStepSize As Integer = CInt((m_Height - m_HeaderHeight) / 4)

Private Sub picMinimize_Click(sender As Object, e As EventArgs) Handles picMinimize.Click
    While (Me.Height > m_HeaderHeight)
        Me.Height -= Math.Min(Me.Height - m_HeaderHeight, ShrinkStepSize)
        Application.DoEvents()
    End While

    picMaximise.Visible = True
    picMinimize.Visible = False
    m_Minimized = True
    Me.Invalidate(pnlHeader.ClientRectangle, True)
End Sub

Private Sub picMaximise_Click(sender As Object, e As EventArgs) Handles picMaximise.Click
    While (Me.Height < m_Height)
        Me.Height += Math.Min(m_Height - Me.Height, ExpandStepSize)
        Application.DoEvents()
    End While

    picMaximise.Visible = False
    picMinimize.Visible = True
    m_Minimized = False
    Me.Invalidate(pnlHeader.ClientRectangle, True)
End Sub

And without posting all of my code (unless it's required), here are all my paint events for the header, content and footer panels:

Private Sub pnlHeader_Paint(sender As Object, e As PaintEventArgs) Handles pnlHeader.Paint
    Dim rect As Rectangle = pnlHeader.ClientRectangle
    rect.X = rect.X + 1
    rect.Y = rect.Y + 1
    rect.Width -= 2
    rect.Height -= 2

    'Position the icon elements
    picClose.Location = New Point(rect.Width - (picClose.Width + 8), CInt(((rect.Height - picClose.Height) / 2) + 3))
    picOptions.Location = New Point(rect.Width - ((picClose.Width + picOptions.Width) + 10), CInt(((rect.Height - picOptions.Height) / 2) + 2))
    picMinimize.Location = New Point(rect.Width - ((picMinimize.Width + picOptions.Width + picClose.Width) + 15), CInt(((rect.Height - picMinimize.Height) / 2) + 3))
    picMaximise.Location = New Point(rect.Width - ((picMaximise.Width + picOptions.Width + picClose.Width) + 15), CInt(((rect.Height - picMaximise.Height) / 2) + 3))

    Dim path As Drawing2D.GraphicsPath = RoundRectangle(rect, CornerRadius, Me.CornerRounding)

    If m_Minimized Then
        'Draw the background
        Using br As Brush = New SolidBrush(Color.White)
            e.Graphics.FillPath(br, path)
        End Using

        'Draw the border
        Using br As Brush = New SolidBrush(BorderColour)
            e.Graphics.DrawPath(New Pen(br, 1), path)
        End Using
    End If

    'Draw the text
    Dim textRect As Rectangle = rect
    textRect.X += m_HeaderAdjustment

    Using string_format As New StringFormat()
        string_format.Alignment = StringAlignment.Near
        string_format.LineAlignment = StringAlignment.Center
        e.Graphics.DrawString(HeaderText, New Font("Segoe UI", 13, FontStyle.Bold, GraphicsUnit.Pixel), New SolidBrush(Color.FromArgb(157, 159, 162)), textRect, string_format)
    End Using
End Sub

Private Sub pnlContent_Paint(sender As Object, e As PaintEventArgs) Handles pnlContent.Paint
    Dim rect As Rectangle = pnlContent.ClientRectangle
    rect.X = rect.X + 1
    rect.Y = rect.Y + 1
    rect.Width -= 2
    rect.Height -= 2

    Dim path As Drawing2D.GraphicsPath = RoundRectangle(rect, CornerRadius, Me.CornerRounding)

    'Draw the background
    Using br As Brush = New SolidBrush(Color.White)
        e.Graphics.FillPath(br, path)
    End Using

    'Draw the border
    Using br As Brush = New SolidBrush(BorderColour)
        rect.Inflate(-1, -1)
        e.Graphics.DrawPath(New Pen(br, 1), path)
    End Using
End Sub

Private Sub pnlFooter_Paint(sender As Object, e As PaintEventArgs) Handles pnlFooter.Paint
    Dim rect As Rectangle = pnlFooter.ClientRectangle
    rect.X = rect.X + 1
    rect.Y = rect.Y + 1
    rect.Width -= 2
    rect.Height -= 2

    Dim rounding As Corners = Corners.BottomLeft Or Corners.BottomRight
    Dim path As Drawing2D.GraphicsPath = RoundRectangle(rect, CornerRadius, rounding)

    'Draw the background
    Using br As Brush = New SolidBrush(FooterBackColour)
        e.Graphics.FillPath(br, path)
    End Using

    'Draw the border
    Using br As Brush = New SolidBrush(BorderColour)
        e.Graphics.DrawPath(New Pen(br, 1), path)
    End Using

    'Draw the text
    Dim textRect As Rectangle = rect
    textRect.X += m_FooterAdjustment
    textRect.Y += 1

    Using string_format As New StringFormat()
        string_format.Alignment = StringAlignment.Near
        string_format.LineAlignment = StringAlignment.Center
        e.Graphics.DrawString(FooterText, New Font("Segoe UI", 11, FontStyle.Regular, GraphicsUnit.Pixel), New SolidBrush(FooterForeColour), textRect, string_format)
    End Using
End Sub

Any help with this would be greatly appreciated. Thanks heaps.

Riples
  • 1,167
  • 2
  • 21
  • 54
  • "by 1 pixel at a time". Pick a number, any number, as long as it is not 1. Use at least 5. – Hans Passant Apr 30 '15 at 09:41
  • @HansPassant I had tried multiple values, that's why I ended up dividing by 4. But even after I remove the animation and simply set a single height value, it seems like my paint event it getting called multiple times and still gives a flickering effect. – Riples Apr 30 '15 at 11:09
  • Judging from the hungarian, you are using the Panel class. Not a good choice, it was optimized to act like a container. Use PictureBox instead. If you need the scrollbar then derive your own class from Panel and set the ResizeRedraw and DoubleBuffered properties to True in the constructor. Further improve it by using only *one* control so the painting being out of sync is not visible. And do get rid of DoEvents. – Hans Passant May 07 '15 at 08:05
  • Thanks Hans for the feedback. So I will use a single picturebox to house the entire contents of my control, but am I still able to use this like a container to house other controls/panels, etc? I don't need scrollbars as the control will need to be resized manually to accommodate the contents. I only added the DoEvents as it did seem to help a little, but it's not normal practice for me. Also, not sure what you meant about the Hungarian?? Thanks – Riples May 07 '15 at 12:51
  • @HansPassant I have redrawn my control now to draw everything onto the blank canvas and not use any panel controls. It has sped things up dramatically and improved the performance of the drawing. But how would I now allow users to embed controls into my imitation panel control? – Riples May 10 '15 at 23:40
  • Back to a panel, use [this one](http://stackoverflow.com/a/3113515/17034). – Hans Passant May 10 '15 at 23:54
  • @HansPassant Thanks. I have used your double buffered panel and implemented into this code: [link](http://www.vbforums.com/showthread.php?588073-Creating-a-Container-Control-Out-of-a-UserControl-Can-t-Access-Controls-at-Runtime) which now allows me to embed controls at design time. Not sure if this is the correct method. But I have had to make the panel invisible whilst maximizing it and then visible once finished otherwise I still get a delay on the opening effect of the control. – Riples May 11 '15 at 05:15
  • It seems that as soon as I add controls to my panel at design time, all the drawing slows down again and I end up with the same result. – Riples May 11 '15 at 05:34

0 Answers0