Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WTL

Screen Capture

3.21/5 (8 votes)
15 Nov 20063 min read 1   1.9K  
A screen capture application.

Image 1

Introduction

There are many screen capture programs available, but I was not happy with most of them. So I made a screen capture program myself. This program will not offer you the facility to take the picture of  drop down menu and this feature is intentionally excluded. If you would like to implement IT yourself; you will find enough information on the internet. Also, it involves another module as a DLL, where I wanted to keep it simple.

Background

The complete application is not only about screen capturing, but other subjects like WTL, STL, and more.

Using the code

There are three classes inside the CMainFrame window: WindowInfo, CBorderWnd, and CClientView.

There are many interesting attributes of Windows, but this program is only interested in three, the title of the window, the application (or module) name, and the thread ID of the application. The application does not use object-oriented/encapsulation guidelines, so everything is public.

First, you need to create an MDI application with the WTL wizard.

Image 2

The wizard will create the CMainFrame class in the MainForm (header and CPP) files. The following classes are declared inside MainFrm.h:

//
class WindowInfo
{
public:
    WindowInfo(TCHAR* pstrWindowTitle=NULL, 
               TCHAR* pstrModuleName=NULL, DWORD dwPID=0) 
    {
        memset(m_strWindowTitle,0,_MAX_PATH);
        memset(m_strModuleName,0,_MAX_PATH);

        if (pstrWindowTitle != NULL)
            strcpy_s(m_strWindowTitle,pstrWindowTitle);
        if (pstrModuleName != NULL)
            strcpy_s(m_strModuleName,pstrModuleName);
        m_dwPID = dwPID;

    }
    TCHAR m_strWindowTitle[_MAX_PATH];
    TCHAR m_strModuleName[_MAX_PATH];
    DWORD m_dwPID;
}; 
//

The class CBorderWnd is responsible for drawing the border around another window, and only WM_ERASEBKGND is handled.

//
class CBorderWnd : public CWindowImpl<CBorderWnd>
{
public:
    BEGIN_MSG_MAP(CBorderWnd)
        MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBkgnd)
    END_MSG_MAP()
      LRESULT OnEraseBkgnd(UINT uMsg, WPARAM wParam, 
              LPARAM lParam, BOOL& bHandled)
    {
        RECT rect;
        GetClientRect(&rect);
        bHandled = TRUE;
        HDC hDC  = (HDC)wParam;
        HPEN hRedPen = ::CreatePen(PS_SOLID, 16, RGB(255,0,0));
        HPEN hOldPen = (HPEN) ::SelectObject(hDC,hRedPen);
        HBRUSH hOldBrush = (HBRUSH) SelectObject(hDC,
                           (HBRUSH)GetStockObject(NULL_BRUSH));
        Rectangle(hDC,rect.left,rect.top,rect.right,rect.bottom);
        ::SelectObject(hDC,hOldPen);
        ::SelectObject(hDC,hOldBrush);
        DeleteObject(hRedPen);
        return 0;
    }
};
//

And CClientView will draw any picture inside the MDI application.

//
class CClientView : public CWindowImpl<CClientView>
{
public:
    HBITMAP m_bmSonyCamera;
    SIZE m_size;

    BEGIN_MSG_MAP(CSlideView)
        MESSAGE_HANDLER(WM_SIZE, OnSize)
        MESSAGE_HANDLER(WM_PAINT, OnPaint)
        MESSAGE_HANDLER(WM_CREATE, OnCreate)
    END_MSG_MAP()
      LRESULT OnPaint(UINT /*uMsg*/, WPARAM /*wParam*/, 
              LPARAM /*lParam*/, BOOL& /*bHandled*/);
    LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, 
            LPARAM /*lParam*/, BOOL& /*bHandled*/);
    LRESULT OnSize(UINT uMsg, WPARAM wParam, 
            LPARAM lParam, BOOL& bHandled);
};
//

When we create a view with a new keyword, WTL will delete that memory location and there will be no memory leak, but still we want to keep track of all the views whenever we create them. For this reason, the vector<CChildFrame*> m_bmList; is included. Before creating a view, we also need to keep information about all Desktop Windows where map<HWND,WindowInfo> m_HanldeList; is used. A static function is also needed for the EnumDesktopWindows function.

static BOOL CALLBACK EnumWindowsProc(HWND hwnd,LPARAM lParam);

This application has an owner draw combo box, and in owner draw code, you can create a font and delete the font after using it, but the best way to handle it is to assign the font to the object (combo box) by:

