|
An ordinary VB developer shares his own successes and failures |
|
| Home News Articles Resources Tips Downloads About me | ||
The ColorPicker WinForms ControlDeveloping a simple ColorPicker WinForms control by implementing the IWindowsFormsEditorService interface and leveraging the WinForms design-time infrastructure.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. Based on these observations, I've decided to implement a ColorPicker control with the following features:
ColorPicker.vb file into your project and you're done).
The following is the important part of the Namespace LaMarvin.Windows.Forms Public Class ColorPicker Inherits Control Public Event ColorChanged As EventHandler Public Property Color() As ColorFor those of you in a hurry, you can download the control along with an accompanying demo application here. But wait!
If you want to learn something about the inner workings of the WinForms design-time support, as well as some undocumented .NET framework tricks, please, read on. The core requirement for theColorPicker control was to
display the same drop-down color selector that is used
within the WinForms' PropertyGrid control.
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
To better understand the process of hosting the
When a row within the 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 UITypeEditorEditStyleThe 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 Namespace System.Drawing.Design Public Class UITypeEditor Public Overridable Function EditValue( _ ByVal context As ITypeDescriptorContext, _ ByVal provider As IServiceProvider, _ ByVal value As Object _ ) As ObjectThe ColorEditor implementation of this method does the
following:
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 in the There was one issue, however, that I'd like to discuss here in more detail.
When the drop-down color selector is displayed by calling
the
In other words, the
Do you remember the old pal,
This time, however, it is exposed as the
Here is a pseudocode for my first
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 Too bad!
Once again, I've turned to the .NET Reflector tool in order
to see how the 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 ( 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
You can download the
The Enjoy!
© Palo Mraz, Tuesday, October 21, 2003
|
||
|
|
||
| ©2003-2008 Palo Mraz. All Rights Reserved. See my 'new browser window' policy | ||