C++: How to Catch Mouse Clicks Wherever They Happen

c++: How to catch mouse clicks wherever they happen

You need to set a mouse hook as described in MSDN.

Note that in your case the hook would need to be global. This means that you need to implement a handler function in a DLL, which will be loaded into all processes in the system which receive mouse message. Such DLL will communicate with your main application using some interprocess communication (IPC) mechanism like shared memory or via Windows messages posted (not sent) from the DLL to the main application.

You can use the source code from this CodeProject article as a guide.

Update: as per Chris' correction, I should note that above applies to "regular" mouse hook which is synchronous. Low-level hook doesn't need to be located in the DLL, but it has certain limitations which are described in the corresponding MSDN article.

c# Detect mouse clicks anywhere (Inside and Outside the Form)

Here is a starter, if I understood your needs of "clicking from outside the window" and Hans Passant's suggestion doesn't fit your needs. You might need to add an event handler for Form1_Click.

CAUTION: This code is provided to illustrate the concept. The threading synchronization in this sample is not 100% correct. Check the history of this answer for an attempt at a more "threading correct" one that sometimes throws exceptions. As an alternative, to get rid of all threading issues, you could have the task in StartWaitingForClickFromOutside be instead always running (aka be always in "listen" mode) as opposed to trying to detect the "within the form" or "outside the form" states and starting/stopping the loop accordingly.

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();

this.MouseLeave += Form1_MouseLeave;
this.Leave += Form1_Leave;
this.Deactivate += Form1_Deactivate;
this.MouseEnter += Form1_MouseEnter;
this.Activated += Form1_Activated;
this.Enter += Form1_Enter;
this.VisibleChanged += Form1_VisibleChanged;
}

private AutoResetEvent are = new AutoResetEvent(false);

// You could create just one handler, but this is to show what you need to link to
private void Form1_MouseLeave(object sender, EventArgs e) => StartWaitingForClickFromOutside();
private void Form1_Leave(object sender, EventArgs e) => StartWaitingForClickFromOutside();
private void Form1_Deactivate(object sender, EventArgs e) => StartWaitingForClickFromOutside();
private void StartWaitingForClickFromOutside()
{
are.Reset();
var ctx = new SynchronizationContext();

var task = Task.Run(() =>
{
while (true)
{
if (are.WaitOne(1)) break;
if (MouseButtons == MouseButtons.Left)
{
ctx.Send(CLickFromOutside, null);
// You might need to put in a delay here and not break depending on what you want to accomplish
break;
}
}
});
}

private void CLickFromOutside(object state) => MessageBox.Show("Clicked from outside of the window");
private void Form1_MouseEnter(object sender, EventArgs e) => are.Set();
private void Form1_Activated(object sender, EventArgs e) => are.Set();
private void Form1_Enter(object sender, EventArgs e) => are.Set();
private void Form1_VisibleChanged(object sender, EventArgs e)
{
if (Visible) are.Set();
else StartWaitingForClickFromOutside();
}
}
}

If I understood you incorrectly, you might find this useful: Pass click event of child control to the parent control

QT/c++ How to check globaly is mouse press or release?

It sounds like hooks are what you're looking for.

Basically, hooks let you interact with the activity within your system, and can be used alter its behavior.

The event listeners in your code will only catch the mouse events that your OS delegates to your QT application. This is why your code only works when you use it inside your application. Using hooks, you can intercept mouse events at a system level and have them handled them in your app instead of letting the OS decide where they should be handled.

Here's what to use to set them up, and here's a little guide on the implementation details.

Is there any way to global hook the mouse actions like i'm hooking the keyboard keys?

Yes, i used the following code in a project of mine, it allows direct access to most windows mouse events:

using System;
using System.Runtime.InteropServices;

/// <summary>
/// The CallWndProc hook procedure is an application-defined or library-defined
/// callback function used with the SetWindowsHookEx function. The HOOKPROC type
/// defines a pointer to this callback function. CallWndProc is a placeholder for
/// the application-defined or library-defined function name.
/// </summary>
/// <param name="nCode">
/// Specifies whether the hook procedure must process the message.
/// </param>
/// <param name="wParam">
/// Specifies whether the message was sent by the current thread.
/// </param>
/// <param name="lParam">
/// Pointer to a CWPSTRUCT structure that contains details about the message.
/// </param>
/// <returns>
/// If nCode is less than zero, the hook procedure must return the value returned
/// by CallNextHookEx. If nCode is greater than or equal to zero, it is highly
/// recommended that you call CallNextHookEx and return the value it returns;
/// otherwise, other applications that have installed WH_CALLWNDPROC hooks will
/// not receive hook notifications and may behave incorrectly as a result. If the
/// hook procedure does not call CallNextHookEx, the return value should be zero.
/// </returns>
internal delegate int HookProc(int nCode, IntPtr wParam, IntPtr lParam);

