Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Capture ALL mouse clicks and key presses in a user control

Tags:

c#

winforms

I want to capture all key presses and mouse clicks in a specific user control. I need to do that to be able to create a idle detection so that I can unlock the entity if the user has not done anything in the user control for a while.

My first attempt was to PreProcessMessage to detect the windows messages, but it doesn't seem to be called? My second was to use DefWndProc, but it isn't called for those messages.

How can I get WM_KEYDOWN & WM_MOUSEUP for a user control and all of it's children?

Update

ProcessKeyPreview works to detect keys

like image 289
jgauffin Avatar asked Apr 22 '13 12:04

jgauffin


3 Answers

For the keyboard aspect you could try overriding the ProcessCmdKey event within the UserControl.

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

For the mouse, I imagine your UserControl implementing the IMessageFilter interface's PreFilterMessage method may be the direction to head in (although I'm not sure if that can be specific to a user control).

Edit

I had a quick look at IMessageFilter and I think I have something that may work although it does seem a bit convoluted.

Create a MessageHandler that implements IMessageFilter.

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

In your user control (or new class) you can use P/Invoke to get the child windows of a given parent. From 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;
}

The GetChildWindows will return the windows we need. I modified it a bit and added an optional addParent bool which will add the user control itself to the list.

In your UserControl.cs constructor we can register the MessageFilter and pass the list of handles (or you can add this via a different form as long as you know the handle of the user control).

public MyControl()
{
    InitializeComponent();
    Application.AddMessageFilter(new MessageHandler(GetChildWindows(this.Handle)));
}

You should now be able to detect the three mouse button up events in your user control and any of its child windows. Don't forget to call Application.RemoveMessageFilter when your application is closed or when your UserControl is closed/disposed.

As I say, this isn't the most straightforward way, but it'll capture the clicks if the standard event handlers aren't working for you (I assume you've tried subscribing to the standard mouseclick events).

like image 143
keyboardP Avatar answered Nov 13 '22 12:11

keyboardP


I would catch the following events:

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 And any click/changed events for subcontrols. Have all the event handlers call the same common function that saves a timestamp of the last action.

Click += ClickHandler;
TextChanged += ChangedHandler;

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

(Don't forget to unsubscribe to these controls later, and if you have controls dynamically getting added and removed, handle the subscribe/unsubscribe appropriately.)

like image 36
Denise Skidmore Avatar answered Nov 13 '22 14:11

Denise Skidmore


For keyboard, as already mentioned, use:

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

This should solve for mouse events, create new ones for each existing mouse event you need:

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;
        }
    }
}
like image 1
Pedro77 Avatar answered Nov 13 '22 14:11

Pedro77