Intercepting the ENTER and ESC keys in custom, drop-down UITypeEditors.
This article and the accompanying source code shows you how to intercept
the ENTER and ESC keys in your own UITypeEditor implementation.
Intercepting the keys is essential for emulating the behavior of
the .NET Framework's built-in, drop-down editors in order to provide
your users with a consistent user experience.
When developing Windows Forms controls it is often useful to provide
your own, drop-down type editors for some of the control's properties.
Custom type editors provide for richer design-time experience and they
might be the deciding factor whether your users like your controls or
not.
If you decide to create your own drop-down type editor, it should,
presumably, follow the same behavioral pattern exposed by the built-in
drop-down editors. Let's take the Anchor property as an example. A
typical design-time user interaction with the property is as follows:
-
The user selects the
Anchor property in the property grid and clicks
the down-arrow button to the right of the property's cell.
-
A nice graphical control is dropped down allowing the user to either
click the edges by the mouse or use the arrow keys to highlight an
edge and the
SPACEBAR key to select / deselect it.
-
The user presses the
ENTER key or clicks outside of the
drop-down control for accepting the changes. In order to cancel the
changes, the user presses the ESC key.
Pretty intuitive!
So what does it take to replicate the above-mentioned behavior?
In order to explore the implementation choices, let's build a
ResourceImageEditor type editor that allows for picking an image file
from the file system (just like the built-in ImageEditor class) or
picking an image resource from an assembly's manifest. With regards to
user experience, the ResourceImageEditor should behave like the
built-in type editors. Here are our requirements in a nutshell:
- When the user selects a property in the property grid, the grid
should display a down-arrow button indicating that the property will
be edited with a drop-down UI.
- When the down-arrow button is clicked, a list of all image
resources from the current assembly should be displayed.
- When the user selects an image resource item, the image will be
loaded from the assembly.
- To allow for selecting an image file, the last item in the
drop-down list will be labeled as "Browse...". When the user clicks
the "Browse..." item, the "classic" open file dialog will be displayed
and the user will be able to pick an image file from the file system.
- The drop-down list will allow the user to select an item by
single-clicking it with the mouse, or by using the arrow keys to
highlight an item and pressing the
ENTER key to actually select it.
The drop-down selection will be also accepted by clicking off of the
list. The drop-down selection will be canceled by pressing the ESC
key.
The ResourceImageEditor is a type editor so it should derive (directly
or indirectly) from the System.Drawing.Design.UITypeEditor class. I've
decided to inherit from the built-in
System.Drawing.Design.ImageEditor, because it already implements the
image file selection functionality specified in the fourth requirement
above. That is, the ImageEditor.EditValue implementation displays a
file open dialog allowing the user to select an image file from the
file system. Invoking this functionality from within my derived class
is then a simple call to MyBase.EditValue.
In order satisfy the first requirement (displaying the down-arrow
button in the property grid), I've had to override the GetEditStyle
method to return the appropriate constant from the
UITypeEditorEditStyle enumeration:
Public Overloads Overrides Function GetEditStyle( _
ByVal context As ITypeDescriptorContext) As UITypeEditorEditStyle
Return UITypeEditorEditStyle.DropDown
End Function
To display the list of image resources, I've had to enumerate all
resources in a given assembly and display only the image resources in
the list. To keep things simple, I've decided to employ a simple
convention - when a resource name ends with a valid image file
extension (.bmp, .jpg, .gif...), it is considered an image resource
and it will be included in the drop-down list. The collection of image
resource names is used to populate the drop-down ListBox control as
described later.
When the user selects an image resource name in the list box, the
image should be loaded from the given assembly's manifest. This is
implemented within the LoadResourceImage method:
Private Function LoadResourceImage(ByVal resourceName As String) As Image
Debug.Assert(Not resourceName Is Nothing)
Dim ImageStream As System.IO.Stream = _
Me.ResourceAssembly.GetManifestResourceStream(resourceName)
Return System.Drawing.Bitmap.FromStream(ImageStream)
End Function
The drop-down user interface is implemented by dynamically creating
and populating a ListBox control inside the overridden EditValue
method. The editor also handles the Click and KeyDown events generated
by the ListBox, which is necessary to intercept the ENTER and ESC
keys. Here is pseudocode illustrating the logic inside the EditValue
method:
Public Overloads Overrides Function EditValue(...)
' Store the context information for use in the drop-down ListBox event handlers.
' Create and fill the ListBox with the names of the available image resources.
' Add our special "Browse..." item.
' Wire the ListBox events.
' Display the ListBox in a drop-down window.
End Function
That's it.
I've written the ResourceImageEditor code.
I've created a sample MyPictureBox (derived from
System.Windows.Forms.PictureBox) overriding the Image property as to
have the ResourceImageEditor specified as the property's type editor.
I've compiled the code, placed the MyPictureBox control onto a form
and invoked the drop-down user interface...
The mouse interface worked well. However, when I've selected an item
with the keyboard and then pressed the ENTER key, the drop-down list
disappeared, but my selection has been lost (i.e. the previously
selected image hasn't changed). I've quickly discovered that when the
ENTER key is pressed, the ListBox doesn't generate the KeyDown event.
The property grid has apparently "stolen" the ENTER and ESC keys
before the ListBox control could get a chance to process them. Or did
it?
To make a long story short, the solution that worked was to employ the
ProcessDialogKey method. The method is called during message
preprocessing to handle dialog characters, such as TAB, RETURN, ESCAPE
and also the arrow keys. The method is declared within the
System.Windows.Forms.Control class in such a way that it simply
delegates the call to the control's parent (if any). I've "subclassed"
the ListBox control and I've overridden the ProcessDialogKey method to
intercept the ENTER key like this:
Protected Overrides Function ProcessDialogKey(ByVal keyData As Keys) As Boolean
If keyData = System.Windows.Forms.Keys.Return Then
RaiseEvent EnterPressed(Me, EventArgs.Empty)
Return True ' True means we've processed the key
Else
Return MyBase.ProcessDialogKey(keyData)
End If
End Function
Instead of generating the KeyDown event from within the
ProcessDialogKey implementation, I've decided that a more
straightforward approach would be to generate a new, distinguished
event - the EnterPressed event. The ResourceImageEditor.EditValue
implementation has been changed to handle this event (instead of the
KeyDown) event and everything finally worked correctly.
You can use this technique to intercept the ENTER key in any
Control-derived class that you use for implementing the drop-down UI
inside your type editor. For implementation details, please have a
look at the source code in the solution accompanying
this article.
©Palo Mraz, Monday, February 23, 2004
|
|