

Introduction
In some applications there is a need to allow the users to select a given window. An example of such an application is Spy++ or a screen capturing/recording application. Since I'm working in my free time on a window recording application, I wanted to have a simple interface to select a given window.
Background
The idea of a tool for selecting a window is not new. Since I was used to the Spy++ simple mode of choosing a window, when I first needed such an interface, I used similar principles. I must give the credit here to the developers of the above, for the design idea as well as for the icons and cursors that I extracted from the Spy++ executable file.
Using the code
The usage of the tool is simple and it runs as a modal topmost dialog that can be opened by the client application whenever needed. Since the operation of choosing a window can occur in many applications, I also packed it in a DLL exporting a single function HWND SelectHWND()
that will open the modal dialog and returns to the user the HWND
of the selected window. If the dialog was closed or dismissed, the return value is 0.
The function is defined like this:
extern "C" HWND PASCAL EXPORT SelectHWND()
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
HWND hWndRet = 0;
CWinSelectorDlg dlg;
int nRetCode = dlg.DoModal();
if (nRetCode == IDOK) {
hWndRet = dlg.GetCapturedWindow();
}
return hWndRet;
}
The AFX_MANAGE_STATE(AfxGetStaticModuleState());
must occur on the first line and it takes care to properly locate the resources (icons, cursors, dialogs) from the DLL by the dialog (see more on this topic in MSDN).
The simple client application that tests the DLL is a trivial dialog that displays the returned value of the selected window. I added the following code to OnInitDialog()
to load the window selector DLL and to obtain a pointer to its exported function.
m_hLib = LoadLibrary("WinSelectDLL.dll");
if (NULL == m_hLib) {
AfxMessageBox("Could not load the tool library WinSelectDLL.dll",
MB_ICONERROR);
}
else {
OpenSelectWindow = (pFnSelectHWND)GetProcAddress(m_hLib,
"SelectHWND");
if (0 == OpenSelectWindow) {
AfxMessageBox("Could not locate function SelectHWND",
MB_ICONERROR);
}
}
When select window is pressed, the click handler simply executes the following code:
if (OpenSelectWindow == NULL) {
AfxMessageBox("Function unavailable. WinSelectDLL was not found!",
MB_ICONERROR);
return;
}
HWND hWndSelected = OpenSelectWindow();
CString sHwnd;
sHwnd.Format("0x%08x", hWndSelected);
GetDlgItem(IDC_STATIC_HWND)->SetWindowText(sHwnd);
In destructor of the client dialog, class FreeLibrary
is called (see source code)
All the code above is all that is needed to activate the tool from any client application assuming explicit (dynamically) linking to the DLL.
And now a word on the class CWinSelectorDlg
. It inherits from CDialog
since the behavior wanted for such a tool is generally the behavior of a modal dialog. The idea is to capture the mouse whenever the user presses the left mouse button over the tool icon by calling SetCapture()
and to check where the mouse cursor is, while the user drags it. The code also illustrates how to change the cursor by properly handling WM_SETCURSOR
. When the left mouse button is released we save the HWND
of the window that was last under the mouse. When leaving a window and entering a new one a border is drawn around the rectangle of the new window and deleted from the previous window (using a pen style PS_INSIDEFRAME
and drawing mode R2_NOT
).Also when entering a new window, some window attributes are written: HWND
, class name, caption, and bounding rectangle.
void CWinSelectorDlg::OnLButtonDown(UINT nFlags, CPoint point)
{
CRect cr;
m_Tool.GetClientRect(&cr);
m_Tool.MapWindowPoints(this, &cr);
if (cr.PtInRect(point)) {
SetCursor(m_hToolCursor);
m_Tool.SetIcon(m_hToolEmptyIcon);
SetCapture();
}
}
void CWinSelectorDlg::OnLButtonUp(UINT nFlags, CPoint point)
{
if (this == GetCapture()) {
ReleaseCapture();
m_Tool.SetIcon(m_hToolIcon);
if (m_hWndLastOver) {
m_hWndSaved = m_hWndLastOver;
CWnd* pLastOver = FromHandle(m_hWndLastOver);
if (pLastOver != NULL) {
DrawBorderInverted(pLastOver);
}
m_hWndLastOver = 0;
}
}
}
void CWinSelectorDlg::OnMouseMove(UINT nFlags, CPoint point)
{
if (this == GetCapture()) {
ClientToScreen (&point);
CWnd* pCurrentOver = WindowFromPoint(point);
CWnd* pLastOver = CWnd::FromHandle(m_hWndLastOver);
if (pCurrentOver && (pCurrentOver->m_hWnd != m_hWndLastOver)) {
if (pLastOver != 0) {
DrawBorderInverted(pLastOver);
}
CRect rect;
GetWindowRect(&rect);
if (!rect.PtInRect(point)) {
LogWindowInfo(pCurrentOver);
DrawBorderInverted(pCurrentOver);
m_hWndLastOver = pCurrentOver->m_hWnd;
}
else if (m_hWndLastOver != 0) {
m_Log.SetWindowText(m_sDefaultLog);
m_hWndLastOver = 0;
m_hWndSaved = 0;
}
}
}
}
BOOL CWinSelectorDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
if (this == GetCapture()) {
return TRUE;
}
return CDialog::OnSetCursor(pWnd, nHitTest, message);
}
void CWinSelectorDlg::LogWindowInfo(CWnd* pWnd)
{
ASSERT(pWnd != 0);
const int MAX_CLASS_NAME = 50;
const int MAX_TITLE = 30;
CString sHwnd, sHwndClass, sHwndCaption, sRect;
sHwnd.Format("Window handle:\t0x%08x", pWnd->m_hWnd);
sHwnd += "\n\n";
::GetClassName(pWnd->m_hWnd, sHwndClass.GetBuffer(MAX_CLASS_NAME),
MAX_CLASS_NAME);
sHwndClass.ReleaseBuffer();
sHwndClass = "Window class:\t\"" + sHwndClass + "\"\n\n";
pWnd->GetWindowText(sHwndCaption);
sHwndCaption = "Window title:\t\"" +
sHwndCaption.Left(MAX_TITLE) + "\"\n\n";
CRect cr;
pWnd->GetWindowRect(&cr);
sRect.Format("Window rect:\tL:%d, T:%d, W:%d, H:%d",
cr.left, cr.top, cr.Width(), cr.Height());
m_Log.SetWindowText(sHwnd + sHwndClass + sHwndCaption + sRect);
}
void CWinSelectorDlg::DrawBorderInverted(CWnd* pWnd)
{
ASSERT(pWnd != 0);
CWindowDC dc(pWnd);
CRect rc;
pWnd->GetWindowRect(&rc);
int dcSave = dc.SaveDC();
dc.SetROP2(R2_NOT);
CPen pen;
pen.CreatePen(PS_INSIDEFRAME,
3 * GetSystemMetrics(SM_CXBORDER), RGB(0, 0, 0));
dc.SelectObject(&pen);
dc.SelectObject(GetStockObject(NULL_BRUSH));
dc.Rectangle(0, 0, rc.Width(), rc.Height());
dc.RestoreDC(dcSave);
}
Files included
The zip archive contains a VC++ 6.0 workspace containing two projects: the DLL and the client dialog. After unzipping, open the workspace and compile the client application. Since this is dependent upon the DLL project, the DLL will also be compiled and copied to the application directory, because I added a post-build command to the DLL project. Run the client dialog and press the select button. That's it!
Thanks for taking time to read the article!