Solved

How do you detect desktop mouse clicks

Posted on 1998-11-12
19
207 Views
Last Modified: 2010-08-05
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.
0
Comment
Question by:cordes
  • 9
  • 9
19 Comments
 

Author Comment

by:cordes
ID: 1444510
Edited text of question
0
 
LVL 13

Expert Comment

by:Mirkwood
ID: 1444511
Subclassing is the answer here. Subclass the desktop window and trap the messages.
0
 
LVL 3

Expert Comment

by:vikiing
ID: 1444512
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.


0
 

Author Comment

by:cordes
ID: 1444513
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?
0
 
LVL 13

Accepted Solution

by:
Mirkwood earned 20 total points
ID: 1444514
Subclass the desktopwindow.
You retrieve the handle as follows.

Declare Function GetDesktopWindow Lib "user32" Alias "GetDesktopWindow" () As Long

Here is how to subclass

HOWTO: Build a Windows Message Handler with AddressOf in VB5

Last reviewed: October 10, 1997
Article ID: Q170570 The information in this article applies to:

•Microsoft Visual Basic Professional and Enterprise Editions for Windows, version 5.0 •Microsoft Visual Studio 97







SUMMARY

Prior to the release of Visual Basic 5.0, many developers used third-party Windows message-handling tools or developed their own with tools such as Microsoft Visual C++. With the addition of the AddressOf function to Visual Basic 5.0, developers can now create their own Windows message-handling routines within their Visual Basic applications.

For example, when a user right-clicks on a textbox in Windows 95 or Windows NT 4.0, the operating system automatically displays a default context menu. This default behavior occurs before the Visual Basic application fires the MouseUp event. Without the use of a Windows message handler, there is no way to replace the default context menu with a custom built context menu.





MORE INFORMATION

WARNING: Using AddressOf may cause unpredictable results if you don't completely understand the concept of function callbacks. You must understand how the Basic portion of the callback works, and also the code of the DLL into which you are passing your function address. Debugging such interactions is difficult because the program runs in the same process as the development environment. In some cases, systematic debugging may not be possible. See details in the REFERENCES section of this article for more information.

The following sample shows how to build a Windows message handler to trap and discard the right-click message to replace the default context menu with a custom built one:



1.Create a new Standard EXE project. Form1 is created by default.

2.Use the Menu Editor to add two menu items to the form:





    - For the first item, set its Caption property to "My Popup," its Name
      property to "mnuPopup," and its Visible property to "False."







    - For the second item, set its Caption property to "My Context," its
      Name property to "mnuPopupContext," and use the arrow keys to indent
      the item to be a submenu of the first item.





3.Add two CommandButtons and a textbox to the form:





    - For the first CommandButton, set its Name property to "cmdHook" and
      its Caption property to "&Hook."







    - For the second CommandButton, set its Name property to "cmdUnHook"
      and its Caption property to "&UnHook."







    - For the textbox, set its Name property to "txtHook" and its Text
      property to "Right Click On Me!"





4.Add the following code to the form:





      Private Sub cmdHook_Click()

          Hook
      End Sub

      Private Sub cmdUnHook_Click()
          UnHook
      End Sub

      Private Sub Form_Load()
          gHW = txtHook.hWnd
      End Sub

      Private Sub txtHook_MouseUp(Button As Integer, Shift As Integer, _
                                  X As Single, Y As Single)
          If Button = vbRightButton Then
              PopupMenu mnuPopup
          End If
      End Sub






5.Add a Module to the project.

6.Add the following code to the new Module:





    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

    Public Const GWL_WNDPROC = -4

    Public Const WM_RBUTTONUP = &H205

    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

        Select Case uMsg
            Case WM_RBUTTONUP
                Form1.PopupMenu Form1.mnuPopup
            Case Else
                WindowProc = CallWindowProc(lpPrevWndProc, hw, _
                                           uMsg, wParam, lParam)
        End Select
    End Function






7.Save the project and run it. Right-click on the textbox and notice that the default context menu appears before the custom menu. Click on the CommandButton marked "Hook" to enable the Windows message handler. Right-click on the textbox and note that the default context menu no longer appears. Be sure to click on the CommandButton marked "UnHook" before quitting the application. Always disable a custom Windows message handler before the application terminates. Clicking the End button while this sample is running with the Windows message handler still enabled will cause an IPF.







REFERENCES

This as an advanced topic for users experienced with using callbacks, typically C developers. If you are not familiar with the use of callbacks, then the following references may apply:

The Visual Basic 5.0 Programmers Guide to the Win32 API by Dan Appleman, ISBN 1-56276-446-2 published by Ziff-Davis

The Win32 SDK Online Help

(c) Microsoft Corporation 1997, All Rights Reserved. Contributions by David Sceppa, Microsoft Corporation
0
 

Author Comment

by:cordes
ID: 1444515
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.
0
 
LVL 13

Expert Comment

by:Mirkwood
ID: 1444516
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 =)
0
 

Author Comment

by:cordes
ID: 1444517
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.  


0
 
LVL 13

Expert Comment

by:Mirkwood
ID: 1444518
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

0
Threat Intelligence Starter Resources

Integrating threat intelligence can be challenging, and not all companies are ready. These resources can help you build awareness and prepare for defense.

 

Author Comment

by:cordes
ID: 1444519
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.  
0
 
LVL 13

Expert Comment

by:Mirkwood
ID: 1444520
Pass this window handle to the hook class as described before.
0
 

Author Comment

by:cordes
ID: 1444521
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


0
 
LVL 13

Expert Comment

by:Mirkwood
ID: 1444522
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
0
 

Author Comment

by:cordes
ID: 1444523
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.    
0
 
LVL 13

Expert Comment

by:Mirkwood
ID: 1444524
Have you tried it with the FindWindow api's?
0
 
LVL 13

Expert Comment

by:Mirkwood
ID: 1444525
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.
0
 

Author Comment

by:cordes
ID: 1444526
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.
0
 
LVL 13

Expert Comment

by:Mirkwood
ID: 1444527
Not much. Read about hooks maybe. I'm more or less out of options.
0
 

Author Comment

by:cordes
ID: 1444528
Adjusted points to 20
0

Featured Post

Threat Intelligence Starter Resources

Integrating threat intelligence can be challenging, and not all companies are ready. These resources can help you build awareness and prepare for defense.

Join & Write a Comment

Suggested Solutions

Introduction I needed to skip over some file processing within a For...Next loop in some old production code and wished that VB (classic) had a statement that would drop down to the end of the current iteration, bypassing the statements that were c…
You can of course define an array to hold data that is of a particular type like an array of Strings to hold customer names or an array of Doubles to hold customer sales, but what do you do if you want to coordinate that data? This article describes…
Get people started with the process of using Access VBA to control Excel using automation, Microsoft Access can control other applications. An example is the ability to programmatically talk to Excel. Using automation, an Access application can laun…
Get people started with the utilization of class modules. Class modules can be a powerful tool in Microsoft Access. They allow you to create self-contained objects that encapsulate functionality. They can easily hide the complexity of a process from…

762 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

19 Experts available now in Live!

Get 1:1 Help Now