如何在设置 MouseHook 时检测鼠标点击事件?
How to detect a mouseclick event while MouseHook is set?
我正在创建一个工具,可以抓取屏幕上任何内容的颜色,无论是在表单内部还是表单外部。用户将使用鼠标光标通过单击来抓取屏幕上的任何像素。问题是,根据他们点击的内容,它可能会点击一个按钮或 select 并突出显示一个图标。为了解决这个问题,我使用了 SetWindowsHookEx() 方法来全局禁用 mouseclick 事件。这行得通。但现在的问题是我的应用程序无法检测到点击事件已经发生。我曾尝试在不同的位置和不同的时刻解开鼠标,但它要么使鼠标处于禁用状态,要么重新启用鼠标,并且仍然会按照上面提到的不需要的点击进行操作。
在用户可以抓取像素颜色的时间内,计时器为运行。很明显,我别无选择,只能在 "pixel grabbing mode" 中保持鼠标松开,以便继续抓取像素颜色。我试过设置鼠标挂钩,然后立即将其取下,但这也不起作用。这似乎是一个陷阱 22。当然,我有一个用于 ESC 和 ENTER 键的键盘事件,作为停止计时器和松开鼠标的替代方法。 "canceling" 操作的 ESC 键和抓取像素颜色和停止计时器的 ENTER 键。但我希望 mouseclick 执行相同的功能而无需 clicking/activating 其他任何东西。这是我的代码。
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
或者,我也想过用不同的方法来解决这个问题。在此像素颜色抓取模式下,鼠标光标悬停在辅助窗体上的图片框上。表单跟随鼠标光标并保持在表单的死点。它悬停的图片框有 Lime 的背景色,这是表单的透明键,因此使其透明,允许用户看到表单后面的屏幕并抓取鼠标光标指向的像素颜色。
所以有了这个已知的,也许我可以以某种方式让它表现得好像它点击了图片框而不是它后面的东西。但我还没有找到解决方案。我对您可能拥有的任何方法持开放态度。谢谢你。我考虑过使图片框半透明,使其透明但不完全透明,因为它会阻止任何点击,但这会使它抓取的像素颜色不准确。我曾经制作过一个应用程序,其中无边界表单在整个屏幕上最大化,其不透明度调整为 "screen brightness filter" 并允许点击它,但这与我在这里尝试做的相反.所以我想知道。
我认为你把事情搞得太复杂了......你已经有了一个鼠标钩子来检测鼠标点击,所以不要在钩子回调中只在 WM_LBUTTONDOWN
上返回 1,而是做你的 "get color"-里面有逻辑。
例如,创建一个获取特定位置颜色的函数:
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
然后你需要在你的钩子中做的就是调用函数并对结果颜色做一些事情:
If wParam.ToInt32() = WM_LBUTTONDOWN Then
Dim PickedColor As Color = GetScreenPixelColor(Cursor.Position)
'Do something with PickedColor...
UnHookMouse()
Return 1
End If
如果您要在多个场景中应用此逻辑,只需创建另一种方法即可为您完成所有工作:
Private Sub PickColor()
Dim PickedColor As Color = GetScreenPixelColor(Cursor.Position)
'Do something with PickedColor...
End Sub
然后在你的钩子中你需要做的就是:
If wParam.ToInt32() = WM_LBUTTONDOWN Then
PickColor()
UnHookMouse()
Return 1
End If
以为我会根据 Visual Vincent 给出的答案添加我的解决方案。
我创建了一个执行获取像素颜色方法的函数:
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
然后调用mousehook函数中的函数:
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
我正在创建一个工具,可以抓取屏幕上任何内容的颜色,无论是在表单内部还是表单外部。用户将使用鼠标光标通过单击来抓取屏幕上的任何像素。问题是,根据他们点击的内容,它可能会点击一个按钮或 select 并突出显示一个图标。为了解决这个问题,我使用了 SetWindowsHookEx() 方法来全局禁用 mouseclick 事件。这行得通。但现在的问题是我的应用程序无法检测到点击事件已经发生。我曾尝试在不同的位置和不同的时刻解开鼠标,但它要么使鼠标处于禁用状态,要么重新启用鼠标,并且仍然会按照上面提到的不需要的点击进行操作。
在用户可以抓取像素颜色的时间内,计时器为运行。很明显,我别无选择,只能在 "pixel grabbing mode" 中保持鼠标松开,以便继续抓取像素颜色。我试过设置鼠标挂钩,然后立即将其取下,但这也不起作用。这似乎是一个陷阱 22。当然,我有一个用于 ESC 和 ENTER 键的键盘事件,作为停止计时器和松开鼠标的替代方法。 "canceling" 操作的 ESC 键和抓取像素颜色和停止计时器的 ENTER 键。但我希望 mouseclick 执行相同的功能而无需 clicking/activating 其他任何东西。这是我的代码。
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
或者,我也想过用不同的方法来解决这个问题。在此像素颜色抓取模式下,鼠标光标悬停在辅助窗体上的图片框上。表单跟随鼠标光标并保持在表单的死点。它悬停的图片框有 Lime 的背景色,这是表单的透明键,因此使其透明,允许用户看到表单后面的屏幕并抓取鼠标光标指向的像素颜色。
所以有了这个已知的,也许我可以以某种方式让它表现得好像它点击了图片框而不是它后面的东西。但我还没有找到解决方案。我对您可能拥有的任何方法持开放态度。谢谢你。我考虑过使图片框半透明,使其透明但不完全透明,因为它会阻止任何点击,但这会使它抓取的像素颜色不准确。我曾经制作过一个应用程序,其中无边界表单在整个屏幕上最大化,其不透明度调整为 "screen brightness filter" 并允许点击它,但这与我在这里尝试做的相反.所以我想知道。
我认为你把事情搞得太复杂了......你已经有了一个鼠标钩子来检测鼠标点击,所以不要在钩子回调中只在 WM_LBUTTONDOWN
上返回 1,而是做你的 "get color"-里面有逻辑。
例如,创建一个获取特定位置颜色的函数:
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
然后你需要在你的钩子中做的就是调用函数并对结果颜色做一些事情:
If wParam.ToInt32() = WM_LBUTTONDOWN Then
Dim PickedColor As Color = GetScreenPixelColor(Cursor.Position)
'Do something with PickedColor...
UnHookMouse()
Return 1
End If
如果您要在多个场景中应用此逻辑,只需创建另一种方法即可为您完成所有工作:
Private Sub PickColor()
Dim PickedColor As Color = GetScreenPixelColor(Cursor.Position)
'Do something with PickedColor...
End Sub
然后在你的钩子中你需要做的就是:
If wParam.ToInt32() = WM_LBUTTONDOWN Then
PickColor()
UnHookMouse()
Return 1
End If
以为我会根据 Visual Vincent 给出的答案添加我的解决方案。
我创建了一个执行获取像素颜色方法的函数:
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
然后调用mousehook函数中的函数:
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