Creating a Custom Color Picker in .NET, Part 2: Putting It All Together

Introduction

Welcome to the second installment of this short series on creating your own color picker in .NET. In Part 1, “Creating a Custom Color Picker in .NET, Part 1: The Color Wells,” we concentrated on creating the color wells. It was a lot of work… What we will do today is to bring it all together; luckily it is not so much work as the previous installment.

Let’s get started!

Currently (if you have followed Part 1), we are sitting with the Color Wells object that we’ve created. This object just hosts all the various colors we would like to be able to choose from. We need to put this object inside a parent object, such as a panel, or a group control, or, even better, another User Control. The reason why a User Control is more apt for this situation is because of versatility, and the ability to add our own Properties, Methods, and flavor to our project.

Add a new Class to your project. Name it anything useful, such as ctlColorPanel, because this will ultimately be the name of the object we will make use of in code and from anywhere on our Forms or other projects.

Add the necessary Namespaces to include all the libraries we will work with.

C#

using System.ComponentModel;
using System.Windows.Forms;

VB.NET

Imports System.ComponentModel

Add a Delegate.

C#

   internal delegate void ColorPanelClosingEventHandler(object
      sender, System.EventArgs e);

VB.NET

Friend Delegate Sub ColorPanelClosingEventHandler(ByVal sender _
   As Object, ByVal e As EventArgs)

Add the events and Properties.

C#

   internal class ctlColorPanel : WellPanel
   {

      private IContainer components = null;
      private int width = 300;

      public ctlColorPanel()
      {

         InitializeComponent();

      }

      protected override void Dispose(bool disposing)
      {

         if (disposing)
         {

            if (components != null)
            {

               components.Dispose();

            }

         }

         base.Dispose(disposing);

      }

      private void InitializeComponent()
      {

         Name = "ctlColorPanel";

      }

      protected override void OnMouseDown(MouseEventArgs e)
      {

         base.OnMouseDown(e);

         if (Capture)
         {

            if (!ClientRectangle.Contains(e.X, e.Y))
            {

               OnClosePanel();

            }
         }
      }

      protected override void OnMouseUp(MouseEventArgs e)
      {

         base.OnMouseUp(e);

         Capture = true;

      }

      protected override void OnKeyDown(KeyEventArgs e)
      {

         base.OnKeyDown(e);

         if (e.KeyCode == Keys.Escape)
         {

            OnClosePanel();

         }
      }

      [Browsable(true)]
      internal event ColorPanelClosingEventHandler PanelClosing;

      protected virtual void OnClosePanel()
      {

         PanelClosing?.Invoke(this, new System.EventArgs());

      }

      internal int ParentWidth
      {

         set
         {

            width = value;
            AutoSizePanel();

         }
      }

      protected override int GetPreferredWidth()
      {

         return width;

      }

      protected override void OnGotFocus(System.EventArgs e)
      {

         base.OnGotFocus(e);

         Capture = true;

      }

   }

VB.NET

Friend Class ctlColorPanel
   Inherits WellPanel

   Private components As IContainer = Nothing
   Private wwidth As Integer = 300

   Public Sub New()
      InitializeComponent()
   End Sub

   Protected Overrides Sub Dispose(ByVal disposing As Boolean)
      If disposing Then

         If components IsNot Nothing Then
            components.Dispose()
         End If
      End If

      MyBase.Dispose(disposing)
   End Sub

   Private Sub InitializeComponent()
      Name = "ctlColorPanel"
   End Sub

   Protected Overrides Sub OnMouseDown(ByVal e As MouseEventArgs)
      MyBase.OnMouseDown(e)

      If Capture Then

         If Not ClientRectangle.Contains(e.X, e.Y) Then
            OnClosePanel()
         End If
      End If
   End Sub

   Protected Overrides Sub OnMouseUp(ByVal e As MouseEventArgs)
      MyBase.OnMouseUp(e)
      Capture = True
   End Sub

   Protected Overrides Sub OnKeyDown(ByVal e As KeyEventArgs)
      MyBase.OnKeyDown(e)

      If e.KeyCode = Keys.Escape Then
         OnClosePanel()
      End If
   End Sub

   <Browsable(True)>
   Friend Event PanelClosing As ColorPanelClosingEventHandler

   Protected Overridable Sub OnClosePanel()
      RaiseEvent PanelClosing(Me, New EventArgs())
   End Sub

   Friend WriteOnly Property ParentWidth As Integer
      Set(ByVal value As Integer)
         wwidth = value
         AutoSizePanel()
      End Set
   End Property

   Protected Overrides Function GetPreferredWidth() As Integer
      Return wwidth
   End Function

   Protected Overrides Sub OnGotFocus(ByVal e As EventArgs)
      MyBase.OnGotFocus(e)
      Capture = True
   End Sub
End Class

We Inherit from the WellPanel object we created in Part 1 and handle the events.

Add the Utilities Class.

C#

using System;

