
Introduction
Have you ever wondered how the Microsoft Engineers developed
that cool Window Finder Tool inside Spy++ (see diagram below) ?
This utility (symbolized by the "bulls eye" icon) can seemingly be
"dragged out" (with the mouse magically turning into the "bulls
eye") and be used
to highlight and select windows on the desktop. After a window has been selected,
Spy++ does its stuff with spying on
windows messages targeted at that window as well as providing various
kinds of information on that window.
After studying carefully some Win32 APIs and closely watching this Spy++
Window Searching Facility, I tried my hand at re-creating a similar Window
Finder Utility of my own.
After a few days of coding and testing, I came up with one imitation (see
diagram above). I'd like to share my code with readers out there, especially the
newbies who may be wondering just how such a window selecting utility is
implemented.
The demo application and its source code aims to demonstrate window
highlighting and selection via mouse tracking. The Window Search dialog box will
be the focal point of our discussion and the main frame window serves only as a
container of the dialog box. I have developed my sample using Win32 and avoided MFC. My intension is to
demonstrate the raw principles. After learning the principles, readers can
easily incorporate the source codes into MFC.
Usage
-
Run the demo application and select the "Search|Find Window" menu item. A Search Window dialog box will appear.
- My version of the Window Finder Utility works exactly the same way as the one in Microsoft Spy++.
- Move the mouse cursor inside the Finder Tool icon (the bulls eye).

- Press and hold down the left-mouse button on the bulls eye icon.
The mouse cursor will "disappear" and be changed into the bulls eye icon.
Drag the mouse and the bulls eye will follow.
- The main frame window of the demo app will also be hidden.
This is to help in window selection.
- From here on, move the bulls eye across any window on the screen and
the window underneath the mouse will be highlighted by having its borders
highlighted with a rectangular band. The screen position, rectangular
dimensions and the Window Class Name of the selected window will also
be displayed on the Search Window dialog box.
- The demo app will not perform any action on any selected window.
The principles of window finding and highlighting via mouse tracking are explained
in the following sections.
Summary Of How It Works
The general principles can be summed up in the following pseudocode :
- Capture the mouse using
SetCapture.
- Monitor all mouse movement by handling
WM_MOUSEMOVE.
- Whenever the mouse moves, get the screen position of the mouse (
GetCursorPos)
- Get the
HWND of the window beneath the mouse (WindowFromPoint)
- Highlight the window (
GetWindowRect, GetWindowDC,
SelectObject, Rectangle, ReleaseDC).
- When the user lifts up the left-mouse button, stop monitoring
mouse movements (
WM_LBUTTONUP, ReleaseCapture).
The other parts of the program (the bulls eye cursor and the special
effects) are just cosmetics.
Regular, fully documented Win32 APIs are used.
Detailed Explanation Of How It Works
The Window Finder Demo Application is composed of several source
and resource files but I'd like to zoom-in on 4 of the main ones :
- main.h - the header of the main module of the program.
- main.cpp - the main running module.
- WindowFinder.h - the header for WindowFinder.cpp.
- WindowFinder.cpp - module that contains functions to capture and track mouse
movement, determining the window underneath the mouse and displaying information on that window.
main.cpp
This source contains the usual window startup code e.g. WinMain,
MainWndProc, etc. However, I'd like to point out that InitializeApplication
contains code to disallow more that one instance of the demo app from running. This is done via a
Mutex object. Spy programs usually need to perform its job exclusively
with exactly one instance of it running in the system. Spy++, for instance, allows only one
instance of itself. This is understandable since Spy++ will hook on windows messages streaming towards
one particular window. What if another Spy++ starts up and
spies on the same target window ? In theory, everything should work as per normal. But it is
definitely prudent to limit the running of spy programs to only one
instance. I have provided the sample code to demonstrate how this can be done.
If you feel that it is not necessary, simply remove it.
WindowFinder.cpp
The starting point of the Window Finder Tool is the StartSearchWindowDialog function.
This function will be invoked by the handlers of the "Search|Find Window" menu item
of the Main Frame Window.
long StartSearchWindowDialog (HWND hwndMain)
{
long lRet = 0;
lRet = (long)DialogBox
(
(HINSTANCE)g_hInst,
(LPCTSTR)MAKEINTRESOURCE
(IDD_DIALOG_SEARCH_WINDOW),
(HWND)hwndMain,
(DLGPROC)SearchWindowDialogProc
);
return lRet;
}
This routine launches the "Search Window" dialog box. The dialog box
is a modal dialog box that will not return until the user clicks on
the "OK" or "Cancel" button. Thereafter, the SearchWindowDialogProc
dialog proc takes over the message processing for the dialog box.
Besides the Window Finding facility, the dialog box is very simple in
nature and will leave most messages to the default dialog proc provided
by the system. The actions starts only when the user presses
the left mouse button on the
bulls eye image at which time the WM_COMMAND message will be sent to
SearchWindowDialogProc :
case WM_COMMAND :
{
WORD wNotifyCode = HIWORD(wParam);
WORD wID = LOWORD(wParam);
HWND hwndCtl = (HWND)lParam;
if ((wID == IDOK) || (wID == IDCANCEL))
{
bRet = TRUE;
EndDialog (hwndDlg, wID);
}
if (wID == IDC_STATIC_ICON_FINDER_TOOL)
{
bRet = TRUE;
SearchWindow(hwndDlg);
break;
}
break;
}
The "bulls eye in a window" image is really a static
control (ID : IDC_STATIC_ICON_FINDER_TOOL) that is set with the SS_BITMAP
and the SS_NOTIFY flags. With the SS_NOTIFY flag, the Search Window's dialog box
will be sent a WM_COMMAND message when the static control is clicked.
The static control will display either the IDB_BITMAP_FINDER_FILLED bitmap (bitmap that shows a tiny window with the
bulls eye image) or the IDB_BITMAP_FINDER_EMPTY bitmap (bitmap that shows the same tiny window without the
bulls eye image). The function SetFinderToolImage controls this.
We start the window finding operation by calling on the SearchWindow function
which is described next. The SearchWindow function starts the window searching operation.
long SearchWindow (HWND hwndDialog)
{
long lRet = 0;
g_bStartSearchWindow = TRUE;
SetFinderToolImage (hwndDialog, FALSE);
MoveCursorPositionToBullsEye (hwndDialog);
if (g_hCursorSearchWindow)
{
g_hCursorPrevious = SetCursor (g_hCursorSearchWindow);
}
else
{
g_hCursorPrevious = NULL;
}
SetCapture (hwndDialog);
ShowWindow (g_hwndMainWnd, SW_HIDE);
return lRet;
}
The first thing we do here is to set the global BOOL flag g_bStartSearchWindow
to TRUE to indicate that we are now in the middle of a window searching operation.
Next comes the software magic show that gives the illusion of the mouse grabbing the
bulls eye image and moving it out of its (bulls eye's) "tiny window".
This is done by first changing the image on the static control from the
IDB_BITMAP_FINDER_FILLED image to the IDB_BITMAP_FINDER_EMPTY image. We
do this via the SetFinderToolImage function. Next, we call on
MoveCursorPositionToBullsEye to move the cursor position from wherever it
currently is to the exact screen position of the bulls eye point on the static
control :

Finally, we call the SetCursor API to change the cursor to the bulls eye cursor.
These three actions give the smooth illusion that the user has somehow
grabbed the bulls eye image from its tiny window and has transformed it to
become a mouse cursor. How's that for some software "smoke-and-mirrors" ?
Next comes the very important operation of capturing all mouse messages
from now onwards and re-directing them all to the "Search Window" dialog box procedure.
This is done by calling the SetCapture API. This will ensure that
SearchWindowDialogProc can hook the WM_MOUSEMOVE and the
WM_LBUTTONUP
messages which will have special processing. More on these later.
Lastly, we hide the main frame window for convenience. We will later
unhide it when the window searching operation has completed.
Now that we have captured all mouse messages, when the mouse is moved (with the left-button down at the same time),
WM_MOUSEMOVE will be sent to SearchWindowDialogProc and the following is the handler :
case WM_MOUSEMOVE :
{
bRet = TRUE;
if (g_bStartSearchWindow)
{
DoMouseMove(hwndDlg, uMsg, wParam, lParam);
}
break;
}
Note that we will perform action only when the global flag g_bStartSearchWindow is
TRUE.
We will call on DoMouseMove to process mouse movement.
Now, because the "Search Window" dialog has now captured the mouse, all
mouse movement will be monitored by SearchWindowDialogProc. This is
regardless of whether the mouse is inside or outside the "Search Window" dialog.
The DoMouseMove function is listed below :
long DoMouseMove
(
HWND hwndDialog,
UINT message,
WPARAM wParam,
LPARAM lParam
)
{
POINT screenpoint;
HWND hwndFoundWindow = NULL;
char szText[256];
long lRet = 0;
GetCursorPos (&screenpoint);
wsprintf (szText, "%d", screenpoint.x);
SetDlgItemText (hwndDialog, IDC_STATIC_X_POS, szText);
wsprintf (szText, "%d", screenpoint.y);
SetDlgItemText (hwndDialog, IDC_STATIC_Y_POS, szText);
hwndFoundWindow = WindowFromPoint (screenpoint);
if (CheckWindowValidity (hwndDialog, hwndFoundWindow))
{
DisplayInfoOnFoundWindow (hwndDialog, hwndFoundWindow);
if (g_hwndFoundWindow)
{
RefreshWindow (g_hwndFoundWindow);
}
g_hwndFoundWindow = hwndFoundWindow;
HighlightFoundWindow (hwndDialog, g_hwndFoundWindow);
}
return lRet;
}
One important note is that the horizontal and vertical positions of the mouse
cannot be calculated from the lParam
that is sent together with WM_MOUSEMOVE. These values can be inaccurate
when the mouse is outside the dialog box. Instead, we use the GetCursorPos
API to capture the screen position of the mouse. We next determine the window
that lies underneath the mouse cursor. This is done by the WindowFromPoint API.
Next, we check for validity of the window handle returned from WindowFromPoint.
This is encapsulated by the function CheckWindowValidity. CheckWindowValidity checks a
HWND to see if it is actually
the "Search Window" dialog's or main window's own window or one
of their child windows. If so a FALSE will be returned
so that these windows will not be selected.
Also, this routine checks to see if the HWND to be checked
is already a currently found (highlighted) window. If so,
a FALSE will also be returned to avoid repetitions.
When a HWND returned from WindowFromPoint is found
to be valid, we first call DisplayInfoOnFoundWindow to display some
information about the window. I have kept this function short and bland
but all kinds of sophisticated data can be retrieved and displayed with a
HWND. Adventurous readers can experiment with this.
Next, if there was a previously highlighted window, we call on
RefreshWindow on the HWND
of that window. This is done to remove the rectangular band surrounding the
borders of that window. RefreshWindow is not a Win32 API.
I have defined it myself. It calls on
three Win32 APIs InvalidateRect, UpdateWindow and
RedrawWindow
to perform a complete job of repainting the entire client and non-client area of a window.
We then set the global HWND g_hwndFoundWindow to the new window returned from
WindowFromPoint.
Finally, we call the HighlightFoundWindow function on g_hwndFoundWindow
to highlight the new window by drawing a rectangular band around its borders.
The code for HighlightFoundWindow is listed below.
long HighlightFoundWindow (HWND hwndDialog, HWND hwndFoundWindow)
{
HDC hWindowDC = NULL;
HGDIOBJ hPrevPen = NULL;
HGDIOBJ hPrevBrush = NULL;
RECT rect;
long lRet = 0;
GetWindowRect (hwndFoundWindow, &rect);
hWindowDC = GetWindowDC (hwndFoundWindow);
if (hWindowDC)
{
hPrevPen = SelectObject (hWindowDC, g_hRectanglePen);
hPrevBrush = SelectObject (hWindowDC,
GetStockObject(HOLLOW_BRUSH));
Rectangle (hWindowDC, 0, 0,
rect.right - rect.left, rect.bottom - rect.top);
SelectObject (hWindowDC, hPrevPen);
SelectObject (hWindowDC, hPrevBrush);
ReleaseDC (hwndFoundWindow, hWindowDC);
}
return lRet;
}
We first get the screen dimensions of the window to be highlighted.
Next we get the full window DC of the window
to be highlight. We use the GetWindowDC API instead of the
GetDC API because the GetWindowDC API will retrieve the device context (DC)
for the entire window, including title bar, menus, and scroll bars.
This is important for us because we may have to highlight top-level windows and
dialogs that contain title bars. Our rectangular band must surround the entire
window and not just the client area of such windows.
We then select our own created pen and a transparent brush into the
window DC of the target window and then draw a rectangle around the borders of the window.
It is important to then restore the previous pen and brush into the
window DC and also to release the DC (via ReleaseDC).
Finally, when the user lifts up the left mouse button, the WM_LBUTTONUP message
will be sent to SearchWindowDialogProc and the handler for this message is displayed below :
case WM_LBUTTONUP :
{
bRet = TRUE;
if (g_bStartSearchWindow)
{
DoMouseUp(hwndDlg, uMsg, wParam, lParam);
}
break;
}
Now the DoMouseUp function is called. Its code is listed below :
long DoMouseUp
(
HWND hwndDialog,
UINT message,
WPARAM wParam,
LPARAM lParam
)
{
long lRet = 0;
if (g_hCursorPrevious)
{
SetCursor (g_hCursorPrevious);
}
if (g_hwndFoundWindow)
{
RefreshWindow (g_hwndFoundWindow);
}
SetFinderToolImage (hwndDialog, TRUE);
ReleaseCapture ();
ShowWindow (g_hwndMainWnd, SW_SHOWNORMAL);
g_bStartSearchWindow = FALSE;
return lRet;
}
We set the screen cursor to the previous one that was in
use before we changed the cursor. However, the cursor position
is to remain unchanged and so the cursor itself will stay exactly
where it is currently located when the left mouse button is lifted.
This is the same behaviour as in Microsoft's Spy++.
We must also not forget to RefreshWindow any
existing found window to remove any rectangular band highlight.
We then call SetFinderToolImage to restore
the image on the static control to the "bulls eye in a window" bitmap.
We then release the mouse capture by calling the ReleaseCapture
API. We also unhide the main frame window and also set the search window flag
g_bStartSearchWindow to FALSE.
Updates
- v1 :- Monday January 7th 2002.
- v1.1 :- Let me say a very big thanks to all the readers who have sent me very supportive comments and have given very good ratings to this article. It's very encouraging. Thanks !
- v1.2 :- I have submitted another article :
WindowFloater -
A System Tray Utility To Make A Window Float To The Top
that gives a good demonstration of an effective use of the WindowFinder utility. I hope it will be of benefit to all.
Conclusion
And that's it. A complete re-creation of the Microsoft Spy++ Window Finder Tool.
I plan to write another article in the near future to make practical use of my
Window Finder. I hope that the sample code will be of benefit to all readers. It
is said that "imitation is the sincerest form of flattery". I do have great
esteem for the Microsoft Engineers and I hope that my demo app is not a cheap
imitation but an effective one that demonstrates how things can be done using
regular and fully documented Win32 APIs.
| You must Sign In to use this message board. |
|
|
 |
|
 |
I am developing application on smartphone WM5,WM6,WM6.1. I have a problem, I don't know HOW TO GET ACTIVE CONTROL IN ACTIVE WINDOW IN OTHER PROCESS ?
I have tried some solutions:
SOLUTION 1: Subclassing: Public Class clsSubclass #Region "DllImport" Public Const GWL_WNDPROC As Integer = (-4) Public Const WM_RAFFAEL As Integer = 123456789 Public Const WM_CLOSE As Integer = &H10 Public Const WM_KEYDOWN = &H100
Public Delegate Function WndProcDelegate(ByVal hWnd As IntPtr, ByVal msg As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As IntPtr
_ Public Shared Function GetWindowLong(ByVal hWnd As IntPtr, ByVal nIndex As Integer) As IntPtr End Function _ Public Shared Function SetWindowLongEx(ByVal hWnd As IntPtr, ByVal nIndex As Integer, ByVal newWndProc As WndProcDelegate) As Integer End Function _ Public Shared Function SetWindowLong(ByVal hWnd As IntPtr, ByVal nIndex As Integer, ByVal newWndProc As IntPtr) As Integer End Function _ Public Shared Function CallWindowProc(ByVal lpPrevWndFunc As IntPtr, ByVal hWnd As IntPtr, ByVal Msg As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As IntPtr End Function _ Public Shared Function FindWindow(ByVal _ClassName As String, ByVal _WindowName As String) As IntPtr End Function _ Public Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal Msg As Integer, ByVal wParam As Integer, ByVal lParam As Integer) As IntPtr End Function _ Public Shared Function SetForegroundWindow(ByVal hWnd As IntPtr) As Integer End Function
_ Private Shared Function RegisterWindowMessage(ByVal lpMessage As String) As Integer End Function
_ Public Shared Function GetFocus() As IntPtr End Function
_ Public Shared Function IsWindow(ByVal hWnd As IntPtr) As Boolean End Function
_ Public Shared Function GetForegroundWindow() As IntPtr End Function
#End Region Private oldWndProc As IntPtr 'Private newWndProc As WndProc_WRAPPER = New WndProc_WRAPPER Private hWndTarget As IntPtr = IntPtr.Zero Private strClassName = String.Empty Private strWindowName = String.Empty
Private c_MyMsg As Integer = RegisterWindowMessage("MyMessageCheck") Public Sub StartSubclass(ByVal a_hWndTarget As IntPtr) hWndTarget = a_hWndTarget Dim success As Integer = SetWindowLongEx(hWndTarget, GWL_WNDPROC, AddressOf _NewWndProcMyMsg) End Sub Public Sub StopSubclass() Dim success As Integer = SetWindowLong(hWndTarget, GWL_WNDPROC, oldWndProc) 'MessageBox.Show(success) End Sub
'SUBCLASSING PROCEDURE Public Function _NewWndProcMyMsg(ByVal hWnd As IntPtr, ByVal msg As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As IntPtr If msg = c_MyMsg Then Return GetFocus() Else Return CallWindowProc(oldWndProc, hWnd, msg, wParam, lParam) End If End Function
Function GetFocusEx() As IntPtr Dim hWnd As IntPtr = GetForegroundWindow() If (Not IsWindow(hWnd)) Then Return IntPtr.Zero End If
StartSubclass(hWnd) Dim hResult As IntPtr = SendMessage(hWnd, c_MyMsg, 0, 0) StopSubclass()
Return hResult End Function
End Class
---------------------------------------------------------------------------------- 'In my Main() Public Sub main() Dim oSubclass As clsSubclass = New clsSubclass MessageBox.Show(oSubclass.GetFocusEx().ToString) End Sub
BUT oSubclass.GetFocusEx() ALWAYS RETURN ZERO. SEEM TO HAVE PROPLEM WITH _NewWndProcMyMsg, IT MAKE SUBCLASSED WINDOW (IN OTHER APPLICATION) TO BE CLOSED OR HANG. WHAT MY WRONG?
======================================================================================== SOLUTION 2: Using AttachThreadInput() I tried to use this function as the same in Win32 but WinCE don't support this function. HAVE ALTERNATIVE FUNCTION FOR THAT IN WINCE ?
========================================================================================= SOLUTION 3: Enum all child Windows in Active Window and Check if it is Active by USING GetWindowInfo() I tried to use this function as the same in Win32 but WinCE don't support. HAVE ALTERNATIVE FUNCTION FOR THAT IN WINCE ? =========================================================================================
Above are 3 solutions which i have tried but not get successful. Could you help me fixed or show me Other solutions to GET ACTIVE CONTROL IN ACTIVE WINDOW IN OTHER PROCESS ?
Who can help me please, Thanks for your answer!
|
| Sign In·View Thread·PermaLink | 1.00/5 |
|
|
|
 |
|
 |
When I test your demo,I found that you demo can't find the window what IsEnable()==FALSE.But SPY++ in VS can do that.And I want to know why. Regards.
LOVEZ3
|
| Sign In·View Thread·PermaLink | 1.00/5 |
|
|
|
 |
|
 |
I have a little quesion for you: are you sure that MS Spy++ uses the SetCapture()&ReleaseCapture() win32api functions for getting a window by the mouse coordinates? Because I also made a proggy that search for windows to "read" their styles, but instead of these functions I have used SetWindowsHookEx() win32api function with WH_MOUSE and I want to say that *ALL* visible windows was "caputred"...For this kind of applications this is the sollution. In win32 nothing is "greater" than the hooks... Believe me!
|
| Sign In·View Thread·PermaLink | 4.00/5 |
|
|
|
 |
|
 |
Hello,
need some help. I would like to use SysTreeView32. Do someone know how to identify and iterate through the icons in the SysTreeView32 without using the coordinates xy.Click(x,y)?
Thanks.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
 |
I have tried to change the cursor using SetCursor from my MFC CDialog, but the cursor changes when it's outside the dialog. I have checked a don't get the WM_MOUSEMOVE message even when I used SetCapture.
Any idea why it doesn't work fine in MFC?
Thanks in advance. _Leo_
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
In spy++, option to find the windows / all the control IDs.
My Requirement is : =================== A function with an input argument strCaption(caption of the window) should 1. Check whether the specified window exist or not. 2. If exist, It should return all the belonging control IDs of the selected window along with all the control captions.
Kindly pass me some idea of "How to achieve this with VC++/MFC/Win32" .
|
| Sign In·View Thread·PermaLink | 3.25/5 |
|
|
|
 |
|
 |
If I have a custom window with nonrectangular form, they are not highlighted as others when I pass over them with cursor. MS Spy++ does it very well.
Can you tell me how I can find the paint region of every window? I want to know whether some custom window covers some part from my windows.
Thank you in advance, Alexi
|
| Sign In·View Thread·PermaLink | 1.00/5 |
|
|
|
 |
|
|
 |
|
|
 |
|
 |
Dear all,
I want to develop a tool like winrunner(from mercury interactive), that can help testers automate their testing steps.
The first hurdle i'm facing is this: Using DOM and VB, I'm able to see all the fields present in any web page. I want this to happen: When i move my mouse over a field, I want that field to be highlighted with a box drawn around it and the details of that field in my application's text box. The problem is, i don't know how to capture the mouse events that occur when I move my mouse over these fields on the web page. This is something like the Spy++ program. Only that, this is for web pages.
Please help me in this regard!
with warm regards, marcq.
|
| Sign In·View Thread·PermaLink | 1.00/5 |
|
|
|
 |
|
 |
Hello Lim Bio liong and the code project community,
Since I found this excellent code yesterday, I've started utilizing it in my WTL projects. This code is just what I was looking for.
Lim Bio Liong, thank you for your contribution.
The only problem that I had with the original was a refreshing windows as Mr. virkrams and some others suggested. (did not know until I read those comments.)
Nothing is per-fec-to in this world and I guess I am not the only one who's having this problem. But that's what makes the rest of us reading articles eventually do something better for our community, isn't it? 
Ok. It appears that this problem occurs when many windows are crammed. (hint: windows may be decendents of another window - that's right. that's Windows.) Like your MSDEV's toolbar or toolbox in the Paint application can reproduce this problem. (ok, so it's reproducible.)
Anyhow, I summarized three more things that I think you can do yourself. I hope you are glad you did not have to pull your hair after reading this. If you go through these steps, probably your application will run as expected or better (no guarantee though).
#1 - POINT-FINGER-FREE window selection style
Note: if you stay with SPY++ style window selection style, skip this.
Sample application uses grab-bull's-eye-and-set-focus, that is, you need to hold mouse button down while moving mouse cursor. (yep, this is called "SPY++ style".) I wanted to select a window without keeping holding down the mouse button and use a button to engage window selection. So here's what I did:
1. Add WM_TIMER handler (the name of the function is most likely to be OnTimer() when using "Add Windows Message Handler...")
2. Add a new button and Start the timer when the button was pressed. Set timer interval to 500 or whatever interval you wish to use.
3. In WM_TIMER event handler, throw WM_MOUSEMOVE at yourself (your Dialog). For example,
LRESULT OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { SendMessage(m_hWnd, WM_MOUSEMOVE, 0, (LPARAM)0); return 0; }
You can rebuild and examine now at this point.
#2 - ONE-REFRESH-ONLY please! (imagine Sean Connery played in the movie Red October)
Note if you stay with SPY++ style window selection, skip this too and proceed to #3, "GETTING RID OF GARBAGE HIGHTLIGHTS"
If you want to go with POINT-FINGER-FREE window selection style as mentioned in #1. After rebuilding your app, you will see window flickering going on because of timer you set in step #1 and probably want to suppress it as much as you can.
WindowFinder app's StartSearchWindow Dialog calls DoMouseMove(), trying to refresh the OLD window to get rid of highlight before highlighting the new window found (discovered). In DoMouseMove(),
ORIGINAL CODE
if (g_hwndFoundWindow) { RefreshWindow (g_hwndFoundWindow); }
Here's what you can do:
AFTER
if (g_hwndFoundWindow && g_hwndFoundWindow != hwndFoundWindow) { RefreshWindow (g_hwndFoundWindow); }
This way you don't cause flicker with Timer-driven redrawing because you call RefreshWindow() when you need it.
#3 - GETTING RID OF GARBAGE HIGHTLIGHTS
So what's up with the refreshing problem, eh? you might wonder.
Remember I mentioned about "windows may be decendents of another window" thing? Well, when I was watching the garbage highlight problem, I noticed that when I move cursor to select the outer window it can remove the garbage highlights. This led me to an easy fix after several experiments:
If (Found parent from windowTobeRefreshed) Pass that instead to RedrawWindow() along with RDW_ALLCHILDREN
The final code of RefreshWindow() may look something like this:
long RefreshWindow (HWND hwndWindowToBeRefreshed) { long lRet = 0;
HWND hwndParent = ::GetParent(hwndWindowToBeRefreshed); if (hwndParent) { hwndWindowToBeRefreshed = hwndParent; ATLTRACE("I am using parent. Handle:%p\n", hwndWindowToBeRefreshed); }
::InvalidateRect(hwndWindowToBeRefreshed, NULL, TRUE); ::UpdateWindow (hwndWindowToBeRefreshed); ::RedrawWindow (hwndWindowToBeRefreshed, NULL, NULL, RDW_FRAME | RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASENOW | RDW_ALLCHILDREN); }
If you have more findings or better solutions, please be constructive and contribute yours as we all respect codeproject community.
Lim Bio Liong, You da man! Keep up the good work!!
Tom
|
| Sign In·View Thread·PermaLink | 5.00/5 |
|
|
|
 |
|
 |
Open MSDN help from Visual Studio. Go to Index tab. Your program fails to find the edit box, the list below the edit box, or the button below the list. Strangely enough, it works fine on the other tabs.
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
 |
Anonymous,
I don't know how you found this but if I think the problem lurking behind this failure is related to window visibility and implementation of WindowFromPoint() API.
It appears that WindowFromPoint() can't always help us find windows while they are currently visible to us (user's view point), but not from Windows perspective.
See, if you carefully watch behaviors of two applications, SPY++ and Window Finder app, interesting enough, WindowFinder app highlights a dialog under Tab control (SysTabControl32) on while SPY++ is not doing it.
And imagine when you are coding with a control like tab, you would typically have to take care of visibility (hide and show) of controls necessary to make tab selection effect, right? So in this case, I mean the controls on Index tab of MSDN help, the dialog under the tab control does not have WS_VISIBLE set, which is causing of this problem, I think. So unless we dig up the controls blocked by the invisible state dialog (window), we can't get around.
- Tom
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Tom Furuya wrote: the dialog under the tab control does not have WS_VISIBLE set, which is causing of this problem, I think.
If you are referring to the window whose class is "HH Child" (the immediate parent of the SysTabControl32 window), this window *does* have WS_VISIBLE set, so this cannot be the cause of the problem. Please reply if you were referring to a different window.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
I am not referring to HH Child, but the dialog (identified as #32770), which is the first child of the Tab control.
You are observing "HH Child" and Tab control but I want you to look further at the first child of Tab control, which is a dialog. This is the window that I am talking about.
Interestingly, SPY++ does not spot (highlight) this dialog placed under Tab control (SysTabControl32) when you are on the Index tab.
Let me try to get you (and readers) there. I suppose you are familiar with SPY++ tool but just in case there are many other readers who might want to be on the same page here.
1. Open MSDN Library and click Index tab.
2. Launch MS SPY++, press ctrl+F and move bulls-eye over the Tab control and the client area of the tab control where we are having a trouble locating controls with our WindowFinder.
3. You can see all controls highlited (I know you already know) but you don't see any dialog there. This is when SPY++ deceives human eye, I think  So don't trust hightlight, but rather focus on window hierarchy.
4. Now press ctrl+F again. After selecting the tab control (SysTabControl32), go back to SPY's "Find Window" dialog and click OK. Click "Windows" Tab of "Window Property".
5. Click on window handle (hyperlink, colored in green) of "First Child".
That's all. This is the way I see this symptom. I am not 100% positive but I would like to hear what you and the silent think because besides MS I know there are people out there who knew why.
Hope this helps.
- Tom@wish there was a WindowFromPointEx()...not
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
 |
Yes, this is interesting. There does not seem to be any advantage to hiding the dialog, so perhaps it is just accidental.
Out of curiosity, have you tried EnumWindows on these windows? I wonder if there is any way to get the true hierarchy, instead of just the z-order hierarchy? Perhaps if you could get the entire list, you could temporarily set the WS_VISIBLE bit, so WindowFinder could show what's really there.
Mildly amusing.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Anonymous wrote: Perhaps if you could get the entire list, you could temporarily set the WS_VISIBLE bit
you can, but probably you don't want to do it. Changing the state of window (window style) could cause a bad side-effect; those controls stayed invisible would start appearing while mouse were moving over them. Besides, primary goal is to perform a good window salvation? or discovery (I don't know how to put this) as good as SPY++ does.
Anonymous wrote: have you tried EnumWindows on these windows? I wonder if there is any way to get the true hierarchy, instead of just the z-order hierarchy?
Hmm, I have been thinking about other thing - the WindowFromPoint() API and its window search strategy. I had a hard time reading between lines about the API. After I slept on it, I am getting ideas. As you said, we need to walk on a trail of windows. I'd rather not manipulate a child window but just simply checking it like we do with WindowFromPoint():
1. What we need is to get around the blocking window, like the dialog in our case.
2. Don't want to alter any window styles, but just check 'em out
3. Ok. The question is how. No WindowFromPointEx API, and obviously a single call to WindowFromPoint is not good enough to 'dig up' the window which we can't highlight. However, I've found a similar API which seems to fulfill our desire. ChildWindowFromPointEx() examines child windows like WindowFromPoint does. With this, check the visibility of the window as a 'cue' (to determine whether we are hitting the barrier and therefore prepare to run the search mission).
4. Mostly, we don't have a situation like this. So only if we hit the invisible window, we start the salvation(?) to dig the window, which is supposed to be visible in human eye and also contains the specified point (current mouse-cursor position).
5. If we start scanning the tree, chances are these blocked control windows (from programming viewpoint) are sitting very close to us, as SPY++ shows us. This is because the window hierarchy is actually in the same application, for example, MSDN's HH Child subtree part, correct? So we don't have to pay too much cost when locating the window we should present. I don't want to speculate too much on this, but what do you think? It may not be a silver bullet but it could be a simple remedy?
Hope this helps.
- Tom@one api and our desire
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Tom Furuya wrote: Changing the state of window (window style) could cause a bad side-effect; those controls stayed invisible would start appearing while mouse were moving over them. Besides, primary goal is to perform a good window salvation? or discovery (I don't know how to put this) as good as SPY++ does
I think you have hit the nail on the head here. The primary goal is to to implement a "Windows explorer", which *should* include invisible windows. I have used spy++ when I was trying to reverse-engineer a non-trivial UI, and being able to see non-visible windows would really have helped. Maybe you could have a "Show invisible windows" option? In any case, I think the goal should be to discover what's really there, not just what Windows is showing you. My two cents.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Ok. Say, provide a better presentation than SPY++ does. 
I thought the original question was that there are windows visible on screen and for some reason they can't be highlighted, however SPY++ manages somehow. That's where I said "as good as SPY++" does.
Anonymous wrote: being able to see non-visible windows would really have helped. Maybe you could have a "Show invisible windows" option?
if you only need to pick up the invisible dialog, for example, then call ChildWindowFromPointEx with CWP_ALL param to see if child is really there and subsequently call SetWindowLong to check WS_VISIBLE (0x10000000L) flag. This is what they call 'a nonrestrictive search', I think.
And about the funky ditched window thing, There are very important remarks found on MSDN. Just few lines gives you an implication and insights on how the system performs the window search when using coordinates and because of that this funky situation can be caused.
Quote "If more than one child window contains the specified point, the system returns a handle to the first window in the list that contains the point.". - Platform SDK: Windows User Interface, ChildWindowFromPoint()
I've never thought about this window search strategy behind these WindowFrom** API family. But now I figured why.
- Tom
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Well, with ChildWindowFromPointEx you have another problem - what HWND to pass to it? Doing a quick hack to WindowFinder, I was unable to find a suitable HWND, that enabled WindowFinder to see the items on the invisible dialog.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Anonymous wrote: what HWND to pass to it? ..., I was unable to find a suitable HWND
Suppose, you are still using MSDN HH Child with Index tab selected. Then, HWND to pass would be of SysTabControl32. Did you use CWP_SKIPINVISIBLE when calling it? If so, that's the pitfall. Use CWP_ALL instead and examine the style out of HWND you obtain. If you are not getting any HWND, I guess we are not on the same page.
What do you think the next action ChildWindowFromPointEx() should take when specified SKIP parameter? Give up or proceed until find the right one??
You need to be able to draw the whole picture of window tree under HH Child or HH Parent using SPY++ before getting your hand dirty. After all this is the only tool that helps you out, at least for now.
I just said 'figured why', meaning being able to see the behind scenes of this funky situation.
how bout this? It's a static.
inline BOOL WndInfoHelper::IsChildWindowVisible(HWND hWnd, const CPoint& screenpoint) { ATLASSERT(::IsWindow(hWnd));
CPoint clientpoint(screenpoint); ::ScreenToClient(hWnd, &clientpoint);
HWND hwndChild = ::ChildWindowFromPointEx(hWnd, clientpoint, CWP_ALL); if (hwndChild) { const LONG style = ::GetWindowLong(hwndChild, GWL_STYLE); return style & WS_VISIBLE; }
return FALSE; }
- Tom
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
 |
is that commercial software? You could then expect the fix in the next release.
More interesting issue is the logic of the APIs. It may deserve another article (well, I have posted to give my perspective in the best way I know how) because I find this hilarious yet thought-provoking.
- Tom
p.s. I am done last week. My speculation was not that bad after all. If you read back my old 2 cents (more than 2??). The key was, again, the presence of the invisible (child) window in MSDN-Help's left pane (see below). And this is what I am most afraid of, since I have yet not seen any other patterns so far...
After all,
It appears that Window search of WindowFrom*** is somehow performed downward due to container-contained relationship, and they could care less about those siblings (called spec.)
Consider your mouse movement on the screen when scanning windows, you go like:
[HH PARENT] | V [HH CHILD] | V [SysTabControl32]->[The Orphans...] | V [INVISIBLE] Oops
It was amuzing, funky and yet another unhealthy programming situation. Is this another reason why another API called RealChildWindowFromPoint() (Requires Windows NT 4.0 SP4 or later) was born? Afterthought? Think about it 
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Yes, I agree. I would not be surprised if there was some secret API like you say. The problem (for people trying to do spy-type utilities) is that a user might suddenly mouse into the middle of the "Index" tab, without first mousing over its parent. Then, you would need a complete windows hierarchy list, in order to determine what window the mouse was *really* over.
I appreciate your candid comments. To be honest with you in return, I have been working with a company that is developing a "super spy++". I found the Index problem by testing this tool on every window I could find, in every application I had. The tool now has no problem with the Index tab. There are several amusing differences between the versions of Windows (98/NT/2000/XP) and how the documented APIs work or don't work.
I know what you're going to ask, but I can't reveal the algorithms that I developed for use in this tool, because they were paid for by my client under contract.
Actually, I think the ease-of-use issues are just as important for a developer's tool like this, and I never liked being forced to drag the "finder" image around, then having to go back and drag it around again - do this several dozen times for several dozen windows, and it gets old real fast.
BTW, the Winspector I mentioned is available here on CodeProject: http://www.codeproject.com/useritems/winspector.asp
(I am not involved with Winspector in any way.)
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
|