
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
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,
const HANDLE* pHandles,
BOOL bWaitAll,
DWORD dwMilliseconds,
DWORD dwWakeMask
);
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.