::SendMessage(m_hWndComboBox,(UINT) WM_SETFONT,(WPARAM) m_hFont,TRUE);

This way, you need to create a font only once and delete it at exit.

As you can see from the picture, the combo box is inside the Toolbar, so the resource file has to be modified. Depending on the size of any object in the Toolbar, many SEPARATORs (8 pixels) are needed. In this case, 32 SEPARATORs were included. Now, make that combo box and set the toolbar as the parent. The resource file has to be modified with the text editor, else the wizard will override your modification and the result might not be desirable.

IDR_MAINFRAME TOOLBAR  16, 15
BEGIN
    BUTTON      ID_FILE_NEW
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    SEPARATOR
    BUTTON      ID_REFRESH
    BUTTON      ID_EDIT_COPY
    BUTTON      ID_FILE_SAVE
    SEPARATOR
    BUTTON      ID_APP_ABOUT
END

Inside the CMainFrame::Create function, the combo box and the font are created.

HWND hWndToolBar = CreateSimpleToolBarCtrl(m_hWnd, IDR_MAINFRAME, 
                   FALSE, ATL_SIMPLE_TOOLBAR_PANE_STYLE);

DWORD dwComboStyle = CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | 
                     WS_CHILD | WS_VISIBLE;
dwComboStyle |= CBS_HASSTRINGS | CBS_OWNERDRAWFIXED;
m_hWndComboBox = ::CreateWindowEx(0,"COMBOBOX", NULL, dwComboStyle, 
                 20,0,400,20,hWndToolBar, (HMENU) COMBOBOX_ID, 
                 _Module.m_hInst, NULL);

m_hFont = CreateFont(-14,                 // nHeight
               0,                         // nWidth
               0,                         // nEscapement
               0,                         // nOrientation
               FW_BOLD,                   // nWeight
               FALSE,                     // bItalic
               FALSE,                     // bUnderline
               0,                         // cStrikeOut
               ANSI_CHARSET,              // nCharSet
               OUT_DEFAULT_PRECIS,        // nOutPrecision
               CLIP_DEFAULT_PRECIS,       // nClipPrecision
               DEFAULT_QUALITY,           // nQuality
               DEFAULT_PITCH | FF_SWISS,  // PitchAndFamily
               "Comic Sans MS"); 

::SendMessage(m_hWndComboBox,(UINT) WM_SETFONT,(WPARAM) m_hFont,TRUE);

Inside CMainFrame, WM_SIZE is also implemented, so when the size is changed, the CClientView will change its size, and place the picture in the middle of the client area. Here, PostMessage is used, so the client always has to find out the area of the parent window. The SendMessage will not work.

You will also find a refresh or window indicator button. When you press it, it will put a border around the window of the selected item in the combo box. Actually, it is a popup window where the middle of the window is painted with a NULL brush. This is the way I implemented it, or you can use the DrawAnimatedRects function.

LRESULT CMainFrame::OnRefresh(WORD /*wNotifyCode*/, WORD /*wID*/, 
                              HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
    HWND hSavedWnd = BringToTheTop();
    Sleep(5);
    RECT rectFrom;
    ::GetWindowRect(hSavedWnd,&rectFrom);
//  RECT rectTo;
//    ::SetRect(&rectTo,rectFrom.left,rectFrom.top,
//            rectFrom.right,rectFrom.bottom);
//    ::InflateRect(&rectTo,-100,-100);
//    ::DrawAnimatedRects(hSavedWnd, IDANI_CAPTION, 
//                      &rectFrom, &rectTo);

    WindowInfo WInfo = m_HanldeList[hSavedWnd];

    if (_stricmp(_T("Program Manager"),WInfo.m_strWindowTitle))
       ::InflateRect(&rectFrom,8,8);

    int nWidth  = rectFrom.right - rectFrom.left;
    int nHeight = rectFrom.bottom - rectFrom.top;

    ::SetWindowPos(m_BackGround,HWND_BOTTOM,rectFrom.left,
                   rectFrom.top,nWidth,nHeight,SWP_SHOWWINDOW);
    Sleep(2000);
    ::SetWindowPos(m_BackGround,HWND_TOP,rectFrom.left,
                   rectFrom.top,nWidth,nHeight,SWP_HIDEWINDOW);
    return 0;
}
The heart of the program is adding enum windows to the list box. We are only interested in the application windows (not child windows, or invisible windows, or zero size windows).

