Click here to Skip to main content
15,860,861 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.3K   979   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

 
Generalgreat Pin
behnam_k9-May-08 14:52
behnam_k9-May-08 14:52 
Generalcapture window details that has focus Pin
rm_pkt19-Dec-06 16:51
rm_pkt19-Dec-06 16:51 
GeneralUsing your dll Pin
GPilotino15-Oct-06 4:03
GPilotino15-Oct-06 4:03 
GeneralRe: Using your dll Pin
Andrei Isac15-Oct-06 7:57
Andrei Isac15-Oct-06 7:57 
GeneralRe: Using your dll Pin
GPilotino15-Oct-06 13:10
GPilotino15-Oct-06 13:10 
QuestionPurpose of GetStockObject(NULL_BRUSH)? Pin
Nick Z.2-Mar-05 16:17
Nick Z.2-Mar-05 16:17 
AnswerRe: Purpose of GetStockObject(NULL_BRUSH)? Pin
Andrei Isac3-Mar-05 3:54
Andrei Isac3-Mar-05 3:54 
GeneralRe: Purpose of GetStockObject(NULL_BRUSH)? Pin
Nick Z.3-Mar-05 12:55
Nick Z.3-Mar-05 12:55 
GeneralCString to Hwnd Pin
freak238620-Jul-04 10:16
freak238620-Jul-04 10:16 
GeneralCString to Hwnd Pin
freak238620-Jul-04 10:14
freak238620-Jul-04 10:14 
GeneralRe: CString to Hwnd Pin
Andrei Isac20-Jul-04 11:37
Andrei Isac20-Jul-04 11:37 
GeneralRe: CString to Hwnd Pin
freak238621-Jul-04 4:45
freak238621-Jul-04 4:45 
GeneralRe: CString to Hwnd Pin
Andrei Isac22-Jul-04 12:27
Andrei Isac22-Jul-04 12:27 
GeneralThanks Pin
freak238623-Jul-04 11:21
freak238623-Jul-04 11:21 
QuestionMainFrame Windows? Pin
Matthew Merritt3-Apr-04 6:10
Matthew Merritt3-Apr-04 6:10 
AnswerRe: MainFrame Windows? Pin
Matthew Merritt6-Apr-04 12:49
Matthew Merritt6-Apr-04 12:49 
Generalwondeful article , and can i have a question Pin
4-Feb-04 15:28
suss4-Feb-04 15:28 
GeneralRe: wondeful article , and can i have a question Pin
Andrei Isac4-Feb-04 23:24
Andrei Isac4-Feb-04 23:24 
GeneralRe: wondeful article , and can i have a question Pin
Wuzhiming5-Feb-04 17:02
Wuzhiming5-Feb-04 17:02 
GeneralRe: wondeful article , and can i have a question Pin
Andrei Isac7-Feb-04 20:56
Andrei Isac7-Feb-04 20:56 
GeneralThanks for a code. Pin
dim13327-May-03 3:20
dim13327-May-03 3:20 
GeneralRe: Thanks for a code. Pin
Andrei Isac27-May-03 11:21
Andrei Isac27-May-03 11:21 
GeneralSimple but useful Pin
yingyuheng22-Apr-03 7:32
yingyuheng22-Apr-03 7:32 
Generalthere is already another realization exists Pin
m_harriss21-Apr-03 21:38
m_harriss21-Apr-03 21:38 
GeneralRe: there is already another realization exists Pin
Andrei Isac22-Apr-03 2:53
Andrei Isac22-Apr-03 2:53 

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.