2009-11-15 136 views
0

我有一个窗体显示,而不是ShowDialog,但通过将其可见属性设置为true。这就像是一个下拉菜单。NET和Windows窗体中鼠标钩的奇怪行为

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

我检测鼠标是否在下拉菜单之外单击,如果是,请在我的HookProc方法中返回1,然后关闭下拉菜单。

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

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

任何想法是怎么回事?

ETA 2:

你可以忽略我下面的所有代码,因为,在进一步的调查中,我发现,这种行为是在实现一个下拉型窗体至少一个框架控制展出。

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

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

发生了什么事?

ETA 1:

下面是How to set a Windows hook in Visual C# .NET一些裸机代码来说明这是怎么回事。我用转换器将代码转换回C#,但希望它没问题。我不确定,但您可能需要用Debug.WriteLine替换Console.WriteLine

创建两个表单Form1DropDown

(1)VB.NET

Form1,添加一个按钮,标签和文本框,和下面的代码。

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中添加一个按钮,标签和文本框和以下代码:

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"); 
    } 
} 

在下拉菜单中,放置以下代码:

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); 
     } 
    } 
} 

回答

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。

+0

嗨,谢谢你。我从来没有听说过IMessageFilter,它似乎照顾着我。我尝试用IMessageFilter替换现有的代码,但遇到了问题。为了忽略与我的下拉式无关的事件,我在我的下拉列表中激活和取消激活事件中使用了Hook和UnHook。但是,在PreFilterMessage之前会激活停用。使用PreFilterMessage,我可以确定被点击的控件以及它属于哪种形式。如何确定该表单是否在我的下拉菜单后创建? – Jules 2009-11-15 17:41:02

+0

我可能有办法做到这一点。首先,我会在显示我的下拉列表之前列出所有打开的表单,在PreFiltureMessage中,如果点击的控件属于已保存集合中的表单,则我处理点击并关闭下拉菜单。否则,我忽略。 – Jules 2009-11-15 18:36:59

+0

忽略我上面的动作。我决定坚持使用我的钩子而不是IMessageFilter,并且添加了鼠标事件过滤器已经解决了我的问题。再次感谢。 – Jules 2009-11-16 14:13:09