Introduction
The Collapsible SplitContainer
control enhances the Windows Forms SplitContainer
by providing a region on the movable bar that collapses the associated panel when one of two buttons is clicked. You customize the button region by selecting a button style and, when the selected style is Image
or PushButton
, by importing a user-supplied image either in the Visual Studio designer or during runtime.
When a left-oriented image is loaded into the control, the other three collapse directions (right, up, and down) are automatically generated. In addition, the splitter width automatically adjusts to fit the size of the image.
Multiple visual styles are available for the splitter buttons, ranging from basic image display to functional pushbuttons with images. Buttons can be located at the left/top, center, or right/bottom of the splitter bar. The ScrollBar
option of the button style property uses the Windows Forms ScrollBarRenderer
class to generate scrollbar buttons on the splitter.
A few bits of custom code add functionality or correct quirks inherent in the native Windows Forms SplitContainer
control, such as controlling flickering, correcting the inability of the native control to make bitmap format background images transparent, and adding the ability to hide the focus rectangle.
Using the Control
The class name is CollapsibleSplitContainer
. The control is installable as a Toolbox item that can be dropped onto your Windows Form. Alternatively, the control can be created programmatically by instantiating the CollapsibleSplitContainer
in your code.
Properties
Settable properties are grouped in the Collapsible
section of the control's property window in the Visual Studio designer when the control is installed as a Toolbox item.
SplitterButtonBitmap
- The bitmap used on the splitter pushbuttons. It is set via the LoadImage
method. Bmp, gif, ico, and png formats are supported (see method description below). SplitterButtonStyle
- Visual style applied to the splitter control. PushButton
and ScrollBar
styles support hot highlights on mouse hover. The picture below shows a few examples of button images and styles.
- None - Standard splitter control
- Image - User supplied image is shown on splitter
- PushButton - Functional buttons using image as background
- ScrollBar - ScrollBar-style arrow buttons
SplitterButtonPosition
- Where the collapse buttons are located on the splitter
TopLeft
, Center
, BottomRight
SplitterCollapseDistance
- How completely the affected panel collapses
Collapsed
- Only a single pane is visible MinSize
- Affected pane collapses to splitter minimum size property
SplitterFocusHide
- Whether the focus rectangle is shown or hidden. Base class processing still activates the halftone hatch pattern that is shown when the splitter is dragged. There is no way to disable the drag crosshatch without a major rewrite of the base class.
LoadImage Method
Displays a file open dialog so user can select the image used for the buttons. Supported image types include bitmap, png, gif, and icon. The initial image must be left oriented and will be used to create arrows for the 3 other directions. The splitter width and button size are set automatically to accommodate the loaded image's size.
Best visual appeal for pushbutton style is obtained when the image is between 12 and 32 pixels wide and high and there are 3 pixels of clearance on all sides of the arrow to accommodate the button borders. See the Resources folder of the demo project for sample button images.
Drawing Routines
There are two primary drawing functions that display the buttons on the splitter bar. In addition, a support routine that handles drawing a resized focus rectangle around the buttons is provided.
DrawSplitterBackground
Fills the splitter background with the background color and, if available, a background image.
private void DrawSplitterBackground(Graphics g)
{
Color backcolor = this.BackColor;
if (backcolor == Color.Transparent)
{
Control parent = this.Parent;
while (parent.BackColor == Color.Transparent)
{
parent = parent.Parent;
}
backcolor = parent.BackColor;
}
using (SolidBrush brush = new SolidBrush(backcolor))
{
g.FillRectangle(brush, this.SplitterRectangle);
}
if (this.BackgroundImage != null)
{
using (TextureBrush brush = new TextureBrush(this.BackgroundImage))
{
g.FillRectangle(brush, this.SplitterRectangle);
}
}
}
DrawSplitterButtons
Renders the splitter buttons based on system capability and button style. Uses DrawImage
, ButtonRenderer
, or ScrollbarRenderer
to draw the buttons depending upon SplitterButtonStyle
. ScrollBar button states are mapped to Push Button button states to reduce complexity.
private void DrawSplitterButtons(Graphics g)
{
if (splitterButtonStyle == ButtonStyle.Image)
{
if (!panel1Minimized)
{
if (splitterVertical) { g.DrawImage(splitterButtonBitmap, rectLeftDown); }
else { g.DrawImage(bitmapUp, rectRightUp); }
}
if (!panel2Minimized)
{
if (splitterVertical) { g.DrawImage(bitmapRight, rectRightUp); }
else { g.DrawImage(bitmapDown, rectLeftDown); }
}
}
else if (splitterButtonStyle == ButtonStyle.PushButton)
{
PushButtonState pbs1 = (PushButtonState)((int)button1State & 3);
PushButtonState pbs2 = (PushButtonState)((int)button2State & 3);
if (!panel1Minimized)
{
if (splitterVertical) { ButtonRenderer.DrawButton(g, rectLeftDown, pbs1); }
else { ButtonRenderer.DrawButton(g, rectRightUp, pbs1); }
if (splitterButtonBitmap != null)
{
if (splitterVertical) { g.DrawImage(splitterButtonBitmap, rectLeftDown); }
else { g.DrawImage(bitmapUp, rectRightUp); }
}
}
if (!panel2Minimized)
{
if (splitterVertical) { ButtonRenderer.DrawButton(g, rectRightUp, pbs2); }
else { ButtonRenderer.DrawButton(g, rectLeftDown, pbs2); }
if (splitterButtonBitmap != null)
{
if (splitterVertical) { g.DrawImage(bitmapRight, rectRightUp); }
else { g.DrawImage(bitmapDown, rectLeftDown); }
}
}
}
else if (ScrollBarRenderer.IsSupported && splitterButtonStyle == ButtonStyle.ScrollBar)
{
if (!panel1Minimized)
{
if (splitterVertical) { ScrollBarRenderer.DrawArrowButton(g, rectLeftDown, button1State); }
else{ ScrollBarRenderer.DrawArrowButton(g, rectRightUp, button1State); }
}
if (!panel2Minimized)
{
if (splitterVertical) { ScrollBarRenderer.DrawArrowButton(g, rectRightUp, button2State); }
else { ScrollBarRenderer.DrawArrowButton(g, rectLeftDown, button2State); }
}
}
}
DrawSplitterFocus
Redimensions and draws the focus rectangle with space to accommodate the splitter buttons. Note the use of the DrawFocusRectangle
function from ControlPaint
class. ControlPaint
contains a plethora of useful drawing routines for rendering controls.
private void DrawSplitterFocus(Graphics g)
{
if (splitterButtonStyle == ButtonStyle.None) return;
if (this.Focused && !splitterFocusHide)
{
Rectangle focus = new Rectangle(this.SplitterRectangle.Location, this.SplitterRectangle.Size);
if (splitterVertical) { focus.Height = rectLeftDown.Top; }
else { focus.Width = rectLeftDown.Left; }
focus.Inflate(-1, -1);
ControlPaint.DrawFocusRectangle(g, focus, this.ForeColor, this.BackColor);
if (splitterVertical)
{
focus.Location = new Point(rectRightUp.Left, rectRightUp.Bottom);
focus.Size = new Size(rectRightUp.Width, this.SplitterRectangle.Bottom - rectRightUp.Bottom);
}
else
{
focus.Location = new Point(rectRightUp.Right + 1, rectRightUp.Top);
focus.Size = new Size(this.SplitterRectangle.Right - rectRightUp.Right - 1,
rectRightUp.Height);
}
focus.Inflate(-1, -1);
ControlPaint.DrawFocusRectangle(g, focus, this.ForeColor, this.BackColor);
}
}
Overridden Event Handlers
A substantial portion of all the code in CollapsibleSplitContainer
deals with housekeeping measures, such as keeping track of button orientation and button hover/activation. Several base class event handlers are overridden to add button functionality to the control.
OnLayout
Forces redraw of the splitter after a property change that affects the control's layout.
OnPaint
Draws the splitter and, if enabled, the buttons.
OnKeyUp, OnMouseMove, etc.
Handle keyboard and mouse actions. Overridden to add tests for button style, orientation, and the like.
OnBackgroundImageChanged
Adds image transparency for background bitmaps. The base class supports it for PNG and GIF but not bitmap.
protected override void OnBackgroundImageChanged(EventArgs e)
{
base.OnBackgroundImageChanged(e);
if (this.BackgroundImage != null)
{
((Bitmap)this.BackgroundImage).MakeTransparent();
this.Refresh();
}
}
Points of Interest
The SplitContainer
has a few quirks and bugs. This adaptation of the control corrects some of them. For example, the following code helps reduce the native control's flicker and resize problems:
public CollapsibleSplitContainer()
{
ControlStyles cs = ControlStyles.ResizeRedraw | ControlStyles.AllPaintingInWmPaint |
ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer;
this.SetStyle(cs, true);
object[] objArgs = new object[] { cs, true };
MethodInfo objMethodInfo = typeof(Control).GetMethod
("SetStyle", BindingFlags.NonPublic | BindingFlags.Instance);
objMethodInfo.Invoke(this.Panel1, objArgs);
objMethodInfo.Invoke(this.Panel2, objArgs);
}
Known Issue: When switching from vertical to horizontal orientation and back, the position of the splitter bar may differ from what you expect because the width and height dimensions are used in the calculation and affect where the splitter bar is positioned.
History
- 9/19/2014: V1.0. Initial release