Click here to Skip to main content
15,878,809 members
Articles / Desktop Programming / Windows Forms
Article

Expandable panel on all four directions

Rate me:
Please Sign up or sign in to vote.
4.70/5 (41 votes)
6 Oct 20069 min read 211.1K   8.4K   161   106
An expandable panel that you can set to expand/collapse bottom to top, top to bottom, left to right, or right to left.

Sample Image - maximum width is 600 pixels

Introduction

I was needing a panel that was able to expand/collapse on both X and Y axis, but could not find one that would do it for the Y axis so decided to write my own. The main idea of this is inheriting the Panel control and having a "caption" control docked left, right, up or down.

Using the code

In order to use this control, while in design mode go to the toolbox window pickup your category to include the control, right click it and select "Choose Items..." and then locate the ExtendedPanel.dll file. Soon after, you should be able to see the ExpandedPanel as one of the items available to drag and drop on your forms.

Control properties

The control provides the following properties :

  • State - Returns the state of the object which can be either collapsed, collapsing, expanded, or expanderd
  • Animation - States whether this control should animate the collapsing / expanding process
  • BorderColor - The color used to draw the border lines
  • CornerStyle - The way the border should appear rounded or normal
  • Moveable - Specifies if this control can be moved by dragging the caption with the mouse
  • CornerStyle - Set up if the border will have normal corners or rounded ones
  • AnimationStep - Specifies the size used to expand/collapse the control
  • CaptionAlign - Set where the caption control for this panel should be. It can be left, right, up or down
  • CaptionBrush - Set how the painting of the caption should occur. It can either be solid or gradient
  • CaptionColorOne - Starting color used if gradient option is chosen or the color used for the solid brush style
  • CaptionColorTwo - Starting color used if gradient option is chosen or the color used for the solid brush style
  • CaptionFont - Font used in drawing the caption
  • CaptionText - The text present in the caption control
  • CaptionTextColor - The color used in drawing the caption text
  • CaptionSize - Set the size in pixels for the caption
  • CaptionImage - The icon displayed in the caption

Architecture

The main classes defined in this assembly are present in the following list:
  • BufferPaintingCtrl - Inherits the Panel control, adding support for double buffering. Will talk shortly about this.
  • DirectionCtrl - Defines the control displaying the arrow showing the direction the panel will expand or collapse.
  • CornerCtrl - Defines the control supporting the border drawing either with normal or rounded corners.
  • CaptionCtrl - Defines the caption control for the panel.
  • ExpandedPanel - The extended panel supporting collapsing/expanding and also dragging
  • CollapseAnimation - The class creating the background worker responsible for the expanding/collapsing animation.

Class diagram (main classes available)

By default, whenever a Paint event is raised (WM_PAINT - message is sent ) the control paints itself directly to the Graphics object (Device Context for the ones familiar with Win32). If this process is done repeatedly the annoying flickering effect comes into play. Double buffering technique is quite known among developers involved in windows programming, and comes to help us reduce the flickering. The guys at Microsoft made the use of this double buffering quite simple in .NET, all you have to do is set up some flags for your control. For most scenarios this will do the job, but in some scenarios manual control over the process is required. All you have to do in order to enable this for your control is set up the following styles to true: ControlStyles.AllPaintingInWmPaint, ControlStyles.UserPaint, ControlStyles.DoubleBuffer.

Once you have done this the drawing process is changed a little bit, instead of the Graphics object (device context) for the screen the Paint handler receives another Graphics object for an in-memory bitmap. So what happens is that the control is drawing itself to an invisible image. Once the drawing process finishes this image is copied on the screen device context. Because only one graphic operation is performed on the screen graphics the flickering effect can be reduced if not removed.

So the idea behind the BufferPaintingCtrl class is to set up this flags in its constructor, as some of the classes defined in the assembly are handling the paint event:

C#
protected BufferPaintingCtrl()
{
    ///set up the control styles so that it support double buffering painting
    this.SetStyle(  ControlStyles.AllPaintingInWmPaint |
                    ControlStyles.UserPaint |
                    ControlStyles.OptimizedDoubleBuffer |
                    ControlStyles.DoubleBuffer,true);

    UpdateStyles();
}

CornerCtrl

