我有一个窗体显示,而不是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
。
创建两个表单Form1
和DropDown
。
(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);
}
}
}
嗨,谢谢你。我从来没有听说过IMessageFilter,它似乎照顾着我。我尝试用IMessageFilter替换现有的代码,但遇到了问题。为了忽略与我的下拉式无关的事件,我在我的下拉列表中激活和取消激活事件中使用了Hook和UnHook。但是,在PreFilterMessage之前会激活停用。使用PreFilterMessage,我可以确定被点击的控件以及它属于哪种形式。如何确定该表单是否在我的下拉菜单后创建? – Jules 2009-11-15 17:41:02
我可能有办法做到这一点。首先,我会在显示我的下拉列表之前列出所有打开的表单,在PreFiltureMessage中,如果点击的控件属于已保存集合中的表单,则我处理点击并关闭下拉菜单。否则,我忽略。 – Jules 2009-11-15 18:36:59
忽略我上面的动作。我决定坚持使用我的钩子而不是IMessageFilter,并且添加了鼠标事件过滤器已经解决了我的问题。再次感谢。 – Jules 2009-11-16 14:13:09