Click here to Skip to main content
12,634,712 members (28,562 online)
Click here to Skip to main content
Add your own
alternative version

Tagged as

Stats

141.6K views
11K downloads
146 bookmarked
Posted

C# Custom Control Featuring a Collapsible Panel

, 19 Jan 2010 CPOL
Rate this:
Please Sign up or sign in to vote.
A custom control in C# representing a collapsible panel with design time support

Introduction

Having worked with ASP.NET AJAX CollapsiblePanel control extender and finding it practical to fit more content into a relatively small part of a web page or screen real estate, I thought it would be good to have such control for WinForm applications since VS 2005 and VS 2008 toolboxes do not offer such a control.

Background

CollapsiblePanel AJAX web control extender is a cool web control. But unfortunately, there is no such control in VS 2005 or VS 2008 standard toolbox, so I have decided to create one and share it with the community.

Using the Control at Design Time

Using the control at design time is very simple. Basically it behaves the same way as a normal Panel except that it can be expanded and collapsed.

After adding the Control to the toolbox, drag it on the design surface of your form and you'll get:

Control on desing surface

The control shows a smart tag when clicked it opens a window showing panel's new features and properties that we can use to customize the panel.

Control at Runtime

Here are a couple of images showing the control at runtime:

When a mouse hovers over the expand/Collapse image, the image gets highlighted to show that the operation can be triggered.

Action 1

Some panels are collapsed.

Collapsed panels are expanded again.

Using the Code

This sample shows how to instantiate a CollapsiblePanel and use it.

this.collapsiblePanel1 = new OVT.CustomControls.CollapsiblePanel();
//
// collapsiblePanel1
//
            this.collapsiblePanel1.BackColor = System.Drawing.Color.Transparent;
            this.collapsiblePanel1.HeaderCornersRadius = 5;
            this.collapsiblePanel1.HeaderFont =
		new System.Drawing.Font("Microsoft Sans Serif",
			8.25F, System.Drawing.FontStyle.Bold);
            this.collapsiblePanel1.HeaderImage = null;
            this.collapsiblePanel1.HeaderText = "My Collapsible panel\'s header";
            this.collapsiblePanel1.HeaderTextColor = System.Drawing.Color.Black;
            this.collapsiblePanel1.Location = new System.Drawing.Point(88, 50);
            this.collapsiblePanel1.Name = "collapsiblePanel1";
            this.collapsiblePanel1.RoundedCorners = true;
            this.collapsiblePanel1.Size = new System.Drawing.Size(316, 204);
            this.collapsiblePanel1.TabIndex = 0;
            this.collapsiblePanel1.UseAnimation = true; 
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(571, 353);
this.Controls.Add(this.collapsiblePanel1);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false); 

Explaining the Code

CollapsiblePanel derives simply from System.Windows.Forms.Panel class.

I have added a child panel that I have used to render the panel's header, but most importantly to host two PictureImage controls, one on the Top right corner that will be used to show Expand/Collapse buttons, and another at the Top left that will host any image that is specified by the developer.

The most important part of the code lays in the OnPaint method where custom control rendering takes place. This method was overridden so that custom painting takes place.

