最近没写什么,稍稍有点忙,再加上研读modern C++ design太考验脑细胞了,所以赶紧随便研究个什么来继续充实我这个blog……
进入正题,如何实现像QQ或者红蜻蜓或者lava-lava那样的抓图功能呢?
根据下面的分析已经实现了这个程序,可是不知道怎么跟大家共享,
如果有兴趣留下Email,我给你把源程序发过去,收到后请来确认一下,谢谢。整个项目的开发环境是VS2005,中文、英文、专业版、Team suit for developer版本都编译测试通过。
时间仓促,因为只是实验性的程序,代码比较乱,注释比较少,还有很多需要改进的地方……请谅解,最近项目比较忙。
分析
经过“苦苦”思索和试验,发现了其中的奥妙,本来神秘的功能原来是个纸老虎!
让我们一起来分析一下:
1. 当我点击截图按钮(或者按快捷键)截图,发现整个屏幕都不动了(别说我废话阿!),经过观察屏幕上一直不断动的一些gif动画也静止了。
2. 我们可以在截图上挥动鼠标(会出现一个选择框)圈起一块,然后图片自动就放到聊天的输入框中。
3. 截图的过程中会有一些漂浮的提示文字在画面上,如果截图的时候选上了这些文字,截出来的图片却没有这些提示文字。
首先想,可不可以让桌面静止,并在上面画东西呢?可能可以,我资质愚笨没有找到方法。
根据现象1,可以推测我们截图后所看到的是一个静止的图片,那么怎么给用户看到图片呢?Windows程序只能在自己的窗口上画东西,就是说我们所看到的截图是被显示在一个窗口上!
如何验证这个推测呢,我在截图画面尝试按下了Alt+Tab键,令人兴奋的是看到了我们所熟悉的窗口切换选择的Windows对话框,这说明目前看到的确实是一个窗口,图片就显示在这个窗口里面!
那么用鼠标在这个窗口上面选择一块区域的时候,我们所看到的选择框实际上就是在这个窗口上绘制的。截图后图片被放到剪切板中,粘贴到合适的窗口中。
现象3很容易解释,有一种方案是抓图的时候记住用户所选择的坐标,然后从刚才抓的桌面的那个位图上把这块截下来,当然不会有后来绘制的图案或者文字了。
实现
好,经过上面的分析,经过不断的实验我已经做出了一个可以很好工作的MFC程序,在此跟大家分享一下很多有趣的细节和一些零散的技术。
用到的技术
GDI+ & GDI : 实现抓图绘制等功能。自认为对GDI了解过于肤浅,所以能采用GDI+就尽量使用。
创建全屏顶置的窗口: 用来显示用户截图,以及在上面的操作。
全局键盘钩子: 实现快捷键截图。
托盘 & 托盘气球: 恩…… 酷一点,用起来不碍眼。
上面提到的技术很有趣、也很简单,稍稍懂VC的人很容易就能学会。下面我会详细地介绍这些技术。
托盘
请参考我的另一篇blog,关于托盘的使用,比较容易。
http://blog.csdn.net/smalllixin/archive/2007/09/13/1783133.aspx
实现
好,经过上面的分析,经过不断的实验我已经做出了一个可以很好工作的MFC程序,在此跟大家分享一下很多有趣的细节和一些零散的技术。
用到的技术
GDI+ & GDI : 实现抓图绘制等功能。自认为对GDI了解过于肤浅,所以能采用GDI+就尽量使用。
创建全屏顶置的窗口: 用来显示用户截图,以及在上面的操作。
全局键盘钩子: 实现快捷键截图。
托盘 & 托盘气球: 恩…… 酷一点,用起来不碍眼。
上面提到的技术很有趣、也很简单,稍稍懂VC的人很容易就能学会。下面我会详细地介绍这些技术。
托盘
请参考我的另一篇blog,关于托盘的使用,比较容易。
全局键盘钩子
Windows的全局钩子需要定义在DLL中,其原因大概是需要一个被所有进程共享的数据区吧,通过link中的SHARE来声明,一会将会看到具体做法。
声明一下,这段代码是根据《精通MFC》中介绍钩子那一节稍加修改得来的。
代码详细请在我给你的程序中看。
注意 在连接器->命令行->附加选项中有
/SECTION:SHARDATA,SRW
全局键盘钩子
Windows的全局钩子需要定义在DLL中,其原因大概是需要一个被所有进程共享的数据区吧,通过link中的SHARE来声明,一会将会看到具体做法。
声明一下,这段代码是根据《精通MFC》中介绍钩子那一节稍加修改得来的。
代码详细请在我给你的程序中看。
注意 在连接器->命令行->附加选项中有
/SECTION:SHARDATA,SRW
创建全屏顶置的窗口
这个技术比较简单,您只需要了解一点背景知识。
Windows窗口有一个层次顺序,叫做Z序,英文名Z order。表示窗口之间的层次关系,把窗口放在最前面就是在Z序的最上面。
这里用一个API改变窗口Z序并扩大这个对话框到全屏幕(注意这是一个无模式对话框)
int screenWidth = GetSystemMetrics(SM_CXSCREEN);
int screenHeight = GetSystemMetrics(SM_CYSCREEN);
::SetWindowPos(screenDlg, HWND_TOPMOST, 0, 0, screenWidth, screenHeight,SWP_HIDEWINDOW);
这个函数将对话框扩张至全屏,并让这个对话框一直在最顶端,初始状态为隐藏,你可以之后调用ShowWindow将这个对话框显示出来并激活。无模式对话框的创建与显示这里就不详细说了,网上有很多的资源。
抓图技术
我尽量使用GDI+,有些GDI+解决不了的情况(或许是我不知道)用GDI的函数来处理。
下面的程序段是抓全屏的代码。
int screenHeight = GetSystemMetrics(SM_CYSCREEN);
Bitmap * bmp = :: new Bitmap(screenWidth, screenHeight,PixelFormat32bppARGB);
Graphics g(bmp);
HDC hDC = g.GetHDC();
HDC hDesktopDC = ::GetDC(HWND_DESKTOP);
BitBlt(hDC, 0 , 0 , screenWidth, screenHeight, hDesktopDC, 0 , 0 , SRCCOPY);
g.ReleaseHDC(hDC);
::ReleaseDC(HWND_DESKTOP, hDesktopDC);
... {
HBITMAP hBitmap;// HBITMAP created from your Gdiplus Bitmap (i.e., screenBmp)
gdipBitmap->GetHBITMAP(Color(255,0,0,0), &hBitmap);
CBitmap bitmap;// temp bitmap
CClientDC dcClient(this);// client DC from your window handle
CDC dcDest;// Destination DC (i.e., memory dc for your temporay bitmap )
dcDest.CreateCompatibleDC(&dcClient);
CDC dcSource;// Source DC (i.e., memory dc for your Gdiplus bitmap )
dcSource.CreateCompatibleDC(&dcClient);
// Select your Gdiplus HBITMAP into the memory DC
HBITMAP oldhBitmap = (HBITMAP)dcSource.SelectObject(hBitmap);
// Create a compatible bitmap that matches the width and height of your Gdiplus Bitmap
bitmap.CreateCompatibleBitmap(&dcClient, gdipBitmap->GetWidth(), gdipBitmap->GetHeight());
// Select this bitmap into the destination DC
CBitmap* pOldBitmap = dcDest.SelectObject(&bitmap);
// Copy the source bitmap to the destination bitmap
::BitBlt(dcDest, 0, 0, gdipBitmap->GetWidth(), gdipBitmap->GetHeight(), dcSource, 0, 0, SRCCOPY);
// Always select your objects out of their respective DC's
dcSource.SelectObject(oldhBitmap);
dcDest.SelectObject(pOldBitmap);
VERIFY(::OpenClipboard(m_hWnd));
// Empty it out
VERIFY(::EmptyClipboard());
// Place your global HBITMAP handle on the clipboard
VERIFY(::SetClipboardData(CF_BITMAP, bitmap.m_hObject));
// Always close the clipboard
::CloseClipboard();
// We don't need the Gdiplus HBITMAP anymore
::DeleteObject((HGDIOBJ)hBitmap);
return TRUE;
}
PasteBitmapToClipboard(forClip);
delete forClip;