第5章 截图软件
截图软件的应用越来越广泛。Windows操作系统也自带了一个截图软件,单独使用PrtScSysRq键,可以截取整个屏幕;如果使用Alt+PrtScSysRq组合键,可以截取获得焦点的窗体。QQ聊天软件也具有一项简单的截图功能,另外,还有一些截图软件带有基本的图片编辑功能。本章要使用API函数设计一个截图软件。
5.1 需求分析
通常,截图软件主要具有截取屏幕、区域截图、截取焦点窗体等功能,本章设计的截图软件包括以下功能。
(1)可以截取整个计算机屏幕,包括开始菜单和任务栏等。
(2)只截取获得焦点的窗体,该窗体包含标题栏和边框等。
(3)只截取获得焦点窗体的用户区,不能包括标题栏。
(4)可以选择截取整个屏幕的任意区域。
(5)延时截取获得焦点的图片,可以切换窗体,指定不同的窗体获得焦点。
(6)截图软件的界面的大小可以任意改变,使用滚动条来浏览图片被遮挡的部分。
(7)可以使用热键F11实现截图。
(8)可以使用热键Esc取消截图。
(9)可以保存和打印当前的图片。
(10)可以使用菜单栏和工具栏进行相关操作。图5-1为截图软件界面图。
图5-1 截图软件界面
5.2 技术要点
截图软件的技术要点是使用API函数获得相应的图片。事实上,不同的截图功能具有一定的相似性,都调用CaptureWindow函数获得相应窗口的句柄,再调用CreateBitmapPicture函数创建图片对象,实现截图功能。但是,在调用这些函数之前,需要对各种不同的截图情况做相应的前期处理。
截取整个屏幕比较简单,截取时使用GetDesktopWindow函数获取桌面窗口句柄即可,该桌面窗口覆盖了整个屏幕。然后在CaptureWindow函数中调用GetDC函数获取整个屏幕的显示设备的上下文句柄。
截取活动窗体时,首先使用GetForegroundWindow函数获取焦点窗口的上下文环境句柄。然后,使用GetWindowRect函数获取该窗口的边框矩形尺寸,该尺寸是以屏幕系统坐标给出的。最后,在CaptureWindow函数中调用GetWindowDC函数获取整个窗口的显示设备的上下文句柄,包括边框、滚动条、标题栏、菜单等。
截取窗口用户区时,方法和截取活动窗体相同。但是,在CaptureWindow函数中调用GetDC函数获取窗口的客户区显示设备的上下文句柄,而不是调用GetWindowDC函数。
截取区域图片时,首先,自动截取整个屏幕,将屏幕图片放置在PictureBox控件中。然后,使用鼠标拖动Shape控件来确定需要截取的区域。最后,根据所选区域的位置和尺寸参数,使用截取整个屏幕的方法,截取区域图片。
延时截屏的方法和截取活动窗体是相同的。只是在截图之前使用DateAdd函数和Do Until循环语句延时一定的时间后自动截图。
在截图程序中,需要使用BitBlt函数对需要截取的屏幕或窗体的设备环境区域中的像素进行位块转换,并将其传送到目标设备环境。
此外,在区域截图时,需要使用Shape控件选择被截区域。截图前,Shape控件的Visual属性为False。在显示整个屏幕的PictureBox控件的MouseDown事件中,使得Shape控件可见,并确定该控件的位置,即Left和Top属性。在MouseMove事件中,确定Shape控件的尺寸,即Width和Height属性。在MouseUp事件中,已经由Shape控件确定所要截取的区域,此时,调用相应函数即可截取区域图片。
5.3 系统结构
创建一个新的工程,添加两个Form窗体和一个Module模块,根据图5-1在该Form窗体上添加相应控件。工程的对象及其属性值如表5-1所示。
表5-1 工程的对象及其属性值
此外,还要为主窗体添加一个菜单栏,用于实现上述截图、清除图片、保存和打印图片等操作。主窗体的菜单栏列表,如表5-2所示。
表5-2 菜单栏列表
主窗体frmMain还有工具栏,该工具栏中的命令与窗体的菜单项命令的功能相同。表5-3为主窗体中的工具栏列表。
表5-3 主窗体中的工具栏列表
工具栏控件Toolbar中索引为3和9的按钮的样式被设置为3,作为按钮间的分隔使用。
5.4 实现过程
本章设计的截图软件需要使用两个Form窗体,和一个Module模块。其中一个Form窗体用做主窗体,实现截图的操作和截取图片的存放功能;另一个Form窗体在截取区域图片的时候作为一个辅助窗体。Module模块中声明API函数和结构类型变量,以及公用函数。根据图5-2和图5-3分别向主窗体和辅窗体上添加相应控件,控件的属性设置如表5-1所示。
图5-2 主窗体设计界面
图5-3 辅窗体设计界面
根据截图软件的功能和模块化程序设计方法,可以将程序分为API函数和变量声明、截取获得焦点的窗体、截取整个屏幕、截取用户区、截取区域图片、延时截图、保存和打印图片等。其中,截取整个屏幕、截取用户区、延时截图与截取获得焦点的窗体方法相似。因此,本章只详细介绍截取获得焦点的窗体,截取整个屏幕、而截取用户区、延时截图的程序可以参考光盘中的源程序。此外,保存和打印图片等功能,读者也可以参考光盘中的源程序。
5.4.1 API函数和变量声明
截图软件的截图过程主要由API函数实现。在使用这些API函数之前,首先要对其进行声明,包括在API函数中用到的结构变量。本章将API函数的声明放在Module模块mdlCommon中,读者可以参考光盘中的源程序。
5.4.2 截取焦点窗体
截取获得焦点的窗体的过程可以分为开始截图、扫描热键、获得欲截取的窗体和创建位图对象等部分,下面分别予以详细介绍。
1. 开始截图
开始截图主要是发布截图开始命令,使得截图软件最小化,并处于扫描热键等准备工作阶段。开始截图的程序代码如程序清单5-1所示。
程序清单5-1开始截图
1. Private Sub mnuEntireWindow_Click() '截取整个窗口,包括标题栏和边框 2. intCaptureType=2 3. tmrDelay.Interval=100 4. Me.WindowState=vbMinimized 5. End Sub
程序说明:开始截图命令是由主窗体中的相关菜单命令发布的,因此本段程序是“活动窗体”菜单项命令的Click过程。因为截取整个屏幕、截取用户区与截取获得焦点的窗体3种截图方法是类似的,所以第2行使用intCaptureType变量截图类型。第3行是设置计时器的热键扫描频率,即0.1 秒扫描一次。第4 行是将主窗体最小化,便于选择欲截图的获得焦点的窗体。
2. 扫描热键
本章设计的截图软件使用F11键作为热键。当用户键入F11键后,截图软件才真正开始图片处理过程。使用热键来实现截图的最大好处,是用户可以在启动截图命令后,有充分的时间选择需要截取的窗体,或布局整个屏幕。扫描热键的程序代码如程序清单5-2所示。
程序清单5-2扫描热键
1. Private Sub tmrDelay_Timer() 2. Dim intKeyIn As Integer 3. 4. intKeyIn=GetKeyState(vbKeyF11)And&H8000 5. If intKeyIn=&H8000 Then 6. If intCaptureType=1 Then 7. Set picCapture.Picture=CaptureScreen() 8. ElseIf intCaptureType=2 Then 9. Set picCapture.Picture=CaptureActiveWindow() 10. ElseIf intCaptureType=3 Then 11. Set picCapture.Picture=CaptureClient()
12. End If 13. 14. tmrDelay.Interval=0 15. Me.WindowState=0 16. End If 17. 18. intKeyIn=GetKeyState(vbKeyEscape)And&H8000 19. If intKeyIn=&H8000 Then 20. tmrDelay.Interval=0 21. frmMain.WindowState=0 22. End If 23. End Sub
程序说明:第4行使用GetKeyState函数读取热键F11。第5行判断读取的是否为F11键,如果是F11键,就表示用户已经选择了需要截取的窗体,开始进入截图过程。第6~7行是使用CaptureScreen函数过程截取整个屏幕。第8~9行是使用CaptureActiveWindow函数过程截取获得焦点的窗体。第10~11行是使用CaptureClient函数过程截取用户区。第14行是关闭热键扫描计时器,停止扫描。第15行是截图结束,恢复截图软件在截图前的位置,此时,被截取的窗体已经存在于截图软件的界面中。第18~22 行扫描Esc键,用户可以通过热键Esc取消截图。
3. 获得焦点窗体数据
本章使用API函数获得焦点窗体的数据,包括窗体的句柄和窗体的尺寸大小。然后,将窗体句柄和尺寸数据作为参数,调用CaptureWindow函数截取该窗体。程序代码如程序清单5-3所示。
程序清单5-3获得焦点窗体数据
1. Public Function CaptureActiveWindow()As Picture 2. Dim hWndActive As Long 3. Dim r As Long 4. Dim RectActive As RECT 5. 6. hWndActive=GetForegroundWindow() 7. r=GetWindowRect(hWndActive,RectActive) 8. 9. Set CaptureActiveWindow=CaptureWindow(hWndActive,False,0,0,_ 10. RectActive.Right-RectActive.Left,RectActive.Bottom-RectActive.Top) 11. End Function
程序说明:第6行是获得焦点窗体的句柄,第7行是获得焦点窗体的尺寸。第9~10行是调用CaptureWindow函数,截取焦点窗体,返回图片对象。
4. 截取窗体图片
在截取窗体图片前,需要获得被截取窗体的句柄和尺寸大小。然后将图像复制到内存中,并对调色板等进行设置。最后必须释放相关对象句柄,调用位图处理函数获得图片对象。程序代码如程序清单5-4所示。
程序清单5-4截取窗体图片
1. Public Function CaptureWindow(ByVal hWndSrc As Long,ByVal Client_ 2. As Boolean,ByVal LeftSrc As Long,ByVal TopSrc As Long,_ 3. ByVal WidthSrc As Long,ByVal HeightSrc As Long)As Picture 4. Dim hDCMemory As Long 5. Dim hBmp As Long 6. Dim hBmpPrev As Long 7. Dim r As Long 8. Dim hDCSrc As Long 9. Dim hPal As Long 10. Dim hPalPrev As Long 11. Dim RasterCapsScrn As Long 12. Dim HasPaletteScrn As Long 13. Dim PaletteSizeScrn As Long 14. Dim LogPal As LOGPALETTE 15. 16. If Client Then 17. hDCSrc=GetDC(hWndSrc) 18. Else 19. hDCSrc=GetWindowDC(hWndSrc) 20. End If 21. 22. hDCMemory=CreateCompatibleDC(hDCSrc) 23. hBmp=CreateCompatibleBitmap(hDCSrc,WidthSrc,HeightSrc) 24. hBmpPrev=SelectObject(hDCMemory,hBmp) 25. 26. RasterCapsScrn=GetDeviceCaps(hDCSrc,RASTERCAPS) 27. HasPaletteScrn=RasterCapsScrn And RC_PALETTE 28. PaletteSizeScrn=GetDeviceCaps(hDCSrc,SIZEPALETTE) 29. 30. If HasPaletteScrn And(PaletteSizeScrn=256)Then 31. LogPal.palVersion=&H300 32. LogPal.palNumEntries=256 33. r=GetSystemPaletteEntries(hDCSrc,0,256,LogPal.palPalEntry(0)) 34. hPal=CreatePalette(LogPal) 35. hPalPrev=SelectPalette(hDCMemory,hPal,0) 36. r=RealizePalette(hDCMemory) 37. End If 38. 39. r=BitBlt(hDCMemory,0,0,WidthSrc,HeightSrc,hDCSrc,_ 40. LeftSrc,TopSrc,vbSrcCopy) 41. hBmp=SelectObject(hDCMemory,hBmpPrev) 42. 43. If HasPaletteScrn And(PaletteSizeScrn=256)Then 44. hPal=SelectPalette(hDCMemory,hPalPrev,0) 45. End If 46. 47. r=DeleteDC(hDCMemory) 48. r=ReleaseDC(hWndSrc,hDCSrc)
49. 50. Set CaptureWindow=CreateBitmapPicture(hBmp,hPal) 51. End Function
程序说明:第4~14行声明一些在API函数调用中使用到的变量。第16~20行用于判断截取的窗还是用户区或整个屏幕。第17行使用GetDC函数获得焦点窗体的用户区或整个屏幕。第19 行使用GetWindowDC函数获得整个焦点窗体,包括标题栏等。第22 行使用CreateCompatibleDC函数创建一个内存设备句柄,用于图片的复制过程。第23 行使用CreateCompatibleBitmap函数创建位图,并将其放到内存设备句柄中。第26~28行用于获得焦点窗体或整个屏幕的一些特性。第30~37行用于创建一个新的系统调色板的复制。第39~40行使用BitBlt函数将图像复制到内存设备句柄中。第41行使用SelectObject函数移除图像的复制。第43~45行返回先前选定的调色板。第47~48行删除和释放相关内存句柄。第50行调用CreateBitmapPicture函数过程创建并返回图片对象。
5. 创建位图对象
程序清单5-4中使用BitBlt函数将图像复制到内存设备句柄中,本段程序要对所选择的位图创建位图对象。程序代码如程序清单5-5所示。
程序清单5-5创建位图对象
1. Public Function CreateBitmapPicture(ByVal hBmp As Long,ByVal hPal As Long)_ 2. As Picture 3. Dim r As Long 4. Dim Pic As PicBmp 5. Dim IPic As IPicture 6. Dim IID_IDispatch As GUID 7. 8. With IID_IDispatch 9. .Data1=&H20400 10. .Data4(0)=&HC0 11. .Data4(7)=&H46 12. End With 13. 14. With Pic 15. .Size=Len(Pic) 16. .Type=vbPicTypeBitmap 17. .hBmp=hBmp 18. .hPal=hPal 19. End With 20. 21. r=OleCreatePictureIndirect(Pic,IID_IDispatch,1,IPic) 22. 23. Set CreateBitmapPicture=IPic 24. End Function
程序说明:第3~6行声明一些结构类型的变量。第8~19行对结构类型的变量赋值。第21 行创建新的图片对象。第23 行返回图片对象,由此图片将显示在主窗体中的PictureBox控件中。
5.4.3 区域截图
区域截图的设计思路是先截取整个屏幕,将截取的图片最大化地放在辅窗体的PictureBox控件中,然后从该控件中选择需要截取的区域,将该区域的图片再截取到主窗体中的PictureBox中。截取区域图片的过程可以分为开始截图、截取整个屏幕、选择截取区域等部分。其中,开始截图与程序清单5-1 相似,区域截图功能主要是在窗体frmRegion中实现的,下面分别予以详细介绍。
1. 截取整个屏幕
截取整个屏幕的过程和截取焦点窗体程序相似,本段程序不对该过程进行详细叙述,但是,在截取整个屏幕之前,需要进行相应的设置。程序代码如程序清单5-6所示。
程序清单5-6截取整个屏幕
1. Private Sub Form_Load() 2. Dim hWndScreen As Long 3. Dim EndTime As Date 4. 5. Me.WindowState=2 6. tmrExit.Interval=100 7. 8. EndTime=DateAdd("s",0.2,Now) 9. Do Until Now>EndTime 10. DoEvents 11. Loop 12. 13. hWndScreen=GetDesktopWindow() 14. 15. Set picRegion.Picture=CaptureWindow(hWndScreen,False,0,0,_ 16. Screen.Width\Screen.TwipsPerPixelX,_ 17. Screen.Height\Screen.TwipsPerPixelY) 18. End Sub 19. 20. Private Sub Form_Resize() 21. picRegion.Left=0 22. picRegion.Top=0 23. picRegion.Width=Me.Width 24. picRegion.Height=Me.Height 25. End Sub 26. 27. Private Sub tmrExit_Timer() 28. Dim intKeyIn As Integer 29. 30. intKeyIn=GetKeyState(vbKeyEscape)And&H8000 31. If intKeyIn=&H8000 Then 32. tmrExit.Interval=0 33. Set frmRegion=Nothing 34. Unload Me
35. frmMain.WindowState=0 36. End If 37. End Sub
程序说明:第5行是在窗体加载时最大化窗体,第6行是启动定时程序。第27~30行即定时程序的代码,该定时程序主要用于捕捉Esc键,使程序退出截图状态。第8~11行是延时程序。因为窗体的切换动作需要一些时间,而程序执行的时间非常短,如果不延时直接截取屏幕,会出现屏幕不完整的情况。第13~17行就是调用函数截取整个屏幕。第20~25行是在窗体的Resize事件中将用于放置屏幕图片的PictureBox控件picRegion和窗体frmRegion一样大,以便覆盖整个屏幕。
2. 选择截取区域
截取区域的选择主要是在控件picRegion的MouseDown、MouseMove和MouseUp事件中实现的,区域的选择主要由用户拖动Shape控件来确定。程序代码如程序清单5-7所示。
程序清单5-7选择截取区域
1. Private Sub picRegion_MouseDown(Button As Integer,Shift As Integer,_ 2. X As Single,Y As Single) 3. If Button=1 Then 4. shpRegion.Visible=True 5. shpRegion.Left=X 6. shpRegion.Top=Y 7. shpRegion.Width=0 8. shpRegion.Height=0 9. End If 10. End Sub 11. 12. Private Sub picRegion_MouseMove(Button As Integer,Shift As Integer,_ 13. X As Single,Y As Single) 14. If Button=1 Then 15. If X<shpRegion.Left Then 16. shpRegion.Width=0 17. Else 18. shpRegion.Width=X-shpRegion.Left 19. End If 20. If Y<shpRegion.Top Then 21. shpRegion.Height=0 22. Else 23. shpRegion.Height=Y-shpRegion.Top 24. End If 25. End If 26. End Sub 27. 28. Private Sub picRegion_MouseUp(Button As Integer,Shift As Integer,_ 29. X As Single,Y As Single) 30. Dim EndTime As Date 31. 32. If Button=1 Then 33. shpRegion.Visible=False
34. 35. EndTime=DateAdd("s",0.2,Now) 36. Do Until Now>EndTime 37. DoEvents 38. Loop 39. 40. '截取区域图片 41. Set frmMain.picCapture.Picture=CaptureWindow(hWndScreen,False,_ 42. shpRegion.Left\Screen.TwipsPerPixelX,_ 43. shpRegion.Top\Screen.TwipsPerPixelY,_ 44. shpRegion.Width\Screen.TwipsPerPixelX,_ 45. shpRegion.Height\Screen.TwipsPerPixelY) 46. 47. tmrExit.Interval=0 48. Set frmRegion=Nothing 49. Unload Me 50. frmMain.WindowState=0 51. End If 52. End Sub
程序说明:第1~10行是在MouseDown事件中确定Shape控件的初始位置。第15~19和第20~24行分别在MouseMove事件中确定Shape控件的的宽度和高度,即确定选择区域的尺寸。第41~45行调用CaptureWindow函数截取所选择的区域。第47~50行是结束截取图片程序后,卸载窗体frmRegion,返回到主窗体,显示所截取的图片。
5.4.4 滚动显示图片
如果图片比显示该图片的PictureBox控件大,那么需要使用滚动条显示该图片被遮挡的部分。同时,在用户改变主窗体大小的时候,需要重新调整滚动条及图片的显示。程序代码如程序清单5-8所示。
程序清单5-8滚动显示图片
1. Private Sub Form_Resize() 2. On Error Resume Next 3. 4. picContainer.Width=Me.ScaleWidth-vscCapture.Width 5. picContainer.Height=Me.ScaleHeight-hscCapture.Height-picContainer.Top 6. 7. vscCapture.Left=picContainer.Width 8. hscCapture.Top=picContainer.Height+picContainer.Top 9. vscCapture.Height=picContainer.Height 10. hscCapture.Width=picContainer.Width 11. 12. '水平滚动条设置 13. picCapture.Left=0 14. hscCapture.Min=0 15. hscCapture.Max=picCapture.Width-picContainer.Width 16. hscCapture.Value=0 17. hscCapture.SmallChange=hscCapture.Max/100
18. hscCapture.LargeChange=hscCapture.Max/10 19. '垂直滚动条设置 20. picCapture.Top=0 21. vscCapture.Min=0 22. vscCapture.Max=picCapture.Height-picContainer.Height 23. vscCapture.Value=0 24. vscCapture.SmallChange=vscCapture.Max/100 25. vscCapture.LargeChange=vscCapture.Max/10 26. End Sub 27. 28. Private Sub hscCapture_Change() 29. picCapture.Left=-hscCapture.Value 30. End Sub 31. 32. Private Sub vscCapture_Change() 33. picCapture.Top=-vscCapture.Value 34. End Sub
程序说明:PictureBox控件picCapture用于放置被截取的图片,PictureBox控件picContainer作为放置picCapture的容器,当图片较大时不遮挡滚动条。第4~5行设置容器picContainer的大小,第7~10行设置垂直和水平滚动条的尺寸,第14~18和第21~25行分别设置水平滚动条和垂直滚动条的相应属性,第28~30和第32~34行分别是水平和垂直滚动条的Value属性变化时滚动显示图片。