protected override void OnPaint(PaintEventArgs e)
{
    base.OnPaint(e);
    DrawHeaderPanel(e);
}
private void DrawHeaderPanel(PaintEventArgs e)
{
    Rectangle headerRect = pnlHeader.ClientRectangle;
// if header rectangle is empty then escape painting.
if (headerRect.Width * headerRect.Height == 0)
    return;
LinearGradientBrush headerBrush = new LinearGradientBrush(
    headerRect, Color.Snow, Color.LightBlue, LinearGradientMode.Horizontal);
    
if (!roundedCorners)
{// Fill a regular rectangle.
    e.Graphics.FillRectangle(headerBrush, headerRect);
    if (showHeaderSeparator)
    {
        e.Graphics.DrawRectangle(new Pen(headerTextColor), headerRect);
    }
}
else
// Fill rounded rectanlge
DrawHeaderCorners(e.Graphics, headerBrush, headerRect.X, 
	headerRect.Y, headerRect.Width, headerRect.Height, headerCornersRadius);
        
        // Draw header separator
if (showHeaderSeparator)
{
    Point start = new Point(pnlHeader.Location.X, 
	pnlHeader.Location.Y+ pnlHeader.Height);
    Point end = new Point(pnlHeader.Location.X+ 
	pnlHeader.Width, pnlHeader.Location.Y+ pnlHeader.Height);
    e.Graphics.DrawLine(new Pen(headerTextColor, 2), start, end);
    // Draw rectangle lines for the rest of the control.
    Rectangle bodyRect = this.ClientRectangle;
    bodyRect.Y += this.pnlHeader.Height;
    bodyRect.Height -= (this.pnlHeader.Height+1);
    bodyRect.Width -= 1;
    e.Graphics.DrawRectangle(new Pen(headerTextColor), bodyRect);
}

int headerRectHeight = pnlHeader.Height;
// Draw header image.
if (headerImage != null)
{
    pictureBoxImage.Image = headerImage;
    pictureBoxImage.Visible = true;
}
else
{
    pictureBoxImage.Image = null;
    pictureBoxImage.Visible = false;
}

// Calculate header string position.
if (!String.IsNullOrEmpty(headerText))
{
    useToolTip = false;
    int delta = pictureBoxExpandCollapse.Width+5;
    int offset = 0;
    if (headerImage != null)
    {
        offset = headerRectHeight;
    }
    PointF headerTextPosition = new PointF();
    Size headerTextSize = TextRenderer.MeasureText(headerText, headerFont);
    if (headerTextAutoEllipsis)
    {
//If using autoEllipsis then manage to show tooltip containing the complete text.
                    if (headerTextSize.Width >= headerRect.Width - (delta+offset))
                    {
                        RectangleF rectLayout =
                            new RectangleF((float)headerRect.X + offset,
                            (float)(headerRect.Height - headerTextSize.Height) / 2,
                            (float)headerRect.Width - delta,
                            (float)headerTextSize.Height);
                        StringFormat format = new StringFormat();
                        format.Trimming = StringTrimming.EllipsisWord;
                        e.Graphics.DrawString(headerText, headerFont, 
			new SolidBrush(headerTextColor),
                            rectLayout, format);

                        toolTipRectangle = rectLayout;
                        useToolTip = true;
                    }
                    else
                    {
                        headerTextPosition.X = 
			(offset + headerRect.Width - headerTextSize.Width) / 2;
                        headerTextPosition.Y = 
			(headerRect.Height - headerTextSize.Height) / 2;
                        e.Graphics.DrawString(headerText, headerFont, 
				new SolidBrush(headerTextColor),
                            headerTextPosition);
                    }
                }
                else
                {
                    headerTextPosition.X = (offset + headerRect.Width - 
			headerTextSize.Width) / 2;
                    headerTextPosition.Y = (headerRect.Height - 
			headerTextSize.Height) / 2;
                    e.Graphics.DrawString(headerText, headerFont, 
			new SolidBrush(headerTextColor),
                        headerTextPosition);
                }
            }
        } 

To draw a rectangle with corners, we use a GraphicPath object as follows:

public void DrawHeaderCorners(Graphics g, Brush brush, float x, 
		float y, float width, float height, float radius)
        {
            GraphicsPath gp = new GraphicsPath();

            gp.AddLine(x + radius, y, x + width - (radius * 2), y); // Line
            gp.AddArc(x + width - (radius * 2), y, 
		radius * 2, radius * 2, 270, 90); // Corner
            gp.AddLine(x + width, y + radius, x + width, y + height ); // Line
            gp.AddLine(x + width , y + height, x , y + height); // Line
            gp.AddLine(x, y + height , x, y + radius); // Line
            gp.AddArc(x, y, radius * 2, radius * 2, 180, 90); // Corner
            gp.CloseFigure();
            g.FillPath(brush, gp);
            if (showHeaderSeparator)
            {
                g.DrawPath(new Pen(headerTextColor), gp);
            }
            gp.Dispose();
        } 

Expanding or collapsing the panel is very simple when not using animation. All it takes is the set the panel's height to be the same as the header panel as in this code taken from ExpandOrCollapse() method:

if (!useAnimation)
            {
                if (collapse)
                {
                    originalHight = this.Height;
                    this.Height = pnlHeader.Height + 3;
                    pictureBoxExpandCollapse.Image = Resources.expand;
                }
                else
                {
                    this.Height = originalHight;
                    pictureBoxExpandCollapse.Image = Resources.collapse;
                }
            } 

Now if the animation is used for Expand/Collapse operation, a timer will be used to control Expand/Collapse speed.

else
            {
                // Keep original height only in case of a collapse operation.
                if(collapse)
                    originalHight = this.Height;

                timerAnimation.Enabled = true;
                timerAnimation.Start();
            } 
