Introduction
Every now and then, you sit back and think you can relax. With me, this doesn’t happen often. But, when it does, my body might be taking a break, but my ever-active brain does not. Yesterday was such a day, or rather: last night was such an occasion. Even when I am sleeping, I am not resting.
Today’s crazy article will show you how you can make your own custom color picker in either Visual Basic.NET or C#.
Background
It is usually a good idea to stick with Microsoft’s built-in Dialog controls, as it is easier to learn for newbies and familiar across all its products. There are cases, though, when you feel that a certain Dialog needs more features, or a better look and feel. This is where custom controls and control libraries come in. You can design anything and inherit from any control to create your own powerful control that can be used over and over.
Practical
There is no design practical yet. This is because you will create the component through code, and only when it is finished you will be able to add it to your Form’s Toolbox and to the Form itself. Open Visual Studio and create a new Windows Forms project in either VB.NET or in C#. You may name the project anything you desire.
Add a Class. I have named my Class WellPanel.cs. Add the NameSpaces.
C#
using System.ComponentModel;
VB.NET
Imports System.ComponentModel
Add Enums to store the various settings.
C#
public enum Scheme { System, Web } public enum Order { Name, Hue, Saturation, Brightness, Distance, Unsorted }
VB.NET
Public Enum Scheme System Web End Enum Public Enum Order Name Hue Saturation Brightness Distance Unsorted End Enum
Scheme identifies which color scheme you want to display and Order identifies the various colors that need to be shown inside the panel. Add a subclass.
C#
public class ColorChangedEventArgs : EventArgs { private Color color; public ColorChangedEventArgs(Color color) { this.color = color; } public Color Color { get { return color; } } } public delegate void ColorChangedEventHandler(object sender, ColorChangedEventArgs e);
VB.NET
Public Class ColorChangedEventArgs Inherits EventArgs Private clr As Color Public Sub New(ByVal cr As Color) clr = cr End Sub Public ReadOnly Property Color As Color Get Return Color End Get End Property End Class Public Delegate Sub ColorChangedEventHandler(ByVal sender _ As Object, ByVal e As ColorChangedEventArgs)
You have made a custom class that will fire when a color has been changed. Ensure your WellPanel Class inherits from UserControl and create the following fields.
C#
partial class WellPanel : UserControl { internal const Order dorder = Order.Hue; internal const Scheme dscheme = Scheme.Web; internal const BorderStyle dborder = BorderStyle.FixedSingle; internal const int dcol = 0; internal static readonly Size dwellsize = new Size(16, 16); internal static readonly Color dcolor = Color.Black; private ToolTip tTip; private BorderStyle borderstyle = dborder; private Size sborder = new Size(1, 1); private Size scolorwell = dwellsize; private ColorWellInfo[] arrWells = null; private ColorWellInfo cwColor = null; private ColorWellInfo cwCurrent = null; private Scheme colorSet = dscheme; private Order sortorder = dorder; private int ccolumns = dcol; private int columns; private int rows; private Point mousepos;
VB.NET
Partial Class WellPanel Inherits UserControl Friend Const dorder As Order = Order.Hue Friend Const dscheme As Scheme = Scheme.Web Friend Const dborder As BorderStyle = borderstyle.FixedSingle Friend Const dcol As Integer = 0 Friend Shared ReadOnly dwellsize As Size = New Size(16, 16) Friend Shared ReadOnly dcolor As Color = Color.Black Private tTip As ToolTip Private MBorderstyle As BorderStyle = dborder Private sborder As Size = New Size(1, 1) Private scolorwell As Size = dwellsize Private arrWells As ColorWellInfo() = Nothing Private cwColor As ColorWellInfo = Nothing Private cwCurrent As ColorWellInfo = Nothing Private colorSet As Scheme = dscheme Private ColorSortOrder As Order = dorder Private ccolumns As Integer = dcol Private icolumns As Integer Private rows As Integer Private mousepos As Point
The preceding variables and constants signify the Size of each color well, the colors, rows and columns and some cosmetic settings, such as a border style and size and a ToolTip. Add the Constructor.
C#
public WellPanel() { arrWells = ColorWellInfo.GetColorWells(colorSet, sortorder); InitializeComponent(); UpdateBorderSize(); AutoSizePanel(); }
VB.NET
Public Sub New() arrWells = ColorWellInfo.GetColorWells(colorSet, SortOrder) InitializeComponent() UpdateBorderSize() AutoSizePanel() End Sub
Add the ColorWellInfo subclass.
C#
private class ColorWellInfo { private int iUnsorted; private long distance; public readonly Color Color; public Rectangle colorPos; public ColorWellInfo(Color color, int unsortedindex) { Color = color; distance = color.R * color.R + color.B * color.B + color.G * color.G; iUnsorted = unsortedindex; } private class DistanceComparer : IComparer { public int Compare(object a, object b) { ColorWellInfo _a = (ColorWellInfo)a; ColorWellInfo _b = (ColorWellInfo)b; return _a.distance.CompareTo(_b.distance); } } private class NameComparer : IComparer { public int Compare(object a, object b) { ColorWellInfo _a = (ColorWellInfo)a; ColorWellInfo _b = (ColorWellInfo)b; return _a.Color.Name.CompareTo(_b.Color.Name); } } private class SaturationComparer : IComparer { public int Compare(object a, object b) { ColorWellInfo _a = (ColorWellInfo)a; ColorWellInfo _b = (ColorWellInfo)b; return _a.Color.GetSaturation().CompareTo(_b.Color .GetSaturation()); } } private class HueComparer : IComparer { public int Compare(object a, object b) { ColorWellInfo _a = (ColorWellInfo)a; ColorWellInfo _b = (ColorWellInfo)b; return _a.Color.GetHue().CompareTo(_b.Color .GetHue()); } } private class BrightnessComparer : IComparer { public int Compare(object a, object b) { ColorWellInfo _a = (ColorWellInfo)a; ColorWellInfo _b = (ColorWellInfo)b; return _a.Color.GetBrightness().CompareTo(_b. .GetBrightness()); } } private class UnsortedComparer : IComparer { public int Compare(object a, object b) { ColorWellInfo _a = (ColorWellInfo)a; ColorWellInfo _b = (ColorWellInfo)b; return _a.iUnsorted.CompareTo(_b.iUnsorted); } } public static IComparer CompareColorDistance() { return new DistanceComparer(); } public static IComparer CompareColorName() { return new NameComparer(); } public static IComparer CompareColorSaturation() { return new SaturationComparer(); } public static IComparer CompareColorHue() { return new HueComparer(); } public static IComparer CompareColorBrightness() { return new BrightnessComparer(); } public static IComparer CompareUnsorted() { return new UnsortedComparer(); } public static ColorWellInfo[] GetColorWells(Scheme sscheme, Order order) { Array arrKNown = Enum.GetValues(typeof(KnownColor)); int iColors = 0; switch (sscheme) { case Scheme.Web: foreach (KnownColor k in arrKNown) { Color c = Color.FromKnownColor(k); if (!c.IsSystemColor && (c.A > 0)) { iColors++; } } break; case Scheme.System: foreach (KnownColor k in arrKNown) { Color c = Color.FromKnownColor(k); if (c.IsSystemColor && (c.A > 0)) { iColors++; } } break; } ColorWellInfo[] wells = new ColorWellInfo[iColors]; int i = 0; switch (sscheme) { case Scheme.Web: foreach (KnownColor k in arrKNown) { Color c = Color.FromKnownColor(k); if (!c.IsSystemColor && (c.A > 0)) { wells[i] = new ColorWellInfo(c, i); i++; } } break; case Scheme.System: foreach (KnownColor k in arrKNown) { Color c = Color.FromKnownColor(k); if (c.IsSystemColor && (c.A > 0)) { wells[i] = new ColorWellInfo(c, i); i++; } } break; } SortWells(wells, order); return wells; } public static void SortWells(ColorWellInfo[] Wells, Order SortOrder) { switch (SortOrder) { case Order.Brightness: Array.Sort(Wells, CompareColorBrightness()); break; case Order.Distance: Array.Sort(Wells, CompareColorDistance()); break; case Order.Hue: Array.Sort(Wells, CompareColorHue()); break; case Order.Name: Array.Sort(Wells, CompareColorName()); break; case Order.Saturation: Array.Sort(Wells, CompareColorSaturation()); break; case Order.Unsorted: Array.Sort(Wells, CompareUnsorted()); break; } } public void DrawColorWell(Graphics g, bool enabled, bool selected, bool pickColor) { if (!enabled) { Rectangle r = colorPos; r.Inflate(-SystemInformation.BorderSize.Width, -SystemInformation.BorderSize.Height); ControlPaint.DrawBorder3D(g, r, Border3DStyle.Flat); r.Inflate(-SystemInformation.BorderSize.Width, -SystemInformation.BorderSize.Height); g.FillRectangle(SystemBrushes.Control, r); } else { SolidBrush br = new SolidBrush(Color); if (pickColor) { Rectangle r = colorPos; ControlPaint.DrawBorder3D(g, r, Border3DStyle.Sunken); r.Inflate(-SystemInformation.Border3DSize .Width, -SystemInformation.Border3DSize .Height); g.FillRectangle(br, r); } else { if (selected) { Rectangle r = colorPos; ControlPaint.DrawBorder3D(g, r, Border3DStyle.Raised); r.Inflate(-SystemInformation .Border3DSize.Width, -SystemInformation .Border3DSize.Height); g.FillRectangle(br, r); } else { Rectangle r = colorPos; g.FillRectangle(SystemBrushes.Control, r); r.Inflate(-SystemInformation.BorderSize .Width, -SystemInformation .BorderSize.Height); ControlPaint.DrawBorder3D(g, r, Border3DStyle.Flat); r.Inflate(-SystemInformation.BorderSize .Width, -SystemInformation.BorderSize .Height); g.FillRectangle(br, r); } } br.Dispose(); br = null; } } }
VB.NET
Private Class ColorWellInfo Private iUnsorted As Long Private distance As Long Public ReadOnly Color As Color Public colorPos As Rectangle Public Sub New(ByVal clr As Color, ByVal unsortedindex As _ Long) Color = clr distance = Color.R * Color.R + Color.B * Color.B + _ Color.G * Color.G iUnsorted = unsortedindex End Sub Private Class DistanceComparer : Implements IComparer Private Function IComparer_Compare(a As Object, b As _ Object) As Integer Implements IComparer.Compare Dim _a As ColorWellInfo = CType(a, ColorWellInfo) Dim _b As ColorWellInfo = CType(b, ColorWellInfo) Return _a.distance.CompareTo(_b.distance) End Class Private Class NameComparer : Implements IComparer Private Function IComparer_Compare(a As Object, b As _ Object) As Integer Implements IComparer.Compare Dim _a As ColorWellInfo = CType(a, ColorWellInfo) Dim _b As ColorWellInfo = CType(b, ColorWellInfo) Return _a.Color.Name.CompareTo(_b.Color.Name) End Function End Class Private Class SaturationComparer : Implements IComparer Private Function IComparer_Compare(a As Object, b As _ Object) As Integer Implements IComparer.Compare Dim _a As ColorWellInfo = CType(a, ColorWellInfo) Dim _b As ColorWellInfo = CType(b, ColorWellInfo) Return _a.Color.GetSaturation().CompareTo(_b.Color _ .GetSaturation()) End Function End Class Private Class HueComparer : Implements IComparer Private Function IComparer_Compare(a As Object, b As _ Object) As Integer Implements IComparer.Compare Dim _a As ColorWellInfo = CType(a, ColorWellInfo) Dim _b As ColorWellInfo = CType(b, ColorWellInfo) Return _a.Color.GetHue().CompareTo(_b.Color.GetHue()) End Function End Class Private Class BrightnessComparer : Implements IComparer Private Function IComparer_Compare(a As Object, b As _ Object) As Integer Implements IComparer.Compare Dim _a As ColorWellInfo = CType(a, ColorWellInfo) Dim _b As ColorWellInfo = CType(b, ColorWellInfo) Return _a.Color.GetBrightness().CompareTo(_b.Color _ .GetBrightness()) End Function End Class Private Class UnsortedComparer : Implements IComparer Private Function IComparer_Compare(a As Object, b As _ Object) As Integer Implements IComparer.Compare Dim _a As ColorWellInfo = CType(a, ColorWellInfo) Dim _b As ColorWellInfo = CType(b, ColorWellInfo) Return _a.iUnsorted.CompareTo(_b.iUnsorted) End Function End Class Public Shared Function CompareColorDistance() As IComparer Return New DistanceComparer() End Function Public Shared Function CompareColorName() As IComparer Return New NameComparer() End Function Public Shared Function CompareColorSaturation() As IComparer Return New SaturationComparer() End Function Public Shared Function CompareColorHue() As IComparer Return New HueComparer() End Function Public Shared Function CompareColorBrightness() As IComparer Return New BrightnessComparer() End Function Public Shared Function CompareUnsorted() As IComparer Return New UnsortedComparer() End Function Public Shared Function GetColorWells(ByVal sscheme As _ Scheme, ByVal order As Order) As ColorWellInfo() Dim arrKNown As Array = [Enum].GetValues(GetType _ (KnownColor)) Dim iColors As Integer = 0 Select Case sscheme Case Scheme.Web For Each k As KnownColor In arrKNown Dim c As Color = Color.FromKnownColor(k) If Not c.IsSystemColor AndAlso (c.A > 0) Then iColors += 1 End If Next Case Scheme.System For Each k As KnownColor In arrKNown Dim c As Color = Color.FromKnownColor(k) If c.IsSystemColor AndAlso (c.A > 0) Then iColors += 1 End If Next End Select Dim wells As ColorWellInfo() = New _ ColorWellInfo(iColors - 1) {} Dim i As Integer = 0 Select Case sscheme Case Scheme.Web For Each k As KnownColor In arrKNown Dim c As Color = Color.FromKnownColor(k) If Not c.IsSystemColor AndAlso (c.A > 0) Then wells(i) = New ColorWellInfo(c, i) i += 1 End If Next Case Scheme.System For Each k As KnownColor In arrKNown Dim c As Color = Color.FromKnownColor(k) If c.IsSystemColor AndAlso (c.A > 0) Then wells(i) = New ColorWellInfo(c, i) i += 1 End If Next End Select SortWells(wells, order) Return wells End Function Public Shared Sub SortWells(ByVal Wells As _ ColorWellInfo(), ByVal SortOrder As Order) Select Case SortOrder Case Order.Brightness Array.Sort(Wells, CompareColorBrightness()) Case Order.Distance Array.Sort(Wells, CompareColorDistance()) Case Order.Hue Array.Sort(Wells, CompareColorHue()) Case Order.Name Array.Sort(Wells, CompareColorName()) Case Order.Saturation Array.Sort(Wells, CompareColorSaturation()) Case Order.Unsorted Array.Sort(Wells, CompareUnsorted()) End Select End Sub Public Sub DrawColorWell(ByVal g As Graphics, ByVal _ enabled As Boolean, ByVal selected As Boolean, _ ByVal pickColor As Boolean) If Not enabled Then Dim r As Rectangle = colorPos r.Inflate(-SystemInformation.BorderSize.Width, _ -SystemInformation.BorderSize.Height) ControlPaint.DrawBorder3D(g, r, Border3DStyle.Flat) r.Inflate(-SystemInformation.BorderSize.Width, _ -SystemInformation.BorderSize.Height) g.FillRectangle(SystemBrushes.Control, r Else Dim br As SolidBrush = New SolidBrush(Color) If pickColor Then Dim r As Rectangle = colorPos ControlPaint.DrawBorder3D(g, r, Border3DStyle.Sunken) r.Inflate(-SystemInformation.Border3DSize.Width, _ -SystemInformation.Border3DSize.Height) g.FillRectangle(br, r) Else If selected Then Dim r As Rectangle = colorPos ControlPaint.DrawBorder3D(g, r, _ Border3DStyle.Raised) r.Inflate(-SystemInformation.Border3DSize.Width, _ -SystemInformation.Border3DSize.Height) g.FillRectangle(br, r) Else Dim r As Rectangle = colorPos g.FillRectangle(SystemBrushes.Control, r) r.Inflate(-SystemInformation.BorderSize.Width, _ -SystemInformation.BorderSize.Height) ControlPaint.DrawBorder3D(g, r, _ Border3DStyle.Flat) r.Inflate(-SystemInformation.BorderSize.Width, _ -SystemInformation.BorderSize.Height) g.FillRectangle(br, r) End If End If br.Dispose() br = Nothing End If End Sub End Class
The ColorWellInfo class is responsible for displaying the colors at the correct spots, depending on which scheme and order are active. The displayed colors get manipulated by comparing each with the current scheme and order so that the correct color variant displays on the correct scheme.
Add the following Functions for the WellPanel Class.
C#
private void Layout() { int x = sborder.Width; int y = sborder.Height; foreach (ColorWellInfo c in arrWells) { c.colorPos = new Rectangle(x, y, scolorwell.Width, _ scolorwell.Height); x += scolorwell.Width; if (x + scolorwell.Width > ClientRectangle.Width) { y += scolorwell.Height; x = sborder.Width; } } } private ColorWellInfo WellFromPoint(int x, int y) { int w = ClientRectangle.Width; int h = ClientRectangle.Height; foreach (ColorWellInfo c in arrWells) { if (c.colorPos.Contains(x, y)) { return c; } } return null; } private ColorWellInfo WellFromColor(Color col) { foreach (ColorWellInfo c in arrWells) { if (c.Color == col) { return c; } } return null; } private int IndexFromWell(ColorWellInfo col) { int num_colorWells = arrWells.Length; int index = -1; for (int i = 0; i < num_colorWells; i++) { if (arrWells[i] == col) { index = i; } } return index; }
VB.NET
Private Sub Layout() Dim x As Integer = sborder.Width Dim y As Integer = sborder.Height For Each c As ColorWellInfo In arrWells c.colorPos = New Rectangle(x, y, scolorwell.Width, _ scolorwell.Height) x += scolorwell.Width If x + scolorwell.Width > ClientRectangle.Width Then y += scolorwell.Height x = sborder.Width End If Next End Sub Private Function WellFromPoint(ByVal x As Integer, ByVal y _ As Integer) As ColorWellInfo Dim w As Integer = ClientRectangle.Width Dim h As Integer = ClientRectangle.Height For Each c As ColorWellInfo In arrWells If c.colorPos.Contains(x, y) Then Return c End If Next Return Nothing End Function Private Function WellFromColor(ByVal col As Color) As _ ColorWellInfo For Each c As ColorWellInfo In arrWells If c.Color = col Then Return c End If Next Return Nothing End Function Private Function IndexFromWell(ByVal col As ColorWellInfo) _ As Integer Dim num_colorWells As Integer = arrWells.Length Dim index As Integer = -1 For i As Integer = 0 To num_colorWells - 1 If arrWells(i).Equals(col) Then index = i End If Next Return index End Function
The previous functions make use of the ColorWellInfo subclass to populate the Panel on the UserControl. Add the remaining events and Properties.
C#
[Browsable(true)] public event ColorChangedEventHandler ColorChanged; private void FireColorChanged() { if (null != cwColor) { OnColorChanged(new ColorChangedEventArgs (cwColor.Color)); } } protected virtual void OnColorChanged(ColorChangedEventArgs e) { ColorChanged?.Invoke(this, e); } protected override void OnClick(EventArgs e) { base.OnClick(e); if (null != cwCurrent) { if (null != cwColor) { Invalidate(cwColor.colorPos); } cwColor = cwCurrent; Invalidate(cwColor.colorPos); FireColorChanged(); } } private void ChangeColor(ColorWellInfo newColor) { if (newColor != cwCurrent) { if (null != cwCurrent) { Invalidate(cwCurrent.colorPos); } cwCurrent = newColor; if (null != cwCurrent) { Invalidate(cwCurrent.colorPos); tTip.SetToolTip(this, cwCurrent.Color.Name); } else { tTip.SetToolTip(this, ""); } Update(); } } protected override void OnMouseMove(MouseEventArgs e) { base.OnMouseMove(e); if (!Enabled) return; Point mousePosition = new Point(e.X, e.Y); if (ClientRectangle.Contains(mousePosition) && (mousepos != mousePosition)) { mousepos = mousePosition; ColorWellInfo newColor = WellFromPoint(e.X, e.Y); ChangeColor(newColor); } } protected override void OnMouseLeave(EventArgs e) { base.OnMouseLeave(e); if (!Enabled) return; ColorWellInfo invalidColor = cwCurrent; cwCurrent = null; if (null != invalidColor) { Invalidate(invalidColor.colorPos); Update(); } } protected override void OnGotFocus(EventArgs e) { base.OnGotFocus(e); Refresh(); } protected override void OnLostFocus(EventArgs e) { base.OnLostFocus(e); Refresh(); } private void MoveColumn(int index, bool bNext) { int numColors = arrWells.Length; int r = index / columns; int c = index - (r * columns); int nextIndex = 0; if (bNext) { c++; if (c >= columns) { c = 0; } nextIndex = r * columns + c; if (nextIndex >= numColors) { nextIndex = r * columns; } } else { c--; if (c < 0) { c = columns - 1; } nextIndex = r * columns + c; if (nextIndex >= numColors) { nextIndex = numColors - 1; } } ChangeColor(arrWells[nextIndex]); } private void MoveRow(int index, bool bNext) { int numColors = arrWells.Length; int r = index / columns; int c = index - (r * columns); int nextIndex = 0; if (bNext) { r++; if (r >= rows) { r = 0; } nextIndex = r * columns + c; if (nextIndex >= numColors) { nextIndex = c; } } else { r--; if (r < 0) { r = rows - 1; } nextIndex = r * columns + c; if (nextIndex >= numColors) { nextIndex = (r - 1) * columns + c; } } ChangeColor(arrWells[nextIndex]); } protected override void OnKeyDown(KeyEventArgs e) { base.OnKeyDown(e); if (!Enabled) return; int index = IndexFromWell((null != cwCurrent) ? (cwCurrent) : (cwColor)); switch (e.KeyCode) { case Keys.Enter: if (null != cwCurrent) { ColorWellInfo oldColor = cwColor; cwColor = cwCurrent; FireColorChanged(); Invalidate(oldColor.colorPos); Invalidate(cwCurrent.colorPos); Update(); } break; case Keys.Left: if (index < 0) { ChangeColor(arrWells[arrWells.Length - 1]); } else { MoveColumn(index, false); } break; case Keys.Right: if (index < 0 || index > (arrWells.Length - 1)) { ChangeColor(arrWells[0]); } else { MoveColumn(index, true); } break; case Keys.Down: if (index < 0) { ChangeColor(arrWells[0]); } else { MoveRow(index, true); } break; case Keys.Up: if (index < 0) { ChangeColor(arrWells[arrWells.Length - 1]); } else { MoveRow(index, false); } break; } } protected virtual int GetPreferredWidth() { return Size.Width; } protected void AutoSizePanel() { if (ccolumns <= 0) { int preferredWidth = GetPreferredWidth(); int w = preferredWidth - sborder.Width * 2; int remw = w % scolorwell.Width; columns = w / scolorwell.Width; rows = arrWells.Length / columns + ((arrWells.Length % columns != 0) ? 1 : 0); int h = rows * scolorwell.Height + sborder.Height * 2; if (remw != 0 || h != Size.Height) { w = preferredWidth - remw; ClientSize = new Size(w, h); } Layout(); Refresh(); } else { int preferred = ccolumns; if (arrWells.Length < ccolumns) { preferred = arrWells.Length; } columns = preferred; int w = preferred * scolorwell.Width + sborder.Width * 2; rows = arrWells.Length / columns + ((arrWells.Length % columns != 0) ? 1 : 0); int h = rows * scolorwell.Height + sborder.Height * 2; ClientSize = new Size(w, h); Layout(); Refresh(); } } protected override void OnResize(EventArgs e) { base.OnResize(e); AutoSizePanel(); } protected override void OnEnabledChanged(EventArgs e) { Refresh(); } protected override void OnPaint(PaintEventArgs e) { foreach (ColorWellInfo c in arrWells) { c.DrawColorWell(e.Graphics, Enabled, c == cwCurrent, c == cwColor); } switch (borderstyle) { case BorderStyle.Fixed3D: ControlPaint.DrawBorder3D(e.Graphics, ClientRectangle, Border3DStyle.Sunken); break; case BorderStyle.FixedSingle: ControlPaint.DrawBorder3D(e.Graphics, ClientRectangle, Border3DStyle.Flat); break; } if (Focused && Enabled) { Rectangle r = ClientRectangle; r.Inflate(-sborder.Width + 1, -sborder.Height + 1); ControlPaint.DrawFocusRectangle(e.Graphics, r); } base.OnPaint(e); } protected override void OnSystemColorsChanged(EventArgs e) { base.OnSystemColorsChanged(e); if (colorSet == Scheme.System) { arrWells = ColorWellInfo.GetColorWells(colorSet, sortorder); Layout(); UpdatePickColor(); FireColorChanged(); Refresh(); } } [Browsable(true), Category("Appearance")] public BorderStyle BorderStyle { get { return borderstyle; } set { Utilities.CheckValidEnumValue("BorderStyle", value, typeof(BorderStyle)); if (borderstyle != value) { borderstyle = value; UpdateBorderSize(); AutoSizePanel(); } } } private void UpdateBorderSize() { Size bs = new Size(); switch (borderstyle) { case BorderStyle.Fixed3D: bs = SystemInformation.Border3DSize; break; case BorderStyle.FixedSingle: bs = SystemInformation.BorderSize; break; case BorderStyle.None: break; } bs.Width++; bs.Height++; sborder = bs; } [Browsable(true), Category("ColorPanel")] public Color Color { get { if (cwColor != null) { return cwColor.Color; } else { return dcolor; } } set { if (((cwColor != null) && (cwColor.Color != value)) || (cwColor == null)) { UpdatePickColor(value); Refresh(); } } } public void ResetColor() { Color = dcolor; } private void UpdatePickColor() { if (cwColor != null) { UpdatePickColor(cwColor.Color); } else { UpdatePickColor(dcolor); } } private void UpdatePickColor(Color c) { cwColor = WellFromColor(c); if (null == cwColor) { cwColor = WellFromColor(dcolor); } if (null == cwColor) { cwColor = arrWells[0]; } } [Browsable(true)] public Scheme ColorScheme { get { return colorSet; } set { Utilities.CheckValidEnumValue("ColorScheme", value, typeof(Scheme)); if (value != colorSet) { arrWells = ColorWellInfo.GetColorWells(value, sortorder); colorSet = value; UpdatePickColor(); FireColorChanged(); AutoSizePanel(); } } } [Browsable(true)] public Size WellSize { get { return scolorwell; } set { if (value.Height > SystemInformation.Border3DSize .Height * 2 + 2 && value.Width > SystemInformation.Border3DSize .Width * 2 + 2) { if (value != WellSize) { scolorwell = value; AutoSizePanel(); } } else { Size min = new Size(SystemInformation.Border3DSize .Height * 2 + 2, SystemInformation.Border3DSize .Width * 2 + 2); string msg = string.Format("Too Small", min); throw new ArgumentOutOfRangeException("WellSize", value, msg); } } } public void ResetWellSize() { WellSize = dwellsize; } [Browsable(true)] public Order SortOrder { get { return sortorder; } set { Utilities.CheckValidEnumValue("SortOrder", value, typeof(Order)); if (value != sortorder) { ColorWellInfo.SortWells(arrWells, value); Layout(); sortorder = value; Refresh(); } } } [Browsable(true)] public int Columns { get { return ccolumns; } set { if (value > 0) { if (value <= arrWells.Length) { ccolumns = value; } else { ccolumns = arrWells.Length; } } else { ccolumns = 0; } AutoSizePanel(); } }
VB.NET
<Browsable(True)> Public Event ColorChanged As ColorChangedEventHandler Private Sub FireColorChanged() If cwColor IsNot Nothing Then OnColorChanged(New ColorChangedEventArgs(cwColor.Color)) End If End Sub Protected Overridable Sub OnColorChanged(ByVal e As _ ColorChangedEventArgs) RaiseEvent ColorChanged(Me, e) End Sub Protected Overrides Sub OnClick(ByVal e As EventArgs) MyBase.OnClick(e) If cwCurrent IsNot Nothing Then If cwColor IsNot Nothing Then Invalidate(cwColor.colorPos) End If cwColor = cwCurrent Invalidate(cwColor.colorPos) FireColorChanged() End If End Sub Private Sub ChangeColor(ByVal newColor As ColorWellInfo) If Not newColor.Equals(cwCurrent) Then If cwCurrent IsNot Nothing Then Invalidate(cwCurrent.colorPos) End If cwCurrent = newColor If cwCurrent IsNot Nothing Then Invalidate(cwCurrent.colorPos) tTip.SetToolTip(Me, cwCurrent.Color.Name) Else tTip.SetToolTip(Me, "") End If Update() End If End Sub Protected Overrides Sub OnMouseMove(ByVal e As MouseEventArgs) MyBase.OnMouseMove(e) If Not Enabled Then Return Dim mousePosition As Point = New Point(e.X, e.Y) If ClientRectangle.Contains(mousePosition) AndAlso (mousepos <> mousePosition) Then mousepos = mousePosition Dim newColor As ColorWellInfo = WellFromPoint(e.X, e.Y) ChangeColor(newColor) End If End Sub Protected Overrides Sub OnMouseLeave(ByVal e As EventArgs) MyBase.OnMouseLeave(e) If Not Enabled Then Return Dim invalidColor As ColorWellInfo = cwCurrent cwCurrent = Nothing If invalidColor IsNot Nothing Then Invalidate(invalidColor.colorPos) Update() End If End Sub Protected Overrides Sub OnGotFocus(ByVal e As EventArgs) MyBase.OnGotFocus(e) Refresh() End Sub Protected Overrides Sub OnLostFocus(ByVal e As EventArgs) MyBase.OnLostFocus(e) Refresh() End Sub Private Sub MoveColumn(ByVal index As Integer, ByVal bNext _ As Boolean) Dim numColors As Integer = arrWells.Length Dim r As Integer = index / Columns Dim c As Integer = index - (r * Columns) Dim nextIndex As Integer = 0 If bNext Then c += 1 If c >= Columns Then c = 0 End If nextIndex = r * Columns + c If nextIndex >= numColors Then nextIndex = r * Columns End If Else c -= 1 If c < 0 Then c = Columns - 1 End If nextIndex = r * Columns + c If nextIndex >= numColors Then nextIndex = numColors - 1 End If End If ChangeColor(arrWells(nextIndex)) End Sub Private Sub MoveRow(ByVal index As Integer, ByVal bNext _ As Boolean) Dim numColors As Integer = arrWells.Length Dim r As Integer = index / Columns Dim c As Integer = index - (r * Columns) Dim nextIndex As Integer = 0 If bNext Then r += 1 If r >= rows Then r = 0 End If nextIndex = r * Columns + c If nextIndex >= numColors Then nextIndex = c End If Else r -= 1 If r < 0 Then r = rows - 1 End If nextIndex = r * Columns + c If nextIndex >= numColors Then nextIndex = (r - 1) * Columns + c End If End If ChangeColor(arrWells(nextIndex)) End Sub Protected Overrides Sub OnKeyDown(ByVal e As KeyEventArgs) MyBase.OnKeyDown(e) If Not Enabled Then Return Dim index As Integer = IndexFromWell(If((cwCurrent IsNot _ Nothing), (cwCurrent), (cwColor))) Select Case e.KeyCode Case Keys.Enter If cwCurrent IsNot Nothing Then Dim oldColor As ColorWellInfo = cwColor cwColor = cwCurrent FireColorChanged() Invalidate(oldColor.colorPos) Invalidate(cwCurrent.colorPos) Update() End If Case Keys.Left If index < 0 Then ChangeColor(arrWells(arrWells.Length - 1)) Else MoveColumn(index, False) End If Case Keys.Right If index < 0 OrElse index > _ (arrWells.Length - 1) Then ChangeColor(arrWells(0)) Else MoveColumn(index, True) End If Case Keys.Down If index < 0 Then ChangeColor(arrWells(0)) Else MoveRow(index, True) End If Case Keys.Up If index < 0 Then ChangeColor(arrWells(arrWells.Length - 1)) Else MoveRow(index, False) End If End Select End Sub Protected Overridable Function GetPreferredWidth() As Integer Return Size.Width End Function Protected Sub AutoSizePanel() If ccolumns <= 0 Then Dim preferredWidth As Integer = GetPreferredWidth() Dim w As Integer = preferredWidth - sborder.Width * 2 Dim remw As Integer = w Mod scolorwell.Width Columns = w / scolorwell.Width rows = arrWells.Length / Columns + (If((arrWells.Length _ Mod Columns <> 0), 1, 0)) Dim h As Integer = rows * scolorwell.Height + _ sborder.Height * 2 If remw <> 0 OrElse h <> Size.Height Then w = preferredWidth - remw ClientSize = New Size(w, h) End If Layout() Refresh() Else Dim preferred As Integer = ccolumns If arrWells.Length < ccolumns Then preferred = arrWells.Length End If Columns = preferred Dim w As Integer = preferred * scolorwell.Width + _ sborder.Width * 2 rows = arrWells.Length / Columns + (If((arrWells.Length _ Mod Columns <> 0), 1, 0)) Dim h As Integer = rows * scolorwell.Height + _ sborder.Height * 2 ClientSize = New Size(w, h) Layout() Refresh() End If End Sub Protected Overrides Sub OnResize(ByVal e As EventArgs) MyBase.OnResize(e) AutoSizePanel() End Sub Protected Overrides Sub OnEnabledChanged(ByVal e As EventArgs) Refresh() End Sub Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs) For Each c As ColorWellInfo In arrWells c.DrawColorWell(e.Graphics, Enabled, _ c.Equals(cwCurrent), c.Equals(cwColor)) Next Select Case BorderStyle Case BorderStyle.Fixed3D ControlPaint.DrawBorder3D(e.Graphics, _ ClientRectangle, Border3DStyle.Sunken) Case BorderStyle.FixedSingle ControlPaint.DrawBorder3D(e.Graphics, _ ClientRectangle, Border3DStyle.Flat) End Select If Focused AndAlso Enabled Then Dim r As Rectangle = ClientRectangle r.Inflate(-sborder.Width + 1, -sborder.Height + 1) ControlPaint.DrawFocusRectangle(e.Graphics, r) End If MyBase.OnPaint(e) End Sub Protected Overrides Sub OnSystemColorsChanged(ByVal e _ As EventArgs) MyBase.OnSystemColorsChanged(e) If colorSet = Scheme.System Then arrWells = ColorWellInfo.GetColorWells(colorSet, _ SortOrder) Layout() UpdatePickColor() FireColorChanged() Refresh() End If End Sub <Browsable(True), Category("Appearance")> Public Property BorderStyle As BorderStyle Get Return BorderStyle End Get Set(ByVal value As BorderStyle) Utilities.CheckValidEnumValue("BorderStyle", value, _ GetType(BorderStyle)) If BorderStyle <> value Then BorderStyle = value UpdateBorderSize() AutoSizePanel() End If End Set End Property Private Sub UpdateBorderSize() Dim bs As Size = New Size() Select Case BorderStyle Case BorderStyle.Fixed3D bs = SystemInformation.Border3DSize Case BorderStyle.FixedSingle bs = SystemInformation.BorderSize Case BorderStyle.None End Select bs.Width += 1 bs.Height += 1 sborder = bs End Sub <Browsable(True), Category("ColorPanel")> Public Property Color As Color Get If cwColor IsNot Nothing Then Return cwColor.Color Else Return dcolor End If End Get Set(ByVal value As Color) If ((cwColor IsNot Nothing) AndAlso (cwColor.Color <> _ value)) OrElse (cwColor Is Nothing) Then UpdatePickColor(value) Refresh() End If End Set End Property Public Sub ResetColor() Color = dcolor End Sub Private Sub UpdatePickColor() If cwColor IsNot Nothing Then UpdatePickColor(cwColor.Color) Else UpdatePickColor(dcolor) End If End Sub Private Sub UpdatePickColor(ByVal c As Color) cwColor = WellFromColor(c) If cwColor Is Nothing Then cwColor = WellFromColor(dcolor) End If If cwColor Is Nothing Then cwColor = arrWells(0) End If End Sub <Browsable(True)> Public Property ColorScheme As Scheme Get Return colorSet End Get Set(ByVal value As Scheme) Utilities.CheckValidEnumValue("ColorScheme", value, _ GetType(Scheme)) If value <> colorSet Then arrWells = ColorWellInfo.GetColorWells(value, _ SortOrder) colorSet = value UpdatePickColor() FireColorChanged() AutoSizePanel() End If End Set End Property <Browsable(True)> Public Property WellSize As Size Get Return scolorwell End Get Set(ByVal value As Size) If value.Height > SystemInformation.Border3DSize.Height _ * 2 + 2 AndAlso value.Width > SystemInformation _ .Border3DSize.Width * 2 + 2 Then If value <> WellSize Then scolorwell = value AutoSizePanel() End If Else Dim min As Size = New Size(SystemInformation _ .Border3DSize.Height * 2 + 2, SystemInformation _ .Border3DSize.Width * 2 + 2) Dim msg As String = String.Format("Too Small", min) Throw New ArgumentOutOfRangeException("WellSize", _ value, msg) End If End Set End Property Public Sub ResetWellSize() WellSize = dwellsize End Sub <Browsable(True)> Public Property SortOrder As Order Get Return SortOrder End Get Set(ByVal value As Order) Utilities.CheckValidEnumValue("SortOrder", value, _ GetType(Order)) If value <> SortOrder Then ColorWellInfo.SortWells(arrWells, value) Layout() SortOrder = value Refresh() End If End Set End Property <Browsable(True)> Public Property Columns As Integer Get Return ccolumns End Get Set(ByVal value As Integer) If value > 0 Then If value <= arrWells.Length Then ccolumns = value Else ccolumns = arrWells.Length End If Else ccolumns = 0 End If AutoSizePanel() End Set End Property
You have now coupled the events with the color blocks (or wells) as well as enabled some properties for manipulation on the Design side.
I think I will stop here for this installment. I want the code to sink in so that you will be able to follow the next part properly.
Conclusion
Having bright ideas can be a lot of work! In the first installment of “Creating a Custom Color Picker in .NET,” you have learned how to create a UserControl that will be able to host various colors and respond to user events. In the next installment, you will finish up the control and experiment with it more. Until then, happy coding!