Click here to Skip to main content
15,891,864 members
Articles / Programming Languages / Visual Basic

The ColorPicker WinForms Control Revisited

Rate me:
Please Sign up or sign in to vote.
4.40/5 (9 votes)
16 Sep 2004CPOL6 min read 94.2K   1.2K   30  
Refactoring the original ColorPicker control by employing the Adapter design pattern to support plug-in display adapters for ComboBox-like appearance and more
' ==============================================================================
'
' THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
' ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
' THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
' PARTICULAR PURPOSE.
'
' � 2003-2004 LaMarvin. All Rights Reserved.
'
' FMI: http://www.vbinfozine.com/a_default.shtml
' ==============================================================================

Imports System.Drawing

' Implements a ComboBox-like appearance to be used with the ColorPicker control
' be means of the ComboBoxDisplayAdapter.
Public Class ComboBoxDisplay
	Inherits Control

	' This flag tells us if we have to emulate a dropped-down appearance in the
	' painting code.
	Private _HasDropDownAppearance As Boolean

	' This is the currently selected color displayed
	Private _Color As Color

	' The client area is partitioned into the following rectangles:
	Private _ColorBoxRect As Rectangle	' the color box rectangle on the left
	Private _TextBoxRect As RectangleF	' the rectangle for the color name in the middle
	Private _TextFormat As StringFormat	' format of the displayed color name
	Private _ButtonRect As Rectangle	 ' the rectanngle for the drop-down button on the right

	' This event is raised when the user changes the drop-down appearance of the
	' control by clicking it, or by pressing the Alt+Down Arrow key combination.
	Public Event DropDownAppearanceChanged As EventHandler


	Public Sub New()
		MyBase.New()
		Me.ForeColor = SystemColors.WindowText
		Me.BackColor = SystemColors.Window
		Me._TextFormat = New StringFormat(StringFormatFlags.NoWrap)
		Me._TextFormat.Alignment = StringAlignment.Near
		Me._TextFormat.LineAlignment = StringAlignment.Center
	End Sub


	Public Overridable Property Color() As Color
		Get
			Return Me._Color
		End Get
		Set(ByVal Value As Color)
			Me._Color = Value
			Me.Invalidate()
		End Set
	End Property


	Public Overridable Property HasDropDownAppearance() As Boolean
		Get
			Return Me._HasDropDownAppearance
		End Get
		Set(ByVal Value As Boolean)
			If Me._HasDropDownAppearance = Value Then
				Return
			End If
			Me._HasDropDownAppearance = Value
			Me.Invalidate()
			Me.OnDropDownAppearanceChanged(EventArgs.Empty)
		End Set
	End Property


	Protected Overridable Sub OnDropDownAppearanceChanged(ByVal e As EventArgs)
		RaiseEvent DropDownAppearanceChanged(Me, EventArgs.Empty)
	End Sub


	Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
		Try
			MyBase.OnPaint(e)

			Dim State As ButtonState = ButtonState.Normal
			If Me.HasDropDownAppearance Then
				State = ButtonState.Pushed
			End If

			Dim ColorBrush As New SolidBrush(Me.Color)
			Try
				e.Graphics.FillRectangle(ColorBrush, Me._ColorBoxRect)
			Finally
				ColorBrush.Dispose()
			End Try

			' I don't like the black outline that the PropertyGrid displays around the PaintValue-drawn
			' image by an UITypeEditor. If you like it, then uncomment the following lines :-)
			'Dim OutlineRect As Rectangle = Me._ColorBoxRect
			'OutlineRect.Width -= 1
			'OutlineRect.Height -= 1
			'e.Graphics.DrawRectangle(Pens.Black, OutlineRect)

			If Not Me._TextBoxRect.IsEmpty Then
				e.Graphics.DrawString(Me.Text, Me.Font, System.Drawing.Brushes.Black, Me._TextBoxRect, Me.TextFormat)
			End If

			ControlPaint.DrawComboButton(e.Graphics, Me._ButtonRect, State)

			ControlPaint.DrawBorder3D(e.Graphics, Me.ClientRectangle, Border3DStyle.Flat)

			If Me.ShouldDrawFocusRect Then
				ControlPaint.DrawFocusRectangle(e.Graphics, Me.ClientRectangle)
			End If

		Catch ex As Exception
			Trace.WriteLine(ex.ToString())
		End Try
	End Sub


	Protected Overridable ReadOnly Property ShouldDrawFocusRect() As Boolean
		Get
			Return Me.HasDropDownAppearance OrElse Me.Parent.ContainsFocus
		End Get
	End Property


	Protected Overrides Sub OnMouseDown(ByVal e As System.Windows.Forms.MouseEventArgs)
		Try
			' Set focus to itself in the case we don't have focus yet. Originally I thought
			' that the control regains focus automatically when clicked by the mouse, but it 
			' didn't work so.
			If Not Me.Focused Then
				Me.Focus()
			End If
			MyBase.OnMouseDown(e)
			Me.HasDropDownAppearance = Not Me.HasDropDownAppearance
		Catch ex As Exception
			Trace.WriteLine(ex.ToString())
		End Try
	End Sub


	Protected Overridable ReadOnly Property DownButtonWidth() As Integer
		Get
			Return SystemInformation.CaptionButtonSize.Width
		End Get
	End Property


	Protected Overridable Property TextFormat() As StringFormat
		Get
			Return Me._TextFormat
		End Get
		Set(ByVal Value As StringFormat)
			If Value Is Nothing Then
				Throw New ArgumentNullException("Value")
			End If
			Me._TextFormat = Value
		End Set
	End Property


	Protected Overridable Sub GetDisplayLayout( _
	 ByRef colorBoxRect As Rectangle, _
	 ByRef textBoxRect As RectangleF, _
	 ByRef buttonRect As Rectangle)

		Const DistanceFromEdge As Integer = 2

		' The button occupies the right portion.
		buttonRect = Rectangle.FromLTRB( _
		 Me.Width - Me.DownButtonWidth, DistanceFromEdge, Me.Width - DistanceFromEdge, Me.Height - DistanceFromEdge)

		If Len(Me.Text) = 0 Then
			' No text to display, so we'll fill the whole client area (except the button)
			' with the current color.
			colorBoxRect = Rectangle.FromLTRB(DistanceFromEdge, DistanceFromEdge, buttonRect.Left, buttonRect.Bottom)
			textBoxRect = RectangleF.Empty
		Else
			' The color box should occupy the left portion of the control. The color box widthg is 
			' set to 1.5 * width of the drop-down button.
			colorBoxRect = Rectangle.FromLTRB( _
			 2 * DistanceFromEdge, 2 * DistanceFromEdge, _
			 CInt(1.5 * Me.DownButtonWidth), _
			 buttonRect.Bottom - DistanceFromEdge)

			' The text occupies the middle portion.
			textBoxRect = RectangleF.FromLTRB( _
			 colorBoxRect.Right + DistanceFromEdge, DistanceFromEdge, _
			 buttonRect.Left - DistanceFromEdge, buttonRect.Bottom)
		End If
	End Sub


	Protected Overrides Sub OnLayout(ByVal levent As System.Windows.Forms.LayoutEventArgs)
		' Recompute the display rectangles here, so they don't have to be recomputed 
		' every time OnPaint is called by the system.
		Me.GetDisplayLayout(Me._ColorBoxRect, Me._TextBoxRect, Me._ButtonRect)
		MyBase.OnLayout(levent)
		Me.Invalidate()
	End Sub


	Protected Overrides Sub OnTextChanged(ByVal e As System.EventArgs)
		MyBase.OnTextChanged(e)

		' If the text changed, we have to recompute the display layout and repaint
		' the control.
		Me.OnLayout(New LayoutEventArgs(Me, "Text"))
	End Sub


	Protected Overrides Sub OnKeyDown(ByVal e As System.Windows.Forms.KeyEventArgs)
		If e.KeyData = (Keys.Down Or Keys.Alt) Then
			' Alt+Down Arrow should switch the drop-down appearance just like the left mouse click.
			e.Handled = True
			Me.HasDropDownAppearance = Not Me.HasDropDownAppearance
		Else
			MyBase.OnKeyDown(e)
		End If
	End Sub

End Class

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
Web Developer
Slovakia Slovakia
I live in Slovakia with my wife, two sons and two daughters. I've been doing Microsoft Windows development since 1988; primarily in VB. I'm a big fan of the MS .NET framework, publisher of the www.vbinfozine.com ezine and the author of components found at www.lamarvin.com.

Comments and Discussions