public class Utilities
{
   private Utilities()
   {
   }

   public static void CheckValidEnumValue(string arg, object val,
      System.Type Class)
   {

      if (!Enum.IsDefined(Class, val))
      {

         throw new System.ComponentModel.InvalidEnumArgument
            Exception(arg, (int)val, Class);

      }

   }

}

VB.NET

Public Class Utilities

   Public Shared Sub CheckValidEnumValue(ByVal arg As String, _
         ByVal val As Object, ByVal [Class] As Type)

      If Not [Enum].IsDefined([Class], val) Then

         Throw New ComponentModel.InvalidEnumArgumentException _
            (arg, CInt(val), [Class])

      End If

   End Sub

End Class

The Utilities class is responsible for determining whether the Enums are validly selected.

All should be fine, so Build the project. After the Build has succeeded, we need to add the Control to the Form. Ensure your Design of your Form resembles Figure 1.

Design
Figure 1: Design

Add the following code to your Form.

C#

using System;
using System.Drawing;
using System.Windows.Forms;

public partial class Form1 : Form
   {
      public Form1()
      {

         InitializeComponent();

         cbEnable.Checked = ctPicker.Enabled;

         lblDisplay.BackColor = ctPicker.Color;
         lblDisplay.Text = ctPicker.Color.Name;

      }

      private void cbEnable_CheckedChanged(object sender,
         EventArgs e)
      {

         ctPicker.Enabled = cbEnable.Checked;

      }

      private void Form1_Load(object sender, EventArgs e)
      {

         duSort.Items.AddRange(Enum.GetValues(typeof(Order)));
         duSort.SelectedIndex = 0;

         duScheme.Items.AddRange(Enum.GetValues(typeof(Scheme)));
         duScheme.SelectedIndex = 0;

         SetColor(lblDisplay, ctPicker.Color);

      }

      private void duSort_SelectedItemChanged(object sender,
         EventArgs e)
      {

         ctPicker.SortOrder = (Order)duSort.SelectedItem;

      }

      private void duScheme_SelectedItemChanged(object sender,
         EventArgs e)
      {

         ctPicker.ColorScheme = (Scheme)duScheme.SelectedItem;

      }

      private void ctPicker_ColorChanged(object sender,
         ColorChangedEventArgs e)
      {

         SetColor(lblDisplay, e.Color);

      }

      private void SetColor(Control ctrl, Color col)
      {

         ctrl.BackColor = col;

         string s = string.Format("{0}, {1:X}", col.Name,
            col.ToArgb());
         ctrl.Text = s;

         ctrl.ForeColor = (col.GetBrightness() < 0.3) ?
            (Color.White) : (Color.Black);

      }

   }

VB.NET

Public Class Form1

   Public Sub New()
      InitializeComponent()
      cbEnable.Checked = ctPicker.Enabled
      lblDisplay.BackColor = ctPicker.Color
      lblDisplay.Text = ctPicker.Color.Name
   End Sub

   Private Sub cbEnable_CheckedChanged(ByVal sender As Object, _
         ByVal e As EventArgs)
      ctPicker.Enabled = cbEnable.Checked
   End Sub

   Private Sub Form1_Load(ByVal sender As Object, _
         ByVal e As EventArgs)
      duSort.Items.AddRange([Enum].GetValues(GetType(Order)))
      duSort.SelectedIndex = 0
      duScheme.Items.AddRange([Enum].GetValues(GetType(Scheme)))
      duScheme.SelectedIndex = 0
      SetColor(lblDisplay, ctPicker.Color)

   End Sub

   Private Sub duSort_SelectedItemChanged(ByVal sender As Object, _
         ByVal e As EventArgs)
      ctPicker.SortOrder = CType(duSort.SelectedItem, Order)
   End Sub

   Private Sub duScheme_SelectedItemChanged(ByVal sender As Object, _
         ByVal e As EventArgs)
      ctPicker.ColorScheme = CType(duScheme.SelectedItem, Scheme)
   End Sub

   Private Sub ctPicker_ColorChanged(ByVal sender As Object, _
         ByVal e As ColorChangedEventArgs)
      SetColor(lblDisplay, e.Color)
   End Sub

   Private Sub SetColor(ByVal ctrl As Control, ByVal col As Color)
      ctrl.BackColor = col
      Dim s As String = String.Format("{0}, {1:X}", col.Name, _
         col.ToArgb())
      ctrl.Text = s
      ctrl.ForeColor = If((col.GetBrightness() < 0.3), _
         (Color.White), (Color.Black))
   End Sub
End Class

On the form, we add the Scheme and the Order to the DomainUpDown controls. Then, we set the new Scheme or Order once a selection is made. This loads a new sequence of colors in our box. Finally, when we have selected, it updates the display label, as shown in Figure 2.

Running

Figure 2: Running

Conclusion

Well, this turned out much more complicated than I initially thought. There is a lot information and code to digest, but I am sure that you will find it useful. Until next time, cheers!

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