Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations John Tel on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Why Mouse_Move returns mouse click event? 2

Status
Not open for further replies.

stranger123

Programmer
Apr 16, 2006
76
GB
Hi,

I believe you all know how to put the application to the system tray (use Shell_NotifyIconA). What I want to ask you is why we can use the X coordinate in the Mouse_Move event to determin double click?

Thank you in advance.
 
The reason behind this is the mechanism Shell_NotifyIcon function uses to send callback notifications.

I believe you are talking in context of the following MSDN article.
How To Manipulate Icons in the System Tray with Visual Basic

In this article all mouse-related messages are interpreted from the X argument in MouseMove event, like this...
___
[tt]
msg = X / Screen.TwipsPerPixelX
Select Case msg
Case WM_LBUTTONDOWN
Case WM_LBUTTONUP
Case WM_LBUTTONDBLCLK
Case WM_RBUTTONDOWN
Case WM_RBUTTONUP
Case WM_RBUTTONDBLCLK
End Select[/tt]
___

Note that Shell_NotifyIcon sends all these (and many more) messages as a single callback message. This callback message is specified by the caller at the time Shell_NotifyIcon function is called. This fact is stated in the documentation of NOTIFYICONDATA Structure.
MSDN said:
uCallbackMessage
Application-defined message identifier. The system uses this identifier to send notifications to the window identified in hWnd. These notifications are sent when a mouse event occurs in the bounding rectangle of the icon, or when the icon is selected or activated with the keyboard. The wParam parameter of the message contains the identifier of the taskbar icon in which the event occurred. The lParam parameter holds the mouse or keyboard message associated with the event. For example, when the pointer moves over a taskbar icon, lParam is set to WM_MOUSEMOVE. See the Taskbar guide chapter for further discussion.

The above fact is also mentioned in the documentation of Shell_NotifyIcon Function.
MSDN said:
Note The messages discussed above are not conventional Windows messages. They are sent as the lParam value of the application-defined message that is specified when the icon is added with NIM_ADD.

In the VB example code mentioned above, WM_MOUSEMOVE message is used as the callback message idendifier.
[tt]
nid.uCallBackMessage = WM_MOUSEMOVE
[/tt]
This means that all messages are sent in the form of WM_MOUSEMOVE message, with lParam parameter specifying the actual mouse (or other) event. Note that using WM_MOUSEMOVE for this purpose is not necessary. You can use some other message as well, even a user-defined message identifier, which is not used as a standard window message.

When VB receives this message (in the form of WM_MOUSEMOVE) it treat it like regular windows message and translate it into VB MouseMove event.

Now take a look at the documentation of WM_MOUSEMOVE Notification.
MSDN said:
lParam
The low-order word specifies the x-coordinate of the cursor. The coordinate is relative to the upper-left corner of the client area.

The high-order word specifies the y-coordinate of the cursor. The coordinate is relative to the upper-left corner of the client area.

When VB receives the WM_MOUSEMOVE message, it translate it to MouseMove event and X and Y arguments of the event are interpreted according to the above description.

In our case, the lParam parameter is holding the "actual" mouse event that occurs in the system tray. As the high-order word of the lParam parameter is absent in this case, Y is set to 0. The low-order word, which is holding the actual mouse event is assigned to the X argument after converting from Pixel-to-Twip scale.

Suppose you pressed the left mouse button in the tray. The value of WM_LBUTTONDOWN is &H201 = 513. Thus X parameter is assigned the value of 513 x Screen.TwipsPerPixelX = 513 x 15 = 7695 (assuming Screen.TwipsPerPixelX = 15).

Now VB code again converts this parameter to pixel scale to obtain the original message identifier that was sent by Shell.
___
[tt]
msg = X / Screen.TwipsPerPixelX
Select Case msg
...[/tt]
___

Note that these callback messages received in MouseMove events are not actually mouse move events. These are in fact callback messages sent by shell with actual event information embedded in X argument, as mentioned above. That is the reason you receive all mouse-related events (like double-click event) through MouseMove event.

The main reason for using WM_MOUSEMOVE message as callback message is that you do not need to subclass your window in order to get callback notifications, which would be, otherwise required if you use some other user-defined callback message identifier instead of WM_MOUSEMOVE.

Hope it all makes some sense.
 
Hypetia,

Yes, your article is a great help for me!

Only one thing.....you said
"Note that using WM_MOUSEMOVE for this purpose is not necessary."
Can you show me a simple code using a developer defined function to receive the message of cliking the tray icon, instead of the mouse events in the Form (I noteced in the MSDN article you quoted mentioned "AddressOf")?
 
