Click here to Skip to main content
15,616,663 members
Articles / Programming Languages / XML
Article
Posted 19 Sep 2014

Stats

45.4K views
3.9K downloads
24 bookmarked

Collapsible Split Container

Rate me:
Please Sign up or sign in to vote.
4.83/5 (24 votes)
20 Sep 2014CPOL4 min read
A collapsible split container control for Windows Forms

Image 1

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

    Image 2

  • 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.

C#
// Fill the splitter background with the background color and image
private void DrawSplitterBackground(Graphics g)
{
	Color backcolor = this.BackColor;
	if (backcolor == Color.Transparent)
	{
		// Find the base color that underlies transparency
		Control parent = this.Parent;
		while (parent.BackColor == Color.Transparent)
		{
			parent = parent.Parent;
		}
		backcolor = parent.BackColor;
	}

	// Paint the background with the underlying background color
	using (SolidBrush brush = new SolidBrush(backcolor))
	{
		g.FillRectangle(brush, this.SplitterRectangle);
	}

	// Draw the background image if present
	if (this.BackgroundImage != null)
	{
		// Use a texture brush to replicate base class tiling
		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.

C#
// Render the splitter buttons based on system capability and button style
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)
	{
		// Map ScrollBarArrowButtonStates to PushButtonStates
		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.

C#
// Draw the modified focus rectangle if focus is not hidden
private void DrawSplitterFocus(Graphics g)
{
	if (splitterButtonStyle == ButtonStyle.None) return;

	if (this.Focused && !splitterFocusHide)
	{
		Rectangle focus = new Rectangle(this.SplitterRectangle.Location, this.SplitterRectangle.Size);

		// Draw the focus rectangle to the left/top of the buttons
		if (splitterVertical) { focus.Height = rectLeftDown.Top; }
		else { focus.Width = rectLeftDown.Left; }
		focus.Inflate(-1, -1);
		ControlPaint.DrawFocusRectangle(g, focus, this.ForeColor, this.BackColor);

		// Draw the focus rectangle to the right/bottom of the buttons
		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.

C#
protected override void OnBackgroundImageChanged(EventArgs e)
{
	base.OnBackgroundImageChanged(e);

	// Add image transparency for bitmap background images. Base class
	// supports it for PNG and GIF but not bitmap format
	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:

C#
public CollapsibleSplitContainer()
{
	// Bug fix for SplitContainer problems with flickering and resizing
	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

License

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


Written By
Founder Choycer
United States United States
Ed has over 40 years experience in computer technology and a bachelor's degree in Business Administration. He's currently a marketing technology consultant. During his career, he's led software development departments and created software still in use in the communications and healthcare industries. Ed is a veteran of the United States Army. He lives in Arizona in the United States.

Find Ed on Linkedin.

This material is copyright 2019 by Ed Gadziemski. Unauthorized use is strictly prohibited. All rights reserved.

Comments and Discussions

 
PraiseMy vote of 5 Pin
avan0622-Oct-22 23:48
avan0622-Oct-22 23:48 
GeneralMy vote of 5 Pin
Franc Morales4-Sep-21 14:02
Franc Morales4-Sep-21 14:02 
GeneralMy vote of 5 Pin
csharpbd5-Nov-19 9:55
professionalcsharpbd5-Nov-19 9:55 
PraiseGood Work Pin
Member 1240695221-Mar-16 3:42
Member 1240695221-Mar-16 3:42 
GeneralGood job Pin
Emre Ataseven25-Feb-16 8:32
professionalEmre Ataseven25-Feb-16 8:32 
QuestionAn Error Pin
Member 1139098821-Jan-15 0:42
Member 1139098821-Jan-15 0:42 
AnswerRe: An Error Pin
Ed Gadziemski21-Jan-15 8:45
professionalEd Gadziemski21-Jan-15 8:45 
Do you have the Resources folder with the cursor files at the same level as the CollapsibleSplitContainer.cs file?

Do you have the 2 cursor files set as Embedded Resources in your project file?

Ed
GeneralMy vote of 4 Pin
majid torfi26-Oct-14 5:00
professionalmajid torfi26-Oct-14 5:00 
GeneralMy vote of 5 Pin
Tridip Bhattacharjee22-Sep-14 3:02
professionalTridip Bhattacharjee22-Sep-14 3:02 
Bugsmall bug Pin
Hermann Jung20-Sep-14 0:31
professionalHermann Jung20-Sep-14 0:31 
GeneralRe: small bug Pin
Ed Gadziemski20-Sep-14 8:29
professionalEd Gadziemski20-Sep-14 8:29 
GeneralMy vote of 5 Pin
Franc Morales19-Sep-14 22:25
Franc Morales19-Sep-14 22:25 

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.