编程高手之路跟我学VB萤幕抓取与萤幕保护程式.docx
《编程高手之路跟我学VB萤幕抓取与萤幕保护程式.docx》由会员分享,可在线阅读,更多相关《编程高手之路跟我学VB萤幕抓取与萤幕保护程式.docx(9页珍藏版)》请在冰豆网上搜索。
![编程高手之路跟我学VB萤幕抓取与萤幕保护程式.docx](https://file1.bdocx.com/fileroot1/2022-11/25/dae3756e-ee85-42e6-b12c-af10e9c52649/dae3756e-ee85-42e6-b12c-af10e9c526491.gif)
编程高手之路跟我学VB萤幕抓取与萤幕保护程式
编程高手之路—跟我学VB—萤幕抓取与萤幕保护程式
「VB没有提供这样的功能,必须呼叫WindowsAPI」,当笔者这样回答读者之後,有些读者会要求笔者介绍WindowsAPI方面的书籍,尤其是中文书,但实际上,目前市面上只看到一本原文书(ZDPress出版的VisualBasic5.0ProgrammingGuidetoWin32API),中文书则未见过,为什麽没有WindowsAPI方面的中文书呢?
道理很简单,WindowsAPI对VB的学习者而言,犹如山峰之颠,能够一路爬上来的人原本就十分有限,而越往山顶,人烟越稀少,因此这样的书写出来,销售成绩必然惨不忍睹,也许有些读者会觉得本土的作者实在很混,只愿意写简单的书,但撰写深入的书籍,费时费力费神而销售成绩又不好,最终结果是这种不混的作者混不下去,当过读者而现在又是作者的笔者,对於此一现象有时也颇感无奈。
不管怎样,笔者现在已经开始介绍WindowsAPI了,也许真的不想混了,但混与不混不是笔者开启本系列讲座最担心的事情,笔者所担心的是这样的主题对读者来说会不会太枯燥?
会不会太艰涩?
会不会像笔者当初研读WindowsAPI时,越研读心中的疑问越多,就好像进入无底黑洞一样?
为了避免以上的结果,在内容的介绍上,笔者会倾向於即学即用的API,短期内尽量避免深入Windows无底黑洞地带,而会先到那些有花有草地方。
本期笔者想引领您去的地方是:
萤幕抓取程式与萤幕保护程式。
萤幕抓取程式简介
如果不写程式,抓取萤幕图像的方法是:
按下PrintScreen键,然後开启Windows的小画家,接着选取小画家功能表的「编辑/贴上」,即可将萤幕图像复制到小画家的绘图区域中。
如果写程式呢?
稍後笔者就会介绍,介绍以前,建议您先进入笔者的网站下载笔者所撰写的萤幕抓取程式(CopyScr.vbp),一来可了解程式的基本运作模式,二来不必自己输入程式,此一程式执行後,选取功能表的「我抓我抓我抓抓」,即可将萤幕的图像复制进来。
图-1「萤幕抓取程式」
萤幕保护程式简介
保护萤幕最简单的方法就是不用时关掉它,但有这种好习惯的人实在不多,所以有人写程式侦测滑鼠与键盘的输入,如果有一段时间没有滑鼠或键盘的输入,便将电脑视为没有人在使用,而把萤幕显示成黑色,用以保护萤幕,後来又有人觉得黑色太难看了,就加了一点动画,慢慢地动画越来越多也越有趣,结果反倒成为萤幕保护程式不可或缺的功能,而不再以保护萤幕为主了。
如何撰写萤幕保护程式当然也是本期的重点,同样的,介绍以前,请进入笔者的网站下载笔者所撰写的萤幕保护程式(saver.vbp),此一程式预设的等待时间是0.5分钟,如果您等待30秒不操作键盘与滑鼠,程式便会进入萤幕保护状态,您也可以设定等待的时间,如图-2,或者直接按下「立刻测试」钮以检视萤幕保护程式的动画。
(特别注意:
请务必以程式右上角的关闭钮结束程式,不要以VB的结束钮来结束程式,否则…,玩看看,就知道会有什麽後果)
图-2「萤幕保护程式」的设定画面
了解萤幕抓取程式及萤幕保护程式的基本运作模式,接下来让笔者来说明相关的工作概念。
Windows的绘图观念
VB用来绘图的方法有Line(画线及矩形)、Circle(画圆)、PSet(画点)、PaintPicture(描绘图片)…等,这些方法有一共通特点—只能描绘在表单(Form)视窗的「显示区域」(Windows称之为clientrectangle),想跨越显示区域而把文字或图形描绘在视窗标题区、功能表区、萤幕任意区域…,想都别想,而以萤幕保护程式为例,则必须在萤幕任意位置绘图。
想在萤幕任意位置绘图,我们必须先了解Windows的绘图观念
绘图之前,先取hDC
所有Windows的绘图,都必须先取得hDC,然後以hDC呼叫绘图的API。
hDC是handleofDC(DeviceContext)的缩写,有关handle的观念笔者上一期已经有所说明,本期不再重述,至於DC呢?
Windows规定任何程式绘图之前都必须先配置一块绘图专用的系统资源(通常是记忆体),这绘图专用的系统资源就是DC啦。
DC与其他系统资源一样,会有一个唯一的handle值,此一handle值便是hDC。
VB程式呼叫Line、Circle、Pset、PaintPicture…等方法时,表面上好像与DC无关,但实际上它们都会预先配置好DC,然後透过DC来绘图,您可以查阅表单及PictureBox物件的说明文件,结果可以发现它们都含有hDC属性,此一属性即为表单及PictureBox物件用来呼叫Windows绘图函数的DChandle。
BitBlt:
图像描绘函数
二话不说,让我们直接观察一个利用DC来绘图的API函数—BitBlt,BitBlt的作用与PaintPicture有点类似,假设表单上含有一PictureBox,而此一PictureBox的Picture属性亦设定有图片,若使用VB的绘图功能,则将PictureBox的图片绘制在Form1的方法是:
Form1.PaintPicturePicture1.Picture,0,0
如果使用BitBlt函数则是:
ret=BitBlt(Form1.hDC,0,0,Picture1.Width,Picture1.Height,Picture1.hDC,0,0,SRCCOPY)
作用是把Picture1的图像复制到Form1,使用此一函数时,请特别注意参数四(如以上叙述之Picture1.Width)及参数五(如以上叙述之Picture1.Height),这两个参数分别表示长与宽,但单位是「像素」(pixel),所以执行以上叙述之前,必须先把表单的ScaleMode属性都设定成"3-像素"才可以,此一BitBlt函数之使用实例请参阅笔者所完成的BitBlt.vbp(包含在下载之档案中)。
萤幕hDC的取得与释回
表单及PictureBox物件的DC是VB预先帮我们准备好的,在萤幕抓取及保护程式中,我们则必须呼叫WindowsAPI取得萤幕的DC,如此方可读取萤幕的图像,以及复制图像到萤幕上。
取得萤幕DC的函数是GetDC,呼叫之叙述如下:
DimhDCAsLong
hDC=GetDC(0)
传回值hDC即等於萤幕的hDC。
在此笔者顺便要说明Windows另一个重要的观念:
系统资源不用时应释回,所以在我们取得萤幕DC,并且完成绘图之後,别忘了将DC释回,此时呼叫的API是:
'hDC是先前GetDC(0)的传回值
ret=ReleaseDC(0,hDC)
将萤幕图像复制到VB物件中
介绍BitBlt及GetDC两个API函数之後,萤幕抓取程式几乎是呼之欲出了,例如使用以下的叙述可以将萤幕图像复制到表单物件上:
DimhDCAsLong
hDC=GetDC(0)
ret=BitBlt(Form1.hDC,0,0,萤幕宽,萤幕高,hDC,0,0,SRCCOPY)
ret=ReleaseDC(0,hDC)
不过请注意「萤幕宽」及「萤幕高」两个参数,这两个参数的长度单位是「像素」,以640×480的萤幕为例,应该指定成640及480,但问题是别人使用的萤幕可能不是640×480,所以我们应该利用Screen物件的Width及Height属性读取萤幕的宽与高(注:
此时读取之宽与高是以Twips为单位),然後再利用以下公式求取萤幕以pixel为单位的宽与高:
pixel单位之萤幕宽=Screen.Width\Screen.TwipsPerPixelX
pixel单位之萤幕高=Screen.Height\Screen.TwipsPerPixelY
将萤幕图像复制到表单上面的完整程式,请参阅笔者所完成的ScrForm.vbp(包含在下载之档案中)。
萤幕抓取程式之制作
接着请以笔者所完成的CopyScr.vbp来例,让我们来了解萤幕抓取程式还有哪些地方该注意。
AutoRedraw(自动重绘)属性的使用
首先笔者想请您做个实验:
执行ScrForm.vbp程式,并且复制萤幕图像到表单上,接着以其他视窗盖住此一表单,然後再移开,结果表单上曾经被盖住的区域其图像都会消失。
解决以上问题常用的方法是将表单的AutoRedraw(自动重绘)属性设定为True,当我们将表单的AutoRedraw属性设定为True之後,将来表单若有区域被覆盖住而又回到萤幕前端,则表单都会自动重绘被覆盖的区域,使得表单能够展现原有的图像。
利用AutoRedraw属性来解决以上的问题看起来十分简单,但它有个严重的缺点:
当我们将表单的AutoRedraw属性设定为True之後,表单将会记录每一个绘图的动作,而当绘图动作越来越多时,所使用的记忆体将会逐渐增加,自动重绘的时间也会越来越长。
为了改善以上的缺点,笔者的作法如下:
Form名.AutoRedraw=True'设定自动重绘
...
...连续性的绘图动作
...
SetForm名.Picture=Form名.Image'将表单的图像设定成常驻性的图片
Form名.AutoRedraw=False'取消自动重绘
由於Picture属性中的图片是常驻的,因此当我们将表单的Image属性(等於表单上的图像)设定给它,便可以将AutoRedraw属性设定为False,以避免不必要的重绘动作。
以上程式使用了AutoRedraw、Image、及Picture属性,这些属性除了适用於表单物件之外,也适用於PictureBox物件,而实际上,笔者所撰写的CopyScr.vbp并不是把萤幕复制到表单上,而是复制到PictureBox物件之中。
浏览图片的操作介面
除了将萤幕图像复制到PictureBox之外,另一个问题则是萤幕总是比表单的显示区域来得大,以致被复制到PictureBox的图像会超出表单的显示区域,为了让使用者可以看到萤幕的全貌,我们必须在表单上布置卷动轴,并提供浏览的操作介面。
有关大图片浏览程式,常见的作法是把显示图片的PictureBox布置在另一个PictureBox里面,然後再利用卷动轴调整内部PictureBox相对於外部PictureBox的位置,而达到浏览图片的目的,如图-3。
图-3图片浏览程式所需之控制元件及其布置
至於相关程式则请参考CopyScr.frm的SetPicture副程式、VScroll1_Change、及HScroll1_Change事件程序。
图片的存档
最後就是图片的存档了,VB提供的存档叙述是SavePicture,呼叫格式如下:
SavePicture图片,档案名称
其中「图片」参数可以是物件的Picture属性或利用LoadPicture所载入的Picture物件,以本程式为例,则指定成PictureBox的Picture属性。
萤幕保护程式的制作
笔者所撰写的萤幕保护程式,是将萤幕切割成M×N个方块,然後在萤幕保护程式启动时,随机变换任意两个方块,此时所需执行的动作是:
(1)将(M1,N1)座标的方块复制到一暂存区。
(2)将(M2,N2)座标的方块复制到(M1,N1)座标。
(3)将暂存区的方块复制到(M2,N2)座标。
假设每一方块的边长等於80,则所需执行的BitBlt叙述如下:
'hDCMem为暂存区之hDC
'hDCScreen为萤幕之hDC
ret=BitBlt(hDCMem,0,0,80,80,hDCScreen,M1*80,N1*80,SRCCOPY)
ret=BitBlt(hDCScreen,M1*80,N1*80,80,80,hDCScreen,M2*80,M2*80,SRCCOPY)
ret=BitBlt(hDCScreen,M2*80,M2*80,80,80,hDCMem,0,0,SRCCOPY)
暂存区DC的取得与释回
在以上程式中,hDCScreen是利用GetDC(0)取得,而hDCMem(暂存区的DC)则必须利用CreateCompatibleDC函数来取得,如下:
DimhDCMemAsLong
hDCMem=CreateCompatibleDC(hDCScreen)
此一函数的作用是建立相容性DC,由於传入的是hDCScreen(萤幕的hDC),所以建立出来的hDCMem将与萤幕相容(主要是色盘之颜色数目相同)。
暂存区DC不再使用时应将其释回,此时呼叫之API是DeleteDC,如下:
(释回萤幕DC的函数是ReleaseDC,两者不可以混合使用)
ret=DeleteDC(hDCMem)
CreateCompatibleBitmap:
为DC建立点阵图
取得hDCMem之後,利用BitBlt应该就可以将hDCScreen的图像复制到暂存区DC中,但实际上不然,BitBlt只对含有「点阵图」(Bitmap)的DC有作用,而经由CreateCompatibleDC所建立的暂存区DC一开始是不含点阵图的,所以呼叫BitBlt函数并没有作用,为了让BitBlt能够复制图像到暂存区DC,以及从暂存区DC读取图像,我们必须为暂存区DC建立点阵图,此时呼叫之API如下:
DimhBitmapAsLong
hBitmap=CreateCompatibleBitmap(hDCScreen,宽,高)'建立点阵图
ret=SelectObject(hDCMem,hBitmap)'将点阵图设定给hDCMem
请特别注意传入CreateCompatibleBitmap的hDC是萤幕的hDC,不是暂存区的hDC,其作用是建立与萤幕相容的点阵图(颜色数目与萤幕相同的点阵图),而接下来必须呼叫SelectObject让此一点阵图的handle(hBitmap)附属於hDCMem,也就是让暂存区DC含有点阵图,如此一来hDCMem方可用於BitBlt函数。
点阵图的释回
不使用点阵图时,可呼叫DeleteObject(hBitmap)将其释回,但请注意附属於DC的点阵图是不可以释回的,除非DC也已经被释回系统,例如:
'hBitmap附属於hDCMem
ret=DeleteDC(hDCMem)'先释回DC
ret=DeleteObject(hBitmap)'再释回点阵图
萤幕保护程式之动画
使用以上所介绍的API函数,总算可以达到随机变换萤幕任意两个方块的目的了,请看笔者所完成的副程式:
(此一副程式放置saver.vbp专案的saver.bas档案中,也包含在下载的档案中)
SubSaver()
DimM1AsLong,M2AsLong,N1AsLong,N2AsLong,retAsLong
DimhDCMemAsLong,hDCScreenAsLong,hBitmapAsLong
DimsxAsInteger,syAsInteger
sx=Screen.Width\Screen.TwipsPerPixelX'以pixel为单位之萤幕宽
sy=Screen.Height\Screen.TwipsPerPixelY'以pixel为单位之萤幕高
hDCScreen=GetDC(0)'取得萤幕DC
hDCMem=CreateCompatibleDC(hDCScreen)'建立暂存区DC
hBitmap=CreateCompatibleBitmap(hDCScreen,BSize,BSize)'建立点阵图
ret=SelectObject(hDCMem,hBitmap)'将点阵图设定给暂存区DC
M1=CInt(Rnd*sx\BSize)
N1=CInt(Rnd*sy\BSize)'(M1,N1)为方块一
M2=CInt(Rnd*sx\BSize)
N2=CInt(Rnd*sy\BSize)'(M2,N2)为方块二
'方块一与方块二互换
ret=BitBlt(hDCMem,0,0,BSize,BSize,hDCScreen,M1*BSize,N1*BSize,SRCCOPY)
ret=BitBlt(hDCScreen,M1*BSize,N1*BSize,BSize,BSize,hDCScreen,M2*BSize,N2*BSize,SRCCOPY)
ret=BitBlt(hDCScreen,M2*BSize,N2*BSize,BSize,BSize,hDCMem,0,0,SRCCOPY)
ret=ReleaseDC(0,hDCScreen)'释回萤幕DC
ret=DeleteDC(hDCMem)'释回暂存区DC
ret=DeleteObject(hBitmap)'释回点阵图,一定要放在DeleteDC之後
EndSub
有了Saver副程式,再配合连续的变换动作,这就是笔者所制作的萤幕保护程式动画。
键盘及滑鼠的侦测
当萤幕保护程式进入动画时,程式必须侦测使用者是否有操作键盘或滑鼠,如果有,则终止动画,并且将萤幕还原,而当程式离开动画或尚未进入动画时,必须侦测使用者没有操作键盘或滑鼠的时间是否超过等待时间,如果是,则进入动画。
但是在Windows的多工作业环境底下,所有键盘及滑鼠的输入是由Windows统筹管里的,当使用者操作键盘或滑鼠时,键盘与滑鼠的接收者是Windows,而Windows会根据当时的情况,决定键盘及滑鼠的输入该传给哪一个程式或视窗,因此每一个程式或视窗只会收到属於自己的键盘及滑鼠输入。
以上的运作模式就好像电话一样,正常情况之下,每个人都只会收到属於自己的电话,除非,嘿嘿,窃听。
在Windows的工作模式底下,想要窃听键盘及滑鼠的输入是可能的,一旦窃听成功,属於其他视窗的键盘及滑鼠输入都会先传到我们的程式中。
有关窃听方面的程式设计,笔者想留待下一回再介绍,因为必须说明的观念实在不少,本期请直接使用笔者所完成的键盘及滑鼠侦测程式—Saver.vbp中的hook.bas模组,此一模组虽然没有提供窃听的功能,但可以侦测到是否有键盘及滑鼠被输入(不管输入的对象是哪一个程式或视窗都可以侦测到)。
使用hook.bas的方法如下:
1.侦测键盘及滑鼠的输入之前,呼叫StartHook副程式。
2.呼叫StartHook之後,只要发生键盘或滑鼠的输入,hook模组便会将键盘或滑鼠被输入的时间记录在tmKbMouse全域变数中,其他程式可以利用目前时间(呼叫Now函数)减去此一变数,得知使用者隔多久没有操作键盘及滑鼠了,如果超过等待时间,便可以进入萤幕保护程式的动画。
3.进入萤幕保护程式的动画之前,记得把hook.bas的KbMouseDetect全域变数设定为False,接着进入萤幕保护状态之後,只要使用者再度操作键盘或滑鼠,此一变数便会被hook.bas模组设定True,而其他程式可以再判断此一变数,以停止动画并还原萤幕。
4.萤幕保护程式结束以前一定要呼叫FreeHook副程式,不然会造成Windows运作不正常,举例来说,笔者所撰写的saver.frm中便是在Form_Load事件程序中呼叫StartHook,在Form_Unload事件程序中呼叫FreeHook。
萤幕的还原
当萤幕保护程式停止动画时,我们必须把萤幕还原,还原的方法有很多种,例如启动动画之前先储存萤幕的图像,结束时再回存图像,不过这个方法比较浪费记忆体,笔者使用的方法是把表单隐藏起来,然後再显示出来,接着放到最大,然後恢复成原大小,作用是利用表单覆盖整个萤幕,则最後当表单又恢复成原大小时,萤幕图像即会自动还原,程式如下:
SubRestoreScreen()
Me.Visible=False
Me.Visible=True
Me.WindowState=vbMaximized
Me.WindowState=vbNormal
EndSub
时间的控制
除了saver及hook两个模组之外,最重要的事情就是时间的控制了,在笔者的saver表单中,布置了两个Timer控制元件(Timer1及Timer2),其中Timer2用来侦测使用者隔多久没有操作键盘及滑鼠,若超过等待时间,则驱动Timer1进入动画描绘状态,其程式如下:
PrivateSubTimer2_Timer()
IfDateDiff("s",tmKbMouse,Now)>WaitTime*60Then
Timer1.Interval=10'驱动动画
EndIf
EndSub
至於Timer1则是用来显示动画,并判断使用者是否操作了键盘及滑鼠以决定是否中断动画,程式如下:
PrivateSubTimer1_Timer()
IfNotStartSaverThen'进入动画的初始动作
StartSaver=True
KbMouseDetect=False
ExitSub
EndIf
IfKbMouseDetectThen'用者操作了键盘或滑鼠
Timer1.Interval=0
StartSaver=False
RestoreScreen'还原萤幕
Else
Saver'显示动画
EndIf
EndSub
制作您自己的萤幕保护程式
以上介绍的是笔者所制作的萤幕保护程式,虽然动画的部分稍嫌简单,但Windows技术的难处均已克服,如果您想制作自己的萤幕保护程式,需要修改的是动画的部分,也就是saver.bas中的saver副程式,此外,萤幕