CornerCtrl is supposed to offer support for drawing the borders either with normal corners or rounded ones. The control has a graphic path object that is used in the Paint handler. The next listing presents the method for instantiating the graphic path object; the cornerSquare is used to define the region that will contain the ellipse whose arc is being drawn, if rounded corners are being selected. This member has to be defined in the child classes in order to control the rounding, otherwise we won't get any.
C#
protected virtual void InitializeGraphicPath()
{
    if (null != graphicPath)
    {
        graphicPath.Dispose();
        graphicPath = null;
    }

    graphicPath = new GraphicsPath();
    

    switch (cornerStyle)
    {
        case CornerStyle.Rounded:

            graphicPath.AddArc(0, 0, cornerSquare, cornerSquare, 180, 90);
            graphicPath.AddLine(cornerSquare - cornerSquare / 2, 0, 
                 Width - cornerSquare + cornerSquare / 2 - 1, 0);
            graphicPath.AddArc(Width - cornerSquare - 1, 0, cornerSquare, 
                 cornerSquare, -90, 90);

            graphicPath.AddLine(Width - 1, cornerSquare - cornerSquare / 2, 
                 Width - 1, Height - cornerSquare + cornerSquare / 2);
            graphicPath.AddArc(Width - cornerSquare - 1, 
                Height - 1 - cornerSquare, cornerSquare, cornerSquare, 0, 90);
            graphicPath.AddLine(cornerSquare - cornerSquare / 2, Height - 1, 
                 Width - cornerSquare + cornerSquare / 2, Height - 1);

            graphicPath.AddArc(0, Height - cornerSquare - 1, cornerSquare, 
                 cornerSquare, 90, 90);
            graphicPath.AddLine(0, cornerSquare - cornerSquare / 2, 0, 
                 Height - cornerSquare + cornerSquare / 2);

            break;

        case CornerStyle.Normal:

            graphicPath.AddLine(0, 0, Width-1, 0);
            graphicPath.AddLine(Width-1, 0, Width-1, Height-1);
            graphicPath.AddLine(Width-1, Height-1, 0, Height-1);
            graphicPath.AddLine(0, Height-1, 0, 0);
            break;

        default:
            throw 
    new ApplicationException("Unrecognized style for rendering the corners");
            break;
    }
}

ExpandedPanel

As shown in the UML class diagram this class is embedding a CaptionCtrl object. Based on the option this will be docked on one of the four directions left, down, right, or up. Just after initialization of all components, InitializeComponent method, the code is setting up two handlers. First one is for handling the event raised when the user clicks the direction control (causing the panel to collapse/expand), and the latter for dragging (in case this option is enabled). Here is the snapshot of the code i am talking about:

C#
//set handler for collapsing/expanding
captionCtrl.SetStyleChangedHandler(new 
                 DirectionCtrlStyleChangedEvent(CollapsingHandler));

