Creating a Custom Color Picker in .NET, Part 1: The Color Wells

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!

Hannes DuPreez
Hannes DuPreez
Ockert J. du Preez is a passionate coder and always willing to learn. He has written hundreds of developer articles over the years detailing his programming quests and adventures. He has written the following books: Visual Studio 2019 In-Depth (BpB Publications) JavaScript for Gurus (BpB Publications) He was the Technical Editor for Professional C++, 5th Edition (Wiley) He was a Microsoft Most Valuable Professional for .NET (2008–2017).

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read