'i00 .Net ToolStrip Color Picker
'©i00 Productions All rights reserved
'Created by Kris Bennett
'----------------------------------------------------------------------------------------------------
'All property in this file is and remains the property of i00 Productions, regardless of its usage,
'unless stated otherwise in writing from i00 Productions.
'
'i00 is not and shall not be held accountable for any damages directly or indirectly caused by the
'use or miss-use of this product. This product is only a component and thus is intended to be used
'as part of other software, it is not a complete software package, thus i00 Productions is not
'responsible for any legal ramifications that software using this product breaches.
<System.ComponentModel.DefaultEvent("ColorChanged")> _
<System.Windows.Forms.Design.ToolStripItemDesignerAvailability(Windows.Forms.Design.ToolStripItemDesignerAvailability.All)> _
<System.ComponentModel.DesignerCategory("")> _
Public Class tsiColorPicker
Inherits ToolStripDropDownItem
Dim OrigWidth As Integer
Dim OrigHeight As Integer
Public Sub New()
Using tsiSizeCheck As New ToolStripMenuItem
OrigWidth = tsiSizeCheck.Width
OrigHeight = tsiSizeCheck.Height
End Using
Resize()
MyBase.AutoToolTip = False
MyBase.AutoSize = False
End Sub
Private Sub Resize()
MyBase.Size = MySize
End Sub
#Region "Properties"
#Region "Custom"
Dim mc_Persistent As Boolean = False
<System.ComponentModel.DefaultValueAttribute(False)> _
Public Property Persistent() As Boolean
Get
Return mc_Persistent
End Get
Set(ByVal value As Boolean)
mc_Persistent = value
End Set
End Property
Dim mc_SelectedColor As Color
Public Property SelectedColor() As Color
Get
Return mc_SelectedColor
End Get
Set(ByVal value As Color)
mc_SelectedColor = value
End Set
End Property
Dim mc_Colors As List(Of Color)
Public Property Colors() As List(Of Color)
Get
If mc_Colors Is Nothing OrElse mc_Colors.Count = 0 Then
FillWithDefaultColors()
End If
Return mc_Colors
End Get
Set(ByVal value As List(Of Color))
mc_Colors = value
'should really use a watched list and call the resize after item/s gets added / removed
Resize()
End Set
End Property
Dim mc_ShowColorToolTip As Boolean = True
<System.ComponentModel.DefaultValueAttribute(True)> _
Public Property ShowColorToolTip() As Boolean
Get
Return mc_ShowColorToolTip
End Get
Set(ByVal value As Boolean)
mc_ShowColorToolTip = value
End Set
End Property
Dim mc_BlocksAcross As Integer = 8
<System.ComponentModel.DefaultValueAttribute(8)> _
Public Property BlocksAcross() As Integer
Get
Return mc_BlocksAcross
End Get
Set(ByVal value As Integer)
mc_BlocksAcross = value
Resize()
End Set
End Property
#End Region
#Region "Private Read Only Properties"
Private ReadOnly Property BlocksHigh() As Integer
Get
Return ((Colors.Count - 1) \ BlocksAcross) + 1
End Get
End Property
Private ReadOnly Property TextSize() As SizeF
Get
If Text <> "" AndAlso Me.Parent IsNot Nothing Then
TextSize = Me.Parent.CreateGraphics.MeasureString(Text, MyBase.Font)
End If
End Get
End Property
Private ReadOnly Property MySize() As Size
Get
Return New Size(CInt((OrigHeight * mc_BlocksAcross) + TextSize.Width), OrigHeight * BlocksHigh)
End Get
End Property
#End Region
#Region "Overridden Properties"
#Region "Hidden Properties"
<System.ComponentModel.Browsable(False)> _
<System.ComponentModel.DesignerSerializationVisibility(System.ComponentModel.DesignerSerializationVisibility.Hidden)> _
Public Overrides Property TextAlign() As System.Drawing.ContentAlignment
Get
Return MyBase.TextAlign
End Get
Set(ByVal value As System.Drawing.ContentAlignment)
MyBase.TextAlign = value
End Set
End Property
<System.ComponentModel.Browsable(False)> _
<System.ComponentModel.DesignerSerializationVisibility(System.ComponentModel.DesignerSerializationVisibility.Hidden)> _
Public Overrides Property TextDirection() As System.Windows.Forms.ToolStripTextDirection
Get
Return MyBase.TextDirection
End Get
Set(ByVal value As System.Windows.Forms.ToolStripTextDirection)
MyBase.TextDirection = value
End Set
End Property
<System.ComponentModel.Browsable(False)> _
<System.ComponentModel.DesignerSerializationVisibility(System.ComponentModel.DesignerSerializationVisibility.Hidden)> _
Public Overrides Property Size() As System.Drawing.Size
Get
Return MySize
End Get
Set(ByVal value As System.Drawing.Size)
End Set
End Property
<System.ComponentModel.Browsable(False)> _
<System.ComponentModel.DesignerSerializationVisibility(System.ComponentModel.DesignerSerializationVisibility.Hidden)> _
Public Overrides Property Image() As System.Drawing.Image
Get
Return Nothing
End Get
Set(ByVal value As System.Drawing.Image)
End Set
End Property
<System.ComponentModel.Browsable(False)> _
Public Overrides Property DisplayStyle() As System.Windows.Forms.ToolStripItemDisplayStyle
Get
Return ToolStripItemDisplayStyle.None
End Get
Set(ByVal value As System.Windows.Forms.ToolStripItemDisplayStyle)
End Set
End Property
<System.ComponentModel.Browsable(False)> _
Overrides ReadOnly Property CanSelect() As Boolean
Get
Return False
End Get
End Property
#End Region
Public Overrides ReadOnly Property HasDropDownItems() As Boolean
Get
Return mc_Persistent
End Get
End Property
Public Overrides Property Text() As String
Get
Return MyBase.Text
End Get
Set(ByVal value As String)
MyBase.Text = value
Resize()
End Set
End Property
#End Region
#End Region
Public Shared DefaultColors() As Color = New Color() {Color.Black, Color.FromArgb(153, 51, 0), Color.FromArgb(51, 51, 0), Color.FromArgb(0, 51, 0), Color.FromArgb(0, 51, 102), Color.Navy, Color.FromArgb(51, 51, 153), Color.FromArgb(51, 51, 51), _
Color.Maroon, Color.FromArgb(255, 102, 0), Color.Olive, Color.Green, Color.Teal, Color.Blue, Color.FromArgb(102, 102, 153), Color.Gray, _
Color.Red, Color.FromArgb(255, 153, 0), Color.FromArgb(153, 204, 0), Color.FromArgb(51, 153, 102), Color.FromArgb(51, 204, 204), Color.FromArgb(51, 102, 255), Color.Purple, Color.FromArgb(153, 153, 153), _
Color.Fuchsia, Color.FromArgb(255, 204, 0), Color.Yellow, Color.Lime, Color.Aqua, Color.FromArgb(0, 204, 255), Color.FromArgb(153, 51, 102), Color.Silver, _
Color.FromArgb(255, 153, 204), Color.FromArgb(255, 204, 153), Color.FromArgb(255, 255, 153), Color.FromArgb(204, 255, 204), Color.FromArgb(204, 255, 255), Color.FromArgb(153, 204, 255), Color.FromArgb(204, 153, 255), Color.White}
Private Sub FillWithDefaultColors()
mc_Colors = New List(Of Color)
mc_Colors.AddRange(DefaultColors)
End Sub
Dim Tooltip As New ToolTip
Private Sub tsiColorPickerItem_Disposed(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Disposed
Tooltip.Dispose()
End Sub
Private Sub tsiColorPickerItem_MouseLeave(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.MouseLeave
Tooltip.Hide(Me.Parent)
If PaintHoverColor <> -1 Then
RaiseEvent HoverOverColor(Me, New ColorEventArgs)
PaintHoverColor = -1
Me.Invalidate()
End If
End Sub
Public Class ColorEventArgs
Inherits EventArgs
Public Color As Color
End Class
Public Event HoverOverColor(ByVal sender As Object, ByVal e As ColorEventArgs)
Private Sub tsiColorPickerItem_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseMove
Dim LastColorNo As Integer = PaintHoverColor
PaintHoverColor = GetColorIndexAtLocation(e.Location)
If LastColorNo <> PaintHoverColor Then
'color has changed
If PaintHoverColor = -1 Then
RaiseEvent HoverOverColor(Me, New ColorEventArgs)
Tooltip.Hide(Me.Parent)
Else
RaiseEvent HoverOverColor(Me, New ColorEventArgs With {.Color = Colors(PaintHoverColor)})
If ShowColorToolTip Then
Dim TipLocation = GetColorRectFromIndex(PaintHoverColor).Location
Dim ts = TextSize
TipLocation.X += MyBase.Bounds.Left + CInt(ts.Width)
TipLocation.Y += MyBase.Bounds.Top + Cursors.Arrow.Size.Height
Dim NiceColor As Color = GetClosestKnownColor(Colors(PaintHoverColor))
If NiceColor.IsEmpty = False Then
Dim ColorName As String = NiceColor.Name
StartOver:
Dim Match = System.Text.RegularExpressions.Regex.Match(ColorName, "(?<!\s|^)[A-Z]")
If Match.Success Then
'do this letter
ColorName = Left(ColorName, Match.Index) & " " & Mid(ColorName, Match.Index + 1)
GoTo StartOver
End If
Tooltip.Show(ColorName, Me.Parent, TipLocation) 'New Point(Me.Bounds.Right, Me.Bounds.Y))
End If
End If
End If
Me.Invalidate()
End If
End Sub
#Region "Cloest Color Matching"
Public Shared Function GetClosestKnownColor(ByVal Color As Color) As Color
'find the closest color...
If Color.IsKnownColor Then
Return Color
Else
Dim ColorMatches = (From xItem In [Enum].GetNames(GetType(KnownColor)) Select Color.FromKnownColor(DirectCast([Enum].Parse(GetType(KnownColor), xItem), KnownColor))).ToArray
Dim ColorMatchesWithCloseness = (From xItem In ColorMatches Where xItem.IsSystemColor = False AndAlso (IsRightColorTone(xItem, Color) OrElse ColorCloseness(xItem, Color) < 25) Select New With {.Color = xItem, .Closeness = ColorCloseness(xItem, Color)}).ToArray
ColorMatchesWithCloseness = (From xItem In ColorMatchesWithCloseness Order By xItem.Closeness Ascending).ToArray
If ColorMatchesWithCloseness.Count > 0 Then
Return ColorMatchesWithCloseness.FirstOrDefault.Color
Else
Return Color.Empty
End If
End If
End Function
Private Class ObjectHolder
Public theObject As Object
Public Sub New(ByVal theObject As Object)
Me.theObject = theObject
End Sub
End Class
Private Shared Function ColorCloseness(ByVal Color1 As Color, ByVal Color2 As Color) As Integer
Return Math.Abs(CInt(Color1.A) - CInt(Color2.A)) + Math.Abs(CInt(Color1.R) - CInt(Color2.R)) + Math.Abs(CInt(Color1.G) - CInt(Color2.G)) + Math.Abs(CInt(Color1.B) - CInt(Color2.B))
End Function
Private Shared Function IsRightColorTone(ByVal Color1 As Color, ByVal Color2 As Color) As Boolean
Dim arrColor1 = New ObjectHolder() {New ObjectHolder(Color1.R), New ObjectHolder(Color1.G), New ObjectHolder(Color1.B)}
Dim arrColor2 = New ObjectHolder() {New ObjectHolder(Color2.R), New ObjectHolder(Color2.G), New ObjectHolder(Color2.B)}
'find the majors
Dim MaxColors1 = Join((From xItem In arrColor1 Where Val(xItem.theObject) = arrColor1.Max(Function(b As ObjectHolder) Val(b.theObject)) Select CStr(Array.IndexOf(arrColor1, xItem))).ToArray, ";")
Dim MaxColors2 = Join((From xItem In arrColor2 Where Val(xItem.theObject) = arrColor2.Max(Function(b As ObjectHolder) Val(b.theObject)) Select CStr(Array.IndexOf(arrColor2, xItem))).ToArray, ";")
If MaxColors1 = MaxColors2 Then
'good sofar...
Else
Return False
End If
'find the minors
Dim MinColors1 = Join((From xItem In arrColor1 Where Val(xItem.theObject) = arrColor1.Min(Function(b As ObjectHolder) Val(b.theObject)) Select CStr(Array.IndexOf(arrColor1, xItem))).ToArray, ";")
Dim MinColors2 = Join((From xItem In arrColor2 Where Val(xItem.theObject) = arrColor2.Min(Function(b As ObjectHolder) Val(b.theObject)) Select CStr(Array.IndexOf(arrColor2, xItem))).ToArray, ";")
If MinColors1 = MinColors2 Then
'good sofar...
Else
Return False
End If
Return True
End Function
#End Region
Dim PaintHoverColor As Integer = -1
Private Function GetColorIndexAtLocation(ByVal Location As Point) As Integer
GetColorIndexAtLocation = -1
Dim ts = TextSize
If Location.X < CInt(ts.Width) Then
'over left margin
Else
GetColorIndexAtLocation = CInt((Int(Location.Y / OrigHeight) * BlocksAcross) + (Int((Location.X - CInt(ts.Width)) / OrigHeight)))
If GetColorIndexAtLocation < Colors.Count Then
'all good
Else
GetColorIndexAtLocation = -1
End If
End If
End Function
Private Function GetColorRectFromIndex(ByVal Index As Integer) As Rectangle
Dim x = Index Mod BlocksAcross
Dim y As Integer = CInt(Int((Index - x) / mc_BlocksAcross))
Return New Rectangle((x * OrigHeight), (y * OrigHeight), OrigHeight, OrigHeight)
End Function
Public Event ColorChanged(ByVal sender As Object, ByVal e As EventArgs)
Private Sub tsiColorPicker_MouseUp(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseUp
Dim ColorIndex = GetColorIndexAtLocation(e.Location)
If ColorIndex = -1 Then
'did not click a color
Else
Me.SelectedColor = Colors(ColorIndex)
RaiseEvent ColorChanged(Me, EventArgs.Empty)
End If
End Sub
Private Sub tsiColorPickerItem_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint
Dim ts = TextSize
If ts.Width <> 0 Then
'draw the text
Using sb As New SolidBrush(Me.ForeColor)
e.Graphics.DrawString(Me.Text, Me.Font, sb, 0, (Me.Height - ts.Height) / 2)
End Using
e.Graphics.TranslateTransform(CInt(ts.Width), 0)
End If
'draw each color
Dim ColorBorderSize As Integer = CInt(OrigHeight * 0.33)
Dim MaxY = BlocksHigh - 1
For y = 0 To MaxY
For x = 0 To BlocksAcross - 1
Dim ColorNo = (mc_BlocksAcross * y) + x
If ColorNo < Colors.Count Then
Dim Color = Colors(ColorNo)
Dim Rect = New RectangleF(CSng((x * OrigHeight) + (ColorBorderSize / 2)), CSng((y * OrigHeight) + (ColorBorderSize / 2)), OrigHeight - ColorBorderSize, OrigHeight - ColorBorderSize)
If PaintHoverColor = ColorNo OrElse Color = mc_SelectedColor Then
'draw selection rectangle
'had to create a new object to call DrawItemCheck on as otherwise it draws the item at the full height
Using tsi As New ToolStripButton
tsi.AutoSize = False
tsi.Width = OrigHeight
tsi.Height = OrigHeight
tsi.Select()
Using b As New Bitmap(tsi.Width, tsi.Height)
Using g = Graphics.FromImage(b)
Me.Parent.Renderer.DrawButtonBackground(New ToolStripItemRenderEventArgs(g, tsi))
If Color <> mc_SelectedColor Then
'alpha image
b.Filters.Alpha()
Else
'just draw
End If
e.Graphics.DrawImage(b, New PointF(CInt(Rect.X - (ColorBorderSize / 2)), CInt(Rect.Y - (ColorBorderSize / 2))))
End Using
End Using
End Using
End If
Using sb As New SolidBrush(Color)
e.Graphics.FillRectangle(sb, Rect)
End Using
If PaintHoverColor <> ColorNo AndAlso Color <> mc_SelectedColor Then
'draw border
Using p As New Pen(AlphaColor(Color.FromKnownColor(KnownColor.MenuText), 63))
e.Graphics.DrawRectangle(p, Rect.X, Rect.Y, Rect.Width, Rect.Height)
End Using
End If
End If
Next
Next
e.Graphics.ResetTransform()
End Sub
Public Shared Function AlphaColor(ByVal theColor As Color, Optional ByVal AlphaLevel As Byte = 255) As Color
AlphaColor = Color.FromArgb(AlphaLevel, theColor.R, theColor.G, theColor.B)
End Function
End Class