Search code examples
vb.netdisabled-inputmouse-hook

How to detect a mouseclick event while MouseHook is set?


I am creating a tool that grabs the color of anything on the screen, whether it be inside the form or even outside the form. The user will use their mouse cursor to grab any pixel on the screen by clicking on it. The problem is that depending on what they click, it may click a button or select and highlight an icon. To get around this, I have used the SetWindowsHookEx() method to disable the mouseclick event globally. This works. But the problem now is my application cannot detect that the click event has happened. I have tried unhooking the mouse in various locations and at various moments but it will either leave the mouse disabled or re-enable the mouse and still follow through with the unwanted clicking as mentioned above.

During the time in which the user can grab a pixel color, a timer is running. So obviously, I would have no choice but to keep the mouse unhooked while in "pixel grabbing mode" in order to follow through with grabbing the pixel color. I have tried setting the mousehook and then unhooking it right after but that does not work either. This seems to be a catch 22. Of course, I have a keyboard event for the ESC and ENTER keys as an alternative to stop the timer and unhook the mouse. The ESC key for "canceling" the operation and the ENTER key for grabbing the pixel color and stopping the timer. But I want the mouseclick to carry out the same function without clicking/activating anything else. Here is my code.

         Private Structure MSLLHOOKSTRUCT
                    Public pt As Point
                    Public mouseData As Int32
                    Public flags As Int32
                    Public time As Int32
                    Public extra As IntPtr
                End Structure

                Private mHook As IntPtr = IntPtr.Zero
                Private Const WH_MOUSE_LL As Int32 = &HE
                Private Const WM_RBUTTONDOWN As Int32 = &H204
                Private Const WM_LBUTTONDOWN As Int32 = &H201
                <MarshalAs(UnmanagedType.FunctionPtr)> Private mProc As MouseHookDelegate
                Private Declare Function SetWindowsHookExW Lib "user32.dll" (ByVal idHook As Int32, ByVal HookProc As MouseHookDelegate, ByVal hInstance As IntPtr, ByVal wParam As Int32) As IntPtr
                Private Declare Function UnhookWindowsHookEx Lib "user32.dll" (ByVal hook As IntPtr) As Boolean
                Private Declare Function CallNextHookEx Lib "user32.dll" (ByVal idHook As Int32, ByVal nCode As Int32, ByVal wParam As IntPtr, ByRef lParam As MSLLHOOKSTRUCT) As Int32
                Private Declare Function GetModuleHandleW Lib "kernel32.dll" (ByVal fakezero As IntPtr) As IntPtr
                Private Delegate Function MouseHookDelegate(ByVal nCode As Int32, ByVal wParam As IntPtr, ByRef lParam As MSLLHOOKSTRUCT) As Int32

                Public Function SetHookMouse() As Boolean
                    If mHook = IntPtr.Zero Then
                        mProc = New MouseHookDelegate(AddressOf MouseHookProc)
                        mHook = SetWindowsHookExW(WH_MOUSE_LL, mProc, GetModuleHandleW(IntPtr.Zero), 0)
                    End If
                    Return mHook <> IntPtr.Zero
                End Function

                Public Sub UnHookMouse()
                    If mHook = IntPtr.Zero Then Return
                    UnhookWindowsHookEx(mHook)
                    mHook = IntPtr.Zero
                End Sub

                Private Function MouseHookProc(ByVal nCode As Int32, ByVal wParam As IntPtr, ByRef lParam As MSLLHOOKSTRUCT) As Int32
                    'Label1.Text = "Message=" & wParam.ToInt32.ToString & "  X=" & lParam.pt.X.ToString & "  Y=" & lParam.pt.Y.ToString
                    If wParam.ToInt32 = WM_LBUTTONDOWN Then
                        Return 1
                    End If

                    Return CallNextHookEx(WH_MOUSE_LL, nCode, wParam, lParam)
                End Function



        Private Sub Timer2_Tick(sender As Object, e As EventArgs) Handles Timer2.Tick

