Cyotek Development Bloghttps://devblog.cyotek.com/tag/nativewindow/atom.xml2014-10-11T07:30:31ZAdding Double Click support to the ComboBox controlurn:uuid:ea23b0c5-bc2a-40ba-9fb2-ca1533294f222014-10-11T07:30:31Z2014-10-11T07:30:31Z<p>I was recently using a <code>ComboBox</code> control with the
<code>DropDownStyle</code> set to <code>Simple</code>, effectively turning into a
combined text box and list box.</p>
<p>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 <code>ComboBox</code> to support double clicks.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/combobox-double-click.png" class="gallery" title="Double click events from a simple mode ComboBox control" ><img src="https://images.cyotek.com/image/thumbnail/devblog/combobox-double-click.png" alt="Double click events from a simple mode ComboBox control" decoding="async" loading="lazy" /></a><figcaption>Double click events from a simple mode ComboBox control</figcaption></figure><h2 id="hmm-no-wm_lbuttondblclk-message">Hmm, no WM_LBUTTONDBLCLK message?</h2>
<p>I had assumed I could simply get the handle of the list
component, set the <code>CS_DBLCLKS</code> style, and start receiving
<code>WM_LBUTTONDBLCLK</code> messages. Unfortunately I couldn't get this
to work. Something to revisit another day perhaps.</p>
<h2 id="fine-lets-fake-it-with-wm_lbuttonup-instead">Fine, lets fake it with WM_LBUTTONUP instead</h2>
<p>So plan A was a bust. Not to worry, I had another idea. In a
<a href="/post/getting-the-hwnd-of-the-edit-component-within-a-combobox-control">previous post</a> I described how to use the <code>GetComboBoxInfo</code>
Win32 API call to obtain the handles to the integrated controls.
We'll use this along with a <code>NativeWindow</code> to watch for
<code>WM_LBUTTONUP</code> messages and handle our double clicks that way.</p>
<h2 id="what-is-nativewindow">What is NativeWindow?</h2>
<p>I haven't described <code>NativeWindow</code> in any previous post, so I'll
briefly cover it now. <code>NativeWindow</code> 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</p>
<ul>
<li><code>AssignHandle</code> - attaches the class to a window</li>
<li><code>ReleaseHandle</code> - detaches the handle once you're finished
with it</li>
<li><code>WndProc</code> - allows you to process messages, otherwise there's
not really much point in using the class!</li>
</ul>
<p>One final point, in most cases you're probably going to want to
subclass <code>NativeWindow</code> as <code>WndProc</code> is protected. And that's
what we'll do here, using a new <code>ListBoxNativeWindow</code> class.</p>
<h2 id="attaching-the-handle">Attaching the handle</h2>
<p>As I mentioned above, you have to explicitly attached your
<code>NativeWindow</code> 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 <code>AllowDoubleClick</code> property to control the new behaviour,
so we'll also set it from there.</p>
<blockquote>
<p><code>NativeWindow</code> doesn't implement <code>IDisposable</code> so for best
practice you should make sure you manually clean up by calling
<code>ReleaseHandle</code> when you are done.</p>
</blockquote>
<p>As I've previously covered the <code>COMBOBOXINFO</code> structure and
<code>GetComboBoxInfo</code> call I won't go over these again - please
refer to my <a href="/post/getting-the-hwnd-of-the-edit-component-within-a-combobox-control">previous post</a> if you need more info.</p>
<p>Assuming we successfully obtain the combo box information, we
instantiate a new instance of our <code>ListBoxNativeWindow</code> and
attach it to the handle of the list box.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> ListBoxNativeWindow _listBoxWindow<span class="symbol">;</span>

