Click here to Skip to main content
15,892,575 members
Articles / Desktop Programming / Windows Forms

Creating a Custom DropDown Control

Rate me:
Please Sign up or sign in to vote.
4.93/5 (72 votes)
20 Jul 2010CPOL7 min read 209.6K   9.1K   228  
Explains how to effectively create virtually any type of dropdown control
/******************************************************************/
/*****                                                        *****/
/*****     Project:           Adobe Color Picker Clone 1      *****/
/*****     Filename:          ctrlVerticalColorSlider.cs      *****/
/*****     Original Author:   Danny Blanchard                 *****/
/*****                        - scrabcakes@gmail.com          *****/
/*****     Updates:	                                          *****/
/*****      3/28/2005 - Initial Version : Danny Blanchard     *****/
/*****                                                        *****/
/******************************************************************/

using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Windows.Forms;

namespace Unity3.Controls
{
    /// <summary>
	/// A vertical slider control that shows a range for a color property (a.k.a. Hue, Saturation, Brightness,
	/// Red, Green, Blue) and sends an event when the slider is changed.
	/// </summary>
	public class VerticalColorSlider : System.Windows.Forms.UserControl
	{
		#region Class Variables

		public enum eDrawStyle
		{
			Hue,
			Saturation,
			Brightness,
			Red,
			Green,
			Blue
		}



		//	Slider properties
		private int			m_iMarker_Start_Y = 0;
		private bool		m_bDragging = false;

		//	These variables keep track of how to fill in the content inside the box;
		private eDrawStyle		m_eDrawStyle = eDrawStyle.Hue;
		private ColorManager.HSL	m_hsl;
		private Color			m_rgb;

		private System.ComponentModel.Container components = null;

		#endregion

		#region Constructors / Destructors

		public VerticalColorSlider()
		{
			// This call is required by the Windows.Forms Form Designer.
			InitializeComponent();

			//	Initialize Colors
			m_hsl = new ColorManager.HSL();
			m_hsl.H = 1.0;
			m_hsl.S = 1.0;
			m_hsl.L = 1.0;
			m_rgb = ColorManager.HSL_to_RGB(m_hsl);
			m_eDrawStyle = eDrawStyle.Hue;
		}


		/// <summary> 
		/// Clean up any resources being used.
		/// </summary>
		protected override void Dispose( bool disposing )
		{
			if( disposing )
			{
				if(components != null)
				{
					components.Dispose();
				}
			}
			base.Dispose( disposing );
		}


		#endregion

		#region Component Designer generated code
		/// <summary> 
		/// Required method for Designer support - do not modify 
		/// the contents of this method with the code editor.
		/// </summary>
		private void InitializeComponent()
		{
			// 
			// ctrl1DColorBar
			// 
			this.Name = "ctrl1DColorBar";
			this.Size = new System.Drawing.Size(40, 264);
			this.Resize += new System.EventHandler(this.ctrl1DColorBar_Resize);
			this.Load += new System.EventHandler(this.ctrl1DColorBar_Load);
			this.MouseUp += new System.Windows.Forms.MouseEventHandler(this.ctrl1DColorBar_MouseUp);
			this.Paint += new System.Windows.Forms.PaintEventHandler(this.ctrl1DColorBar_Paint);
			this.MouseMove += new System.Windows.Forms.MouseEventHandler(this.ctrl1DColorBar_MouseMove);
			this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.ctrl1DColorBar_MouseDown);

		}
		#endregion

		#region Control Events

		private void ctrl1DColorBar_Load(object sender, System.EventArgs e)
		{
			Redraw_Control();
		}


		private void ctrl1DColorBar_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
		{
			if ( e.Button != MouseButtons.Left )	//	Only respond to left mouse button events
				return;

			m_bDragging = true;		//	Begin dragging which notifies MouseMove function that it needs to update the marker

			int y;
			y = e.Y;
			y -= 4;											//	Calculate slider position
			if ( y < 0 ) y = 0;
			if ( y > this.Height - 9 ) y = this.Height - 9;

			if ( y == m_iMarker_Start_Y )					//	If the slider hasn't moved, no need to redraw it.
				return;										//	or send a scroll notification

			DrawSlider(y, false);	//	Redraw the slider
			ResetHSLRGB();			//	Reset the color

			if ( Scroll != null )	//	Notify anyone who cares that the controls slider(color) has changed
				Scroll(this, e);
		}