internal class NativeMethods
{
/// <summary>
/// The SetWindowsHookEx function installs an application-defined hook
/// procedure into a hook chain. You would install a hook procedure to monitor
/// the system for certain types of events. These events are associated either
/// with a specific thread or with all threads in the same desktop as the
/// calling thread.
/// </summary>
/// <param name="hookType">
/// Specifies the type of hook procedure to be installed
/// </param>
/// <param name="callback">Pointer to the hook procedure.</param>
/// <param name="hMod">
/// Handle to the DLL containing the hook procedure pointed to by the lpfn
/// parameter. The hMod parameter must be set to NULL if the dwThreadId
/// parameter specifies a thread created by the current process and if the
/// hook procedure is within the code associated with the current process.
/// </param>
/// <param name="dwThreadId">
/// Specifies the identifier of the thread with which the hook procedure is
/// to be associated.
/// </param>
/// <returns>
/// If the function succeeds, the return value is the handle to the hook
/// procedure. If the function fails, the return value is 0.
/// </returns>
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SetWindowsHookEx(HookType hookType,
HookProc callback, IntPtr hMod, uint dwThreadId);

/// <summary>
/// The UnhookWindowsHookEx function removes a hook procedure installed in
/// a hook chain by the SetWindowsHookEx function.
/// </summary>
/// <param name="hhk">Handle to the hook to be removed.</param>
/// <returns>
/// If the function succeeds, the return value is true.
/// If the function fails, the return value is false.
/// </returns>
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern bool UnhookWindowsHookEx(IntPtr hhk);

/// <summary>
/// The CallNextHookEx function passes the hook information to the next hook
/// procedure in the current hook chain. A hook procedure can call this
/// function either before or after processing the hook information.
/// </summary>
/// <param name="idHook">Handle to the current hook.</param>
/// <param name="nCode">
/// Specifies the hook code passed to the current hook procedure.
/// </param>
/// <param name="wParam">
/// Specifies the wParam value passed to the current hook procedure.
/// </param>
/// <param name="lParam">
/// Specifies the lParam value passed to the current hook procedure.
/// </param>
/// <returns>
/// This value is returned by the next hook procedure in the chain.
/// </returns>
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int CallNextHookEx(IntPtr hhk, int nCode,
IntPtr wParam, IntPtr lParam);
}

internal static class HookCodes
{
public const int HC_ACTION = 0;
public const int HC_GETNEXT = 1;
public const int HC_SKIP = 2;
public const int HC_NOREMOVE = 3;
public const int HC_NOREM = HC_NOREMOVE;
public const int HC_SYSMODALON = 4;
public const int HC_SYSMODALOFF = 5;
}

internal enum HookType
{
WH_KEYBOARD = 2,
WH_MOUSE = 7,
WH_KEYBOARD_LL = 13,
WH_MOUSE_LL = 14
}

[StructLayout(LayoutKind.Sequential)]
internal class POINT
{
public int x;
public int y;
}

/// <summary>
/// The MSLLHOOKSTRUCT structure contains information about a low-level keyboard
/// input event.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct MOUSEHOOKSTRUCT
{
public POINT pt; // The x and y coordinates in screen coordinates
public int hwnd; // Handle to the window that'll receive the mouse message
public int wHitTestCode;
public int dwExtraInfo;
}

/// <summary>
/// The MOUSEHOOKSTRUCT structure contains information about a mouse event passed
/// to a WH_MOUSE hook procedure, MouseProc.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct MSLLHOOKSTRUCT
{
public POINT pt; // The x and y coordinates in screen coordinates.
public int mouseData; // The mouse wheel and button info.
public int flags;
public int time; // Specifies the time stamp for this message.
public IntPtr dwExtraInfo;
}

internal enum MouseMessage
{
WM_MOUSEMOVE = 0x0200,
WM_LBUTTONDOWN = 0x0201,
WM_LBUTTONUP = 0x0202,
WM_LBUTTONDBLCLK = 0x0203,
WM_RBUTTONDOWN = 0x0204,
WM_RBUTTONUP = 0x0205,
WM_RBUTTONDBLCLK = 0x0206,
WM_MBUTTONDOWN = 0x0207,
WM_MBUTTONUP = 0x0208,
WM_MBUTTONDBLCLK = 0x0209,

WM_MOUSEWHEEL = 0x020A,
WM_MOUSEHWHEEL = 0x020E,

WM_NCMOUSEMOVE = 0x00A0,
WM_NCLBUTTONDOWN = 0x00A1,
WM_NCLBUTTONUP = 0x00A2,
WM_NCLBUTTONDBLCLK = 0x00A3,
WM_NCRBUTTONDOWN = 0x00A4,
WM_NCRBUTTONUP = 0x00A5,
WM_NCRBUTTONDBLCLK = 0x00A6,
WM_NCMBUTTONDOWN = 0x00A7,
WM_NCMBUTTONUP = 0x00A8,
WM_NCMBUTTONDBLCLK = 0x00A9
}

