【问题标题】:Strange behaviour with mouse hook in .NET and Windows Forms.NET 和 Windows 窗体中鼠标钩子的奇怪行为
【发布时间】:2012-02-16 06:26:25
【问题描述】:

我有一个显示的表单,不是通过 ShowDialog,而是通过将其 visible 属性设置为 true。这样它的行为就像一个下拉菜单。

表单安装鼠标钩子,使用SetWindowsHookEx(WH_MOUSE, ...)

我检测鼠标是否在下拉列表之外被点击,如果是,则在我的HookProc 方法中返回 1 并关闭下拉列表。

奇怪的是,如果我在下拉菜单之外点击文本框,在我的下拉菜单关闭后,文本框仍然会收到鼠标点击,即使它是由我的 HookProc 方法处理的。

它变得陌生...如果我点击一个标签或按钮,他们不会像预期的那样在下拉关闭后收到鼠标点击!

知道发生了什么吗?

预计到达时间 2:

您可以忽略我下面的所有代码,因为经过进一步调查,我发现这种行为至少在一个实现下拉类型表单的框架控件中表现出来。

要复制,创建一个表单并添加一个属性网格、按钮、文本框和标签。将属性网格的选定对象设置为字体。

运行表单并选择字体名称。出现一个下拉列表。现在单击表单的文本框。触发文本框单击事件。但是,按钮或标签不会发生同样的情况。

发生了什么事?

预计到达时间 1:

这里有一些来自 How to set a Windows hook in Visual C# .NET 的基本代码来演示发生了什么。我使用转换器将代码转换回 C#,但希望没问题。我不确定,但您可能需要将 Console.WriteLine 替换为 Debug.WriteLine

创建两个表单,Form1DropDown

(1) VB.NET

Form1中,添加Button、Label和TextBox,代码如下。

Imports System.Runtime.InteropServices

Public Class Form1

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        Console.WriteLine("Button1_Click")
        Dim dd As New DropDown
        dd.Visible = True
        Do While dd.Visible
            Application.DoEvents()
            MsgWaitForMultipleObjectsEx(0, IntPtr.Zero, 250, &HFF, 4)
        Loop
    End Sub

    <DllImport("user32.dll", CharSet:=CharSet.Auto, ExactSpelling:=True)> _
    Public Shared Function MsgWaitForMultipleObjectsEx(ByVal nCount As Integer, ByVal pHandles As IntPtr, ByVal dwMilliseconds As Integer, ByVal dwWakeMask As Integer, ByVal dwFlags As Integer) As Integer
    End Function

    Private Sub Label1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Label1.Click
        Console.WriteLine("Label1_Click")
    End Sub

    Private Sub TextBox1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles TextBox1.Click
        Console.WriteLine("TextBox1_Click")
    End Sub

End Class

在 DropDown 中,放置以下代码。

Imports System.Runtime.InteropServices

