|
在VB中“遥控”鼠标 |
|
| 来自: | 作者:况正谦 |
|
尽管Windows的API函数在当今“Visual”成风的时代正渐渐被人们忘却,但是正如当年开发高技巧的DOS软件离不开汇编语言和DOS系统功能调用一样,无论开发平台高级到什么地步,只要Windows还在最底层运作,就要用到API函数。无论VB的开发手段多么高明,功能多么强大,但是在现实应用中,总有一些功能的实现是VB无能为力的。微软也早意识到这一点,在VB的诸多版本中始终为API留有一席之地。根据笔者多年的VB开发经验,每当我们为了实现一个功能在VB浩瀚的对象、属性、方法、函数中苦苦搜索最终无功而返时,如果换一个角度,到API函数库中去探寻,往往能得到一个惊喜。 Windows API API函数是应用程序接口函数的缩写。Windows在API函数库中,为用户开发基于Windows的应用程序提供了所需的各种基本功能。API函数库的几百个函数分布在Windows系统的三个动态链接库中。这三个库在Windows 3.x中名为“KERNEL”、“USER”和“GDI”,在Windows 95中名为“KERNEL32”、“USER32”和“GDI32”。这些API函数的功能和接口在Windows的编程手册中有系统详尽的说明,也可以从Visual C++ for Windows或Borland C++ for Windows的联机帮助手册中查到。 在VB中引用API函数,就像混合语言编程一样,要声明所引用的函数调用接口以及函数所在的动态链接库。声明一个API函数的语法为: Declare Function 函数名 Lib “库名" ([参数表]) As 返回类型 如果一个API函数返回值为空(void),可以把它声明为过程: Declare Sub 过程名 Lib “库名" ([参数表]) 此外,相当一部分API函数的调用参数中用到了Windows系统定义的结构数据类型,这些类型在VB中也要用Type语句定义。 好在VB在提供访问API函数功能的同时,也给用户提供了一个友好的工具,可以省去我们声明API函数、定义结构数据类型的绝大部分工作。这个工具在VB 5.0中叫“API Text Viewer”。在该工具中,载入“Win32api.txt”后,就可以在“Declares”类中搜索API函数声明,在“Types”类中搜索结构数据类型的定义,或在“Costants”类中搜索API函数中用到的常量定义。把搜索到的声明、定义直接通过剪贴板复制到VB程序中比较省力省心。 把需要用到的函数声明和相关的数据结构定义以及常数定义加到窗体(Form)的General部分,就可以在程序中像调用普通VB函数一样方便地调用API函数。 应用实例 以下从实用的角度出发,援引笔者在实际工作开发中的API应用一例,谨供参考。 漫游过Windows 3.2的五分钟教程的用户,大概还记得演示鼠标用法。鼠标指针自动随教程的指令移动,自动点开菜单、按动按钮,完全脱离了对物理鼠标器的依赖。这种高级控制技巧在VB的函数和方法中是找不到的,仍然要借助于API函数。 本例用API函数实现对鼠标的“遥控”:无论鼠标指针在何处,一个菜单命令Alt-C就能使鼠标指针自动平滑移动到窗口中一个按钮的中心,然后自动按动按钮,激活按钮的Click事件处理过程。掌握了本例的方法后,前面提到的五分钟教程软件就不难制作了。 本例中,以下几个功能的实现至关重要:获取鼠标光标在屏幕上的位置、获取按钮在屏幕上(注意:不是在窗口中)的位置、移动鼠标光标、自动按下和放开按钮,下面分别介绍。 获取鼠标光标在屏幕上的位置和移动鼠标光标,分别需调用API函数GetCursorPos和SetCursorPos。这两个函数的接口声明如下: Declare Function GetCursorPos Lib “user32" (lpPoint As POINTAPI) As Long Declare Function SetCursorPos Lib “user32" (ByVal X As Long, ByVal Y As Long) As Long 在SetCursorPos函数中,参数X和Y指定了鼠标光标在屏幕上的坐标。GetCursorPos函数把鼠标光标的当前位置存到结构变量lpPoint中。结构变量的定义如下: Private Type POINTAPI X As Long Y As Long End Type 按钮的Left和Top属性给出的是按钮的左上角在窗口客户区坐标系中的坐标位置。要把鼠标光标移到按钮正中,需要得到按钮中心在屏幕坐标系中的坐标位置。窗口客户区坐标系与屏幕坐标系不仅坐标原点不同,二者的坐标单位(scale)也不同。窗口客户区坐标系的单位是Twip,屏幕坐标系单位是Pixel,二者的关系可以从屏幕对象Screen的TwipsPerPixelX和TwipsPerPixelY属性获取。这两个属性分别代表了水平和垂直两个方向上的单位转换比例。 把按钮中心在窗口客户区中的坐标单位转换为屏幕坐标系的单位后,还要通过一个API函数进行坐标平移变换,最终取得按钮中心在屏幕坐标系中的坐标位置。这个API函数的接口声明如下: Declare Function ClientToScreen Lib “user32" (ByVal hwnd As Long, lpPoint As POINTAPI) As Long 最后一步是自动按下和放开按钮,这是通过模拟鼠标左键的按下和放开来实现的。该功能需调用API函数SendMessage向按钮发一对鼠标左键按下和放开的消息,函数接口声明如下: Declare Function SendMessage Lib “user32" Alias “SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long SendMessage函数的hwnd参数为接收消息的窗口或控件的hwnd句柄。wMsg参数指定具体的消息值。在本例中,要发送鼠标左键按下和释放的消息,wMsg的取值分别为WM_LBUTTONDOWN和WM_LBUTTONUP,这两个常量定义如下: Const WM_LBUTTONDOWN = &H201 Const WM_LBUTTONUP = &H202 把这两个消息发送给一个按钮,按钮就会像真的被鼠标左键单击了一样。SendMessage函数中的后两个参数在本例中可以不理,简单置0即可。 还有一点细节需注意。如果连续给按钮发送一对WM_LBUTTONDOWN和WM_LBUTTONUP消息,Windows会来不及进行一些必要的系统操作,这样在视觉上就看不出按钮被按下后又放开的效果,好像按钮没有按动。但按钮的Click事件处理过程被激活执行表明按钮确实被按过。为了达到视觉上的完美效果,我们不妨在WM_LBUTTONDOWN和WM_LBUTTONUP两个消息之间插入一段短短的延时,比如说200毫秒,在这段延时期间,把处理权交给Windows,这样Windows就有时间显示按钮被按下的效果了。插入延时的办法有很多,可以加入一段空循环或利用计时器控件,这里再介绍一个API函数GetTickCount,该函数获取自Windows启动至被调用时所经过的毫秒数。利用这个函数控制延时,不仅精确,而且节省资源。 GetTickCount 函数的接口声明如下: Declare Function GetTickCount Lib “kernel32" () As Long 下面进行窗体设计。我们在窗体Form1中安插一个按钮Command1。Command1的Click事件处理过程调用VB的Beep产生一声蜂鸣。另外在Form1的主菜单上加上一个ClickButton的菜单命令,热键设为Alt-C,该菜单命令的事件处理过程完成对鼠标光标的遥控。 最后给出Form1的完整程序清单: Begin VB.Form Form1 BorderStyle = 1 'Fixed Single Caption = “Auto-click demonstration" ClientHeight = 3195 ClientLeft = 150 ClientTop = 720 ClientWidth = 4680 LinkTopic = “Form1" MaxButton = 0 'False ScaleHeight = 3195 ScaleWidth = 4680 StartUpPosition = 3 'Windows Default Begin VB.CommandButton Command1 Caption = “Click me!" Height = 495 Left = 1740 TabIndex = 0 Top = 1380 Width = 1215 End Begin VB.Menu mnuClickButton Caption = “&ClickButton" End End Attribute VB_Name = “Form1" Attribute VB_GlobalNameSpace = False Attribute VB_Creatable = False Attribute VB_PredeclaredId = True Attribute VB_Exposed = False Private Type POINTAPI X As Long Y As Long End Type Const WM_LBUTTONDOWN = &H201 Const WM_LBUTTONUP = &H202 Dim ButtonPos As POINTAPI Private Declare Function GetCursorPos Lib “ user32" (lpPoint As POINTAPI) As LongPrivate Declare Function SetCursorPos Lib “user32" (ByVal X As Long, ByVal Y As Long) As Long Private Declare Function SendMessage Lib “ user32" Alias “SendMessageA" (ByValhwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As LongPrivate Declare Function ClientToScreen Lib “user32" (ByVal hwnd As Long, lpPoint As POINTAPI) As Long Private Declare Function GetTickCount Lib “ kernel32" () As Long Private Sub Command1_Click() Beep End Sub Private Sub Form_Load() Dim tmp As Long With Command1 ButtonPos.X = (.Left + .Width / 2) / Screen.TwipsPerPixelX ButtonPos.Y = (.Top + .Height / 2) / Screen.TwipsPerPixelY End With tmp = ClientToScreen(Me.hwnd, ButtonPos) End Sub Private Sub mnuClickButton_Click() Const MoveStep As Integer = 50 Dim CursorPos As POINTAPI Dim DistX As Double, DistY As Double Dim tmp As Long Dim i As Integer Dim PosX As Integer, PosY As Integer Dim TickCount As Long tmp = GetCursorPos(CursorPos) DistX = ButtonPos.X - CursorPos.X DistY = ButtonPos.Y - CursorPos.Y For i = 1 To MoveStep PosX = CursorPos.X+DistX*i / MoveStep PosY = CursorPos.Y+DistY*i / MoveStep tmp = SetCursorPos(PosX, PosY) Next i tmp = SendMessage(Command1.hwnd, WM_LBUTTONDOWN, 0, 0) TickCount = GetTickCount() While GetTickCount() - TickCount < 200 tmp = DoEvents() Wend tmp = SendMessage(Command1.hwnd, WM_LBUTTONUP, 0, 0) End Sub 笔者所用的操作系统为Windows 95,开发环境为VB企业版5.0。以上两例已调试运行成功,所介绍的方法同样适用于Windows 3.x下的VB 3.0和VB 4.0。 结语 API函数在Windows程序开发中的地位就相当于DOS的INT 21H系统功能调用,无论高级语言、可视平台,最终都要归结于对这些底层功能的调用。我们开发高级的应用软件不能纯粹依赖这些底层调用(当然不是技术上实现不了,而是工作量吃不消),但也不能彻底撇开,就像盖楼时既要充分利用大量的预制件,也要用最基本的泥砂砖石一样。 |
|