views:

483

answers:

6

I was wondering if anybody could give insight on how to implement the window selector in Winspector Spy. Basically, I would want to provide a panel that I could mouse down on, drag over to another processes window (or sub window) and get something like the HWND out of it. Ideally, I would do this in C#, but if it's only possible by wrapping the C APIs, then I can just do it in C++.

I messed around with the DragDrop event and calling DoDragDrop on mouse down in C#, but wasn't really sure if that could give me what I want. Will it be easier to just get the global X/Y pos of the mouse and find the topmost window at that location? Is there an API which does that automagically for me given x, y parameters?

EDIT: Just discovered WindowFromPoint for the latter question

A: 

Imports: using System.Runtime.InteropServices;

My suggestion, when the mouse is on your form, handle the mouse move/mouse up events (To capture the mouse outside of your form using a windows hook, look here: http://support.microsoft.com/kb/318804 ), and when the mouse button is released, get the mouse position on the screen, and get the window behind the cursor, using the link you provided like this:

[DllImport("user32.dll")]
public static extern IntPtr WindowFromPoint(Point lpPoint);
[DllImport("user32.dll")]
public static extern bool GetCursorPos(out Point lpPoint);
public static IntPtr GetWindowUnderCursor()
{
   Point ptCursor = new Point();
   if (!(PInvoke.GetCursorPos(out ptCursor)))
       return IntPtr.Zero;
   return WindowFromPoint(ptCursor);
}

Now you have your window handle, from there the possibilities are endless.

NOTE: The link above, (the windows hook) will only work if the mouse down occurs on your form, and the hook ends when the mouse is lifted

Tommy
A: 

Simple. You just set the mouse capture on mouse down, so you get all mouse messages even if they are outside your own window. Then on mouse up, use WindowFromPoint.

I'm not familiar with .NET, but with the Win32 API, you use SetCapture to set the mouse capture.

Frederik Slijkerman
+2  A: 

You don't normally get mouse messages when the mouse isn't over your window. But you need to in order to do drag and drop operations. So, Windows provides a mechanism called mouse capture. To prevent mouse capture from being abused, you can only capture the mouse on a button down message. Once you have capture, you get mouse move messages no matter where the mouse is on screen until you release capture or when Windows sees the corresponding button up message.

The C++ code for this looks something like this

 case WM_LBUTTONDOWN:
     { 
     SetCapture(hwnd);
     }
     break;

 case WM_MOUSEMOVE:
     if (GetCapture() == hwnd)
        {
        POINT pt = {GET_MOUSE_X(lParam), GET_MOUSE_Y(lParam));
        ClientToScreen(hwnd, &pt);
        HWND hwndAtPoint = WindowFromPoint(pt);
        // Show info for hwndAtPoint....
        }
     break;

  case WM_LBUTTONUP:
     if (GetCapture() == hwnd)
        {
        ReleaseCapture();
        }
     break;

  case WM_CANCELMODE:
     // this is a request from Windows that leave my modal state
     if (GetCapture() == hwnd)
        ReleaseCapture(hwnd);
     break;

  case WM_CAPTURECHANGED:
     // notification that I lost capture (I released it or it was taken from me)
     break;      

The GetAncestor function can be helpful to go from the window at the point, to the top level window that own it. GetWindow can be used to walk around the window tree.

In .NET, the Control class has a Capture property that does the same thing see http://msdn.microsoft.com/en-us/library/system.windows.forms.control.capture.aspx

John Knoeller
This was great - thanks a lot.
SB
A: 

You could look at the C++ source for Winspy++, another window inspector program similar to Winspector Spy. I don't know of any open-source C# program like this, though.

Josh Townzen
+1  A: 

You'll need to consider how you are going to draw the rectangle around the window first, that affects the rest of your code. The easiest way to do this is by using a Form that has its TransparencyKey set to its BackColor and FormBorderStyle set to None. Draw a rectangle in the Paint event, the same size as the form's ClientRectangle, that gets you a visible rectangle with everything else transparent. Set the form's Location and Size property to match the window you found.

Now finding the window from the mouse position. You can't use WindowFromPoint(), it doesn't consider disabled windows. You'll need to use EnumWindows(). In the callback, call GetWindowRect() and check if the mouse is located inside the rectangle. Be sure to ignore your rectangle drawing window.

When you get a match, now call GetWindow() repeatedly with the GW_HWNDPREV to find windows that overlap the window you found. Keep checking the rectangle and keep ignoring your rectangle window.

This eventually gets you the top-level window that the mouse cursor is on. Now use ChildWindowFromPoint() to check if the mouse is on a child window, if any. Create your rectangle drawing form, if necessary, and give it the same size and location as the found window.

Call this code from the MouseMove event of, say, a PictureBox that displays a bulls-eye graphic. Set its Capture property to true in its MouseDown event.

Close the Close() method of your rectangle drawing form in the MouseUp event.

Hans Passant
+1  A: 

Since you have tagged this with C#, I can put in a link or two for this job that you are trying to accomplish and hopefully will give you the necessary insight into how to achieve this:

All of the above articles are on CodeProject.

Hope this helps, Best regards, Tom.

tommieb75