/// <summary>
/// The structure contains information about a low-level keyboard input event.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct KBDLLHOOKSTRUCT
{
public int vkCode; // Specifies a virtual-key code
public int scanCode; // Specifies a hardware scan code for the key
public int flags;
public int time; // Specifies the time stamp for this message
public int dwExtraInfo;
}

internal enum KeyboardMessage
{
WM_KEYDOWN = 0x0100,
WM_KEYUP = 0x0101,
WM_SYSKEYDOWN = 0x0104,
WM_SYSKEYUP = 0x0105
}

To use it you have to register the mouse hook. LowLevelMouseProc is the callback. This method is executed every time a new mouse event occures.

private void SetUpHook()
{
Logger.Debug("Setting up global mouse hook");

// Create an instance of HookProc.
_globalLlMouseHookCallback = LowLevelMouseProc;

_hGlobalLlMouseHook = NativeMethods.SetWindowsHookEx(
HookType.WH_MOUSE_LL,
_globalLlMouseHookCallback,
Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]),
0);

if (_hGlobalLlMouseHook == IntPtr.Zero)
{
Logger.Fatal("Unable to set global mouse hook");
throw new Win32Exception("Unable to set MouseHook");
}
}

To clear mouse hook:

private void ClearHook()
{
Logger.Debug("Deleting global mouse hook");

if (_hGlobalLlMouseHook != IntPtr.Zero)
{
// Unhook the low-level mouse hook
if (!NativeMethods.UnhookWindowsHookEx(_hGlobalLlMouseHook))
throw new Win32Exception("Unable to clear MouseHoo;");

_hGlobalLlMouseHook = IntPtr.Zero;
}
}

And last but not least an example of the LowLevelMouseProc, the callback you can use to intercept mouse events:

public int LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0)
{
// Get the mouse WM from the wParam parameter
var wmMouse = (MouseMessage) wParam;
if (wmMouse == MouseMessage.WM_LBUTTONDOWN && LeftButtonState == ButtonState.Released)
{
Logger.Debug("Left Mouse down");
}
if (wmMouse == MouseMessage.WM_LBUTTONUP && LeftButtonState == ButtonState.Down)
{
Logger.Debug("Left Mouse up");
}

if (wmMouse == MouseMessage.WM_RBUTTONDOWN && RightButtonState == ButtonState.Released)
{
Logger.Debug("Right Mouse down");
}
if (wmMouse == MouseMessage.WM_RBUTTONUP && RightButtonState == ButtonState.Down)
{
Logger.Debug("Right Mouse up");
}
}

// Pass the hook information to the next hook procedure in chain
return NativeMethods.CallNextHookEx(_hGlobalLlMouseHook, nCode, wParam, lParam);
}

As with all direct windows calls, the code gets unnecessarily long. But the only thing you have to do, is to call SetUpHook and provide your own version of LowLevelMouseProc.

EDIT: There are shorter versions to do this. But this method allows you to catch global mouse events. Not just events issued to your window. All mouse events, system wide will be piped into LowLevelMouseProc

Getting mouse coordinates on mouse click

You were almost there. The problem is that the EventArgs will give you the position relative to the button at the time of the click.

If you want the cursor position instead of the click, you can use the Cursor class to get its Position property:

private void button12_Click(object sender, EventArgs e)
{
if (MessageBox.Show("Pick a position after clicking OK", "OK", MessageBoxButtons.OK, MessageBoxIcon.Exclamation) == DialogResult.OK)
{
// user clicked ok
Point coordinates = Cursor.Position;
MessageBox.Show("Coordinates are: " + coordinates);
}
}

To get the coordinates after the user closed the MessageBox, you can use a timer. In order to do so, you will have to declare one at the class level, set its Tick event and move your cursor login into it.

The button12_Click method will now start the timer, which will show the cursor position once it expires (In this example, after one second).

private Timer timer; //Declare the timer at class level
public Form1()
{
InitializeComponent();
// We set it to expire after one second, and link it to the method below
timer = new Timer {Interval = 1000}; //Interval is the amount of time in millis before it fires
timer.Tick += OnTick;
}

private void OnTick(object sender, EventArgs eventArgs)
{
timer.Stop(); //Don't forget to stop the timer, or it'll continue to tick
Point coordinates = Cursor.Position;
MessageBox.Show("Coordinates are: " + coordinates);
}

private void button1_Click(object sender, EventArgs e)
{
if (MessageBox.Show("Pick a position after clicking OK", "OK", MessageBoxButtons.OK, MessageBoxIcon.Exclamation) == DialogResult.OK)
{
timer.Start();
}
}

Detecting a left button mouse click Winform

How to add the EventHandler:

public Form1()
{
InitializeComponent();
// This line should you place in the InitializeComponent() method.
this.MouseClick += mouseClick;
}


Related Topics



Leave a reply



Submit