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
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).
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.)
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;
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With