0

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.

BuddyRoach
  • 162
  • 1
  • 19

2 Answers2

1

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
Visual Vincent
  • 18,045
  • 5
  • 28
  • 75
  • Holy crap. It worked. Thank you! I didn't realize the mousehook function was capturing mouse events. Thank you so much!!!! – BuddyRoach Jun 15 '18 at 10:52
  • @BuddyRoach : That's what hooks are for! ;) Glad I could help! – Visual Vincent Jun 15 '18 at 10:55
  • Is there a mouse_hover for the hook? – BuddyRoach Jun 15 '18 at 12:43
  • @BuddyRoach : Unfortunately no. You'd have to construct your own solution for that. You can use a timer set to 400 ms (default mouse hover time), and if the mouse hook detects movement stop and restart the timer. If the timer `Tick`s, stop it and do whatever you want as the mouse has then hovered for 400 ms. – Visual Vincent Jun 15 '18 at 16:38
  • @BuddyRoach : Have a look at my [InputHelper library](https://github.com/Visual-Vincent/InputHelper) (compiled download [here](https://github.com/Visual-Vincent/InputHelper/releases)) for an easy-to-use mouse hook. My project's wiki describes how it works: https://github.com/Visual-Vincent/InputHelper/wiki/Low-level-mouse-hook – Visual Vincent Jun 15 '18 at 16:41
  • damn. i was wanting to temporarily disable the hovering too so icons and buttons would not highlight and changed color when hovered. In case the user wanted to get the color of something in it's unhovered state. But also add the option to toggle this so they can also get the color of something in it's hovered state, depending on what the user needed. I found this: https://autohotkey.com/docs/misc/SendMessageList.htm and it appears to have the hex for the mouse hover but that one doesnt seem to do anything like the others. – BuddyRoach Jun 15 '18 at 19:17
  • @BuddyRoach : The hook only receives certain mouse messages (documented [here](https://msdn.microsoft.com/en-us/library/windows/desktop/ms644986(v=vs.85).aspx) under _wParam_, plus the missing scroll and X-button messages), so using others won't work. I suppose a workaround would be to cover the screen(s) with a form that has its `Opacity` set to 0.01 (which will cause it to receive all mouse events), move the mouse away and _then_ hide the form, pick the color and then move the mouse back again. _Or_ you could just take a screenshot when the user begins the "pick" mode. – Visual Vincent Jun 15 '18 at 21:23
0

Thought I would add my solution based on the answer given by Visual Vincent.

I created a function that carried out the get pixel color method:

Private Sub getcolor()
        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
    End Sub

Then called the function in the mousehook function:

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
            getcolor()
            Return 1
        End If
        Return CallNextHookEx(WH_MOUSE_LL, nCode, wParam, lParam)
    End Function
BuddyRoach
  • 162
  • 1
  • 19