Using bmp2 As New Bitmap(1, 1)
                Using g As Graphics = Graphics.FromImage(bmp2)
                    g.CopyFromScreen(Cursor.Position,
                                          New Point(0, 0), New Size(1, 1))
                End Using

                firsty = bmp2.GetPixel(0, 0).ToArgb
                ToolStripButton7.BackColor = Color.FromArgb(firsty)
                Form4.PictureBox1.BackColor = Color.FromArgb(firsty)
                Form4.PictureBox5.BackColor = Color.FromArgb(firsty)
                Form4.PictureBox4.BackColor = Color.FromArgb(firsty)
                Form4.PictureBox3.BackColor = Color.FromArgb(firsty)
                Panel3.BackColor = Color.FromArgb(firsty)
                Me.Invalidate()

                Dim CodeCodeInHex As String = bmp2.GetPixel(0, 0).ToArgb().ToString("X")
                CodeCodeInHex = CodeCodeInHex.Remove(0, 2)
                BGCOLOR.Text = "#" & CodeCodeInHex
                Form4.Label1.Text = "#" & CodeCodeInHex
                Label3.Text = "#" & CodeCodeInHex
            End Using


SetHookMouse()
UnHookMouse()
    Select Case MouseButtons
                    Case MouseButtons.Left

                        Cursor = Cursors.Default
                        Form4.Close()
                        SplitContainer3.SplitterDistance = SplitContainer3.Width - SPLITC3
                        Me.TopMost = True

                        ColorDialog1.Color = Color.FromArgb(firsty)
                        ToolStripTextBox1.Text = ColorDialog1.Color.A & ", " & ColorDialog1.Color.R & ", " & ColorDialog1.Color.G & ", " & ColorDialog1.Color.B
                        Panel2.Visible = False
                        If CSSToolbarToolStripMenuItem.Checked = True Then
                        Else
                            SplitContainer3.Panel2Collapsed = True
                        End If
                        Timer2.Stop()
                        Me.TopMost = False

                    Case MouseButtons.Right

                    Case MouseButtons.Middle

                    Case MouseButtons.Left + MouseButtons.Right

                End Select
        End Sub

Alternatively, I have also thought about going a different way to solve this problem. The mouse cursor is hovering over a picturebox on a secondary form while in this pixel color grabbing mode. The form follows the mouse cursor and keeps it dead center of the form. The picturebox which it is hovering has a backcolor of Lime which is the transparency key for the form, thus making it see-through, allowing the user to see the screen behind the form and grabbing the pixel color that the mouse cursor is pointing to.

So with that known, perhaps I could somehow make it act as if it clicks the picturebox and not what is behind it. But I have not yet found a solution to that either. I am open to any methods that you may have. Thank you. I have thought about making the picturebox translucent, to where it is see-through but not completely, as it would prevent any click-throughs but then that would make the pixel color it grabs inaccurate. I have once made an application where a borderless form is maximized across the entire screen with it's opacity adjusted to make a "screen brightness filter" and allowed for it to be clicked-through but that is the opposite of what I am trying to do here. So I wonder.


Solution

  • I think you are overcomplicating things... You already have a mouse hook which detects mouse clicks, so instead of only returning 1 on WM_LBUTTONDOWN in the hook callback, do your "get color"-logic in there.

    For instance, create a function that gets the color at a certain position:

    Private Function GetScreenPixelColor(ByVal Position As Point) As Color
        Using bmp As New Bitmap(1, 1)
            Using g As Graphics = Graphics.FromImage(bmp)
                 g.CopyFromScreen(Position, New Point(0, 0), New Size(1, 1))
            End Using
    
            Return bmp.GetPixel(0, 0)
        End Using
    End Function
    

    Then all you need to do in your hook is to call the function and do something with the resulting color:

    If wParam.ToInt32() = WM_LBUTTONDOWN Then
        Dim PickedColor As Color = GetScreenPixelColor(Cursor.Position)
    
        'Do something with PickedColor...
    
        UnHookMouse()
        Return 1
    End If
    

    If you are to apply this logic in multiple scenarios just make yet another method that does everything for you:

    Private Sub PickColor()
        Dim PickedColor As Color = GetScreenPixelColor(Cursor.Position)
    
        'Do something with PickedColor...
    End Sub
    

    Then in your hook all you need to do is:

    If wParam.ToInt32() = WM_LBUTTONDOWN Then
        PickColor()
        UnHookMouse()
        Return 1
    End If