How to programmatically simulate a click on a window

I recently got an e-mail from an OnTopReplica user (they exist!  :S) asking for a new feature that allows clicking on the window's thumbnail in order to generate corresponding clicks on the window that is being cloned. It's a feature I would find quite useful myself, therefore I'm currently wasting lots of time I should be using to prepare my exams to work on the next version of OnTopReplica: 2.5.

Somehow, it's strange that a seemingly "interesting" things like the generation of fake clicks on an arbitrary window (using the Win32 APIs) appearently isn't talked about at all on the internet. So, instead of blatantly stealing some code from someone else, I had to find the way to do what I wanted by digging around in the Win32 documentation. I'm not completely sure the following code is correct, but it works (mostly).

So, assuming you have the HWND to the target window and the point where the user clicked (in client area coordinates):

public void SendClientClick(IntPtr hWindow, Point clientClick) {
NativeMethods.Point scrClickLocation = NativeMethods.ClientToScreen(hWindow,
	NativeMethods.Point.FromPoint(clientClick));

//Menu adjustment (see below for explanation)
IntPtr hMenu = NativeMethods.GetMenu(hWindow);
if (hMenu != IntPtr.Zero)
	scrClickLocation.Y -= SystemInformation.MenuHeight;

//Seek clicked child control
IntPtr curr = _lastWindowHandle.Handle, child = IntPtr.Zero;
do {
	child = NativeMethods.RealChildWindowFromPoint(curr,
		NativeMethods.ScreenToClient(curr, scrClickLocation));

	if (child == IntPtr.Zero || child == curr)
		break;

	curr = child;
}
while (true);

NativeMethods.Point clntClickLocation = NativeMethods.ScreenToClient(curr, scrClickLocation);
IntPtr lParamClickLocation = NativeMethods.MakeLParam(clntClickLocation.X, clntClickLocation.Y);

//Send click messages
NativeMethods.PostMessage(curr, NativeMethods.WM_LBUTTONDOWN,
	new IntPtr(NativeMethods.MK_LBUTTON), lParamClickLocation);

NativeMethods.PostMessage(curr, NativeMethods.WM_LBUTTONUP,
	new IntPtr(NativeMethods.MK_LBUTTON), lParamClickLocation);
}

To simulate a click on a target window it isn't enough to generate a click message on the window itself, but you must find out which child control in particular has been clicked by the user. Since there seems to be no way to find out a control's position in terms of his parent's client coordinates, the first step is to convert the click location to screen coordinates (using the ClientToScreen() function) that will allow us to convert the click location back to client coordinates as soon as we find out which control has been clicked exactly.

The client area of a window and the client area of child controls. The RealChildWindowFromPoint() function queries a window for one of his child controls that corresponds to a particular location (given using the window's client coordinates), is visible and non transparent (i.e., clickable by the user). The code simply loops recursively down the control hierarchy and stops as soon as the clicked control has been found.

Finally, sending a WM_LBUTTONDOWN and WM_LBUTTONUP message to the target control (using its own client coordinates) causes the window to react to the user input. You could do the same with a WM_LBUTTONDBLCLK message in order to fake a double click or with the corresponding WM_RBUTTON messages to issue right click commands.

Native methods and structures

The native method signatures used are the following (you'll find lots of informations on how to do Win32 interop on Pinvoke.net).

static class NativeMethods {
	[StructLayout(LayoutKind.Sequential)]
	public struct Point {
		public int X, Y;

		public Point(int x, int y) {
			X = x;
			Y = y;
		}

		public Point(Point copy) {
			X = copy.X;
			Y = copy.Y;
		}

		public static Point FromPoint(System.Drawing.Point point) {
			return new Point(point.X, point.Y);
		}

		public System.Drawing.Point ToPoint() {
			return new System.Drawing.Point(X, Y);
		}

		public override string ToString() {
			return "{" + X + "," + Y + "}";
		}
	}

	[DllImport("user32.dll")]
	public static extern IntPtr RealChildWindowFromPoint(IntPtr parent, Point point);
	
	[DllImport("user32.dll")]
	public static extern IntPtr GetMenu(IntPtr hwnd);

	[DllImport("user32.dll")]
	static extern bool ClientToScreen(IntPtr hwnd, ref Point point);

	public static Point ClientToScreen(IntPtr hwnd, Point clientPoint) {
		Point localCopy = new Point(clientPoint);

		if (ClientToScreen(hwnd, ref localCopy))
			return localCopy;
		else
			return new Point();
	}

	[DllImport("user32.dll")]
	static extern bool ScreenToClient(IntPtr hwnd, ref Point point);

	public static Point ScreenToClient(IntPtr hwnd, Point screenPoint) {
		Point localCopy = new Point(screenPoint);

		if (ScreenToClient(hwnd, ref localCopy))
			return localCopy;
		else
			return new Point();
	}

	public const int WM_LBUTTONDOWN = 0x0201;
	public const int WM_LBUTTONUP = 0x0202;
	public const int WM_LBUTTONDBLCLK = 0x0203;

	public const int MK_LBUTTON = 0x0001;
	
	[return: MarshalAs(UnmanagedType.Bool)]
	[DllImport("user32.dll", SetLastError = false)]
	public static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

	public static IntPtr MakeLParam(int LoWord, int HiWord) {
		return new IntPtr((HiWord << 16) | (LoWord & 0xffff));
	}
}

Menu adjustement

The DWM thumbnails, that OnTopReplica uses to replicate a window's contents, include the standard menu of the window (meaning, the "old school" Windows 95 menu bars right under the title bar, which are slowly beginning to disappear with Vista). However, those menus are not considered part of the client area (coordinate Y = 0 starts right below the menu). Therefore, all clicks by the user on a DWM clone are displaced by the exact height of the menu (if there is one). The "menu adjustment" code above checks whether the window has a menu and corrects the click location if needed.

Other issues

As said, this code works almost always. Some complex interfaces (Visual Studio 2008 for instance) don't work correctly: you can click on the text editor, but toolbar buttons don't respond to your clicks for some reason. Also, scrollbars usually do not work because most scrollbars aren't controls of the target window's client area. There may be other issues as well that I haven't found out yet. However, it works perfectly on all windows that use client drawn, windowless, controls (Internet Explorer, Opera... all browsers I guess), it works on embedded plug-ins (you can stop-resume playing youtube videos) and it works on Windows Media Player (except the scrollbars, that is).

That's more than enough for now, I think.  :)
The next version of OnTopReplica will be released soon.