|

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)
{
Rectangle bounds = new Rectangle(ClientRectangle.X,
ClientRectangle.Y + 6, ClientRectangle.Width,
ClientRectangle.Height - 6);
GroupBoxRenderer.DrawGroupBox(g, bounds, Enabled ?
GroupBoxState.Normal : GroupBoxState.Disabled);
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;
g.DrawLine(SystemPens.Control, i_textPos,
bounds.Y, i_endPos, bounds.Y);
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. |
|
| | Msgs 1 to 25 of 45 (Total in Forum: 45) (Refresh) | FirstPrevNext |
|
 |
|
|
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
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
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); }
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
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
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
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
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
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 );
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
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. /// [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. /// [ToolboxBitmap(typeof(CollapsibleGroupBox))] [DesignerAttribute(typeof(CollapsibleGroupBoxDesigner))] public partial class CollapsibleGroupBox : GroupBox {
.... public Rectangle m_toggleRect = new Rectangle(8, 2, 11, 11); // not private ....
/// /// Save the size if control is collapsed. VS will serialize the attribute for us. /// It is also shown in properties. /// [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
|
| Sign In·View Thread·PermaLink | 5.00/5 (1 vote) |
|
|
|
 |
|
|
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.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Anyone have an idea why localization (ie: use resource manager and reex files) changes the control to width of 503 in the Designer
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
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); }
|
| Sign In·View Thread·PermaLink | 5.00/5 (1 vote) |
|
|
|
 |
|
|
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?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
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 control
"Failure is the best reason to start all over again with more knowledge" -Henry Ford
|
| Sign In·View Thread·PermaLink | 5.00/5 (1 vote) |
|
|
|
 |
|
|
 |
|
|
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
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
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
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
If I use the below code, the box appears collapsed but when you click to expand, it disappears entirely. Same thing happens if I manually add it to the "Windows Form Designer generated code" area. However, if I use code to achive the same effect later than 'on load' (eg: using a button to toggle this property), then everything works.
I need a way to start the program with this box collapsed. How can this be achieved?
private void Form1_Load(object sender, EventArgs e) { collapsibleGroupBox1.IsCollapsed = true; }
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi, see Mike's post regarding this topic, you need to modify the IsCollapsed property to be as follows:
public bool IsCollapsed { get { return m_collapsed; } set { if (value != m_collapsed) { m_collapsed = value;
if (!value) // Expand this.Size = m_FullSize; else { if (m_FullSize == Size.Empty) m_FullSize = this.Size;
// Collapse m_bResizingFromCollapse = true; this.Height = m_collapsedHeight; m_bResizingFromCollapse = false; }
foreach (Control c in Controls) c.Visible = !value;
Invalidate(); } } }
When I get time I will update the solution.
J.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
1st of all, great control! 1 question though:
The groupbox has the typical outline: with 1px wide being dark and 1px being light to give the pseudo-3d effect. When I use this control, the text seems to keep the dark line behind it from being visible but not the light line. So there's a destracting line behind the text. How can I make that go away?
Again, great job!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi, what OS are you using the control on? Do you have XP styling? GroupBoxes with XP styling do not employ the drop shadow effect, however the Windows Classic UI in Windows does.
J.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I am using XP with the Classic UI. When I switch to XP theme, the problem goes away. This is fine for me but some of the people in my company also use XP w/ Classic UI and some use pre XP versions of Windows. Is there a fix for the drop-shadow problem for these cases?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
The problem is this line here:
// 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); in the render group box code.
You could replace it with this:
Rectangle textArea = new Rectangle(i_textPos, 0, i_endPos, Font.Height);
if(Application.RenderWithVisualStyles) GroupBoxRenderer.DrawParentBackground(g, textArea, this); else g.FillRectangle(SystemBrushes.Control, textArea); This will draw the background from the parent control onto this area on the application rendered with XP styles and on a non XP styles machine will just draw a grey block which is what you need. I think.
-- modified at 10:01 Tuesday 14th February, 2006
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
To be honest, I completed forgot to test for Classic Windows styling. I am going to have to do this soon as some of my users will have this setting. Once I have updated the code I will post a new copy of the solution.
J.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Good to see a nice looking control that works as it's supposed to.
You might want to add a few properties to it though, such as 'headerFont' and 'headerColor' instead of just the blue text.
Keep up the great work.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
General News Question Answer Joke Rant Admin
|