private void timerAnimation_Tick(object sender, EventArgs e)
{
    if (collapse)
    {
        if (this.Height <= pnlHeader.Height + 3)
        {
            timerAnimation.Stop();
            timerAnimation.Enabled = false;
            pictureBoxExpandCollapse.Image = Resources.expand;
        }
        else
        {
            int newHight = this.Height - 20;
            if (newHight <= pnlHeader.Height + 3)
                newHight = pnlHeader.Height + 3;
            this.Height = newHight;
        }
    }
    else
    {
        if (this.Height >=  originalHight)
        {
            timerAnimation.Stop();
            timerAnimation.Enabled = false;
            pictureBoxExpandCollapse.Image = Resources.collapse;
        }
        else
        {
            int newHeight = this.Height + 20;
            if (newHeight >= originalHight)
                newHeight = originalHight;
            this.Height = newHeight;
        }
    }
} 

Points of Interest

Coding this custom control was pretty simple. All that it takes is to know how to play with graphics and draw items.

History

  • First version published on 19/01/2010

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Mokdes Hamid
Software Developer (Senior)
Canada Canada
I've been working with .Net since 2006, I use C# as my favorite programming language and I have a strong knowledge about .Net frameworks, Windows and Web development.

I'm interested in a broad range of technologies that include ASP.net, ADO.Net, WinForms, WPF, Silverlight as well as Best practices in development and design patterns.

I hold currently the following certifications:
MCTS: .NET Framework 2.0 Web Applications
MCTS: .NET Framework 2.0 Windows Applications
MCTS: .NET Framework 2.0 Distributed Applications
MCPD: Designing and Developing Enterprise Applications by Using the Microsoft .NET

For more posts:
C# and Windows Form Programming

Walkthrough on how to create a Control Extender in C#

Control Anchoring Class

You may also be interested in...

Pro
Pro

