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.
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 , WPARAM ,
LPARAM , BOOL& );
LRESULT OnCreate(UINT , WPARAM ,
LPARAM , BOOL& );
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,
0,
0,
0,
FW_BOLD,
FALSE,
FALSE,
0,
ANSI_CHARSET,
OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS,
DEFAULT_QUALITY,
DEFAULT_PITCH | FF_SWISS,
"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 , WORD ,
HWND , BOOL& )
{
HWND hSavedWnd = BringToTheTop();
Sleep(5);
RECT rectFrom;
::GetWindowRect(hSavedWnd,&rectFrom);
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);
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.