The easiest way to verify this is to set the callback message identifier to something other than WM_MOUSEMOVE.

Suppose in the VB code discussed above, you change the callback message id to WM_LBUTTONDOWN, like this.
[tt]
nid.uCallBackMessage = WM_LBUTTONDOWN 'WM_MOUSEMOVE
[/tt]
As you change the callback message Id, the sample application stops receiving notifications in MouseMove event. The reason is that the Shell is now sending callbacks in the form of WM_LBUTTONDOWN messages whereas our application is still monitoring the MouseMove event.

To fix this problem, just change the MouseMove event to MouseDown event with rest of the code unchanged in the event procedure. Now the application will again start receiving these notifications through MouseDown events, because VB translate WM_LBUTTONDOWN message into MouseDown event. The rest of the details about the parameters remain unchanged, because both of these messages are sent and processed in a similar fashion.

To experiment the same thing using a user-defined message, see the following simplified code. It uses a user-defined message to receive shell notifications and the window is subclassed to intercept these user-defined messages.

Start a new project, insert the following code in the form.
___
[tt]
Option Explicit
Dim nid As NOTIFYICONDATA
Private Declare Function Shell_NotifyIcon Lib "shell32" Alias "Shell_NotifyIconA" (ByVal dwMessage As Long, pnid As NOTIFYICONDATA) As Boolean
Const NIM_ADD = &H0
Const NIM_DELETE = &H2
Const NIF_MESSAGE = &H1
Const NIF_ICON = &H2
Const NIF_TIP = &H4

Private Sub Form_Load()
'setup icon in the tray
nid.cbSize = Len(nid)
nid.hWnd = hWnd
nid.uFlags = NIF_ICON Or NIF_TIP Or NIF_MESSAGE
nid.uCallBackMessage = TRAY_MESSAGE 'user-defined callback message
nid.hIcon = Icon.Handle
nid.szTip = "Taskbar Status Area Sample Program" & vbNullChar
Shell_NotifyIcon NIM_ADD, nid

'subclass the window to receive callback notifications
SubClass hWnd
End Sub

Private Sub Form_Unload(Cancel As Integer)
Shell_NotifyIcon NIM_DELETE, nid
End Sub[/tt]
___

Now add a standard module to your project and insert the following code.
___
[tt]
Option Explicit
Private Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal hWnd As Long, ByVal msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Const WM_MOUSEMOVE = &H200
Const WM_LBUTTONDBLCLK = &H203
Const WM_LBUTTONDOWN = &H201
Const WM_LBUTTONUP = &H202
Const WM_RBUTTONDBLCLK = &H206
Const WM_RBUTTONDOWN = &H204
Const WM_RBUTTONUP = &H205
Const WM_USER = &H400

Const GWL_WNDPROC = -4
Type NOTIFYICONDATA
cbSize As Long
hWnd As Long
uId As Long
uFlags As Long
uCallBackMessage As Long
hIcon As Long
szTip As String * 64
End Type
Dim lpPrevWndProc As Long
Public Const TRAY_MESSAGE = WM_USER + 100 'just an arbitrary, user-defined message

Function WndProc(ByVal hWnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
If uMsg = TRAY_MESSAGE Then
Select Case lParam 'event identifier
' Case WM_MOUSEMOVE: Debug.Print "Mouse Move"
Case WM_LBUTTONDBLCLK: Debug.Print "Left Double-click"
Case WM_LBUTTONDOWN: Debug.Print "Left Down"
Case WM_LBUTTONUP: Debug.Print "Left Up"
Case WM_RBUTTONDBLCLK: Debug.Print "Right Double-click"
Case WM_RBUTTONDOWN: Debug.Print "Right Down"
Case WM_RBUTTONUP: Debug.Print "Right Up"
End Select
End If
WndProc = CallWindowProc(lpPrevWndProc, hWnd, uMsg, wParam, lParam)
End Function

Sub SubClass(hWnd As Long)
lpPrevWndProc = SetWindowLong(hWnd, GWL_WNDPROC, AddressOf WndProc)
End Sub[/tt]
___

Run the program and do some tricks with the icon in system tray. The program will log events in the Immediate window.

As this program uses subclassing to intercept callback notifications, you must be cautious while running and debugging your program in IDE mode. Once a window is subclassed, it becomes very difficult to debug your program in IDE mode, and a small mistake can lead VB to crash.

Due to the same reason programmers tend to use standard window messages (like WM_MOUSEMOVE) instead of user-defined messages to receive shell notifications. These events are received from standard VB events (like MouseMove) and you do not need to subclass your window as it is required for user-defined messages.
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top