Comments and Discussions

 
Questionhides collapse expand button in flowlayout pannel Pin
lathe rhishikesh5-Sep-13 20:38
memberlathe rhishikesh5-Sep-13 20:38 
BugProduces error when the form is minimized and maximized Pin
Member 895414629-Nov-12 21:43
memberMember 895414629-Nov-12 21:43 
GeneralGood Start Pin
MVEMike10-Oct-12 12:42
memberMVEMike10-Oct-12 12:42 
Questionmissing any discussion of the most interesting feature: the Smart-Tag implementation Pin
BillWoodruff22-Jul-12 21:49
memberBillWoodruff22-Jul-12 21:49 
GeneralMy vote of 3 Pin
FlavioAR7-Jun-12 14:16
memberFlavioAR7-Jun-12 14:16 
GeneralDockStyle.Fill container controls issues resolved Pin
Curt C2-Mar-12 4:39
memberCurt C2-Mar-12 4:39 
GeneralRe: DockStyle.Fill container controls issues resolved Pin
alex_kl8-Sep-12 11:30
memberalex_kl8-Sep-12 11:30 
GeneralRe: DockStyle.Fill container controls issues resolved Pin
Bharat Subedi26-Feb-13 20:25
memberBharat Subedi26-Feb-13 20:25 
GeneralRe: DockStyle.Fill container controls issues resolved Pin
Member 951970721-Mar-14 4:49
memberMember 951970721-Mar-14 4:49 
QuestionProblem with adding a control with .Dock=DockStyle.Fill Pin
mailsink1-Mar-12 9:03
membermailsink1-Mar-12 9:03 
AnswerRe: Problem with adding a control with .Dock=DockStyle.Fill Pin
Bharat Subedi26-Feb-13 20:26
memberBharat Subedi26-Feb-13 20:26 
Questionerror Pin
mayur csharp G13-Jan-12 0:06
membermayur csharp G13-Jan-12 0:06 
AnswerRe: error Pin
tvviewer16-Feb-12 10:19
membertvviewer16-Feb-12 10:19 
Questionhow can collapse from left to right as well as vice versa Pin
RaviKumar.nikkala26-Dec-11 23:48
memberRaviKumar.nikkala26-Dec-11 23:48 
QuestionCollapse doesn't move other panels Pin
Member 834884925-Oct-11 11:43
memberMember 834884925-Oct-11 11:43 
QuestionCan you collapse sideways? Pin
devnet24723-Sep-11 23:19
memberdevnet24723-Sep-11 23:19 
Questionthis user control not work in visual studio 2010 .net framework 4 Pin
Member 161414022-Sep-11 5:46
memberMember 161414022-Sep-11 5:46 
AnswerRe: this user control not work in visual studio 2010 .net framework 4 Pin
Shaun Walsh19-Dec-11 18:57
memberShaun Walsh19-Dec-11 18:57 
AnswerRe: this user control not work in visual studio 2010 .net framework 4 Pin
RatnaSai24-Jan-12 20:35
memberRatnaSai24-Jan-12 20:35 
QuestionGreat Work ! Pin
koolprasad200321-Sep-11 19:54
memberkoolprasad200321-Sep-11 19:54 
QuestionHeader colour Pin
Yvan Rodrigues19-Jul-11 6:12
memberYvan Rodrigues19-Jul-11 6:12 
GeneralAdd a control to pnlHeader? Pin
Josh Noe23-May-11 9:25
memberJosh Noe23-May-11 9:25 
GeneralRe: Add a control to pnlHeader? Pin
Shaun Walsh19-Dec-11 18:35
memberShaun Walsh19-Dec-11 18:35 
GeneralMy vote of 5 Pin
Egyptian_Thinker3-Mar-11 0:22
memberEgyptian_Thinker3-Mar-11 0:22 
GeneralRe: My vote of 5 Pin
maris9828-Apr-11 12:15
membermaris9828-Apr-11 12:15 
QuestionHow to use install Pin
vasilyok18-Dec-10 12:18
membervasilyok18-Dec-10 12:18 
GeneralMy vote of 5 Pin
Ahmed Al-Ghareeb Mostafa23-Nov-10 3:32
memberAhmed Al-Ghareeb Mostafa23-Nov-10 3:32 
GeneralAdding Complex Content Pin
Jerry Merchand6-Aug-10 8:44
memberJerry Merchand6-Aug-10 8:44 
GeneralRe: Adding Complex Content Pin
Mokdes Hamid10-Aug-10 4:27
memberMokdes Hamid10-Aug-10 4:27 
GeneralRe: Adding Complex Content Pin
BillWoodruff22-Jul-12 21:43
memberBillWoodruff22-Jul-12 21:43 
GeneralMy vote of 4 Pin
Jerry Merchand6-Aug-10 8:36
memberJerry Merchand6-Aug-10 8:36 
QuestionProblem when using header image? Pin
Biker196514-Jul-10 4:35
memberBiker196514-Jul-10 4:35 
QuestionSubscribe to pictureBoxExpandCollapse_Click Event Pin
leyroyjenkins9-Jun-10 15:24
memberleyroyjenkins9-Jun-10 15:24 
AnswerRe: Subscribe to pictureBoxExpandCollapse_Click Event Pin
leyroyjenkins13-Jun-10 21:31
memberleyroyjenkins13-Jun-10 21:31 
GeneralGreat control, one question [modified] Pin
Yusuf Bar-Sawme16-Feb-10 22:16
memberYusuf Bar-Sawme16-Feb-10 22:16 
GeneralRe: Great control, one question Pin
Mokdes Hamid17-Feb-10 4:39
memberMokdes Hamid17-Feb-10 4:39 
GeneralRe: Great control, one question Pin
Member 603819626-Apr-10 10:10
memberMember 603819626-Apr-10 10:10 
QuestionDynamically Adding Controls at Run time Pin
stixoffire11-Feb-10 18:43
memberstixoffire11-Feb-10 18:43 
GeneralToolStrip Pin
tonyt9-Feb-10 3:46
membertonyt9-Feb-10 3:46 
QuestionRe: ToolStrip Pin
stixoffire11-Feb-10 17:32
memberstixoffire11-Feb-10 17:32 
AnswerRe: ToolStrip Pin
tonyt7-May-10 2:49
membertonyt7-May-10 2:49 
GeneralGood one but Pin
scosta_FST25-Jan-10 21:41
memberscosta_FST25-Jan-10 21:41 
GeneralRe: Good one but Pin
Mokdes Hamid26-Jan-10 6:06
memberMokdes Hamid26-Jan-10 6:06 
GeneralRe: Good one but Pin
scosta_FST26-Jan-10 22:39
memberscosta_FST26-Jan-10 22:39 
GeneralRe: Good one but Pin
Spectre2x29-Jan-10 0:26
memberSpectre2x29-Jan-10 0:26 
GeneralRe: Good one but Pin
scosta_FST29-Jan-10 0:39
memberscosta_FST29-Jan-10 0:39 
GeneralRe: Good one but Pin
debeysm12-May-10 4:12
memberdebeysm12-May-10 4:12 
GeneralRe: Good one but [modified] Pin
leyroyjenkins8-Jun-10 15:17
memberleyroyjenkins8-Jun-10 15:17 
General5 from me Pin
redspiderke20-Jan-10 3:15
memberredspiderke20-Jan-10 3:15 
Joke! Pin
SuperToha20-Jan-10 3:05
memberSuperToha20-Jan-10 3:05 

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.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.161208.2 | Last Updated 19 Jan 2010
Article Copyright 2010 by Mokdes Hamid
Everything else Copyright © CodeProject, 1999-2016
Layout: fixed | fluid