Click here to Skip to main content
Email Password   helpLost your password?

Sample Image

Introduction

Sometimes, there is the need to display information in a container that can be collapsed. Sure there are controls like the XP Sidebar et all, but for my requirements, I wanted something that looked and worked exactly like a standard GroupBox. So, I simply extended the GroupBox control to provide this functionality.

Background

I searched the web for existing controls and came across one by jeffb42 which was pretty much what I wanted, however I have been developing in .NET 2.0 only for some time now, and the old format design did not sit well with the normal GroupBoxes on my forms. Also, that version did not allow me to easily add other controls to the box for some reason. So, I created my own based on the one by jeffb42.

Using the code

This control is so simple that it barely warrants instructions. It can be used as a normal GroupBox is. Full design time support is provided so simply drag & drop the control onto your form and add what you wish to it.

Behind the control

Looking through the code, one can see that not much was required to create this control. The usual suspects were overridden to control the paint and the handling of the mouse clicking the Collapsed state toggle button.

protected override void OnMouseUp(MouseEventArgs e)
{
    if (m_toggleRect.Contains(e.Location))
        ToggleCollapsed();
    else
        base.OnMouseUp(e);
}

protected override void OnPaint(PaintEventArgs e)
{
    HandleResize();
    DrawGroupBox(e.Graphics);
    DrawToggleButton(e.Graphics);
}

For the painting, I used a method found in System.Windows.Forms called DrawGroupBox. This takes away all need to draw arcs and lines or worry about matching the colour of the normal GroupBox. However, when the GroupBox text is drawn on this box, you will get a strikeout effect. To overcome this, I draw a line the same length as the measured text in the colour of the control so that it will not be seen.

void DrawGroupBox(Graphics g)
{
    // Get windows to draw the GroupBox

    Rectangle bounds = new Rectangle(ClientRectangle.X, 
       ClientRectangle.Y + 6, ClientRectangle.Width, 
       ClientRectangle.Height - 6);
    GroupBoxRenderer.DrawGroupBox(g, bounds, Enabled ? 
       GroupBoxState.Normal : GroupBoxState.Disabled);

    // Text Formating positioning & Size

    StringFormat sf = new StringFormat();
    int i_textPos = (bounds.X + 8) + m_toggleRect.Width + 2;
    int i_textSize = (int)g.MeasureString(Text, this.Font).Width;
    i_textSize = i_textSize < 1 ? 1 : i_textSize;
    int i_endPos = i_textPos + i_textSize + 1;

    // Draw a line to cover the GroupBox

    // border where the text will sit

    g.DrawLine(SystemPens.Control, i_textPos, 
               bounds.Y, i_endPos, bounds.Y);

    // Draw the GroupBox text

    using (SolidBrush drawBrush = new 
               SolidBrush(Color.FromArgb(0, 70, 213)))
        g.DrawString(Text, this.Font, drawBrush, i_textPos, 0);
}

void DrawToggleButton(Graphics g)
{
    if(IsCollapsed)
        g.DrawImage(Properties.Resources.plus, m_toggleRect);
    else
        g.DrawImage(Properties.Resources.minus, m_toggleRect);
}

Points of Interest

If you are using this control in a DockStyle state of Fill then you will notice the control does not collapse. This is because the parent control forces the control to fill its space. Therefore, there is an event on the control which fires when the collapsed state changes. This can be used by the parent control to resize itself to the size of the GroupBox.

History

OK, after a request for a 1.1 version of this control, I thought I would give it a shot. After not using Visual Studio 2003 for around a year, it was a shock to switch back to it. Now, a lot of the methods that I used in the 2.0 solution were not available in 1.1, especially drawing a rounded rectangle. Rather than calculating all the points and curves myself, I looked through CodeProject because surely someone had done it before. And of course someone has, Arun Reginald's article provided a simple solution.

The rest is pretty much the same as in the 2.0 solution. I used Barretto VN's idea to add XP theming to all the controls used in the project.

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralBroken link
Wolexie
5:48 22 Jan '10  
I am trying to download the demo and source from the link at the top of the page but...

