cordes
asked on
How do you detect desktop mouse clicks
Whether inside or outside a VB form, I need to detect and count mouse click events (right and left). There are API's for detecting keystrokes and for determining the mouse-pointer's position, but I can't find a way of counting mouse clicks outside of a VB form or control.
Subclassing is the answer here. Subclass the desktop window and trap the messages.
One more comment: MouseMove procedure returns you a variable named "Button", which has binary-coded the buttons pressed, in this way:
If (Button and 1) then <left button pressed>
If (Button and 2) then <right button pressed>
If (Button and 4) then <mid button pressed>
Of course, if more than one button is pressed, corresponding bits in "Button" will come in 1-state.
With that, you can determine which button were pressed and count them accordingly to your needs.
If (Button and 1) then <left button pressed>
If (Button and 2) then <right button pressed>
If (Button and 4) then <mid button pressed>
Of course, if more than one button is pressed, corresponding bits in "Button" will come in 1-state.
With that, you can determine which button were pressed and count them accordingly to your needs.
ASKER
MouseMove is only available as an event for VB controls. That I know how to do. However, I also need to be able to detect mouse clicks in other running apps outside of VB. MouseMove obviously cannot provide this. Thanks, but I need a solution to handle both cases.
Subclassing sounds interesting. Is this something I can do within VB? If so, how?
Subclassing sounds interesting. Is this something I can do within VB? If so, how?
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
The example works for the purpose of the example. When I add and call the function GetDesktopWindow in WindowProc, all that is returned is 65566. Please show how I should be using GetDesktopWindow. I believe you are on the right track. However, so far I can't get it to provide anything useful in regards to counting mouse clicks.. Thanks.
The hook example hooks a window. A window is defined by its window handle. In your case you want to hook the desktop. So retrieve the handle of the desktop (65566 in your case, the last time).
Replace form_load in the example by
Private Sub Form_Load()
gHW = GetDesktopWindow
End Sub
BTW: I'm doing all this for 10 points. I must be crazy =)
Replace form_load in the example by
Private Sub Form_Load()
gHW = GetDesktopWindow
End Sub
BTW: I'm doing all this for 10 points. I must be crazy =)
ASKER
With the window handle provided by GetDeskTopWindow, WindowProc never seems to be evoked. This is obviously more difficult than I previously thought. I'll up the points to 100 if you can provide a working example I can use or continue to work with me until successful. Else, I'll give you the 10 for work already done. Thanks.
Try it using the listview window as retrieved below.
Private Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal hWnd1 As Long, ByVal hWnd2 As Long, ByVal lpsz1 As String, ByVal lpsz2 As String) As Long
Private Sub Command1_Click()
Dim pmHwnd As Long
Dim dvHwnd As Long
Dim lvHwnd As Long
pmHwnd = FindWindowEx(0, 0, "Progman", "Program Manager")
dvHwnd = FindWindowEx(pmHwnd, 0, "SHELLDLL_DefView", "")
lvHwnd = FindWindowEx(dvHwnd, 0, "SysListView32", "")
MsgBox lvHwnd
End Sub
Private Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal hWnd1 As Long, ByVal hWnd2 As Long, ByVal lpsz1 As String, ByVal lpsz2 As String) As Long
Private Sub Command1_Click()
Dim pmHwnd As Long
Dim dvHwnd As Long
Dim lvHwnd As Long
pmHwnd = FindWindowEx(0, 0, "Progman", "Program Manager")
dvHwnd = FindWindowEx(pmHwnd, 0, "SHELLDLL_DefView", "")
lvHwnd = FindWindowEx(dvHwnd, 0, "SysListView32", "")
MsgBox lvHwnd
End Sub
ASKER
OK, I added a command button to the subclass VB program and pasted the code you sent. The message box returns 655438. I'm not sure what this means or where to go from here. Thanks.
Pass this window handle to the hook class as described before.
ASKER
Again, WindowProc never seems to be called. Below is the code I'm running. I've added a timer to constantly check uMsg. However, even if I assign Mes a string (e.g., Mes = "Here"), it's clear that this function is never called. It only was called when the handle was the textbox. The only reason I can figure why it isn't called is that I'm not performing a user action that gets intercepted. Try the code yourself. What's wrong? Thanks.
Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" _
(ByVal hwnd As Long, _
ByVal nIndex As Long, _
ByVal dwNewLong As Long) As Long
Declare Function GetDesktopWindow Lib "user32" () As Long
Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" _
(ByVal hWnd1 As Long, ByVal hWnd2 As Long, _
ByVal lpsz1 As String, ByVal lpsz2 As String) As Long
Public Const GWL_WNDPROC = -4
Public Const WM_RBUTTONUP = &H205
Global Mes
Global lpPrevWndProc As Long
Global gHW As Long
Public Sub Hook()
lpPrevWndProc = SetWindowLong(gHW, GWL_WNDPROC, _
AddressOf WindowProc)
End Sub
Public Sub UnHook()
Dim lngReturnValue As Long
lngReturnValue = SetWindowLong(gHW, GWL_WNDPROC, lpPrevWndProc)
End Sub
Function WindowProc(ByVal hw As Long, _
ByVal uMsg As Long, _
ByVal wParam As Long, _
ByVal lParam As Long) As Long
Mes = uMsg
WindowProc = CallWindowProc(lpPrevWndPr oc, hw, _
uMsg, wParam, lParam)
End Function
-------------------------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ------
Private Sub cmdHook_Click()
Hook
End Sub
Private Sub cmdUnHook_Click()
UnHook
End Sub
Private Sub Form_Load()
' gHW = txtHook.hwnd
' gHW = GetDesktopWindow
Dim pmHwnd As Long
Dim dvHwnd As Long
Dim lvHwnd As Long
pmHwnd = FindWindowEx(0, 0, "Progman", "Program Manager")
dvHwnd = FindWindowEx(pmHwnd, 0, "SHELLDLL_DefView", "")
lvHwnd = FindWindowEx(dvHwnd, 0, "SysListView32", "")
gHW = lvHwnd
End Sub
Private Sub Timer1_Timer()
txtHook = Mes
End Sub
Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" _
(ByVal hwnd As Long, _
ByVal nIndex As Long, _
ByVal dwNewLong As Long) As Long
Declare Function GetDesktopWindow Lib "user32" () As Long
Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" _
(ByVal hWnd1 As Long, ByVal hWnd2 As Long, _
ByVal lpsz1 As String, ByVal lpsz2 As String) As Long
Public Const GWL_WNDPROC = -4
Public Const WM_RBUTTONUP = &H205
Global Mes
Global lpPrevWndProc As Long
Global gHW As Long
Public Sub Hook()
lpPrevWndProc = SetWindowLong(gHW, GWL_WNDPROC, _
AddressOf WindowProc)
End Sub
Public Sub UnHook()
Dim lngReturnValue As Long
lngReturnValue = SetWindowLong(gHW, GWL_WNDPROC, lpPrevWndProc)
End Sub
Function WindowProc(ByVal hw As Long, _
ByVal uMsg As Long, _
ByVal wParam As Long, _
ByVal lParam As Long) As Long
Mes = uMsg
WindowProc = CallWindowProc(lpPrevWndPr
uMsg, wParam, lParam)
End Function
--------------------------
Private Sub cmdHook_Click()
Hook
End Sub
Private Sub cmdUnHook_Click()
UnHook
End Sub
Private Sub Form_Load()
' gHW = txtHook.hwnd
' gHW = GetDesktopWindow
Dim pmHwnd As Long
Dim dvHwnd As Long
Dim lvHwnd As Long
pmHwnd = FindWindowEx(0, 0, "Progman", "Program Manager")
dvHwnd = FindWindowEx(pmHwnd, 0, "SHELLDLL_DefView", "")
lvHwnd = FindWindowEx(dvHwnd, 0, "SysListView32", "")
gHW = lvHwnd
End Sub
Private Sub Timer1_Timer()
txtHook = Mes
End Sub
HOWTO: Position a MsgBox Using a Windows Hook Procedure
Last reviewed: February 13, 1998
Article ID: Q180936 The information in this article applies to:
•Microsoft Visual Basic Learning, Professional, and Enterprise Editions for Windows, version 5.0
SUMMARY
This article illustrates how to position a message box on the screen.
MORE INFORMATION
You can create a CBT hook for your application so that it receives notifications when windows are created and destroyed. If you display a message box with this CBT hook in place, your application will receive a HCBT_ACTIVATE message when the message box is activated. Once you receive this HCBT_ACTIVATE message, you can position the window with the SetWindowPos API function and then release the CBT hook if it is no longer needed.
Step-by-Step Example
1.Start a new Standard EXE project. Form1 is created by default.
2.Add a module to the project and add the following code to the new module:
Type RECT
Left As Long
Top As Long
Right As Long
Bottom As Long
End Type
Public Declare Function UnhookWindowsHookEx Lib "user32" ( _
ByVal hHook As Long) As Long
Public Declare Function GetWindowLong Lib "user32" Alias _
"GetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long) _
As Long
Public Declare Function GetCurrentThreadId Lib "kernel32" () As Long
Public Declare Function SetWindowsHookEx Lib "user32" Alias _
"SetWindowsHookExA" (ByVal idHook As Long, ByVal lpfn As Long, _
ByVal hmod As Long, ByVal dwThreadId As Long) As Long
Public Declare Function SetWindowPos Lib "user32" ( _
ByVal hwnd As Long, ByVal hWndInsertAfter As Long, _
ByVal x As Long, ByVal y As Long, ByVal cx As Long, _
ByVal cy As Long, ByVal wFlags As Long) As Long
Public Declare Function GetWindowRect Lib "user32" (ByVal hwnd _
As Long, lpRect As RECT) As Long
Public Const GWL_HINSTANCE = (-6)
Public Const SWP_NOSIZE = &H1
Public Const SWP_NOZORDER = &H4
Public Const SWP_NOACTIVATE = &H10
Public Const HCBT_ACTIVATE = 5
Public Const WH_CBT = 5
Public hHook As Long
Function WinProc1(ByVal lMsg As Long, ByVal wParam As Long, _
ByVal lParam As Long) As Long
If lMsg = HCBT_ACTIVATE Then
'Show the MsgBox at a fixed location (0,0)
SetWindowPos wParam, 0, 0, 0, 0, 0, _
SWP_NOSIZE Or SWP_NOZORDER Or SWP_NOACTIVATE
'Release the CBT hook
UnhookWindowsHookEx hHook
End If
WinProc = False
End Function
Function WinProc2(ByVal lMsg As Long, ByVal wParam As Long, _
ByVal lParam As Long) As Long
Dim rectForm As RECT, rectMsg As RECT
Dim x As Long, y As Long
'On HCBT_ACTIVATE, show the MsgBox centered over Form1
If lMsg = HCBT_ACTIVATE Then
'Get the coordinates of the form and the message box so that
'you can determine where the center of the form is located
GetWindowRect Form1.hwnd, rectForm
GetWindowRect wParam, rectMsg
x = (rectForm.Left + (rectForm.Right - rectForm.Left) / 2) - _
((rectMsg.Right - rectMsg.Left) / 2)
y = (rectForm.Top + (rectForm.Bottom - rectForm.Top) / 2) - _
((rectMsg.Bottom - rectMsg.Top) / 2)
'Position the msgbox
SetWindowPos wParam, 0, x, y, 0, 0, _
SWP_NOSIZE Or SWP_NOZORDER Or SWP_NOACTIVATE
'Release the CBT hook
UnhookWindowsHookEx hHook
End If
WinProc = False
End Function
3.Add two CommandButtons to Form1.
4.Add the following code to Form1:
Private Sub Command1_Click()
Dim hInst As Long
Dim Thread As Long
'Set up the CBT hook
hInst = GetWindowLong(Me.hwnd, GWL_HINSTANCE)
Thread = GetCurrentThreadId()
hHook = SetWindowsHookEx(WH_CBT, AddressOf WinProc1, hInst, _
Thread)
'Display the message box
MsgBox "This message box has been positioned at (0,0)."
End Sub
Private Sub Command2_Click()
Dim hInst As Long
Dim Thread As Long
'Set up the CBT hook
hInst = GetWindowLong(Me.hwnd, GWL_HINSTANCE)
Thread = GetCurrentThreadId()
hHook = SetWindowsHookEx(WH_CBT, AddressOf WinProc2, hInst, _
Thread)
'Display the message box
MsgBox "This message box is centered over Form1."
End Sub
5.Press the F5 key to run the program. Click Command1 and the message box appears at the upper-left corner of the screen (0,0). Click OK to dismiss the message box. Click Command2 and the message box appears at the center of Form1. Click OK to dismiss the message box.
REFERENCES
For additional information about creating message hooks with Visual Basic, please see the following articles in the Microsoft Knowledge Base:
ARTICLE-ID: Q168795
TITLE : HOWTO: Hook Into a Window's Messages Using AddressOf
ARTICLE-ID: Q170570
TITLE : HOWTO: Build a Windows Message Handler with AddressOf in VB5
Last reviewed: February 13, 1998
Article ID: Q180936 The information in this article applies to:
•Microsoft Visual Basic Learning, Professional, and Enterprise Editions for Windows, version 5.0
SUMMARY
This article illustrates how to position a message box on the screen.
MORE INFORMATION
You can create a CBT hook for your application so that it receives notifications when windows are created and destroyed. If you display a message box with this CBT hook in place, your application will receive a HCBT_ACTIVATE message when the message box is activated. Once you receive this HCBT_ACTIVATE message, you can position the window with the SetWindowPos API function and then release the CBT hook if it is no longer needed.
Step-by-Step Example
1.Start a new Standard EXE project. Form1 is created by default.
2.Add a module to the project and add the following code to the new module:
Type RECT
Left As Long
Top As Long
Right As Long
Bottom As Long
End Type
Public Declare Function UnhookWindowsHookEx Lib "user32" ( _
ByVal hHook As Long) As Long
Public Declare Function GetWindowLong Lib "user32" Alias _
"GetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long) _
As Long
Public Declare Function GetCurrentThreadId Lib "kernel32" () As Long
Public Declare Function SetWindowsHookEx Lib "user32" Alias _
"SetWindowsHookExA" (ByVal idHook As Long, ByVal lpfn As Long, _
ByVal hmod As Long, ByVal dwThreadId As Long) As Long
Public Declare Function SetWindowPos Lib "user32" ( _
ByVal hwnd As Long, ByVal hWndInsertAfter As Long, _
ByVal x As Long, ByVal y As Long, ByVal cx As Long, _
ByVal cy As Long, ByVal wFlags As Long) As Long
Public Declare Function GetWindowRect Lib "user32" (ByVal hwnd _
As Long, lpRect As RECT) As Long
Public Const GWL_HINSTANCE = (-6)
Public Const SWP_NOSIZE = &H1
Public Const SWP_NOZORDER = &H4
Public Const SWP_NOACTIVATE = &H10
Public Const HCBT_ACTIVATE = 5
Public Const WH_CBT = 5
Public hHook As Long
Function WinProc1(ByVal lMsg As Long, ByVal wParam As Long, _
ByVal lParam As Long) As Long
If lMsg = HCBT_ACTIVATE Then
'Show the MsgBox at a fixed location (0,0)
SetWindowPos wParam, 0, 0, 0, 0, 0, _
SWP_NOSIZE Or SWP_NOZORDER Or SWP_NOACTIVATE
'Release the CBT hook
UnhookWindowsHookEx hHook
End If
WinProc = False
End Function
Function WinProc2(ByVal lMsg As Long, ByVal wParam As Long, _
ByVal lParam As Long) As Long
Dim rectForm As RECT, rectMsg As RECT
Dim x As Long, y As Long
'On HCBT_ACTIVATE, show the MsgBox centered over Form1
If lMsg = HCBT_ACTIVATE Then
'Get the coordinates of the form and the message box so that
'you can determine where the center of the form is located
GetWindowRect Form1.hwnd, rectForm
GetWindowRect wParam, rectMsg
x = (rectForm.Left + (rectForm.Right - rectForm.Left) / 2) - _
((rectMsg.Right - rectMsg.Left) / 2)
y = (rectForm.Top + (rectForm.Bottom - rectForm.Top) / 2) - _
((rectMsg.Bottom - rectMsg.Top) / 2)
'Position the msgbox
SetWindowPos wParam, 0, x, y, 0, 0, _
SWP_NOSIZE Or SWP_NOZORDER Or SWP_NOACTIVATE
'Release the CBT hook
UnhookWindowsHookEx hHook
End If
WinProc = False
End Function
3.Add two CommandButtons to Form1.
4.Add the following code to Form1:
Private Sub Command1_Click()
Dim hInst As Long
Dim Thread As Long
'Set up the CBT hook
hInst = GetWindowLong(Me.hwnd, GWL_HINSTANCE)
Thread = GetCurrentThreadId()
hHook = SetWindowsHookEx(WH_CBT, AddressOf WinProc1, hInst, _
Thread)
'Display the message box
MsgBox "This message box has been positioned at (0,0)."
End Sub
Private Sub Command2_Click()
Dim hInst As Long
Dim Thread As Long
'Set up the CBT hook
hInst = GetWindowLong(Me.hwnd, GWL_HINSTANCE)
Thread = GetCurrentThreadId()
hHook = SetWindowsHookEx(WH_CBT, AddressOf WinProc2, hInst, _
Thread)
'Display the message box
MsgBox "This message box is centered over Form1."
End Sub
5.Press the F5 key to run the program. Click Command1 and the message box appears at the upper-left corner of the screen (0,0). Click OK to dismiss the message box. Click Command2 and the message box appears at the center of Form1. Click OK to dismiss the message box.
REFERENCES
For additional information about creating message hooks with Visual Basic, please see the following articles in the Microsoft Knowledge Base:
ARTICLE-ID: Q168795
TITLE : HOWTO: Hook Into a Window's Messages Using AddressOf
ARTICLE-ID: Q170570
TITLE : HOWTO: Build a Windows Message Handler with AddressOf in VB5
ASKER
I ran the sample code - interesting and educational. However, like with the other sample, I'm unable to use it to solve my "count mouse click" problem. I've tried adding the GetDesktopWindow API, but to no avail. Any suggestions that will work? I'll understand if you want to give up. It's baffled me too. Thanks.
Have you tried it with the FindWindow api's?
BTW: The problem with the subclass method was that SetWindowLong does not function on windows that you don't own. The return value of SetWindowLong was 0 (which it shouldn't be). The GetLastError api returned 5, which means access denied. That's why I propose a hook and see if that works.
The FindWindow api's give you the hwnd of the listview that is located on top of the desktop.
The FindWindow api's give you the hwnd of the listview that is located on top of the desktop.
ASKER
Progress, sort of .... Using FindWindowEx returns a 6 for lMsg. If I minimize another window external to VB, it gives me a 9. A mouse-over the VB form, gives me back the 6 (why a mouse-over does this, I have no idea). Other mouse-clicks have no effect - which appears to me that I'm only picking up handles for events at the window level. I need to pick up mouse clicks regardless of where they occur - which includes within external windows. What do you recommend? Thanks.
Not much. Read about hooks maybe. I'm more or less out of options.
ASKER
Adjusted points to 20
ASKER