|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Services
Chapters
Feature Zones
|
IntroductionHave 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
Summary Of How It WorksThe general principles can be summed up in the following pseudocode :
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 WorksThe 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.cppThis source contains the usual window startup code e.g. WindowFinder.cppThe starting point of the Window Finder Tool is the long StartSearchWindowDialog (HWND hwndMain) { long lRet = 0; lRet = (long)DialogBox ( (HINSTANCE)g_hInst, // handle to application instance (LPCTSTR)MAKEINTRESOURCE (IDD_DIALOG_SEARCH_WINDOW), // identifies dialog box template (HWND)hwndMain, // handle to owner window (DLGPROC)SearchWindowDialogProc // pointer to dialog box procedure ); 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 case WM_COMMAND : { // notification code WORD wNotifyCode = HIWORD(wParam); // item, control, or accelerator identifier WORD wID = LOWORD(wParam); // handle of control HWND hwndCtl = (HWND)lParam; if ((wID == IDOK) || (wID == IDCANCEL)) { bRet = TRUE; EndDialog (hwndDlg, wID); } if (wID == IDC_STATIC_ICON_FINDER_TOOL) { // Because the IDC_STATIC_ICON_FINDER_TOOL static // control is set with the SS_NOTIFY // flag, the Search Window's dialog box will be // sent a WM_COMMAND message when this // static control is clicked. bRet = TRUE; // We start the window search operation by // calling the DoSearchWindow() function. 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 long SearchWindow (HWND hwndDialog) { long lRet = 0; // Set the global "g_bStartSearchWindow" // flag to TRUE. g_bStartSearchWindow = TRUE; // Display the empty window bitmap image // in the Finder Tool static control. SetFinderToolImage (hwndDialog, FALSE); MoveCursorPositionToBullsEye (hwndDialog); // Set the screen cursor to the BullsEye cursor. if (g_hCursorSearchWindow) { g_hCursorPrevious = SetCursor (g_hCursorSearchWindow); } else { g_hCursorPrevious = NULL; } // Very important : capture all mouse // activities from now onwards and // direct all mouse messages to the "Search Window" // dialog box procedure. SetCapture (hwndDialog); // Hide the main window. ShowWindow (g_hwndMainWnd, SW_HIDE); return lRet; }
The first thing we do here is to set the global
Finally, we call the Now that we have captured all mouse messages, when the mouse is moved (with the left-button down at the same time),
case WM_MOUSEMOVE : { bRet = TRUE; if (g_bStartSearchWindow) { // Only when we have started the // Window Searching operation will we // track mouse movement. DoMouseMove(hwndDlg, uMsg, wParam, lParam); } break; }
Note that we will perform action only when the global flag The long DoMouseMove ( HWND hwndDialog, UINT message, WPARAM wParam, LPARAM lParam ) { POINT screenpoint; HWND hwndFoundWindow = NULL; char szText[256]; long lRet = 0; // Must use GetCursorPos() instead of calculating // from "lParam". GetCursorPos (&screenpoint); // Display global positioning in the dialog box. wsprintf (szText, "%d", screenpoint.x); SetDlgItemText (hwndDialog, IDC_STATIC_X_POS, szText); wsprintf (szText, "%d", screenpoint.y); SetDlgItemText (hwndDialog, IDC_STATIC_Y_POS, szText); // Determine the window that lies underneath // the mouse cursor. hwndFoundWindow = WindowFromPoint (screenpoint); // Check first for validity. if (CheckWindowValidity (hwndDialog, hwndFoundWindow)) { // We have just found a new window. // Display some information on this found window. DisplayInfoOnFoundWindow (hwndDialog, hwndFoundWindow); // If there was a previously found window, we must // instruct it to refresh itself. // This is done to remove any highlighting // effects drawn by us. if (g_hwndFoundWindow) { RefreshWindow (g_hwndFoundWindow); } // Indicate that this found window is now // the current global found window. g_hwndFoundWindow = hwndFoundWindow; // We now highlight the found window. HighlightFoundWindow (hwndDialog, g_hwndFoundWindow); } return lRet; }
One important note is that the horizontal and vertical positions of the mouse
cannot be calculated from the When a The code for long HighlightFoundWindow (HWND hwndDialog, HWND hwndFoundWindow) { // The DC of the found window. HDC hWindowDC = NULL; // Handle of the existing pen in the DC of the found window. HGDIOBJ hPrevPen = NULL; // Handle of the existing brush in the DC of the found window. HGDIOBJ hPrevBrush = NULL; RECT rect; // Rectangle area of the found window. long lRet = 0; // Get the screen coordinates of the rectangle // of the found window. GetWindowRect (hwndFoundWindow, &rect); // Get the window DC of the found window. hWindowDC = GetWindowDC (hwndFoundWindow); if (hWindowDC) { // Select our created pen into the DC and // backup the previous pen. hPrevPen = SelectObject (hWindowDC, g_hRectanglePen); // Select a transparent brush into the DC and // backup the previous brush. hPrevBrush = SelectObject (hWindowDC, GetStockObject(HOLLOW_BRUSH)); // Draw a rectangle in the DC covering // the entire window area of the found window. Rectangle (hWindowDC, 0, 0, rect.right - rect.left, rect.bottom - rect.top); // Reinsert the previous pen and brush // into the found window's DC. SelectObject (hWindowDC, hPrevPen); SelectObject (hWindowDC, hPrevBrush); // Finally release the DC. 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 case WM_LBUTTONUP : { bRet = TRUE; if (g_bStartSearchWindow) { // Only when we have started the // window searching operation will we // be interested when the user lifts // up the left mouse button. DoMouseUp(hwndDlg, uMsg, wParam, lParam); } break; } Now the long DoMouseUp ( HWND hwndDialog, UINT message, WPARAM wParam, LPARAM lParam ) { long lRet = 0; // If we had a previous cursor, set the // screen cursor to the previous one. // The cursor is to stay exactly where it // is currently located when the // left mouse button is lifted. if (g_hCursorPrevious) { SetCursor (g_hCursorPrevious); } // If there was a found window, refresh // it so that its highlighting is erased. if (g_hwndFoundWindow) { RefreshWindow (g_hwndFoundWindow); } // Set the bitmap on the Finder Tool icon // to be the bitmap with the bullseye bitmap. SetFinderToolImage (hwndDialog, TRUE); // Very important : must release the mouse capture. ReleaseCapture (); // Make the main window appear normally. ShowWindow (g_hwndMainWnd, SW_SHOWNORMAL); // Set the global search window flag to FALSE. 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 Updates
ConclusionAnd 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. | ||||||||||||||||||||