Click here to Skip to main content
15,881,803 members
Articles / Desktop Programming / MFC
Article

Window Selector Tool

Rate me:
Please Sign up or sign in to vote.
4.13/5 (8 votes)
20 Apr 20033 min read 106.5K   982   31   27
A dialog enabling window selection

Image 1

Image 2

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.

// HINSTANCE m_hLib - instance member: holding
 // the handle returned by LoadLibrary
// OpenSelectWindow - instance member: declared
// as following pFnSelectHWND OpenSelectWindow;
// pFnSelectHWND    - type definition: typedef HWND (*pFnSelectHWND)();
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)) {
        // user clicked over the icon, start mouse capture 
        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) 
{
    // check if mouse is captured (means mouse input is received
    // even when coursor outside the client area
    if (this == GetCapture()) {
        // mouse position to screen coordinates
        ClientToScreen (&point);
        
        // current window we are over (maybe NULL)
        CWnd* pCurrentOver = WindowFromPoint(point);
        // last window we were over
        CWnd* pLastOver = CWnd::FromHandle(m_hWndLastOver);

        if (pCurrentOver  && (pCurrentOver->m_hWnd != m_hWndLastOver)) {
            if (pLastOver != 0) {
                DrawBorderInverted(pLastOver);
            }
            CRect rect;
            GetWindowRect(&rect);
            // don't allow self selection
            if (!rect.PtInRect(point)) {
                LogWindowInfo(pCurrentOver);
                DrawBorderInverted(pCurrentOver);
                m_hWndLastOver = pCurrentOver->m_hWnd;
            }
            else if (m_hWndLastOver != 0) {
                // we are here if we reentered the window while mouse pressed
                m_Log.SetWindowText(m_sDefaultLog);
                m_hWndLastOver = 0;
                m_hWndSaved    = 0;
            }
        }
    }
}

// 
// If the capture is on the dialog this mean a SetCursor was called
// in WM_LBUTTONDOWN handler thus we return true in order to keep this cursor
// Otherwise just call the default message handler
//
BOOL CWinSelectorDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) 
{
    if (this == GetCapture()) {
        return TRUE;
    }
    return CDialog::OnSetCursor(pWnd, nHitTest, message);
}

//
// Logs window information to the log area
//
void CWinSelectorDlg::LogWindowInfo(CWnd* pWnd)
{
    ASSERT(pWnd != 0);

    const int MAX_CLASS_NAME  = 50;
    const int MAX_TITLE = 30;
    
    CString sHwnd, sHwndClass, sHwndCaption, sRect;
    
    // log handle hexadecimal
    sHwnd.Format("Window handle:\t0x%08x", pWnd->m_hWnd);
    sHwnd  += "\n\n";
    
    // log window class name
    ::GetClassName(pWnd->m_hWnd, sHwndClass.GetBuffer(MAX_CLASS_NAME), 
                                                          MAX_CLASS_NAME);
    sHwndClass.ReleaseBuffer();
    sHwndClass  = "Window class:\t\"" + sHwndClass + "\"\n\n";
    
    // log window caption (truncated to MAX_TITLE)
    pWnd->GetWindowText(sHwndCaption);
    sHwndCaption = "Window title:\t\"" + 
            sHwndCaption.Left(MAX_TITLE) + "\"\n\n";
    
    // log window rectangle (absolute screen coordinates)
    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);
}

//
// The function draws a border inside of the bounding rectangle
// of the target window inverting the pen. Thus when called twice
// on the same target window it has no effect.
//
void CWinSelectorDlg::DrawBorderInverted(CWnd* pWnd)
{
    ASSERT(pWnd != 0);
    
    // the device context of the target window (include non-client area)
    CWindowDC dc(pWnd);
    // the bounding rectangle of the target window
    CRect rc;
    // get window bounding rectangle (absolute screen coordinates)
    pWnd->GetWindowRect(&rc);

    // save current device context state
        int dcSave = dc.SaveDC();
    
    // set drawing mode R2_NOT (Pixel is the inverse of the screen color)
        dc.SetROP2(R2_NOT);
  
    // create a pen that draws inside of a shape (PS_INSIDEFRAME)
    // the pen is three times thicker then the default Windows border size
    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());

    // done drawing, restore the CDC state to the saved one
    dc.RestoreDC(dcSave);
    // ReleaseDC called automatically for dc,
    // as well as DeleteObject for GDI objects
    // since we used automatic variables and the dc state was restored
}

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!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
Israel Israel
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralRe: there is already another realization exists Pin
m_harriss23-Apr-03 15:24
m_harriss23-Apr-03 15:24 
GeneralRe: there is already another realization exists Pin
ed welch24-Apr-03 1:12
ed welch24-Apr-03 1:12 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.