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.
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