在用户控件中捕获所有鼠标点击和按键操作

5
我想要捕获一个特定用户控件中的所有按键和鼠标点击事件。我需要这样做来创建空闲检测,以便在用户在该用户控件中长时间不进行任何操作时,可以解锁实体。
我的第一次尝试是使用`PreProcessMessage`来检测Windows消息,但它似乎没有被调用?我的第二次尝试是使用`DefWndProc`,但对于这些消息它也没有被调用。
如何获取用户控件及其所有子控件的`WM_KEYDOWN`和`WM_MOUSEUP`?
更新:使用`ProcessKeyPreview`可以检测按键。
4个回答

4
对于键盘方面,您可以尝试在UserControl中覆盖ProcessCmdKey事件。

UserControl.cs:

protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
    //a key has been pressed within your user control
    //invoke event or some other action
    //...

    return base.ProcessCmdKey(ref msg, keyData);
}

对于鼠标,我想象您的UserControl实现IMessageFilter接口的PreFilterMessage方法可能是前进的方向(尽管我不确定是否可以特定于用户控件)。 编辑 我快速查看了一下IMessageFilter,我认为我有一个可能有效的解决方案,尽管它似乎有点复杂。
创建一个实现IMessageFilterMessageHandler
class MessageHandler : IMessageFilter
{
     private int WM_LBUTTONUP = 0x0202; //left mouse up
     private int WM_MBUTTONUP = 0x0208; //middle mouse up
     private int WM_RBUTTONUP = 0x0205; //right mouse up

     //stores the handles of the children controls (and actual user control)
     List<IntPtr> windowHandles = new List<IntPtr>();
     public MessageHandler(List<IntPtr> wnds)
     {
         windowHandles = wnds;
     }


     public bool PreFilterMessage(ref Message m)
     {
         //make sure that any click is within the user control's window or 
         //its child controls before considering it a click (required because IMessageFilter works application-wide)
         if (windowHandles.Contains(m.HWnd) && (m.Msg == WM_LBUTTONUP || m.Msg == WM_MBUTTONUP || m.Msg == WM_RBUTTONUP))
         {
              Debug.WriteLine("Clicked");                
         }
         return false;
     }
 }

在您的用户控件(或新类)中,您可以使用P/Invoke来获取给定父级的子窗口。 来自pinvoke.net
public delegate bool EnumWindowProc(IntPtr hWnd, IntPtr parameter);

[DllImport("user32")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool EnumChildWindows(IntPtr window, EnumWindowProc callback, IntPtr i);

private static List<IntPtr> GetChildWindows(IntPtr parent, bool addParent = true)
{
    List<IntPtr> result = new List<IntPtr>();
    GCHandle listHandle = GCHandle.Alloc(result);
    try
    {
        EnumWindowProc childProc = new EnumWindowProc(EnumWindow);
        EnumChildWindows(parent, childProc, GCHandle.ToIntPtr(listHandle));
    }
    finally
    {
        if (listHandle.IsAllocated)
           listHandle.Free();
    }


    if (addParent)
      result.Add(parent);

    return result;
}


private static bool EnumWindow(IntPtr handle, IntPtr pointer)
{
    GCHandle gch = GCHandle.FromIntPtr(pointer);
    List<IntPtr> list = gch.Target as List<IntPtr>;
    if (list == null)
    {
        throw new InvalidCastException("GCHandle Target could not be cast as List<IntPtr>");
    }
    list.Add(handle);
    //  You can modify this to check to see if you want to cancel the operation, then return a null here
    return true;
}

"GetChildWindows"函数将会返回我们所需要的窗口。我稍作修改,添加了一个可选的布尔型参数"addParent",它将会把用户控件自身添加到列表中。
在你的"UserControl.cs"构造函数中,我们可以注册"MessageFilter"并传递句柄列表(或者只要你知道用户控件的句柄,你也可以通过其他方式添加)。
public MyControl()
{
    InitializeComponent();
    Application.AddMessageFilter(new MessageHandler(GetChildWindows(this.Handle)));
}

现在,您应该能够检测到用户控件及其子窗口中的三个鼠标按钮松开事件。不要忘记在应用程序关闭或您的UserControl关闭/释放时调用Application.RemoveMessageFilter。就像我说的那样,这并不是最简单的方法,但如果标准事件处理程序无法正常工作(我假设您已经尝试订阅标准鼠标单击事件),它将捕获点击。

很好的解决方案,你有什么新的补充吗? - Pedro77

1
我将捕获以下事件:

http://msdn.microsoft.com/en-us/library/system.windows.forms.control.click.aspx http://msdn.microsoft.com/en-us/library/system.windows.forms.control.textchanged.aspx ,以及子控件的任何单击/更改事件。所有事件处理程序都调用相同的公共函数,保存最后一次操作的时间戳。

Click += ClickHandler;
TextChanged += ChangedHandler;

foreach(var control in Controls)
{
    control.Click += ClickHandler;
    control.TextChanged += ChangedHandler;
}

(不要忘记稍后取消订阅这些控件,如果您的控件动态添加和删除,请适当处理订阅/取消订阅。)

根据您的控制器的复杂程度,您可能需要将其设置为递归方法。 - Denise Skidmore
修改编码规范的编辑怎么了?每个地方对于使用 this 和 var 的政策都不同。 - Denise Skidmore

1

对于键盘,如前所述,请使用:

protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
    //invoke event or some other action
    return base.ProcessCmdKey(ref msg, keyData);
}

这应该解决鼠标事件的问题,为每个需要的现有鼠标事件创建新的鼠标事件:

public new event MouseEventHandler MouseClick
{
    add
    {
        base.MouseClick += value;
        foreach (Control control in Controls)
        {
            control.MouseClick += value;
        }
    }
    remove
    {
        base.MouseClick -= value;
        foreach (Control control in Controls)
        {
            control.MouseClick -= value;
        }
    }
}

public new event MouseEventHandler MouseDoubleClick
{
    add
    {
        base.MouseDoubleClick += value;
        foreach (Control control in Controls)
        {
            control.MouseDoubleClick += value;
        }
    }
    remove
    {
        base.MouseDoubleClick -= value;
        foreach (Control control in Controls)
        {
            control.MouseDoubleClick -= value;
        }
    }
}

public new event EventHandler DoubleClick
{
    add
    {
        base.DoubleClick += value;
        foreach (Control control in Controls)
        {
            control.DoubleClick += value;
        }
    }
    remove
    {
        base.DoubleClick -= value;
        foreach (Control control in Controls)
        {
            control.DoubleClick -= value;
        }
    }
}

0

如果无论哪个应用程序具有焦点都不是问题,您可以始终使用全局键盘/鼠标钩子来捕获它们。这里详细解释有点复杂,但您可以参考我的问题,我做了类似的事情。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接