|
|
|
|
|
About this page
This article describes how to get rid of 'DblClick' events by using library
'SmartSubclass'. It is a good example of how to use this library
and it also gives a comprehensive understanding of how and when
mouse messages are posted.
Downloads
Requires SmartSubclass
|
|
Each control in Visual Basic normally comes with 5 mouse events: Click, DblClick,
MouseDown, MouseUp and MouseMove.
For most applications that's enough and we rarely need Click and MouseDown at
the same time. But there are situations when our code needs to rely strongly on
mouse messages and that's when we find that VB has its limitations.
If we examine how VB receives mouse events and in what order those are processed
we find the following sequence:
| Action |
Generated VB events |
| Single Mouse Click |
MouseDown |
|
MouseUp |
|
Click |
|
MouseMove |
| Action |
Generated VB events |
| Double Mouse Click |
MouseDown |
|
MouseUp |
|
Click |
|
MouseMove |
|
DblClick |
|
MouseUp |
Have you noticed something strange? Well, if we forget about mousemove and we
focus on the other four events (click, dblclick, mouseup and mousedown) you
will see that the succesion of messages generated by the double mouse click
is slightly different to the one generated by a single mouse click. A single
mouse click generates {mousedown, mouseup, click} whereas a double mouse click
generates {mousedown, mouseup, click, dblclick, mouseup}. The second mousedown
message is translated into a dblclick message and there isn't a click message
following the second mouseup.
Why is that? This is not a VB problem, it comes directly from Microsoft Windows.
It is due to the fact that Visual Basic controls are created with windows belonging
to classes that have the CS_DBLCLKS class style - we will talk about this later.
Now let's propose a simple VB application: imagine that we're in the process
of implementing a button control. We will have to trap both mousedown and mouseup
events in order to draw the button pushed or released.
The problem seems simple and we'll only have to code the UserControl_MouseDown
and UserControl_MouseUp events. We also want the button to only respond to left
clicks because the right click is going to be used for a pop-up menu. The code
will probably look something like:
Private Sub UserControl_MouseDown(Button
As Integer, Shift As Integer,
X As Single, Y As Single)
If Button = vbLeftButton Then
Call DrawMyEdgeSunken
End If
End Sub
Private Sub UserControl_MouseUp(Button As Integer,
Shift As Integer, X As Single,
Y As Single)
If Button = vbLeftButton Then
Call DrawMyEdgeRaised
End If
End Sub
So after finishing our first version of the button we run the program and we
start playing with the new control: we left click... and the button seems to
be pushed, we release the mouse... and the button seems to release too. And
we click, and we click again, and we start clicking faster and... hey!, what's
happening? the mouse seems to be released all the time and we're loosing the
push-pop effect! Well, the explanation is simple: when we fast click over the
button (up, down, up, down) Windows sends a DblClick event instead of the second
MouseDown. This makes it seem as if the button is always released, because the
second MouseDown event is never fired.
Somebody may say: "that's easy, we only have to code the DblClick event
and make it work as a MouseDown". Yes, that could be a good solution but
... how do we know if the user is left double-clicking rather than right double-clicking?
And this is when we start getting problems with VB!
MouseDown and MouseUp events include information about which button the user
is pressing and where (x,y) the user is clicking, whereas both Click and DblClick
events contain none of this information. We wouldn't be able to know if the
user is pressing the right button just by using the DblClick event. Yes, there
are other ways round it (we could use flags and other techniques to solve this
problem) but... maybe there's a better solution.
What if we could get rid of the DblClick event? What if a double-click is just
that, two mouse clicks?
We could think about removing the CS_DBLCLKS style from our window class, but
this would affect the rest of the system. We could also think about sending
a MouseDown event from the DblClick, but we still need to know how and where
the mouse has been clicked.
The solution I propose is based on the SmartSubClass
library. We're going to define a new smartsubclass variable, make it listen
to all messages posted to the usercontrol and to replace all DblClick events
with MouseDowns.
Add the following code to the usercontrol:
Option Explicit
Dim WithEvents oSniff As SmartSubClass
'API declarations:
Private Const WM_LBUTTONDBLCLK = &H203
Private Const WM_RBUTTONDBLCLK = &H206
Private Const WM_LBUTTONDOWN = &H201
Private Const WM_RBUTTONDOWN = &H204
'
' Initialise the SmartSubClass variable...
'
Private Sub UserControl_Initialize()
Set oSniff = New
SmartSubClass
oSniff.SubClassHwnd UserControl.Hwnd, True
End Sub
'
' Make sure that usercontrol is unsubclassed before terminating...
'
Private Sub UserControl_Terminate()
oSniff.SubClassHwnd UserControl.Hwnd, False
End Sub
'
' Convert DoubleClick into MouseDown messages...
'
Private Sub oSniff_NewMessage( _
ByVal hwnd As Long, _
uMsg As Long, _
wParam As Long,
_
lParam As Long, _
Cancel As Boolean)
Select Case uMsg
Case WM_LBUTTONDBLCLK
uMsg
= WM_LBUTTONDOWN
Case WM_RBUTTONDBLCLK
uMsg
= WM_RBUTTONDOWN
End Select
End Sub
Easy, isn't it? Now we have full control, not only of the mouse messages but
of absolutely all messages posted to the usercontrol. We will be the first to
receive a message and we will decide whether to cancel, modify or just let the
message pass through. With this solution the usercontrol will no longer receive
double-click messages. It will receive mousedown events instead.
There are other mouse messages that VB doesn't make visible to the developer.
The following is a list of those messages with a short description of their
purpose. You can modify the above example to receive any of these "extra"
messages.
WM_CAPTURECHANGED
Sent to the window that is losing the mouse capture.
WM_MOUSEACTIVATE
Sent when the cursor is in an inactive window and the user presses a mouse button.
WM_MOUSEHOVER
Posted to a window when the cursor hovers over the client area of the window.
WM_MOUSELEAVE
Posted to a window when the cursor leaves the client area of the window.
WM_MOUSEWHEEL
Sent to the focus window when the mouse wheel is rotated.
Finally, you can find more examples on how to get more control of your mouse
by visiting the article Flat
Button control.
|
|