//set the handler for the dragging event
captionCtrl.Dragging += new CaptionDraggingEvent(CaptionDraggingEvent);  
I won't talk about the dragging part as the code is self explanatory. I will focus though on the part with collapsing/expanding. As mentioned earlier the direction control present in the caption control is defining a handler for the click event. Once the event is captured, the control style is being changed (basically pointing to the opposite direction it was pointing before the click), and raises the event causing ExtendedPanel object to expand/collapse. The panel object in its handler will prepare all the context needed for the animation, will instantiate the CollapseAnimation object (if this would be the case), will set its properties and will start the background thread to perform behind the scenes steps required for the animation (I won't list this method but you can find it in the source code). We are getting now to the most challenging part in writing this code. Within the lines of this method you will find a call to ChangeCaptionParent. This will be the case when the caption has been set to be docked either on the bottom or on the right side. More attention is required, will explain why next. As you know any control has a location (starting point) from where is being rendered and a size (width and height). All containers have their child controls location defined relative to its upper left corner. By changing the width/height of the container all child controls having the location set to be greater than the new size will become invisible. So will be the case in the two scenarios i mentioned; we have to bring the caption control back where it should be, by updating its location. By doing so a WM_PAINT message will be raised. Changing panel size will cause the painting event to be raised for this one as well, and will be facing a situation where seeing-not seeing the caption will repeat very fast, causing the very annoying effect of flickering (and we don't want that).

So my solution for the problem was to take out the panel caption during animation, keeping it though at the "same" location from the user's point of view. So during the animation period the caption control parent would be the same as the panel parent.

C#
private void ChangeCaptionParent()
{
    //take the caption out of the panel beacause of the flickering
    this.captionCtrl.Parent = this.Parent;
    this.captionCtrl.Location = new Point(this.Location.X + this.Width - 
                 this.captionCtrl.Width, this.Location.Y + this.Height - 
                 this.captionCtrl.Height);
    Win32Wrapper.SetWindowPos(this.Handle, this.captionCtrl.Handle, 
                 0, 0, 0, 0, 
                 Win32Wrapper.FlagsSetWindowPos.SWP_NOMOVE | 
                 Win32Wrapper.FlagsSetWindowPos.SWP_NOSIZE | 
                 Win32Wrapper.FlagsSetWindowPos.SWP_NOREDRAW);

    //disable moving 
    backupMoveable = moveable;
    moveable = false;
}

The panel is notified at every step of the animation that it should update its size and in the aforementioned cases its location. The end of the animation needs special treatment as well as in the scenarios where the docking is at the bottom or right, the caption control has to be brought back into the panel control.

C#
private void OnNotifyAnimationFinished(object sender)
{
    if (captionAlign == DirectionStyle.Down)
    {
        //set caption location (no redrawing) and hiding
        Win32Wrapper.SetWindowPos(this.captionCtrl.Handle, IntPtr.Zero, 0, 
                 this.Height - this.captionCtrl.Height, 0, 0, 
                 Win32Wrapper.FlagsSetWindowPos.SWP_NOREDRAW | 
                 Win32Wrapper.FlagsSetWindowPos.SWP_NOZORDER | 
                 Win32Wrapper.FlagsSetWindowPos.SWP_NOSIZE | 
                 Win32Wrapper.FlagsSetWindowPos.SWP_HIDEWINDOW );
        //set back the parent
        this.captionCtrl.Parent = this;
        this.captionCtrl.Visible = true;
       
        //set back the moveable property; during collapsing the movement 
        //is not allowed
        moveable = backupMoveable;
    }
    else
    {
        if (captionAlign == DirectionStyle.Right)
        {
            //set caption location (no redrawing) and hiding
            Win32Wrapper.SetWindowPos(this.captionCtrl.Handle, IntPtr.Zero,
                  this.Width - this.captionCtrl.Width, 0, 0, 0, 
                 Win32Wrapper.FlagsSetWindowPos.SWP_NOREDRAW | 
                 Win32Wrapper.FlagsSetWindowPos.SWP_NOZORDER | 
                 Win32Wrapper.FlagsSetWindowPos.SWP_NOSIZE | 
                 Win32Wrapper.FlagsSetWindowPos.SWP_HIDEWINDOW);
            //set back the parent
            this.captionCtrl.Parent = this;
            this.captionCtrl.Visible = true;
            //set back the moveable property; during collapsing the movement 
            //is not allowed
            moveable = backupMoveable;
        }
    }
    //set the state of the object expanded/collapsed
    SetState();
}
Adding the caption back where it should be it is a bit tricky as well. I have to perform two actions, set the panel as its parent and update its location (there is a great chance the current location within the panel parent control to be different than what we need). Either way it is done, parent set first and then location updated or vice versa, is not a reliable solution. If I set the parent first due to the actual location coordinates the caption will be moved (probably where it will become invisible) into the panel control and only setting the location would bring it back where it should be. We would end up having the unwanted flickering effect. If I set the location first related to the panel control (we would want the caption either at the bottom or on the right side) a paint event is raised and will cause the control to be drawn somewhere else on the screen. Only setting the caption parent to be the panel again we would have the things back to normal. But this is not a solution an user would accept. So Win32 to the rescue. The Windows API SetWindowsPos method is giving us the chance to set the new location without repainting message raised. Having used this method would save me having the caption appearing somewhere else on the screen. I can now safely set the parent to be the panel again, and because I was hiding it set it back to visible.

I know I can't always explain very well (good thing i am not a teacher), but i hope you have got the idea.

Conclusion

Hopefully, someone out there would find this control useful. I am pretty sure there is room for improvement, so any feedback would be appreciated.

History

  • July 2006 - Version 1.0.0
    • First release
  • August 2006 - Version 1.2.0
    • Bug fixing - made control thread safe
    • Bug fixing - while in collapsed mode sometimes you could click on the caption bringing the controls on top
  • August 2006 - Version 1.3.0
    • Bug fixing - there were errors once the control was set to have zero size with/height
    • Changes - CaptionPercent has become CaptionSize and this will no longer be percentage of the control width/height depending of the docking
    • Added - Collapse/Expand method to raise those events without the need to click the mouse anymore
    • Added - Docking corrections. Changing the caption size sometimes was overlapping the contained controls. So whenever the caption size is changed the inner controls are moved accordingly.
    • Added - State property supports the set accessor design time only. Setting it to collapsed the control will first be showed as collapsed.
  • October 2006 - Version 1.4.0
    • Bug fixing - choosing the collapsed method would not update the ">>" control leaving the control stuck.
    • Bug fixing - If the caption was set to be Down/Right then collapsing the control was not possible if the panel had the Anchor set to Bottom/Right.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Software Developer (Senior) Lab49
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Questiondropdownpanel Pin
sagar_prajapati7-Oct-13 23:38
sagar_prajapati7-Oct-13 23:38 
QuestionProblem in compiling Pin
shilyk19-Aug-11 1:28
shilyk19-Aug-11 1:28 
AnswerRe: Problem in compiling Pin
Madhan Mohan Reddy P23-Jul-12 23:00
professionalMadhan Mohan Reddy P23-Jul-12 23:00 
GeneralPLZ help me to reshow the controls again! Pin
Member 767911116-Feb-11 6:27
Member 767911116-Feb-11 6:27 
GeneralAwesome Pin
Xmen Real 1-Jun-10 16:08
professional Xmen Real 1-Jun-10 16:08 
Questionprogramatically expanding the panel Pin
mahone12-Oct-09 2:20
mahone12-Oct-09 2:20 
GeneralNeed for Signing ExtendedPanel.dll Pin
Raguvaran24-Sep-09 1:34
Raguvaran24-Sep-09 1:34 
QuestionCode??? Pin
Abhirash29-Oct-08 9:41
Abhirash29-Oct-08 9:41 
QuestionBug NOT fixed?!?!?!? Pin
Johnny J.26-Oct-08 22:52
professionalJohnny J.26-Oct-08 22:52 
GeneralNice control, BUT... Pin
Johnny J.17-Oct-08 1:12
professionalJohnny J.17-Oct-08 1:12 
GeneralRe: Nice control, BUT... Pin
Johnny J.17-Oct-08 2:08
professionalJohnny J.17-Oct-08 2:08 
GeneralStep error Pin
setiseeker7-Feb-08 2:48
setiseeker7-Feb-08 2:48 
GeneralQuite Edited! Pin
Andrea_861-Feb-08 23:39
Andrea_861-Feb-08 23:39 
GeneralToolbar in Panel doesn't render properly Pin
Johnny J.23-Nov-07 0:30
professionalJohnny J.23-Nov-07 0:30 
GeneralMouse Sizeable ExtendedPanel Pin
deep4227-Sep-07 2:42
deep4227-Sep-07 2:42 
GeneralPanel can be dragged out of parent's client rect [modified] Pin
deep4227-Sep-07 0:43
deep4227-Sep-07 0:43 
GeneralAdded ExpansionBegin Event [modified] Pin
CSharpTpa20-Sep-07 8:26
CSharpTpa20-Sep-07 8:26 
QuestionExpand expandable panel at runtime Pin
Sumit.Rulez17-Sep-07 23:00
Sumit.Rulez17-Sep-07 23:00 
GeneralAdd them in a Table layout and make there dock = Fill. It stops working Pin
Armoghan Asif31-Aug-07 3:35
Armoghan Asif31-Aug-07 3:35 
GeneralControls are not shown upon expanding Pin
ury@work29-Aug-07 8:06
ury@work29-Aug-07 8:06 
GeneralTwo quick additions I made. Pin
Jab195730-Jul-07 11:58
Jab195730-Jul-07 11:58 
GeneralRe: Two quick additions I made. Pin
tingspain13-Feb-09 4:46
tingspain13-Feb-09 4:46 
Questioninactive control item Pin
hubertus9510-Jul-07 6:18
hubertus9510-Jul-07 6:18 
AnswerRe: inactive control item Pin
hubertus9510-Jul-07 6:43
hubertus9510-Jul-07 6:43 
GeneralBug in animation Pin
Haukur Hrafn Þorsteinsson25-May-07 5:13
Haukur Hrafn Þorsteinsson25-May-07 5:13 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.