Link to home
Start Free TrialLog in
Avatar of cordes
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.
Avatar of cordes
cordes

ASKER

Edited text of question
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.


Avatar of cordes

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?
ASKER CERTIFIED SOLUTION
Avatar of Mirkwood
Mirkwood

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of cordes

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 =)
Avatar of cordes

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

Avatar of cordes

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.
Avatar of cordes

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(lpPrevWndProc, 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


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
Avatar of cordes

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.
Avatar of cordes

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.
Avatar of cordes

ASKER

Adjusted points to 20