Title: Override WndProc to see when the user clicks any control on a form in C#
A form's WndProc method processes messages sent to the form by the Windows operating system. This is an extremely important method that allows forms to move, resize, repaint, and perform other critical operations. While you normally don't need to change the default implementation of WndProc, you can override the default WndProc to make it take special actions. This example uses the following code to detect when the user clicks any control contained by the form.
// Look for WM_MOUSEACTIVATE messages.
protected override void WndProc(ref Message m)
// Look for WM_MOUSEACTIVATE.
const UInt32 WM_MOUSEACTIVATE = 0x0021;
if (m.Msg == WM_MOUSEACTIVATE)
// See where the mouse is.
Point screen_coords = Cursor.Position;
// Start looking at the form's children.
Control ctl = this;
for (; ; )
// Convert the mouse's position into
// the control's coordinate system.
Point ctl_coords = ctl.PointToClient(screen_coords);
// See if there is a child at this position.
Control child = ctl.GetChildAtPoint(
if (child == null) break;
// Search the child.
ctl = child;
// Display the innermost control at this point.
txtControl.Text = ctl.Name;
The method is declared so it overrides the default implementation of WndProc.
The method first checks the method's Message parameter to see if the message is WM_MOUSEACTIVATE indicating that a control was clicked.
If it sees that message, the code gets the mouse's cursor position. It will then use the GetChildAtPoint method to see what control is at the mouse's location, but there are two complications to that approach. First, a control contained within a container on the form is not a direct child of the form. For example, in this program label3 is contained in groupBox2, which is contained in tabPage1, which is contained in tabControl1, which is contained on the form. If you click on label3, it isn't a direct child of the form so GetChildAtPoint won't find it. Instead it will find tabControl1 because that is the control contained directly by the form.
The second complication occurs when one control sits above another. In this example, the two tab pages sit above each other. If you select the second tab and then click on it, GetChildAtPoint returns the first tab because it is at the same point and the method encounters that page first in its search.
To solve these problems, the code starts by setting variable ctl to the form. Note that a form is a special kind of Control so you can set the variable ctl equal to the form.
Next the code enters an infinite loop. It calls the current control's PointToClient method to convert the mouse's position from screen coordinates to the control's coordinate system. The program then calls the control's GetChildAtPoint method to see what child control if any is at that mouse position. It passes that method the parameter GetChildAtPointSkip.Invisible to make it not consider any hidden child controls. That allows GetChildAtPoint to return the correct control when one control covers another.
If there is no child control at the mouse's position, the code breaks out of the infinite loop leaving ctl holding a reference to the last control that contained the mouse's position.
If the code does find a child control at the mouse's position, it updates ctl to refer to the newly found child control and continues the loop. The code continues like this, converting the mouse's position into the current control's coordinate system and using GetChildAtPoint to see if the position is contained by a child control. The code drills down through the hierarchy of controls until it finds one that does not have a child that contains the mouse's position.
After it leaves the loop, the code displays the name of the control that it found.
The WndProc method finishes by calling the base class's version of WndProc. That lets the form take all of the usual actions it normally would when processing messages. This is very important. If the code doesn't do this, the form cannot event create itself correctly and the program won't work. Give it a try and see for yourself.
Download the example to experiment with it and to see additional details.