Click here to Skip to main content
Click here to Skip to main content

The ColorPicker WinForms Control

, 26 Oct 2003 CPOL
Rate this:
Please Sign up or sign in to vote.
Developing a simple ColorPicker WinForms control by implementing the IWindowsFormsEditorService interface and leveraging the WinForms design-time infrastructure.

The ColorPicker control in action

Introduction

Although the .NET framework provides quite a rich collection of UI controls and components for WinForms development, there is one particular control that's been missing. I'm talking about a color-picker control with drop-down color selection capabilities, just like the one used within the Visual Studio .NET property browser for editing Color-typed properties.

Of course, there is the standard ColorDialog component, but the drop-down color selector is more user-friendly, IMHO. (Should I say cool?)

The ColorPicker control

Based on the above observations, I've decided to implement a ColorPicker control with the following features:

  • The control should look and act like a button.
  • The button should be colored with the currently selected color and, optionally, it should display the currently selected color's name.
  • Clicking the button should drop-down the built-in WinForms color selector and the control should change its appearance to a "pushed down" look.

The main design goals were implementation simplicity and the ability to reuse the control in either binary, or in source code form.

The following is the important part of the ColorPicker's public contract (the semantics should be self-explanatory):

Namespace LaMarvin.Windows.Forms

Public Class ColorPicker
  Inherits Control
  Public Event ColorChanged As EventHandler
  Public Property Color() As Color

The core requirement for the ColorPicker control was to display the same drop-down color selector that is used within the WinForms' PropertyGrid control.

For more information about the PropertyGrid control and its use of attributes, see the Shawn Burke's article at MSDN.

The built-in color selector is implemented inside a ColorEditor class, which is designated as the EditorAttribute for the Color structure:

Namespace System.Drawing

<EditorAttribute("System.Drawing.Design.ColorEditor"), _
  GetType(System.Drawing.Design.UITypeEditor))> _
Public Structure Color

Unfortunately, the ColorEditor class is currently undocumented. The only thing one can learn from the official documentation is the infamous sentence - "This type supports the .NET Framework infrastructure and is not intended to be used directly from your code".

Nevertheless, with some help of ILDASM and Lutz Roeder's .NET Reflector, I was able to find out how a ColorEditor instance is hosted within the PropertyGrid. Moreover, I was also able to emulate the hosting within the ColorPicker control, which is the focus of the remainder of this article.

To better understand the process of hosting the ColorEditor, let's quickly recap how the PropertyGrid control uses the EditorAttribute when editing properties of a given object:

When a row within the PropertyGrid gets focus, the grid looks first at the property itself, then at the property's type in order to see, if there is an EditorAttribute applied to one of them. The EditorAttribute specifies the System.Type that should be used as the editor for the given property.

In theory, a type can have more than one editor. However, currently only one 'type' of editor is supported - the ones that derive (directly or indirectly) from the System.Drawing.Design.UITypeEditor class.

In the case of a Color-typed property, the PropertyGrid finds the System.Drawing.Design.ColorEditor class to be used as the editor. The grid then calls the overridden ColorEditor.GetEditStyle method:

Namespace System.Drawing.Design

Public Class UITypeEditor

Public Overridable Function GetEditStyle( _
  ByVal context As ITypeDescriptorContext _
) As UITypeEditorEditStyle

The ColorEditor implementation of this method returns always UITypeEditorEditStyle.DropDown. This causes the PropertyGrid to display a drop-down button on the right side of the property row.

When the user clicks the drop-down button, the PropertyGrid calls another overridden method - ColorEditor.EditValue:

Namespace System.Drawing.Design

Public Class UITypeEditor

Public Overridable Function EditValue( _
  ByVal context As ITypeDescriptorContext, _
  ByVal provider As IServiceProvider, _
  ByVal value As Object _
) As Object

The ColorEditor implementation of this method does the following:

  • Queries the passed in IServiceProvider instance for an IWindowsFormEditorService implementation.
  • Stores the IWindowsFormEditorService reference in a member variable.
  • Creates an instance of a private ColorUI class, which implements the actual user interface and interacts with the user.
  • Calls the IWindowsFormEditorService.DropDownControl method passing it the custom control instance.
  • When the user selects a new color, the ColorEditor calls the IWindowsFormEditorService.CloseDropDown method, which (you guessed that) closes the drop-down UI and causes the IWindowsFormEditorService.DropDownControl method to return.

In fact, this implementation of UITypeEditor.EditValue method is common. If you've ever implemented your own drop-down UITypeEditor, it is highly likely that you've implemented it according to the above-described pattern.

Because the ColorEditor doesn't use the ITypeDescriptorContext arguments, all I had to do to host it was to implement just two interfaces:

Namespace System

Public Interface IServiceProvider
  Public Function GetService( _
    ByVal serviceType As Type) As Object
End Interface

...

Namespace System.Windows.Forms.Design

Public Interface IWindowsFormsEditorService
  Public Sub CloseDropDown()
  Public Sub DropDownControl(ByVal control As Control)
  Public Function ShowDialog( _
    ByVal dialog As Form) As DialogResult
End Interface

Because the ColorEditor queries the passed in IServiceProvider just for the IWindowsFormsEditorService, I've implemented both interfaces in one class - the EditorService class, which is nested within the ColorPicker control class.

There were several other minor issues that I had to solve and I've described them as comments in the ColorPicker.vb source file.

There was one issue, however, that I'd like to discuss here in more detail.

The hack