Public Class DropDown

    Public Delegate Function HookProc(ByVal nCode As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As Integer

    Private hHook As Integer = 0
    Public Const WH_MOUSE As Integer = 7
    Private MouseHookProcedure As HookProc

    <StructLayout(LayoutKind.Sequential)> _
    Public Class POINT
        Public x As Integer
        Public y As Integer
    End Class

    <StructLayout(LayoutKind.Sequential)> _
    Public Class MouseHookStruct
        Public pt As POINT
        Public hwnd As Integer
        Public wHitTestCode As Integer
        Public dwExtraInfo As Integer
    End Class

    <DllImport("user32.dll", CharSet:=CharSet.Auto, CallingConvention:=CallingConvention.StdCall)> _
    Public Shared Function SetWindowsHookEx(ByVal idHook As Integer, ByVal lpfn As HookProc, ByVal hInstance As IntPtr, ByVal threadId As Integer) As Integer
    End Function

    <DllImport("user32.dll", CharSet:=CharSet.Auto, CallingConvention:=CallingConvention.StdCall)> _
    Public Shared Function UnhookWindowsHookEx(ByVal idHook As Integer) As Boolean
    End Function

    <DllImport("user32.dll", CharSet:=CharSet.Auto, CallingConvention:=CallingConvention.StdCall)> _
    Public Shared Function CallNextHookEx(ByVal idHook As Integer, ByVal nCode As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As Integer
    End Function

    Protected Overrides Sub OnDeactivate(ByVal e As System.EventArgs)
        MyBase.OnDeactivate(e)
        UnhookWindowsHookEx(hHook)
        hHook = 0
    End Sub

    Public Sub New()
        InitializeComponent()
        MouseHookProcedure = New HookProc(AddressOf MouseHookProc)
        hHook = SetWindowsHookEx(WH_MOUSE, MouseHookProcedure, New IntPtr(0), AppDomain.GetCurrentThreadId())
    End Sub

    Public Function MouseHookProc(ByVal nCode As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As Integer
        Dim MyMouseHookStruct As MouseHookStruct = DirectCast(Marshal.PtrToStructure(lParam, GetType(MouseHookStruct)), MouseHookStruct)
        If nCode < 0 Then
            Return CallNextHookEx(hHook, nCode, wParam, lParam)
        Else
            Select Case CInt(wParam)
                Case &H21, &HA1, &HA4, &H204, &H207, &HA7, &H201
                    Me.Visible = False
                    Return 1
            End Select
            Return CallNextHookEx(hHook, nCode, wParam, lParam)
        End If
    End Function

End Class

(2) C#

在Form1中添加一个Button、Label和TextBox,代码如下:

using System.Runtime.InteropServices;

public class Form1
{
    public Form1()
    {
        InitializeComponent();
        Button1.Click += Button1_Click;
        Label1.Click += Label1_Click;
        TextBox1.Click += TextBox1_Click;
    }

    private void Button1_Click(System.Object sender, System.EventArgs e)
    {
        Console.WriteLine("Button1_Click");
        DropDown dd = new DropDown();
        dd.Visible = true;
        while (dd.Visible) {
            Application.DoEvents();
            MsgWaitForMultipleObjectsEx(0, IntPtr.Zero, 250, 0xff, 4);
        }
    }

    [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
    public static extern int MsgWaitForMultipleObjectsEx(int nCount, IntPtr pHandles, int dwMilliseconds, int dwWakeMask, int dwFlags);

    private void Label1_Click(object sender, System.EventArgs e)
    {
        Console.WriteLine("Label1_Click");
    }

    private void TextBox1_Click(object sender, System.EventArgs e)
    {
        Console.WriteLine("TextBox1_Click");
    }
}

在 DropDown 中,放置以下代码:

using System.Runtime.InteropServices;

public class DropDown
{
    public delegate int HookProc(int nCode, IntPtr wParam, IntPtr lParam);

    private int hHook = 0;
    public const int WH_MOUSE = 7;
    private HookProc MouseHookProcedure;

    [StructLayout(LayoutKind.Sequential)]
    public class POINT
    {
        public int x;
        public int y;
    }

    [StructLayout(LayoutKind.Sequential)]
    public class MouseHookStruct
    {
        public POINT pt;
        public int hwnd;
        public int wHitTestCode;
        public int dwExtraInfo;
    }

    [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
    public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);

    [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
    public static extern bool UnhookWindowsHookEx(int idHook);

    [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
    public static extern int CallNextHookEx(int idHook, int nCode, IntPtr wParam, IntPtr lParam);

    protected override void OnDeactivate(System.EventArgs e)
    {
        base.OnDeactivate(e);
        UnhookWindowsHookEx(hHook);
        hHook = 0;
    }

    public DropDown()
    {
        InitializeComponent();
        MouseHookProcedure = new HookProc(MouseHookProc);
        hHook = SetWindowsHookEx(WH_MOUSE, MouseHookProcedure, new IntPtr(0), AppDomain.GetCurrentThreadId());
    }

    public int MouseHookProc(int nCode, IntPtr wParam, IntPtr lParam)
    {
        MouseHookStruct MyMouseHookStruct = (MouseHookStruct)Marshal.PtrToStructure(lParam, typeof(MouseHookStruct));
        if (nCode < 0) {
            return CallNextHookEx(hHook, nCode, wParam, lParam);
        }
        else {
            switch ((int)wParam) {
                case 0x21:
                case 0xa1:
                case 0xa4:
                case 0x204:
                case 0x207:
                case 0xa7:
                case 0x201:
                    this.Visible = false;
                    return 1;
            }
            return CallNextHookEx(hHook, nCode, wParam, lParam);
        }
    }
}

【问题讨论】:

    标签: .net vb.net winforms mouse-hook


    【解决方案1】:

    这个问题是因为您过滤了鼠标按下消息而不是鼠标按下消息。你可以这样修复它:

      Select Case CInt(wParam)
        Case &HA1, &HA4, &HA7, &H201, &H204, &H207
          Me.Capture = True
        Case &HA2, &hA5, &HA8, &H202, &H205, &H208
          Me.Visible = False
      End Select
    

    考虑改为实现 IMessageFilter。

    【讨论】:

    • 嗨,谢谢。我从未听说过 IMessageFilter 似乎可以为我解决问题。我试图用 IMessageFilter 替换我现有的代码,但遇到了问题。为了忽略与我的下拉菜单无关的事件,我曾经在我的下拉菜单激活和停用事件中挂钩和取消挂钩。但是,在 PreFilterMessage 之前触发 Deactivate。使用 PreFilterMessage,我可以确定被单击的控件及其所属的窗体。如何确定该表单是否是在我的下拉菜单之后创建的?
    • 我可能有办法。首先,我在显示下拉列表之前列出所有打开的表单,在 PreFiltureMessage 中,如果单击的控件属于已保存集合中的表单,则我处理单击并关闭我的下拉列表。否则,我忽略。
    • 忽略我上面的胡言乱语。我决定坚持使用我的钩子而不是 IMessageFilter,并且添加鼠标事件过滤器解决了我的问题。再次感谢。
    猜你喜欢
    • 2012-08-20
    • 2015-04-05
    • 1970-01-01
    • 1970-01-01
    • 2021-04-14
    • 1970-01-01
    • 2012-02-01
    • 1970-01-01
    • 2012-08-28
    相关资源
    最近更新 更多