目录
- 一 程序实现效果
- 二 程序实现思路
- 三 具体实现
一 程序实现效果
本文描述了MFC中实现屏幕截图的一种方式,程序界面如下:
单击【开始截屏】,按住鼠标左键,一直拖动到需要截屏的矩形的右下角,松开鼠标左键,完成截图(由于本程序框选屏幕时会影响其他录屏软件的,所以框选过程没有动图)。录制完成后效果如下:
整个工程文件位于:https://download.csdn.net/download/mary288267/87403939
二 程序实现思路
- 如何实现鼠标事件的捕获
本程序要求用户单击【开始截屏】以后,用户将鼠标放到需要截屏范围的左上角,然后按住左键以后拖动到右下角。这里的难点是:截屏可能在对话框范围以外,对话框如何截获鼠标运动和单击的消息?大家首先想到的可能是SetCapture(),但是这个函数有一定局限性。它能够捕获的鼠标事件包括:当光标位于捕获窗口上方,或者鼠标在捕获窗口上方按下并且持续保持按下状态。可能有点拗口,意思就是鼠标在捕获窗口上方时能够捕获所有鼠标消息,或者在捕获窗口上方按下后保持按下状态移动到窗口以外过程中所产生的鼠标消息均可捕获。
以下为MSDN原文.想实现窗口外鼠标捕获还需要其他办法。请看第2条。
The SetCapture function sets the mouse capture to the specified window belonging to the current thread. SetCapture captures mouse input either when the mouse is over the capturing window, or when the mouse button was pressed while the mouse was over the capturing window and the button is still down.
- 利用钩子函数实现鼠标消息过滤
利用SetWindowsHookEx 函数在程序中设置一个钩子函数,过滤到所有的鼠标消息。这样,当我们按下【屏幕截图】时,让钩子函数过滤对应的鼠标移动、单击消息,就可以实现对话框外截图的目的。 - 位块传输
在确定截屏范围以后,利用GetDesktopWindow获得屏幕的Cwnd对象,然后获取屏幕的DC,反转截屏范围屏幕的颜色。最后,将这部分屏幕位块用BitBlt传输到对应的其他DC上。
三 具体实现
在VS中,新建一个MFC对话框工程,在资源编辑器中添加一个图片控件和按钮控件,图片控件用来显示截取的屏幕位图,需要设为自绘类型。同时在图片控件右侧和下侧增加滚动条,可使用户滚动查看所截取位图全貌。按钮控件单击后即可截屏。
按钮控件单击的消息响应函数为:
void CScreenShotDlg::OnBnClickedBtnScreenShot()
{
SetCapture();
SetCursor(LoadCursor(NULL, IDC_CROSS));
g_hHook = SetWindowsHookEx(WH_MOUSE_LL, MouseProc, GetModuleHandle(NULL), 0);//注册钩子函数
//隐藏对话框,这里不能隐藏对话框,否则会出错
//ShowWindow(SW_HIDE);
//锁定桌面窗口更新
CWnd* pWndDeskTop = GetDesktopWindow();
if (!pWndDeskTop && !pWndDeskTop->LockWindowUpdate())
return;
//开始消息循环
MSG msg;
POINT ptBegin, ptEnd;
m_bDraging = false;
bool bSkipLoop = false;
while (GetMessage(&msg, NULL, WM_MOUSEFIRST, WM_MOUSELAST))
{
if (CWnd::GetCapture() != this)
break;
switch (msg.message)
{
case WM_LBUTTONDOWN:
m_bDraging = true;
ptBegin = msg.pt;
ptEnd = ptBegin;
InvertBlock(pWndDeskTop, ptBegin, ptEnd);
break;
case WM_MOUSEMOVE:
if (m_bDraging)
{
InvertBlock(pWndDeskTop, ptBegin, ptEnd);//先反转为正常的颜色
ptEnd = msg.pt;
InvertBlock(pWndDeskTop, ptBegin, ptEnd);
}
break;
case WM_LBUTTONUP:
if (m_bDraging)
{
InvertBlock(pWndDeskTop, ptBegin, ptEnd);
ptEnd = msg.pt;
//画出正确的矩形,保证起始点和终点的正确性
if (m_bmpMemory.m_hObject)
m_bmpMemory.DeleteObject();
CDC* pDcPic = m_wndPicPreview.GetDC();
m_bmpMemory.CreateCompatibleBitmap(pDcPic, abs(ptEnd.x - ptBegin.x), abs(ptEnd.y - ptBegin.y));
CDC dcMemory;
dcMemory.CreateCompatibleDC(pDcPic);
ReleaseDC(pDcPic);
CBitmap* pOldBmp = dcMemory.SelectObject(&m_bmpMemory);
CDC *pDcScreen = pWndDeskTop->GetDCEx(NULL, DCX_CACHE | DCX_LOCKWINDOWUPDATE);
dcMemory.BitBlt(0, 0, abs(ptEnd.x - ptBegin.x), abs(ptEnd.y - ptBegin.y),
pDcScreen, ptBegin.x, ptBegin.y, SRCCOPY);
dcMemory.SelectObject(pOldBmp);
ReleaseDC(pDcScreen);
//动态设置滚动条滑块的范围和Page
CRect rect;
m_wndPicPreview.GetWindowRect(&rect);
SCROLLINFO infoVert = { sizeof(SCROLLINFO),SIF_ALL, 0,
max(0, abs(ptEnd.y - ptBegin.y)),rect.Height(),0,0 };
m_wndVertScroll.SetScrollInfo(&infoVert);
SCROLLINFO infoHor = { sizeof(SCROLLINFO),SIF_ALL, 0,
max(0, abs(ptEnd.x - ptBegin.x)),rect.Width(),0,0 };
m_wndHorScroll.SetScrollInfo(&infoHor);
bSkipLoop = true;
m_bDraging = false;
}
break;
default:
break;
}
if (bSkipLoop)
break;
}
if (g_hHook)
{
UnhookWindowsHookEx(g_hHook);
g_hHook = NULL;
}
ReleaseCapture();
SetCursor(LoadCursor(NULL, IDC_ARROW));
pWndDeskTop->UnlockWindowUpdate();
m_wndPicPreview.Invalidate();
}
在此函数中,首先设置了鼠标钩子函数,意思是接下来的鼠标操作都会进入到这个钩子函数中处理,钩子函数处理完之后再投递到本窗口的消息队列中。然后获取桌面的窗口对象及对应的DC,并且通过LockWindowUpdate设置桌面窗口为锁定状态,这可以阻止其他任何程序对屏幕进行更新。之后通过一个循环反复取出队列的消息(这些消息都是钩子函数投递进来的),在鼠标左键按下并且移动时,反转屏幕上对应的像素点;当鼠标左键弹起时,将截获的矩形范围图像保存在m_bmpMemory中。最后,我们刷新图片控件的显示。
这里面所调用的鼠标钩子函数实现为:
// 鼠标钩子
HHOOK g_hHook = NULL;
LRESULT CALLBACK MouseProc(int nCode, WPARAM msg, LPARAM lparam)
{
CWnd* pMainWnd = AfxGetApp()->GetMainWnd();
if (pMainWnd && pMainWnd->IsKindOf(RUNTIME_CLASS(CScreenShotDlg)))
{
if (WM_LBUTTONDOWN == msg)
{
PMOUSEHOOKSTRUCT mh = (PMOUSEHOOKSTRUCT)lparam;
POINT pt;
pt.x = mh->pt.x; //此处的坐标均为屏幕坐标
pt.y = mh->pt.y;
pMainWnd->PostMessage(WM_LBUTTONDOWN, 0, MAKELPARAM(pt.x, pt.y));
return 1;
}
else if (WM_MOUSEMOVE == msg)
{
PMOUSEHOOKSTRUCT mh = (PMOUSEHOOKSTRUCT)lparam;
POINT pt;
pt.x = mh->pt.x;
pt.y = mh->pt.y;
pMainWnd->PostMessage(WM_MOUSEMOVE, 0, MAKELPARAM(pt.x, pt.y));
//return 1; //不能返回1,否则鼠标没法动。原因目前不清楚。
}
else if (WM_LBUTTONUP == msg)
{
PMOUSEHOOKSTRUCT mh = (PMOUSEHOOKSTRUCT)lparam;
POINT pt;
pt.x = mh->pt.x;
pt.y = mh->pt.y;
pMainWnd->PostMessage(WM_LBUTTONUP, 0, MAKELPARAM(pt.x, pt.y));
return 1;
}
}
return CallNextHookEx(g_hHook, nCode, msg, lparam);
}
在钩子函数中,我们首先利用MFC的全局函数 AfxGetApp()和GetMainWnd()获得当前线程的主窗口(也就是这个程序的主窗口了),截获了鼠标WM_LBUTTONDOWN 、WM_MOUSEMOVE 和WM_LBUTTONUP 消息,然后把它们投递到主窗口的消息队列中。
按钮单击响应函数中的反转窗口(可以为屏幕)像素的函数如下。注意成员函数CDC::PatBlt最后一个参数,DSTINVERT。
//反转一个矩形屏幕范围的像素
void InvertBlock(CWnd* pWndSrc, POINT ptBeg, POINT ptEnd)
{
CDC* pDC = pWndSrc->GetDCEx(NULL, DCX_CACHE | DCX_LOCKWINDOWUPDATE);
pDC->PatBlt(ptBeg.x, ptBeg.y, ptEnd.x - ptBeg.x, ptEnd.y - ptBeg.y, DSTINVERT);
pWndSrc->ReleaseDC(pDC);
}
图片控件的自绘函数如下,请注意将m_bmpMemory的像素位块blit到图片控件的DC时,由于滚动条位置不同,位块传输的起始点也不相同,起始的横纵坐标为m_nScrolHPos和m_nScrolVPos。
void CScreenShotDlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)
{
if (nIDCtl == IDC_PIC_PREVIEW)
{
if (m_bmpMemory.m_hObject)
{
CDC* pDcPic = m_wndPicPreview.GetDC();
CDC dcMemory;
dcMemory.CreateCompatibleDC(pDcPic);
CBitmap* pOldBmp = dcMemory.SelectObject(&m_bmpMemory);
CRect rect;
m_wndPicPreview.GetWindowRect(&rect);
pDcPic->PatBlt(0, 0, rect.Width(), rect.Height(), PATCOPY);//首先,清空背景
pDcPic->BitBlt(0, 0, rect.Width(), rect.Height(), &dcMemory,
m_nScrolHPos, m_nScrolVPos, SRCCOPY);
dcMemory.SelectObject(pOldBmp);
ReleaseDC(pDcPic);
}
}
}
滚动条滚动时的位置信息保存在类的成员变量m_nScrolHPos和m_nScrolVPos中,滚动条滚动消息响应函数为:
void CScreenShotDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
// Get the minimum and maximum scroll-bar positions.
int minpos;
int maxpos;
pScrollBar->GetScrollRange(&minpos, &maxpos);
maxpos = pScrollBar->GetScrollLimit();
// Get the current position of scroll box.
m_nScrolHPos = pScrollBar->GetScrollPos();
// Determine the new position of scroll box.
switch (nSBCode)
{
case SB_LEFT: // Scroll to far left.
m_nScrolHPos = minpos;
break;
case SB_RIGHT: // Scroll to far right.
m_nScrolHPos = maxpos;
break;
case SB_ENDSCROLL: // End scroll.
break;
case SB_LINELEFT: // Scroll left.
if (m_nScrolHPos > minpos)
m_nScrolHPos--;
break;
case SB_LINERIGHT: // Scroll right.
if (m_nScrolHPos < maxpos)
m_nScrolHPos++;
break;
case SB_PAGELEFT: // Scroll one page left.
{
// Get the page size.
SCROLLINFO info;
pScrollBar->GetScrollInfo(&info, SIF_ALL);
if (m_nScrolHPos > minpos)
m_nScrolHPos = max(minpos, m_nScrolHPos - (int)info.nPage);
}
break;
case SB_PAGERIGHT: // Scroll one page right.
{
// Get the page size.
SCROLLINFO info;
pScrollBar->GetScrollInfo(&info, SIF_ALL);
if (m_nScrolHPos < maxpos)
m_nScrolHPos = min(maxpos, m_nScrolHPos + (int)info.nPage);
}
break;
case SB_THUMBPOSITION: // Scroll to absolute position. nPos is the position
m_nScrolHPos = nPos; // of the scroll box at the end of the drag operation.
break;
case SB_THUMBTRACK: // Drag scroll box to specified position. nPos is the
m_nScrolHPos = nPos; // position that the scroll box has been dragged to.
break;
}
// Set the new position of the thumb (scroll box).
pScrollBar->SetScrollPos( m_nScrolHPos);
m_wndPicPreview.Invalidate();
}
void CScreenShotDlg::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
// Get the minimum and maximum scroll-bar positions.
int minpos;
int maxpos;
pScrollBar->GetScrollRange(&minpos, &maxpos);
maxpos = pScrollBar->GetScrollLimit();
// Get the current position of scroll box.
m_nScrolVPos = pScrollBar->GetScrollPos();
// Determine the new position of scroll box.
switch (nSBCode)
{
case SB_TOP: // Scroll to far left.
m_nScrolVPos = minpos;
break;
case SB_BOTTOM: // Scroll to far right.
m_nScrolVPos = maxpos;
break;
case SB_ENDSCROLL: // End scroll.
break;
case SB_LINEUP: // Scroll left.
if (m_nScrolVPos > minpos)
m_nScrolVPos--;
break;
case SB_LINEDOWN: // Scroll right.
if (m_nScrolVPos < maxpos)
m_nScrolVPos++;
break;
case SB_PAGEUP: // Scroll one page left.
{
// Get the page size.
SCROLLINFO info;
pScrollBar->GetScrollInfo(&info, SIF_ALL);
if (m_nScrolVPos > minpos)
m_nScrolVPos = max(minpos, m_nScrolVPos - (int)info.nPage);
}
break;
case SB_PAGEDOWN: // Scroll one page right.
{
// Get the page size.
SCROLLINFO info;
pScrollBar->GetScrollInfo(&info, SIF_ALL);
if (m_nScrolVPos < maxpos)
m_nScrolVPos = min(maxpos, m_nScrolVPos + (int)info.nPage);
}
break;
case SB_THUMBPOSITION: // Scroll to absolute position. nPos is the position
m_nScrolVPos = nPos; // of the scroll box at the end of the drag operation.
break;
case SB_THUMBTRACK: // Drag scroll box to specified position. nPos is the
m_nScrolVPos = nPos; // position that the scroll box has been dragged to.
break;
}
// Set the new position of the thumb (scroll box).
pScrollBar->SetScrollPos(m_nScrolVPos);
m_wndPicPreview.Invalidate();
}