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.
Collapse
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.
Collapse
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.
Collapse
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.
Collapse
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).
Collapse
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.