When the drop-down color selector is displayed by calling the IWindowsFormsEditorService.DropDownControl method, it is expected not to return only until after the user either selects a new color or she cancels the selection. The cancellation can be performed in a variety of ways: by pressing the Esc key, by clicking outside of the drop-down box, by pressing the Ctrl+Esc system key combination or by clicking the ColorPicker button once again.

In other words, the DropDownControl method implementation should block while dispatching Windows messages caused by the user interaction with Windows (including the drop-down color selector).

Do you remember the old pal, DoEvents?

This time, however, it is exposed as the DoEvents method of the System.Windows.Forms.Application class. Calling the method causes processing of all the Windows messages in the current thread's message queue.

Here is a pseudo code for my first DropDownControl implementation, which uses the DoEvents method:

Namespace LaMarvin.Windows.Forms
...
Public Class ColorPicker
...
  Private Class EditorService
    Public Sub DropDownControl(ByVal control As Control) _
      Implements IWindowsFormsEditorService.DropDownControl
 
      ' Display the drop-down color selector, which
      ' is hosted within a _DropDownHolder Form instance.
    
      ' Wait until the drop-down is closed.
      Do While _DropDownHolder.Visible
        Application.DoEvents()
      Loop
         ...
End Sub

The code shows the drop-down UI and then it enters a loop calling Application.DoEvents until the drop-down form is closed. This way, the DropDownControl method blocks (ignoring the possible reentrancy issues for the moment) while Windows messages are still being dispatched.

It worked fine this way until I realized that when the drop-down color selector is displayed, the process hosting the ColorPicker control eats 100% of the CPU.

Too bad!

Once again, I've turned to the .NET Reflector tool in order to see how the DropDownControl method is implemented within the PropertyGrid itself (which, obviously, doesn't eat 100% CPU while displaying the color selector).

Here is what I found out:

Namespace System.Windows.Forms.PropertyGridInternal
  Private Class PropertyGridView
      Private Class DropDownHolder
      Public Sub DoModalLoop()
        Do While MyBase.Visible
         Application.DoEvents
         UnsafeNativeMethods.MsgWaitForMultipleObjects(1, 0, 1, 250, 255)
        Loop
      End Sub
...

The same code as above plus the MsgWaitForMultipleObjects function call. The function is a standard part of the Win32 API. Here is the function's prototype taken from the MSDN documentation (comments mine):

DWORD MsgWaitForMultipleObjects(
  DWORD nCount,  // number of handles pointed to by the pHandles argument
  const HANDLE* pHandles, // pointer to an array of object
                          // handles whose signaled state is checked
  BOOL bWaitAll,  // all object handles should be signaled (TRUE)
                  // or any one of them (FALSE)
  DWORD dwMilliseconds, // wait timeout
  DWORD dwWakeMask  // which input events (messages) should
                    // cause the function to return (in addition
     // to signaling objects pointed to by pHandles)
);

The purpose of the function is to suspend the calling thread until one or all of the object handles become signaled, OR, until a message appears in the thread's input queue according to the dwWakeMask value.

The weird thing is that .NET framework calls the function with the number of object handles equal to ONE, while the pointer to the array of handles is ZERO (Nothing in VB nomenclature).

This way of calling the function is not documented (AFAIK). One can only guess that such a call is used to suspend the calling thread until a message has to be processed without checking the signaled state of any object handle.

Nevertheless, because the call is used within WinForms implementation itself, I find it quite safe to use it within your own applications.

I've used it with ColorPicker and the problem with the drop-down form consuming 100% CPU cycles disappeared.

Using the code

You can use the provided ColorPicker control in binary form by referencing the assembly LaMarvin.Windows.Forms.ColorPicker.dll in your project (This will work for any .NET language, not just VB.NET). Or, you can include just the ColorPicker.vb source code file in your VB.NET project.

When you download the zipped solution, be sure to:

  • Extract the files with the WinZip's "Use Folder Names" option checked.
  • Rebuild the solution the first time you open it in VS.NET (otherwise, the ColorPicker's reference in the demo application might not be resolved correctly).

History

  • October 26, 2003

    Initial release.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

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

Comments and Discussions

 
GeneralMy vote of 5 Pinmemberpaulsasik8-Sep-10 11:18 
QuestionHow to host Color Picker in DataGridView PinmemberJoe2003324-Mar-08 16:43 
GeneralCan't use it on VB2005Express Pinmemberdherrmann1-Jul-07 11:46 
GeneralRe: Can't use it on VB2005Express Pinmemberpalomraz2-Jul-07 11:46 
GeneralRe: Can't use it on VB2005Express Pinmemberdherrmann2-Jul-07 22:39 
GeneralRe: Can't use it on VB2005Express Pinmemberpalomraz3-Jul-07 2:00 
GeneralRe: Can't use it on VB2005Express Pinmemberdherrmann3-Jul-07 12:25 
GeneralRe: Can't use it on VB2005Express Pinmemberdherrmann4-Jul-07 12:01 
GeneralRe: Can't use it on VB2005Express Pinmemberpalomraz10-Jul-07 7:26 
GeneralRe: Can't use it on VB2005Express Pinmemberdherrmann11-Jul-07 23:44 
GeneralComboBox instead of Checkbox PinsussWarDemon20-Aug-04 7:01 
GeneralRe: ComboBox instead of Checkbox Pinmemberpalomraz19-Sep-04 10:36 
GeneralInteresting Article! Need Guideance... Pinsusslankymart13-Jul-04 2:38 
GeneralNice control PinmemberMark Focas27-Oct-03 16:40 
GeneralRe: Nice control Pinmemberpalomraz28-Oct-03 7:31 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.1411019.1 | Last Updated 27 Oct 2003
Article Copyright 2003 by palomraz
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid