Introduction
This control was modeled from the collapsible splitter used in the Mozilla web browser, but with some added functionality. This control allows a linked form control to be dynamically expanded and collapsed by clicking on the splitter control button, and resized by dragging the splitter. Additionally, there is an option to expanded or contract the parent form so that the control to expand doesn't take any further form area.
How it works
The CollapsibleSplitter
derives from the System.Windows.Forms.Splitter
class, working with 2 overrides, mouse event handlers, and exposing 4 new properties. The code is relatively straight-forward, and is well commented. The only code that I will cover here is the control painting and interaction with the base splitter control.
In creating the control, I first started by creating a new derived class with Stephen Toub's DeriveClass utility. The next step was to draw the control surface, first by finding the clip rectangle for the base splitter and then defining a new rectangle located in the vertical center of the splitter for the collapser control.
protected override void OnPaint(PaintEventArgs e)
{
this.Width = 8;
Graphics g = e.Graphics;
Rectangle r = this.ClientRectangle;
g.FillRectangle(new SolidBrush(this.BackColor), r);
rr = new Rectangle(r.X, (int) r.Y + ((r.Height - 115)/2),
8, 115);
if(hot)
g.FillRectangle(new SolidBrush(hotColor),
new Rectangle(rr.X + 1, rr.Y, 6, 115));
else
g.FillRectangle(new SolidBrush(this.BackColor),
new Rectangle(rr.X + 1, rr.Y, 6, 115));
g.DrawLine(new Pen(SystemColors.ControlDark, 1),
rr.X + 1, rr.Y, rr.X + rr.Width - 2, rr.Y);
g.DrawLine(new Pen(SystemColors.ControlDark, 1),
rr.X + 1, rr.Y + rr.Height, rr.X + rr.Width - 2,
rr.Y + rr.Height);
g.FillPolygon(new SolidBrush(SystemColors.ControlDarkDark),
ArrowPointArray(rr.X + 2, rr.Y + 3));
g.FillPolygon(new SolidBrush(SystemColors.ControlDarkDark),
ArrowPointArray(rr.X + 2, rr.Y + rr.Height - 9));
int x = rr.X + 3;
int y = rr.Y + 14;
switch(visualStyle)
{
case VisualStyles.Mozilla:
for(int i=0; i < 30; i++)
{
g.DrawLine(
new Pen(SystemColors.ControlLightLight),
x, y + (i*3), x+1, y + 1 + (i*3));
g.DrawLine(
new Pen(SystemColors.ControlDarkDark),
x+1, y + 1 + (i*3), x+2, y + 2 + (i*3));
if(hot)
g.DrawLine(new Pen(hotColor),
x+2, y + 1 + (i*3), x+2, y + 2 + (i*3));
else
g.DrawLine(new Pen(this.BackColor),
x+2, y + 1 + (i*3), x+2, y + 2 + (i*3));
}
break;
case VisualStyles.DoubleDots:
for(int i=0; i < 30; i++)
{
g.DrawRectangle(
new Pen(SystemColors.ControlLightLight),
x, y + 1 + (i*3), 1, 1 );
g.DrawRectangle(
new Pen(SystemColors.ControlDark),
x - 1, y +(i*3), 1, 1 );
i++;
g.DrawRectangle(
new Pen(SystemColors.ControlLightLight),
x + 2, y + 1 + (i*3), 1, 1 );
g.DrawRectangle(
new Pen(SystemColors.ControlDark),
x + 1, y + (i*3), 1, 1 );
}
break;
case VisualStyles.Win9x:
g.DrawLine(new Pen(SystemColors.ControlLightLight),
x, y, x + 2, y);
g.DrawLine(new Pen(SystemColors.ControlLightLight),
x, y, x,y + 90);
g.DrawLine(new Pen(SystemColors.ControlDark),
x + 2, y, x + 2, y + 90);
g.DrawLine(new Pen(SystemColors.ControlDark),
x, y + 90, x + 2, y + 90);
break;
case VisualStyles.XP:
for(int i=0; i < 18; i++)
{
g.DrawRectangle(
new Pen(SystemColors.ControlLight),
x, y + (i*5), 2, 2 );
g.DrawRectangle(
new Pen(SystemColors.ControlLightLight),
x + 1, y + 1 + (i*5), 1, 1 );
g.DrawRectangle(
new Pen(SystemColors.ControlDarkDark),
x, y +(i*5), 1, 1 );
g.DrawLine(
new Pen(SystemColors.ControlDark),
x, y + (i*5), x, y + (i*5) + 1);
g.DrawLine(
new Pen(SystemColors.ControlDark),
x, y + (i*5), x + 1, y + (i*5));
}
break;
case VisualStyles.Lines:
for(int i=0; i < 44; i++)
{
g.DrawLine(new Pen(SystemColors.ControlDark),
x, y + (i*2), x + 2, y + (i*2));
}
break;
}
g.Dispose();
}
The control rectangle is used in the MouseMove
event to determine if the cursor is within the control area (hot), or in the base controls splitter area. Once we know whether the cursor is in the 'hot' area we can change various aspects of the control: use a highlighted background color, ignore or pass on the MouseDown
event, and also set the appropriate mouse cursor.
private void OnMouseMove(object sender, MouseEventArgs e)
{
if(e.X >= rr.X && e.X <= rr.X + rr.Width &&
e.Y >= rr.Y && e.Y <= rr.Y + rr.Height)
{
if(!hot)
{
hot = true;
this.Cursor = Cursors.Hand;
this.Refresh();
}
}
else
{
if(hot)
{
hot = false;
this.Refresh();
}
if(!controlToHide.Visible)
this.Cursor = Cursors.Default;
else
this.Cursor = Cursors.VSplit;
}
}
protected override void OnMouseDown(MouseEventArgs e)
{
if(!hot && !collapsed)
base.OnMouseDown(e);
}
Using the control
The CollapsibleSplitter
control can be added to the toolbox using the pre-compiled dll in the demo application, or simply copy the class into your project and manually reference it in your code.
To Add the Collapsible Splitter to your VS.Net toolbox, follow these steps:
- Right-click on the VS.Net Toolbox and select "Customize Toolbox..."
- Select the ".Net Framework Components" tab, and click the "Browse.." button
- Browse to and open the "CollapsibleSplitter.dll" file from the demo application archive
- Click OK to add Collapsible Splitter control to your Toolbox.
Once you have it on a form, set the Dock property, and then set the ControlToHide
property so that the splitter knows which form control to interact with. All properties specific to the collapsing behaviour can be found under the Collapsing Options group in the properties window. Once created on your form, the view state can be programmatically toggled by calling the ToggleState
method, and the current state can be retrieved from the IsCollapsed
property.
I hope you find this control useful, and if you improve this control, please email me the updated source. If you have any comments or suggestions, please post your thoughts in the feedback section below.
Updates
Version 1.1 Changes:
OnPaint
is now overridden instead of being a handled event, and the entire splitter is now painted rather than just the collapser control
- The splitter rectangle is now correctly defined
- The
Collapsed
property was renamed to IsCollapsed
, and the code changed so that no value needs to be set
- New visual styles added:
Win9x
, XP
, DoubleDots
and Lines
Version 1.11 Changes:
- The
OnMouseMove
event handler was updated to address a flickering issue discovered by John O'Byrne
Version 1.2 Changes:
- Added support for Horizontal Splitters
Version 1.3 Changes: (24 Aug 2003)
- Added an optional 3D border
- General code and comment cleaning
- Flagged assembly with the CLSCompliant attribute
- Added a simple designer class to filter unwanted properties
- Added support for inclusion as a VS.Net ToolBox control
- Added a ToolBox bitmap
- Removed extraneous overrides
- Added summaries
- Removed the ParentFolder from public properties - this is now set automatically in the OnHandleCreated event
- Added expand/collapse animation code