一般来说,截屏工具是这样工作的:当用户触发截屏操作后,用户按下鼠标左键并且移动鼠标选定将要截取的区域,然后弹起左键选择保存截屏图片的文件名、格式和位置。
程序截屏效果展示:
所以,不妨新建一个基于对话框的MFC工程,工程名命名为ScreenCapture,这样我们就得到主对话框对应的类名CScreenCaptureDlg,因为用户要截取的区域可能很大,而程序又只能响应到客户区内的消息,所以我们采取一些巧妙地方法:让程序启动时即已最大化显示,但这样是有问题的,因为当用户触及到标题栏时(这时就不属于客户区的范围了),不能达到截屏的效果,因为这时程序的窗口将被鼠标移动。怎么办呢?很简单!在资源视图中选择这个主对话框右键Properties,在General这个标签页中取消Title bar这个复选项即可。但是这样截取到的就是对话框的客户区的内容了,怎么办呢?还好,我们可以把对话框的客户区设为透明啊!因为,对话框的客户区已经铺满了整个桌面,所以当客户区实现了透明的话,用户看到的就只是桌面当前显示的内容了!相关程序代码如下,添加在CScreenCaptureDlg::OnInitDialog()的// TODO: Add extra initialization here底下:
// TODO: Add extra initialization here
//加入WS_EX_LAYERED扩展属性,以下用于对话框透明显示
SetWindowLong(this->GetSafeHwnd(), GWL_EXSTYLE,
GetWindowLong(this->GetSafeHwnd(), GWL_EXSTYLE) ^ 0x80000);
HINSTANCE hInst = LoadLibrary("User32.DLL");
if(hInst)
{
typedef BOOL (WINAPI *MYFUNC)(HWND,COLORREF,BYTE,DWORD);
MYFUNC fun = NULL;
//取得SetLayeredWindowAttributes函数指针
fun = (MYFUNC)GetProcAddress(hInst, "SetLayeredWindowAttributes");
if (fun)
{
fun(this->GetSafeHwnd(), 0, 20, 2);
}
FreeLibrary(hInst);
}
//窗口最大化
ShowWindow(SW_SHOWMAXIMIZED);
void CScreenCaptureDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
if ((nID & 0xFFF0) == IDM_ABOUTBOX)
{
CAboutDlg dlgAbout;
dlgAbout.DoModal();
}
else
{
// 注释掉,使窗口不能被移动
// CDialog::OnSysCommand(nID, lParam);
}
}
同时,我们改变一下程序启动时鼠标的指针形状,从默认的箭头形改为MFC已经提供的另一种十字架形,ID号为IDC_CROSS。这个也很简单,现在CScreenCaptureDlg类中添加HCURSOR型的成员变量m_hMyCursor;在构造函数中初始化,再添加一个WM_SETCURSOR消息,用于设置鼠标指针的形状。
相关代码如下:
private:
BOOL m_bIsEnd;
BOOL m_bIsBegin;
HCURSOR m_hMyCursor;
CPoint m_ptEnd;
CPoint m_ptBegin;
HCURSOR m_hCursor;
除了HCURSOR m_hCursor; 为系统定义的之外,其他都为自己定义的。
CScreenCaptureDlg::CScreenCaptureDlg(CWnd* pParent /*=NULL*/)
: CDialog(CScreenCaptureDlg::IDD, pParent)
{
//{{AFX_DATA_INIT(CScreenCaptureDlg)
//}}AFX_DATA_INIT
// Note that LoadIcon does not require a subsequent DestroyIcon in Win32
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
m_hMyCursor = ::LoadCursor(NULL, IDC_CROSS);
m_bIsBegin = FALSE;
m_bIsEnd = FALSE;
}
这是构造函数中的初始化工作。
BOOL CScreenCaptureDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
// TODO: Add your message handler code here and/or call default
if ((HTCLIENT == nHitTest)
&& (m_hMyCursor != NULL))
{
::SetCursor(m_hMyCursor);
return TRUE;
}
return CDialog::OnSetCursor(pWnd, nHitTest, message);
}
接着就该响应鼠标的消息了。
当鼠标左键按下时,记录下当时鼠标的坐标,鼠标左键移动时,画出边框的线条,实现划定截屏的矩形区域的目的,当鼠标左键弹起时,也记录下此时鼠标的坐标。相关代码实现如下:
左键按下:
void CScreenCaptureDlg::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
if (m_bIsBegin == FALSE)
{
m_bIsBegin = TRUE;
m_ptBegin = point;
}
CDialog::OnLButtonDown(nFlags, point);
}
void CScreenCaptureDlg::OnMouseMove(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
if (m_bIsBegin == TRUE && m_bIsEnd == FALSE)
{
CClientDC dc(this);
CPen pen(PS_SOLID, 4, RGB(0, 0, 0));
dc.SelectObject(&pen);
dc.Rectangle(m_ptBegin.x, m_ptBegin.y, point.x, point.y);
}
CDialog::OnMouseMove(nFlags, point);
}
void CScreenCaptureDlg::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
m_ptEnd = point;
m_bIsEnd = TRUE;
m_bIsBegin = FALSE;
CDialog::OnLButtonUp(nFlags, point);
}
现在最重要的就是,如何把截取区域的图像保存起来,具体的实现方法网上有很多,大家可以Google “截屏 MFC”。大致意思是这样的:先获取屏幕DC,创建屏幕兼容位图、兼容DC,将兼容位图选进兼容DC中,并复制内容到兼容DC中,得到位图相关数据,写入文件,保存到指定位置。这里值得一提的是,保存文件名的问题,因为文件名一样并且保存在同一目录下,另一位图会被替换掉,并且不会给出任何提示。这里我采用文件名=日期+编号,其中日期即为当天的年月日,编号为0-99的随机数。为了便于执行上述操作,我索性将其全部放在右键按下的消息响应函数中,相关代码如下:
void CScreenCaptureDlg::OnRButtonDown(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
SYSTEMTIME localTime;
GetLocalTime(&localTime);
CString strYear, strMonth, strDay;
strYear.Format("%d", localTime.wYear);
if (localTime.wMonth < 10)
{
strMonth.Format("0%d", localTime.wMonth);
}
else
{
strYear.Format("%d", localTime.wYear);
}
if (localTime.wDay < 10)
{
strDay.Format("0%d", localTime.wDay);
}
else
{
strDay.Format("%d", localTime.wDay);
}
CString strRand;
srand((unsigned)time(NULL));
strRand.Format("%d", rand() % 100);
CString strFileName = strYear + strMonth + strDay + strRand + ".bmp";
static char BASED_CODE szFilter[] = "Bitmap Files (*.bmp)|*.bmp | All Files (*.*)|*.*||";
CFileDialog saveAsDlg(FALSE, NULL, strFileName,
OFN_OVERWRITEPROMPT, szFilter, NULL);
if (saveAsDlg.DoModal() == IDOK)
{
CDC dc;
dc.CreateDC("DISPLAY", NULL, NULL, NULL);
int nWidth = GetSystemMetrics(SM_CXSCREEN);
int nHeight = GetSystemMetrics(SM_CYSCREEN);
CBitmap bm;
bm.CreateCompatibleBitmap(&dc, nWidth, nHeight);
CDC tempdc;
tempdc.CreateCompatibleDC(&dc);
CBitmap *pOld = tempdc.SelectObject(&bm);
tempdc.BitBlt(0, 0, m_ptEnd.x - m_ptBegin.x,
m_ptEnd.y - m_ptBegin.y, &dc, m_ptBegin.x, m_ptBegin.y, SRCCOPY);
tempdc.SelectObject(pOld);
BITMAP btm;
bm.GetBitmap(&btm);
DWORD size = btm.bmWidthBytes*btm.bmHeight;
LPSTR lpData = (LPSTR)GlobalAlloc(GPTR,size);
int bmWidth = m_ptEnd.x - m_ptBegin.x;
int bmHeight = m_ptEnd.y - m_ptBegin.y;
BITMAPINFOHEADER bih;
bih.biSize = sizeof(BITMAPINFOHEADER);
bih.biWidth = bmWidth;
bih.biHeight = bmHeight;
bih.biPlanes = 1;
bih.biBitCount = btm.bmBitsPixel;
bih.biCompression = 0;
bih.biSizeImage = bmWidth * bmHeight;
bih.biYPelsPerMeter = 0;
bih.biClrImportant = 0;
bih.biClrUsed = 0;
GetDIBits(dc, bm, 0, bih.biHeight, lpData, (BITMAPINFO*)&bih, DIB_RGB_COLORS);
BITMAPFILEHEADER bfh;
bfh.bfReserved1 = 0;
bfh.bfReserved2 = 0;
bfh.bfType = ((WORD)('M' << 8)| 'B');
bfh.bfSize = 54 + size;
bfh.bfOffBits = 54;
CFile bf;
if(bf.Open(strFileName, CFile::modeCreate
| CFile::modeWrite | CFile::modeNoTruncate))
{
bf.WriteHuge(&bfh, sizeof(BITMAPFILEHEADER));
bf.WriteHuge(&bih, sizeof(BITMAPINFOHEADER));
bf.WriteHuge(lpData, size);
bf.Close();
}
GlobalFree(lpData);
}
CDialog::OnRButtonDown(nFlags, point);
}