<span class="keyword">private</span> <span class="keyword">void</span> AttachHandle<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>ReleaseHandle<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>IsHandleCreated <span class="symbol">&amp;&amp;</span> <span class="keyword">this</span><span class="symbol">.</span>AllowDoubleClick <span class="symbol">&amp;&amp;</span> <span class="keyword">this</span><span class="symbol">.</span>DropDownStyle <span class="symbol">==</span> ComboBoxStyle<span class="symbol">.</span>Simple<span class="symbol">)</span>
 <span class="symbol">{</span>
 COMBOBOXINFO info<span class="symbol">;</span>

 info <span class="symbol">=</span> <span class="keyword">new</span> COMBOBOXINFO<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 info<span class="symbol">.</span>cbSize <span class="symbol">=</span> Marshal<span class="symbol">.</span>SizeOf<span class="symbol">(</span>info<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>GetComboBoxInfo<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Handle<span class="symbol">,</span> <span class="keyword">ref</span> info<span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 IntPtr hWnd<span class="symbol">;</span>

 hWnd <span class="symbol">=</span> info<span class="symbol">.</span>hwndList<span class="symbol">;</span>

 _listBoxWindow <span class="symbol">=</span> <span class="keyword">new</span> ListBoxNativeWindow<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">)</span><span class="symbol">;</span>
 _listBoxWindow<span class="symbol">.</span>AssignHandle<span class="symbol">(</span>hWnd<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Our new class is also storing a reference to the owner
<code>ComboBox</code> control so that we can raise events as appropriate
later on.</p>
<p>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.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">void</span> ReleaseHandle<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>_listBoxWindow <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 _listBoxWindow<span class="symbol">.</span>ReleaseHandle<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 _listBoxWindow <span class="symbol">=</span> <span class="keyword">null</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Now it's time to watch for some messages.</p>
<h2 id="intercepting-messages">Intercepting messages</h2>
<p>Intercepting messages in a <code>NativeWindow</code> is no different to
that of a normal control - just override <code>WndProc</code> and wait for
something interesting.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">const</span> <span class="keyword">int</span> WM_LBUTTONUP <span class="symbol">=</span> <span class="number">0x0202</span><span class="symbol">;</span>

<span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> WndProc<span class="symbol">(</span><span class="keyword">ref</span> Message m<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>m<span class="symbol">.</span>Msg <span class="symbol">==</span> WM_LBUTTONUP<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="comment">// do stuff!</span>
 <span class="symbol">}</span>

 <span class="keyword">base</span><span class="symbol">.</span>WndProc<span class="symbol">(</span><span class="keyword">ref</span> m<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="double-clicks">Double clicks</h2>
<p>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.</p>
<p>The <code>DoubleClickSize</code> and <code>DoubleClickTime</code> properties of the
<code>SystemInformation</code> class provide managed access to these system
values, and so we can now populate our <code>WndProc</code> template with
some real code.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">if</span> <span class="symbol">(</span>m<span class="symbol">.</span>Msg <span class="symbol">==</span> NativeMethods<span class="symbol">.</span>WM_LBUTTONUP<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">long</span> previousMessageTime<span class="symbol">;</span>
 <span class="keyword">long</span> currentMessageTime<span class="symbol">;</span>
 Point currentLocation<span class="symbol">;</span>

 previousMessageTime <span class="symbol">=</span> _lastMessageTime<span class="symbol">;</span>
 currentMessageTime <span class="symbol">=</span> DateTime<span class="symbol">.</span>Now<span class="symbol">.</span>Ticks<span class="symbol">;</span>
 currentLocation <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>GetPoint<span class="symbol">(</span>m<span class="symbol">.</span>LParam<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>_lastMessageTime <span class="symbol">&gt;</span> <span class="number">0</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 Rectangle doubleClickBounds<span class="symbol">;</span>
 Size doubleClickSize<span class="symbol">;</span>

 doubleClickSize <span class="symbol">=</span> SystemInformation<span class="symbol">.</span>DoubleClickSize<span class="symbol">;</span>
 doubleClickBounds <span class="symbol">=</span> <span class="keyword">new</span> Rectangle<span class="symbol">(</span>_lastMousePosition<span class="symbol">.</span>X <span class="symbol">-</span> <span class="symbol">(</span>doubleClickSize<span class="symbol">.</span>Width <span class="symbol">/</span> <span class="number">2</span><span class="symbol">)</span><span class="symbol">,</span> _lastMousePosition<span class="symbol">.</span>Y <span class="symbol">-</span> <span class="symbol">(</span>doubleClickSize<span class="symbol">.</span>Height <span class="symbol">/</span> <span class="number">2</span><span class="symbol">)</span><span class="symbol">,</span> doubleClickSize<span class="symbol">.</span>Width<span class="symbol">,</span> doubleClickSize<span class="symbol">.</span>Height<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>previousMessageTime <span class="symbol">+</span> <span class="symbol">(</span>SystemInformation<span class="symbol">.</span>DoubleClickTime <span class="symbol">*</span> TimeSpan<span class="symbol">.</span>TicksPerMillisecond<span class="symbol">)</span> <span class="symbol">&gt;</span> currentMessageTime <span class="symbol">&amp;&amp;</span> doubleClickBounds<span class="symbol">.</span>Contains<span class="symbol">(</span>currentLocation<span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 MouseEventArgs e<span class="symbol">;</span>

 e <span class="symbol">=</span> <span class="keyword">new</span> MouseEventArgs<span class="symbol">(</span>MouseButtons<span class="symbol">.</span>Left<span class="symbol">,</span> <span class="number">2</span><span class="symbol">,</span> currentLocation<span class="symbol">.</span>X<span class="symbol">,</span> currentLocation<span class="symbol">.</span>Y<span class="symbol">,</span> <span class="number">0</span><span class="symbol">)</span><span class="symbol">;</span>

 _owner<span class="symbol">.</span>RaiseDoubleClick<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>

 _lastMessageTime <span class="symbol">=</span> currentMessageTime<span class="symbol">;</span>
 _lastMousePosition <span class="symbol">=</span> currentLocation<span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Although it might look a little complicated at first glance, it
should be straight forward.</p>
<ul>
<li>The very first time you click with the left mouse button, we
record the current time and the cursor location</li>
<li>Each subsequent click then
<ul>
<li>Compares the current cursor position against a rectangle
centered on the previous position</li>
<li>Compares the previous click time with the current time
subtracted from the interval</li>
<li>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</li>
<li>Regards of if an event is to be raised or not, we then
update the time and position for the next click</li>
</ul>
</li>
</ul>
<h2 id="raising-the-event">Raising the event</h2>
<p>Although I'd like to do the &quot;right thing&quot; and trigger a
<code>WM_LBUTTONDBLCLK </code> 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.</p>
<p>I start by adding an internal method to our <code>ComboBox</code> control -
I tend to avoid internals where possible but I don't really see
a need to expose this publicly.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">internal</span> <span class="keyword">void</span> RaiseDoubleClick<span class="symbol">(</span>MouseEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>OnDoubleClick<span class="symbol">(</span>EventArgs<span class="symbol">.</span>Empty<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>OnMouseDoubleClick<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Short and to the point, it simply raises the two different
events .NET controls have for double clicks.</p>
<p>And back in our <code>WndProc</code>, we construct a new <code>MouseEventArgs</code>
object and then call the new method.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
MouseEventArgs e<span class="symbol">;</span>

e <span class="symbol">=</span> <span class="keyword">new</span> MouseEventArgs<span class="symbol">(</span>MouseButtons<span class="symbol">.</span>Left<span class="symbol">,</span> <span class="number">2</span><span class="symbol">,</span> currentLocation<span class="symbol">.</span>X<span class="symbol">,</span> currentLocation<span class="symbol">.</span>Y<span class="symbol">,</span> <span class="number">0</span><span class="symbol">)</span><span class="symbol">;</span>

_owner<span class="symbol">.</span>RaiseDoubleClick<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>
</pre>
</figure>
<p>It's worth pointing out the fudge in this - the magic number <code>2</code>
which represents the number of times the button was clicked. The
<code>0</code>, while still magic, represents a mouse wheel delta which is
not appropriate for this event.</p>
<p>And with that code in place, this slightly long winded article
has gotten to the point and you now have fully working events.</p>
<h2 id="really-i-cant-see-them">Really? I can't see them</h2>
<p>Oh of course. As the <code>ComboBox</code> control doesn't support the
<code>DoubleClick</code> and <code>MouseDoubleClick</code> events, the <code>DoubleClick</code>
event has been hidden (but not <code>MouseDoubleClick</code> for some
reason). Easy enough to bring it back - just redefine
<code>DoubleClick</code> with the <code>new</code> keyword set the <code>EditorBrowsable</code>
and <code>Browsable</code> attributes so it will appear in designers.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="symbol">[</span>EditorBrowsable<span class="symbol">(</span>EditorBrowsableState<span class="symbol">.</span>Always<span class="symbol">)</span><span class="symbol">]</span>
<span class="symbol">[</span>Browsable<span class="symbol">(</span><span class="keyword">true</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">public</span> <span class="keyword">new</span> <span class="keyword">event</span> EventHandler DoubleClick
<span class="symbol">{</span>
 add <span class="symbol">{</span> <span class="keyword">base</span><span class="symbol">.</span>DoubleClick <span class="symbol">+=</span> value<span class="symbol">;</span> <span class="symbol">}</span>
 remove <span class="symbol">{</span> <span class="keyword">base</span><span class="symbol">.</span>DoubleClick <span class="symbol">-=</span> value<span class="symbol">;</span> <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="always-a-catch">Always a catch</h2>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>As always, a demonstration project accompanies this article.</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2014-10-11 - First published</li>
<li>2020-11-21 - Updated formatting</li>
</ul>

<p><small>
All content <a href="https://devblog.cyotek.com/copyright-and-trademarks">Copyright (c) by Cyotek Ltd</a> or its respective writers. Permission to reproduce news and web log entries and other RSS feed content in unmodified form without notice is granted provided they are not used to endorse or promote any products or opinions (other than what was expressed by the author) and without taking them out of context. Written permission from the copyright owner must be obtained for everything else.<br />Original URL of this content is https://devblog.cyotek.com/post/adding-double-click-support-to-the-combobox-control .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.com