用MFC制作一个截屏小工具

1.思路

    一般来说,截屏工具是这样工作的:当用户触发截屏操作后,用户按下鼠标左键并且移动鼠标选定将要截取的区域,然后弹起左键选择保存截屏图片的文件名、格式和位置。

程序截屏效果展示:

用MFC制作一个截屏小工具_第1张图片

    所以,不妨新建一个基于对话框的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);

上述代码的添加就已经实现了将整个透明的客户区铺满桌面的目的。另外,将CScreenCaptureDlg::OnSysCommand(UINT nID, LPARAM lParam)函数里的CDialog::OnSysCommand(nID, lParam);注释掉,窗口就不能被移动了,

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);
}

这是对WM_SETCURSOR消息的消息响应函数。


接着就该响应鼠标的消息了。

当鼠标左键按下时,记录下当时鼠标的坐标,鼠标左键移动时,画出边框的线条,实现划定截屏的矩形区域的目的,当鼠标左键弹起时,也记录下此时鼠标的坐标。相关代码实现如下:

左键按下:

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);
}

    至此,一个可以截取屏幕任意区域的简易的截屏工具的制作基本已经完成。程序代码中还有很多疏漏和不足,恳请读者批评指正。如果您想看看我制作的这个截屏小工具欢迎到这里下载:http://download.csdn.net/detail/jefferson327/5999459,如果您还想看到所有源码及整个工程,请移步到这里:http://download.csdn.net/detail/jefferson327/6010159

你可能感兴趣的:(VC/MFC,小工具,MFC,截屏,小工具,对话框,位图)