		private void ctrl1DColorBar_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
		{
			if ( !m_bDragging )		//	Only respond when the mouse is dragging the marker.
				return;

			int y;
			y = e.Y;
			y -= 4; 										//	Calculate slider position
			if ( y < 0 ) y = 0;
			if ( y > this.Height - 9 ) y = this.Height - 9;

			if ( y == m_iMarker_Start_Y )					//	If the slider hasn't moved, no need to redraw it.
				return;										//	or send a scroll notification

			DrawSlider(y, false);	//	Redraw the slider
			ResetHSLRGB();			//	Reset the color

			if ( Scroll != null )	//	Notify anyone who cares that the controls slider(color) has changed
				Scroll(this, e);
		}


		private void ctrl1DColorBar_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e)
		{
			if ( e.Button != MouseButtons.Left )	//	Only respond to left mouse button events
				return;

			m_bDragging = false;

			int y;
			y = e.Y;
			y -= 4; 										//	Calculate slider position
			if ( y < 0 ) y = 0;
			if ( y > this.Height - 9 ) y = this.Height - 9;

			if ( y == m_iMarker_Start_Y )					//	If the slider hasn't moved, no need to redraw it.
				return;										//	or send a scroll notification

			DrawSlider(y, false);	//	Redraw the slider
			ResetHSLRGB();			//	Reset the color

			if ( Scroll != null )	//	Notify anyone who cares that the controls slider(color) has changed
				Scroll(this, e);
		}


		private void ctrl1DColorBar_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
		{
			Redraw_Control();
		}


		private void ctrl1DColorBar_Resize(object sender, System.EventArgs e)
		{
			Redraw_Control();
		}


		#endregion

		#region Events

		public event EventHandler Scroll;

		#endregion

		#region Public Methods

		/// <summary>
		/// The drawstyle of the contol (Hue, Saturation, Brightness, Red, Green or Blue)
		/// </summary>
		public eDrawStyle DrawStyle
		{
			get
			{
				return m_eDrawStyle;
			}
			set
			{
				m_eDrawStyle = value;

				//	Redraw the control based on the new eDrawStyle
				Reset_Slider(true);
				Redraw_Control();
			}
		}


		/// <summary>
		/// The HSL color of the control, changing the HSL will automatically change the RGB color for the control.
		/// </summary>
		public ColorManager.HSL HSL
		{
			get
			{
				return m_hsl;
			}
			set
			{
				m_hsl = value;
				m_rgb = ColorManager.HSL_to_RGB(m_hsl);

				//	Redraw the control based on the new color.
				Reset_Slider(true);
				DrawContent();
			}
		}


		/// <summary>
		/// The RGB color of the control, changing the RGB will automatically change the HSL color for the control.
		/// </summary>
		public Color RGB
		{
			get
			{
				return m_rgb;
			}
			set
			{
				m_rgb = value;
				m_hsl = ColorManager.RGB_to_HSL(m_rgb);

				//	Redraw the control based on the new color.
				Reset_Slider(true);
				DrawContent();
			}
		}


		#endregion

		#region Private Methods

		/// <summary>
		/// Redraws the background over the slider area on both sides of the control
		/// </summary>
		private void ClearSlider()
		{
			System.Drawing.Graphics g = this.CreateGraphics();
			Brush brush = System.Drawing.SystemBrushes.Control;
			g.FillRectangle(brush, 0, 0, 8, this.Height);				//	clear left hand slider
			g.FillRectangle(brush, this.Width - 8, 0, 8, this.Height);	//	clear right hand slider

        }


		/// <summary>
		/// Draws the slider arrows on both sides of the control.
		/// </summary>
		/// <param name="position">position value of the slider, lowest being at the bottom.  The range
		/// is between 0 and the controls height-9.  The values will be adjusted if too large/small</param>
		/// <param name="Unconditional">If Unconditional is true, the slider is drawn, otherwise some logic 
		/// is performed to determine is drawing is really neccessary.</param>
		private void DrawSlider(int position, bool Unconditional)
		{
			if ( position < 0 ) position = 0;
			if ( position > this.Height - 9 ) position = this.Height - 9;

			if ( m_iMarker_Start_Y == position && !Unconditional )	//	If the marker position hasn't changed
				return;												//	since the last time it was drawn and we don't HAVE to redraw
			//	then exit procedure

			m_iMarker_Start_Y = position;	//	Update the controls marker position

			this.ClearSlider();		//	Remove old slider

            System.Drawing.Graphics g = this.CreateGraphics();

			Pen pencil = new Pen(Color.FromArgb(116,114,106));	//	Same gray color Photoshop uses
			Brush brush = Brushes.White;
			
			Point[] arrow = new Point[7];				//	 GGG
			arrow[0] = new Point(1,position);			//	G   G
			arrow[1] = new Point(3,position);			//	G    G
			arrow[2] = new Point(7,position + 4);		//	G     G
			arrow[3] = new Point(3,position + 8);		//	G      G
			arrow[4] = new Point(1,position + 8);		//	G     G
			arrow[5] = new Point(0,position + 7);		//	G    G
			arrow[6] = new Point(0,position + 1);		//	G   G
			//	 GGG

			g.FillPolygon(brush, arrow);	//	Fill left arrow with white
			g.DrawPolygon(pencil, arrow);	//	Draw left arrow border with gray

			//	    GGG
			arrow[0] = new Point(this.Width - 2,position);		//	   G   G
			arrow[1] = new Point(this.Width - 4,position);		//	  G    G
			arrow[2] = new Point(this.Width - 8,position + 4);	//	 G     G
			arrow[3] = new Point(this.Width - 4,position + 8);	//	G      G
			arrow[4] = new Point(this.Width - 2,position + 8);	//	 G     G
			arrow[5] = new Point(this.Width - 1,position + 7);	//	  G    G
			arrow[6] = new Point(this.Width - 1,position + 1);	//	   G   G
			//	    GGG

			g.FillPolygon(brush, arrow);	//	Fill right arrow with white
			g.DrawPolygon(pencil, arrow);	//	Draw right arrow border with gray

		}


		/// <summary>
		/// Draws the border around the control, in this case the border around the content area between
		/// the slider arrows.
		/// </summary>
		private void DrawBorder()
		{
            System.Drawing.Graphics g = this.CreateGraphics();

			Pen pencil;
			
			//	To make the control look like Adobe Photoshop's the border around the control will be a gray line
			//	on the top and left side, a white line on the bottom and right side, and a black rectangle (line) 
			//	inside the gray/white rectangle

			pencil = new Pen(Color.FromArgb(172,168,153));	//	The same gray color used by Photoshop
			g.DrawLine(pencil, this.Width - 10, 2, 9, 2);	//	Draw top line
			g.DrawLine(pencil, 9, 2, 9, this.Height - 4);	//	Draw left hand line

			pencil = new Pen(Color.White);
			g.DrawLine(pencil, this.Width - 9, 2, this.Width - 9,this.Height - 3);	//	Draw right hand line
			g.DrawLine(pencil, this.Width - 9,this.Height - 3, 9,this.Height - 3);	//	Draw bottome line

			pencil = new Pen(Color.Black);
			g.DrawRectangle(pencil, 10, 3, this.Width - 20, this.Height - 7);	//	Draw inner black rectangle
		}


		/// <summary>
		/// Evaluates the DrawStyle of the control and calls the appropriate
		/// drawing function for content
		/// </summary>
		private void DrawContent()
		{
			switch (m_eDrawStyle)
			{
				case eDrawStyle.Hue :
					Draw_Style_Hue();
					break;
				case eDrawStyle.Saturation :
					Draw_Style_Saturation();
					break;
				case eDrawStyle.Brightness :
					Draw_Style_Luminance();
					break;
				case eDrawStyle.Red :
					Draw_Style_Red();
					break;
				case eDrawStyle.Green :
					Draw_Style_Green();
					break;
				case eDrawStyle.Blue :
					Draw_Style_Blue();
					break;
			}
		}


		#region Draw_Style_X - Content drawing functions

		//	The following functions do the real work of the control, drawing the primary content (the area between the slider)
		//	

		/// <summary>
		/// Fills in the content of the control showing all values of Hue (from 0 to 360)
		/// </summary>
		private void Draw_Style_Hue()
		{
            System.Drawing.Graphics g = this.CreateGraphics();

			ColorManager.HSL _hsl = new ColorManager.HSL();
			_hsl.S = 1.0;	//	S and L will both be at 100% for this DrawStyle
			_hsl.L = 1.0;

			for ( int i = 0; i < this.Height - 8; i++ )	//	i represents the current line of pixels we want to draw horizontally
			{
				_hsl.H = 1.0 - (double)i/(this.Height - 8);			//	H (hue) is based on the current vertical position
				Pen pen = new Pen(ColorManager.HSL_to_RGB(_hsl));	//	Get the Color for this line

				g.DrawLine(pen, 11, i + 4, this.Width - 11, i + 4);	//	Draw the line and loop back for next line
			}
		}


		/// <summary>
		/// Fills in the content of the control showing all values of Saturation (0 to 100%) for the given
		/// Hue and Luminance.
		/// </summary>
		private void Draw_Style_Saturation()
		{
            System.Drawing.Graphics g = this.CreateGraphics();

			ColorManager.HSL _hsl = new ColorManager.HSL();
			_hsl.H = m_hsl.H;	//	Use the H and L values of the current color (m_hsl)
			_hsl.L = m_hsl.L;

			for ( int i = 0; i < this.Height - 8; i++ ) //	i represents the current line of pixels we want to draw horizontally
			{
				_hsl.S = 1.0 - (double)i/(this.Height - 8);			//	S (Saturation) is based on the current vertical position
				Pen pen = new Pen(ColorManager.HSL_to_RGB(_hsl));	//	Get the Color for this line

				g.DrawLine(pen, 11, i + 4, this.Width - 11, i + 4);	//	Draw the line and loop back for next line
			}
		}


		/// <summary>
		/// Fills in the content of the control showing all values of Luminance (0 to 100%) for the given
		/// Hue and Saturation.
		/// </summary>
		private void Draw_Style_Luminance()
		{
            System.Drawing.Graphics g = this.CreateGraphics();

			ColorManager.HSL _hsl = new ColorManager.HSL();
			_hsl.H = m_hsl.H;	//	Use the H and S values of the current color (m_hsl)
			_hsl.S = m_hsl.S;

			for ( int i = 0; i < this.Height - 8; i++ ) //	i represents the current line of pixels we want to draw horizontally
			{
				_hsl.L = 1.0 - (double)i/(this.Height - 8);			//	L (Luminance) is based on the current vertical position
				Pen pen = new Pen(ColorManager.HSL_to_RGB(_hsl));	//	Get the Color for this line

				g.DrawLine(pen, 11, i + 4, this.Width - 11, i + 4);	//	Draw the line and loop back for next line
			}
		}


		/// <summary>
		/// Fills in the content of the control showing all values of Red (0 to 255) for the given
		/// Green and Blue.
		/// </summary>
		private void Draw_Style_Red()
		{
            System.Drawing.Graphics g = this.CreateGraphics();

			for ( int i = 0; i < this.Height - 8; i++ ) //	i represents the current line of pixels we want to draw horizontally
			{
				int red = 255 - Round(255 * (double)i/(this.Height - 8));	//	red is based on the current vertical position
				Pen pen = new Pen(Color.FromArgb(red, m_rgb.G, m_rgb.B));	//	Get the Color for this line

				g.DrawLine(pen, 11, i + 4, this.Width - 11, i + 4);			//	Draw the line and loop back for next line
			}
		}


		/// <summary>
		/// Fills in the content of the control showing all values of Green (0 to 255) for the given
		/// Red and Blue.
		/// </summary>
		private void Draw_Style_Green()
		{
            System.Drawing.Graphics g = this.CreateGraphics();

			for ( int i = 0; i < this.Height - 8; i++ ) //	i represents the current line of pixels we want to draw horizontally
			{
				int green = 255 - Round(255 * (double)i/(this.Height - 8));	//	green is based on the current vertical position
				Pen pen = new Pen(Color.FromArgb(m_rgb.R, green, m_rgb.B));	//	Get the Color for this line

				g.DrawLine(pen, 11, i + 4, this.Width - 11, i + 4);			//	Draw the line and loop back for next line
			}
		}


		/// <summary>
		/// Fills in the content of the control showing all values of Blue (0 to 255) for the given
		/// Red and Green.
		/// </summary>
		private void Draw_Style_Blue()
		{
            System.Drawing.Graphics g = this.CreateGraphics();

			for ( int i = 0; i < this.Height - 8; i++ ) //	i represents the current line of pixels we want to draw horizontally
			{
				int blue = 255 - Round(255 * (double)i/(this.Height - 8));	//	green is based on the current vertical position
				Pen pen = new Pen(Color.FromArgb(m_rgb.R, m_rgb.G, blue));	//	Get the Color for this line

				g.DrawLine(pen, 11, i + 4, this.Width - 11, i + 4);			//	Draw the line and loop back for next line
			}
		}


		#endregion

		/// <summary>
		/// Calls all the functions neccessary to redraw the entire control.
		/// </summary>
		private void Redraw_Control()
		{
			DrawSlider(m_iMarker_Start_Y, true);
			DrawBorder();
			switch (m_eDrawStyle)
			{
				case eDrawStyle.Hue :
					Draw_Style_Hue();
					break;
				case eDrawStyle.Saturation :
					Draw_Style_Saturation();
					break;
				case eDrawStyle.Brightness :
					Draw_Style_Luminance();
					break;
				case eDrawStyle.Red :
					Draw_Style_Red();
					break;
				case eDrawStyle.Green :
					Draw_Style_Green();
					break;
				case eDrawStyle.Blue :
					Draw_Style_Blue();
					break;
			}
		}


		/// <summary>
		/// Resets the vertical position of the slider to match the controls color.  Gives the option of redrawing the slider.
		/// </summary>
		/// <param name="Redraw">Set to true if you want the function to redraw the slider after determining the best position</param>
		private void Reset_Slider(bool Redraw)
		{
			//	The position of the marker (slider) changes based on the current drawstyle:
			switch (m_eDrawStyle)
			{
				case eDrawStyle.Hue :
					m_iMarker_Start_Y = (this.Height - 8) - Round( (this.Height - 8) * m_hsl.H );
					break;
				case eDrawStyle.Saturation :
					m_iMarker_Start_Y = (this.Height - 8) - Round( (this.Height - 8) * m_hsl.S );
					break;
				case eDrawStyle.Brightness :
					m_iMarker_Start_Y = (this.Height - 8) - Round( (this.Height - 8) * m_hsl.L );
					break;
				case eDrawStyle.Red :
					m_iMarker_Start_Y = (this.Height - 8) - Round( (this.Height - 8) * (double)m_rgb.R/255 );
					break;
				case eDrawStyle.Green :
					m_iMarker_Start_Y = (this.Height - 8) - Round( (this.Height - 8) * (double)m_rgb.G/255 );
					break;
				case eDrawStyle.Blue :
					m_iMarker_Start_Y = (this.Height - 8) - Round( (this.Height - 8) * (double)m_rgb.B/255 );
					break;
			}

			if ( Redraw )
				DrawSlider(m_iMarker_Start_Y, true);
		}


		/// <summary>
		/// Resets the controls color (both HSL and RGB variables) based on the current slider position
		/// </summary>
		private void ResetHSLRGB()
		{
			switch (m_eDrawStyle)
			{
				case eDrawStyle.Hue :
					m_hsl.H = 1.0 - (double)m_iMarker_Start_Y/(this.Height - 9);
					m_rgb = ColorManager.HSL_to_RGB(m_hsl);
					break;
				case eDrawStyle.Saturation :
					m_hsl.S = 1.0 - (double)m_iMarker_Start_Y/(this.Height - 9);
					m_rgb = ColorManager.HSL_to_RGB(m_hsl);
					break;
				case eDrawStyle.Brightness :
					m_hsl.L = 1.0 - (double)m_iMarker_Start_Y/(this.Height - 9);
					m_rgb = ColorManager.HSL_to_RGB(m_hsl);
					break;
				case eDrawStyle.Red :
					m_rgb = Color.FromArgb(255 - Round( 255 * (double)m_iMarker_Start_Y/(this.Height - 9) ), m_rgb.G, m_rgb.B);
					m_hsl = ColorManager.RGB_to_HSL(m_rgb);
					break;
				case eDrawStyle.Green :
					m_rgb = Color.FromArgb(m_rgb.R, 255 - Round( 255 * (double)m_iMarker_Start_Y/(this.Height - 9) ), m_rgb.B);
					m_hsl = ColorManager.RGB_to_HSL(m_rgb);
					break;
				case eDrawStyle.Blue :
					m_rgb = Color.FromArgb(m_rgb.R, m_rgb.G, 255 - Round( 255 * (double)m_iMarker_Start_Y/(this.Height - 9) ));
					m_hsl = ColorManager.RGB_to_HSL(m_rgb);
					break;
			}
		}


		/// <summary>
		/// Kindof self explanitory, I really need to look up the .NET function that does this.
		/// </summary>
		/// <param name="val">double value to be rounded to an integer</param>
		/// <returns></returns>
		private int Round(double val)
		{
			int ret_val = (int)val;
			
			int temp = (int)(val * 100);

			if ( (temp % 100) >= 50 )
				ret_val += 1;

			return ret_val;
			
		}


		#endregion
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

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


Written By
Software Developer Unity3 Software
United States United States
Richard Blythe is founder and CEO of Unity3 Software.
In his spare time he enjoys flying Cessna 172s, reading, playing his Taylor acoustic guitar and recording music. He's latest non-computer endeavor is to learn violin. (Ouch)

Comments and Discussions