void CMainFrame::AddToTheList(HWND hWnd)
{
    TCHAR tchCurrentSelected[_MAX_PATH];
    TCHAR tchBuffer[_MAX_PATH];
    TCHAR tchClassName[_MAX_PATH];
    TCHAR tchModuleName[_MAX_PATH];
    HANDLE hProcess;

    ::GetWindowText(hWnd,tchCurrentSelected,_MAX_PATH);

    int nCount = 0;
    bool bChanged = false;
    do
    {
        bChanged = false;
        nCount = SendMessage(m_hWndComboBox, 
                (UINT) CB_GETCOUNT,0,NULL);
        for (int i = 0; i < nCount; i++) 
        {
            HWND hSavedWnd = (HWND) SendMessage(m_hWndComboBox, 
                             (UINT) CB_GETITEMDATA,i,NULL);
            if (::IsWindow(hSavedWnd) == FALSE)
            {
               ::SendMessage(m_hWndComboBox,CB_DELETESTRING,i,0);
                bChanged = true;
                map<HWND,WindowInfo>::iterator Pos = 
                             m_HanldeList.find(hSavedWnd);
                if (Pos != m_HanldeList.end())
                    m_HanldeList.erase(Pos);
                int nSel = ::SendMessage(m_hWndComboBox,CB_FINDSTRING,
                           -1,(LPARAM) (LPCTSTR)tchCurrentSelected);
                
                int NewSel = (nSel == CB_ERR) ? 0 : nSel;
                SendMessage(m_hWndComboBox, 
                           (UINT) CB_SETCURSEL,NewSel,NULL);
                break;
            }
        }
    }while (bChanged);

    if (::IsWindow(hWnd) == FALSE || ::IsWindowVisible(hWnd) 
        == FALSE || ::GetParent(hWnd) || hWnd == m_hWnd)
        return;
    
    ::GetWindowText(hWnd,tchBuffer,_MAX_PATH);
    if (strlen(tchBuffer) == 0)
        return;

    int nResult = ::SendMessage(m_hWndComboBox,CB_FINDSTRING,
                  -1,(LPARAM) (LPCTSTR)tchBuffer);
    if (nResult != CB_ERR)
    {
        for (int i = 0; i < nCount; i++)
        {
              HWND hSavedWnd = (HWND) SendMessage(m_hWndComboBox, 
                               (UINT) CB_GETITEMDATA,i,NULL);
            if (hSavedWnd == hWnd)
               return;
        }
    }
    ::GetClassName(hWnd,tchClassName,_MAX_PATH);
    RECT rect;
    ::GetWindowRect(hWnd,&rect);
    // get pid
    DWORD dwPID;
    GetWindowThreadProcessId(hWnd, &dwPID);
    hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | 
               PROCESS_VM_READ, false, dwPID);
    GetModuleFileNameEx(hProcess, NULL, tchModuleName, _MAX_PATH);
    CloseHandle(hProcess);

    BOOL bDoNoAccept = ::IsWindow(hWnd) == FALSE || 
         ::IsWindowVisible(hWnd) == FALSE || 
         ::GetParent(hWnd) || hWnd == m_hWnd;
        
    if (!bDoNoAccept)
    {
        map<HWND,WindowInfo>::iterator it = 
                            m_HanldeList.find(hWnd);
        bool bHidden = (rect.left == rect.right || 
                        rect.top == rect.bottom);
        if (it == m_HanldeList.end() && bHidden == false)
        {
            m_HanldeList.insert(std::make_pair(hWnd,
                     WindowInfo(tchBuffer,tchModuleName,dwPID)));
            nResult = ::SendMessage(m_hWndComboBox,CB_ADDSTRING,
                      0,(LPARAM) (LPCTSTR)tchBuffer);
            if (nResult != CB_ERR)
                ::SendMessage(m_hWndComboBox,CB_SETITEMDATA,
                              nResult,(LPARAM) (HWND)hWnd);
     
            nCount = SendMessage(m_hWndComboBox, (UINT) CB_GETCOUNT,0,NULL);
            int CurSel = SendMessage(m_hWndComboBox, 
                        (UINT) CB_GETCURSEL,0,NULL);
            if (nCount && CurSel == -1)
                SendMessage(m_hWndComboBox, (UINT) CB_SETCURSEL,0,NULL);
        }
    }
}

As you can see, first, we find the application windows, and then we find its process to get its icon so we could display it inside the combo box.

I hope everyone will enjoy this program.

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