Does anyone have a valid link to these documents? Or where I can download them from?

Many thanks

Heavy givers are light complainers

QuestionGreat Control but is there a way to disable or hide border
bokee11
8:12 27 Oct '09  
Is there any way to to make the border of the GroupBox disappear or change the border color and inner border color to match the BackColor so it disappears? -- thanks B
AnswerRe: Great Control but is there a way to disable or hide border
bokee11
10:21 27 Oct '09  
I figured out my own question if someone wants the same effect here is the code...
This Hides the GroupBox Border through color (doesn't diasable it)

set SolidBrush to this.BackColor and use same Rectangle as Rectangle bounds
// Get windows to draw the GroupBox
Rectangle bounds = new Rectangle(ClientRectangle.X, ClientRectangle.Y + 14, ClientRectangle.Width, ClientRectangle.Height - 16);
GroupBoxRenderer.DrawGroupBox(g, bounds, GroupBoxState.Normal);


Pen pen = new Pen(BackColor);
g.FillRectangle(new SolidBrush(this.BackColor), g.FillRectangle(new SolidBrush(this.BackColor), bounds););
GeneralSome minor changes...
GMan4911
8:27 4 Oct '09  
Replace this:
private Rectangle m_toggleRect = new Rectangle(8, 2, 11, 11);
with this:
private Rectangle m_toggleRect = new Rectangle(8, 1, 11, 11);
In DrawGroupBox(), replace this:
Rectangle bounds = new Rectangle(ClientRectangle.X, ClientRectangle.Y + 6, ClientRectangle.Width, ClientRectangle.Height - 6);
GroupBoxRenderer.DrawGroupBox(g, bounds, Enabled ? GroupBoxState.Normal : GroupBoxState.Disabled);
.
.
.
with this:
Rectangle bounds = new Rectangle(ClientRectangle.X, ClientRectangle.Y, ClientRectangle.Width, ClientRectangle.Height);
GroupBoxRenderer.DrawGroupBox(g, bounds, String.Format(" {0}", Text), this.Font, Enabled ? GroupBoxState.Normal : GroupBoxState.Disabled);
Remove everything else in DrawGroupBox().

The original code put too much space between the groupbox and the control above it and the font color should use the GroupBox font color. Also, using the GroupBoxRenderer.DrawGroupBox() to render the text eliminates the line behind the text effect.
GeneralRe: Some minor changes...
DominikF
2:19 8 Dec '09  
very good. thank you. now the control is suitable for me.
QuestionCode Usage - Article
stixoffire
6:51 18 May '09  
Hi and thanks for sharing I learned a few things here.

I have merged your code with code from
GroupBox with an icon: the ImageGroupBox control[^]

To Produce the following result:
Collapsible GroupBox With Icon[^]

I hope that this is ok with you. I thought that since the License was COPL that the use would be acceptable. I have not taken any credit for any of the code and simply referred readers back to the two articles..
GeneralLicense
Almagnus1
14:40 3 Mar '09  
I really like the control, but I was wondering what license it has?

Thanks again!
GeneralInvisible controls
pl_meetom
4:09 8 Dec '08  
There's a bug in Collapsible Groupbox.
After collapsing and uncollapsing, all controls inside groupbox have property Visible set to true (even if before the operation it was set to false).
GeneralThe test program crashes if you run it with the "Windows Classic" theme on XP [modified]
bscaer
6:38 10 Jun '08  
The test program throws an exception if you run it with the "Windows Classic" theme on XP as follows:

System.InvalidOperationException: Visual Styles-related operation resulted in an error because no visual style is currently active.

Here is the code that is responsible. It doesn't check to see if a visual style is available before using a VisualStyleRenderer.

        void DrawToggleButton(Graphics g)
{
VisualStyleRenderer glyphRender = null;
if (IsCollapsed)
glyphRender = new VisualStyleRenderer(VisualStyleElement.TreeView.Glyph.Closed);
else glyphRender = new VisualStyleRenderer(VisualStyleElement.TreeView.Glyph.Opened);
glyphRender.DrawBackground(g, m_toggleRect);
}

Here is a fixed version of the function:

        void DrawToggleButton(Graphics g)
{
if (Application.RenderWithVisualStyles)
{
VisualStyleRenderer glyphRender = null;
if (IsCollapsed)
glyphRender = new VisualStyleRenderer(VisualStyleElement.TreeView.Glyph.Closed);
else glyphRender = new VisualStyleRenderer(VisualStyleElement.TreeView.Glyph.Opened);
glyphRender.DrawBackground(g, m_toggleRect);
}
else {
if (IsCollapsed)
g.DrawImage(PlusImage, m_toggleRect);
else g.DrawImage(MinusImage, m_toggleRect);
}
}


modified on Tuesday, June 10, 2008 11:54 AM

GeneralResize parents [modified]
esskar
3:52 17 Sep '07  
Nice control.

i added the following code, to have the parent controls resize on toggle.

HTH,
Sascha Kiefer


void ResizeParent()
{
int deltaHeight = this.FullHeight - m_collapsedHeight;
this.ResizeParent(this.Parent, !this.Collapsed, deltaHeight);
}

void ResizeParent(Control control, bool collapsed, int deltaHeight)
{
if (control != null)
{
this.ResizeParent(control.Parent, collapsed, deltaHeight);

foreach (Control c in control.Controls)
{
if ((c.Anchor & AnchorStyles.Bottom) != 0)
continue;

Point p = c.Location;
if (p.Y <= this.Location.Y)
continue;

if (collapsed)
p.Y -= deltaHeight;
else p.Y += deltaHeight;
c.Location = p;
}

Size s = control.Size;
if (collapsed)
s.Height -= deltaHeight;
else s.Height += deltaHeight;
control.Size = s;
}
}

void ToggleCollapsed()
{
this.ResizeParent(); this.Collapsed = !this.Collapsed;
if (this.CollapseBoxClickedEvent != null)
this.CollapseBoxClickedEvent(this);
}

QuestionRe: Resize parents [modified]
stixoffire
10:08 10 Jun '08  
I used your suggestion but have a few problems with it.
When it resizes it has a tendency to relocate components on the form to (-) values for the vertical positioning. One thing I noticed was when I had it in another groupbox - it resized the parent and the parents and changed positioning on the controls that made it unuseable.

It also has a tendency to displace some controls and place others over top of them. - If I have a GroupBox over a Tab Control say 500 pixels wide. I then place the Collapsible GroupBox to the right of the Groupbox (Even into another Collapsible Groupbox - nothing underneath. How can I keep the resize from happening when there is no need to resize parent - or the parents parent. I would rather not add a property to do this - if it could Automagically detect if it needed to resize the parent...

Any ideas ?

modified on Tuesday, June 10, 2008 3:43 PM

AnswerRe: Resize parents
tretererte
8:37 29 Oct '09  
You need to comment ResizeParent() like this:

if (control != null)
{
//this.ResizeParent(control.Parent, collapsed, deltaHeight);
foreach (Control c in control.Controls)
{
if ((c.Anchor & AnchorStyles.Bottom) != 0)
continue;

Point p = c.Location;

GeneralI like it, small bug (small small) [modified]
bilo81
3:28 4 May '07  
Great article.
I noted that if you change the backgroud color of the form in your tests, the code to draw a line to cover the GroupBox border where the text will sit doesn't work (the line cover the text)

// Draw a line to cover the GroupBox border where the text will sit
g.DrawLine(SystemPens.Control, i_textPos, bounds.Y, i_endPos, bounds.Y);

I have only changed with

Pen pen = new Pen(BackColor);
// Draw a line to cover the GroupBox border where the text will sit
g.DrawLine(pen, i_textPos, bounds.Y, i_endPos, bounds.Y);

I think that's all...

Kind regards,
Marco

modified on Wednesday, July 30, 2008 3:20 PM

GeneralRe: I like it, small bug (small small)
Ross Korsky
7:22 6 Jun '08  
For me i could see the highlight line running through the background of the text so i changed the line to the following as a fix to paint over both the shadow and highlight of the boarder.

Brush lBrush = new SolidBrush( BackColor );
g.FillRectangle( lBrush, i_textPos, bounds.Y, i_endPos - i_textPos, 2 );

GeneralRe: I like it, small bug (small small)
maynardflies
5:28 15 Aug '08  
I fixed it this way

SystemPens.FromSystemColor(Parent.BackColor)

NewsDesign-time enhancement
cDima
3:48 17 Apr '07  
Hey, nice control.

I've got an improvement for it. By adding a class and some attributes, you can enable design-time collapsing of the group box by a mouse click on the box. It will save the size of the non-collapsed group box and all.

It will probably work only for VS 2005 .Net 2.0, haven't checked.

I can make a short article with the implemented details and post it on code project. Or, if you have the time, update the control with these improvements, if you think they actually improve it.

Quite simple. Add a reference to System.Design.dll in your project.
Then add the CollapsibleGroupBoxDesigner class:

    /// 
    /// CollapsibleGroupBoxDesigner is an control designer for the that 
/// CollapsibleGroupBox. Allows mouse clicks on the collapse button.
/// summary
[System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")]
public class CollapsibleGroupBoxDesigner : System.Windows.Forms.Design.ParentControlDesigner
{
// This boolean state reflects whether the mouse is over the control.
protected override bool GetHitTest(Point point)
{
CollapsibleGroupBox me = this.Control as CollapsibleGroupBox;

if (me.m_toggleRect.Contains(me.PointToClient(point)))
{
// we can handle that; user clicked on toggle box.
return true;
}
//Nope not interested
return false;
}
}

Then, add the DesignerAttribute atttribute in front of the class itself. Also, you'll need to make the property toggleRect public.

    /// 
    /// GroupBox control that provides functionality to 
/// allow it to be collapsed.
/// summary
[ToolboxBitmap(typeof(CollapsibleGroupBox))]
[DesignerAttribute(typeof(CollapsibleGroupBoxDesigner))]
public partial class CollapsibleGroupBox : GroupBox
{

....
public Rectangle m_toggleRect = new Rectangle(8, 2, 11, 11); // not private
....

/// summary
/// Save the size if control is collapsed. VS will serialize the attribute for us.
/// It is also shown in properties.
/// summary
[DefaultValue(false), Browsable(true), DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public Size FullSize
{
get
{
return m_FullSize;
}
set
{
m_FullSize = value;
}
}

That's probably all, if I'm not missing anything.
Now recompile the project, drop the collapsable group box control on a form in the designer, and click on the toggle box. It should toggle collapse and save it's original size in it's FullSize property.

Works for me quite nicely. Get back to me if you will update the control, please.

Good luck,
Dmitry Sadakov
GeneralRe: Design-time enhancement
stixoffire
10:10 10 Jun '08  
I used this in the control - Nice - it lets me know what the thing will look like . I use it in conjunction with the resize parent from another poster.
I am not sure if his resize parent is an issue or if this one is the issue - as I get (-) values on the vertical positioning of some of my other components, during design time.
QuestionRe: Design-time enhancement
stixoffire
1:28 18 Apr '09  
I have noticed something in Design time - my control changes namespaces , the system can not find it any more and requests I put Global.Indigo.CollapsibleGroupBox for the namespace - it then works until I click the designer to collapse the control, at that point it disappears.
Any hints, tips or ideas on how to fix this one ?
GeneralLocalization messes up the control
Bo K
8:12 8 Feb '07  
Anyone have an idea why localization (ie: use resource manager and reex files) changes the control to width of 503 in the Designer
GeneralCollapse box look
E! Ray K
9:52 29 Jan '07  
Hi,

I would like to contribute my 2cents about this control by adding code to polish up the collapsed look. As of today, the control kept the groupbox even when the control is collapsed.

Replace DrawGroupBox method and theo bolded text are my contribution. =)


void DrawGroupBox(Graphics g)
{

// Get windows to draw the GroupBox
Rectangle bounds = new Rectangle(ClientRectangle.X, ClientRectangle.Y + 6, ClientRectangle.Width, ClientRectangle.Height - 6);
// display appropriate groupbox look
if (m_collapsed)
{
g.DrawLine(SystemPens.ControlDark, bounds.X, bounds.Y, bounds.X + bounds.Width, bounds.Y);
}
else {
GroupBoxRenderer.DrawGroupBox(g, bounds, Enabled ? GroupBoxState.Normal : GroupBoxState.Disabled);
}
// Text Formating positioning & Size
StringFormat sf = new StringFormat();
int i_textPos = (bounds.X + 8) + m_toggleRect.Width + 2;
int i_textSize = (int)g.MeasureString(Text, this.Font).Width;
i_textSize = i_textSize < 1 ? 1 : i_textSize;
int i_endPos = i_textPos + i_textSize + 1;

// Draw a line to cover the GroupBox border where the text will sit
g.DrawLine(SystemPens.Control, i_textPos, bounds.Y, i_endPos, bounds.Y);

// Draw the GroupBox text
using (SolidBrush drawBrush = new SolidBrush(Color.FromArgb(0, 70, 213)))
g.DrawString(Text, this.Font, drawBrush, i_textPos, 0);
}

QuestionFor some reason..
advance512
8:07 31 Aug '06  
Text boxes I put inside the group box drop one pixel every time I display the form containing the group box in the VS.NET designer.

Anyone knows why?
GeneralCustomize colors
Rogenator_2024
14:35 15 Jul '06  
Greetings:
I was using this control for a new project where the user can change the color scheme of the aplication and i found some problems, the way it was done assumed that the background color of the groupbox was the default control color, so when it draws de rectangle to in wich it will write the text is filled with the default control color, so i made a small modification so it`s filled with the backcolor.
from this:
g.DrawLine(SystemPens.Control, i_textPos, bounds.Y, i_endPos, bounds.Y);
to this:
g.FillRectangle(new SolidBrush(this.BackColor), 8, 2, i_endPos-8, 12);
I also changed it so it draws the text with the selected ForeColor:
From this:
using (SolidBrush drawBrush = new SolidBrush(Color.FromArgb(0, 70, 213)))
g.DrawString(Text, this.Font, drawBrush, i_textPos, 0);
To this:
using (SolidBrush drawBrush = new SolidBrush(this.ForeColor))
g.DrawString(Text, this.Font, drawBrush, i_textPos, 0);

Also i changed the way of drawing the minus/plus sign from an image to custom drawn and added another 2 color propertys so the user can define the color for the signs(masmenos) and its backcolor(bmasmenos), here is the code i used instead of drawing the images
void DrawToggleButton(Graphics g)
{
g.FillRectangle(new SolidBrush(bmasmenos),m_toggleRect);
g.DrawRectangle(new Pen(masmenos),m_toggleRect.Left+1,m_toggleRect.Top+1,8,8);
g.DrawLine(new Pen(masmenos),m_toggleRect.Left+3,m_toggleRect.Bottom-6,m_toggleRect.Right-4,m_toggleRect.Bottom-6);
if(IsCollapsed)
g.DrawLine(new Pen(masmenos),m_toggleRect.Left+5,m_toggleRect.Top+3,m_toggleRect.Left+5,m_toggleRect.Bottom-4);
}
I also added another method so i could change the default border color
public Color BorderColor
{
get{return m_borderColor;}
set{m_borderColor=value;this.Invalidate();}
}
That`s it, maybe you find this useful for changing the the default apperance of this great controlSmile

"Failure is the best reason to start all over again with more knowledge"
-Henry Ford
GeneralGreat Component!
MikeNutshell
14:06 16 Jun '06  
I love this component - working great and looks great!

Laugh
GeneralRe: Great Component!
Rogenator_2024
15:40 11 Jul '06  
I second that, just waht i was looking for to keep my interface really organized.

"Failure is the best reason to start all over again with more knowledge"
-Henry Ford
GeneralRight to left
Ahmad Maatouki
0:44 2 Apr '06  
Hello..

I make the "RightToLeft" Property with value "Yes", but the caption of the Group Box still in the left side...

is the "RightToLeft" Property supported in this component??

Thanks a Lot..

-- modified at 5:44 Sunday 2nd April, 2006


Last Updated 31 Jan 2006 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010