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 the 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 editors are 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
IServiceProviderinstance for anIWindowsFormEditorServiceimplementation. - Stores the
IWindowsFormEditorServicein a member variable. - Creates an instance of a private
ColorUIclass, which implements the actual user interface and interacts with the user. - Calls the
IWindowsFormEditorService.DropDownControlmethod 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.
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.
Comments