I was recently using a ComboBox control with the
DropDownStyle set to Simple, effectively turning into a
combined text box and list box.
However, when I wanted an action to occur on double clicking an
item in the list I found that the control doesn't actually offer
double click support. I suppose I should have just ripped out
the combo box at that point and went with dedicated controls but
instead I decided to extend ComboBox to support double clicks.
Hmm, no WM_LBUTTONDBLCLK message?
I had assumed I could simply get the handle of the list
component, set the CS_DBLCLKS style, and start receiving
WM_LBUTTONDBLCLK messages. Unfortunately I couldn't get this
to work. Something to revisit another day perhaps.
Fine, lets fake it with WM_LBUTTONUP instead
So plan A was a bust. Not to worry, I had another idea. In a
previous post I described how to use the GetComboBoxInfo
Win32 API call to obtain the handles to the integrated controls.
We'll use this along with a NativeWindow to watch for
WM_LBUTTONUP messages and handle our double clicks that way.
What is NativeWindow?
I haven't described NativeWindow in any previous post, so I'll
briefly cover it now. NativeWindow is a managed wrapper around
a Win32 window handle, and allows you to easily hook into it's
window procedure (WndProc) in order to capture and process
messages sent to the window. Very tidy. The most important class
members are
AssignHandle - attaches the class to a window
ReleaseHandle - detaches the handle once you're finished
with it
WndProc - allows you to process messages, otherwise there's
not really much point in using the class!
One final point, in most cases you're probably going to want to
subclass NativeWindow as WndProc is protected. And that's
what we'll do here, using a new ListBoxNativeWindow class.
Attaching the handle
As I mentioned above, you have to explicitly attached your
NativeWindow implementation to a window. For this
demonstration control we'll do it when the control handle is
created, and when the drop down list style is changed. I'll also
add a AllowDoubleClick property to control the new behaviour,
so we'll also set it from there.
NativeWindow doesn't implement IDisposable so for best
practice you should make sure you manually clean up by calling
ReleaseHandle when you are done.
As I've previously covered the COMBOBOXINFO structure and
GetComboBoxInfo call I won't go over these again - please
refer to my previous post if you need more info.
Assuming we successfully obtain the combo box information, we
instantiate a new instance of our ListBoxNativeWindow and
attach it to the handle of the list box.
Our new class is also storing a reference to the owner
ComboBox control so that we can raise events as appropriate
later on.
As we should clean up behind ourselves, there's a helper method
to release any existing handles which we will call when
assigning a new handle, or when disposing of the control.
Now it's time to watch for some messages.
Intercepting messages
Intercepting messages in a NativeWindow is no different to
that of a normal control - just override WndProc and wait for
something interesting.
Double clicks
A double click is a pretty simple thing - it is the second click
to occur within a defined interval and with the cursor within
the region of the first click. These system values are
configurable by the end user so we shouldn't hard code our own
values.
The DoubleClickSize and DoubleClickTime properties of the
SystemInformation class provide managed access to these system
values, and so we can now populate our WndProc template with
some real code.
Although it might look a little complicated at first glance, it
should be straight forward.
The very first time you click with the left mouse button, we
record the current time and the cursor location
Each subsequent click then
Compares the current cursor position against a rectangle
centered on the previous position
Compares the previous click time with the current time
subtracted from the interval
If both the interval since the last click has not elapsed
and the cursor is in the same general area, then we have our
double click
Regards of if an event is to be raised or not, we then
update the time and position for the next click
Raising the event
Although I'd like to do the "right thing" and trigger a
WM_LBUTTONDBLCLK message, the control doesn't support it and
there's not really much point in adding it when it's not going
to have any real value. So we'll manually do it.
I start by adding an internal method to our ComboBox control -
I tend to avoid internals where possible but I don't really see
a need to expose this publicly.
Short and to the point, it simply raises the two different
events .NET controls have for double clicks.
And back in our WndProc, we construct a new MouseEventArgs
object and then call the new method.
It's worth pointing out the fudge in this - the magic number 2
which represents the number of times the button was clicked. The
0, while still magic, represents a mouse wheel delta which is
not appropriate for this event.
And with that code in place, this slightly long winded article
has gotten to the point and you now have fully working events.
Really? I can't see them
Oh of course. As the ComboBox control doesn't support the
DoubleClick and MouseDoubleClick events, the DoubleClick
event has been hidden (but not MouseDoubleClick for some
reason). Easy enough to bring it back - just redefine
DoubleClick with the new keyword set the EditorBrowsable
and Browsable attributes so it will appear in designers.
Always a catch
This was yet another blog post that was written in a hurry after
writing some code in a hurry. I'm positive there must be a
better way using normal window styles and messages rather than
the manual approach I've taken.
There's also a flaw in the code - if you triple click (or more)
then you'll get two (or more) double click events. I don't know
of too many people who spam double clicks so I'm going to ignore
this for now. Possibly at some point I'll be bored enough to
take another look at this and see where I went wrong with the
pure API approach.
Finally, given the hurry with which both of these items were
written, it hasn't had any robust testing, and so may be a
flawed piece of work.
As always, a demonstration project accompanies this article.
Update History
2014-10-11 - First published
2020-11-21 - Updated formatting
Like what you're reading? Perhaps you like to buy us a coffee?
It was unintended, but deadly effective and quick. Here the code:
Private Sub cboProdsServs_Click(sender As Object, e As EventArgs) Handles cboProdsServs.Click
Try
Dim item As Object
For Each item In lbSeleccion.Items
If item = cboProdsServs.Text Then
Exit Sub
End If
Next
lbSeleccion.Items.Add(cboProdsServs.Text)
Catch ex As Exception
Debug.Print(ex.Message)
End Try
End Sub
My idea was avoid user double-add items in the second combo (lbSeleccion), while first (cboProdsServs) has items to move to cboProdsServs list, but finally worked like double click event! Hope it helps. Note that the host event is cboProdsServs_Click.
The founder of Cyotek, Richard enjoys creating new blog content for the site. Much more though, he likes to develop programs, and can often found writing reams of code. A long term gamer, he has aspirations in one day creating an epic video game - but until that time comes, he is mostly content with adding new bugs to WebCopy and the other Cyotek products.
I was recently using a `ComboBox` control with the `DropDownStyle` set to `Simple`, effectively turning into a combined text box and list box.
However, when I wanted an action to occur on double clicking an item in the list I found that the control doesn't actually offer double click support. I suppose I should have just ripped out the combo box at that point and went with dedicated controls but instead I decided to extend `ComboBox` to support double clicks.
# Luis Mendieta