VBGood网站全文搜索 Google

搜索VBGood全站网页(全文搜索)
首页 - 经验之谈 - 遥控鼠标
发表评论(0)作者:, 平台:, 阅读:11928, 日期:2000-03-12




在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”后,就可以在“Declare

s”类中搜索API函数声明,在“Types”类中搜索结构数据类型的定义,

或在“Costants”类中搜索API函数中用到的常量定义。把搜索到的声明

、定义直接通过剪贴板复制到VB程序中比较省力省心。


 

把需要用到的函数声明和相关的数据结构定义以及常数定义加到窗体(Fo

rm)的General部分,就可以在程序中像调用普通VB函数一样方便地调用A

PI函数。


应用实例


以下从实用的角度出发,援引笔者在实际工作开发中的API应用一例,谨

供参考。


 

漫游过Windows

3.2的五分钟教程的用户,大概还记得演示鼠标用法。鼠标指针自动随教

程的指令移动,自动点开菜单、按动按钮,完全脱离了对物理鼠标器的依

赖。这种高级控制技巧在VB的函数和方法中是找不到的,仍然要借助于

API函数。


 

本例用API函数实现对鼠标的“遥控”:无论鼠标指针在何处,一个菜单

命令Alt-

C就能使鼠标指针自动平滑移动到窗口中一个按钮的中心,然后自动按动

按钮,激活按钮的Click事件处理过程。掌握了本例的方法后,前面提到

的五分钟教程软件就不难制作了。

本例中,以下几个功能的实现至关重要:获取鼠标光标在屏幕上的位置、

获取按钮在屏幕上(注意:不是在窗口中)的位置、移动鼠标光标、自动

按下和放开按钮,下面分别介绍。


 

获取鼠标光标在屏幕上的位置和移动鼠标光标,分别需调用API函数GetC

ursorPos和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指定了鼠标光标在屏幕上的坐标。Get

CursorPos函数把鼠标光标的当前位置存到结构变量lpPoint中。结构变量

的定义如下:

Private Type POINTAPI

X As Long

Y As Long

End Type


 

按钮的Left和Top属性给出的是按钮的左上角在窗口客户区坐标系中的坐

标位置。要把鼠标光标移到按钮正中,需要得到按钮中心在屏幕坐标系中

的坐标位置。窗口客户区坐标系与屏幕坐标系不仅坐标原

点不同,二者的坐标单位(scale)也不同。窗口客户区坐标系的单位是Tw

ip,屏幕坐标系单位是Pixel,二者的关系可以从屏幕对象Screen的Twip

sPerPixelX和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


 

把这两个消息发送给一个按钮,按钮就会像真的被鼠标左键单击了一样? ?

endMessage函数中的后两个参数在本例中可以不理,简单置0即可。


 

还有一点细节需注意。如果连续给按钮发送一对WM_LBUTTONDOWN和WM_LB

UTTONUP消息,Windows会来不及进行一些必要的系统操作,这样在视觉上

就看不出按钮被按下后又放开的效果,好像按钮没有按动。但按钮的Cl

ick事件处理过程被激活执行表明按钮确实被按过。为了达到视觉上的完

美效果,我们不妨在WM_LBUTTONDOWN和WM_LBUTTONUP两个消息之间插入一

段短短的延时,比如说200毫秒,在这段延时期间,把处理权交给Windo

ws,这样Windows就有时间显示按钮被按下的效果了。插入延时的办法有

很多,可以加入一段空循

环或利用计时器控件,这里再介绍一个API函数GetTickCount,该函数获

取自Windows启动至被调用时所经

过的毫秒数。利用这个函数控制延时,不仅精确,而且节省资源。GetTi

ckCount函数的接口声明如下:


 

Declare Function GetTickCount Lib “kernel32" () As Long


 

下面进行窗体设计。我们在窗体Form1中安插一个按钮Command1。Comman

d1的Click事件处理过程调用VB的Beep产生一声蜂鸣。另外在Form1的主菜

单上加上一个ClickButton的菜单命令,热键设为Alt-

C,该菜单命令的事件处理过程完成对鼠标光标的遥控。


 

最后给出Form1的完整程序清单:

Begin VB.Form Form1

BorderStyle = 1 'Fixed Single

Caption = “Auto?lick 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系统功能调用,无论高级语言、可视平台,最终都要归结于对这些底

层功能的调用。我们开发高级的应用软件不能纯粹依赖这些底层调用(当

然不是技术上实现不了,而是工作量吃不消),但也不能彻底撇开,就像

盖楼时既要充分利用大量的预制件,也要用最基本的泥砂砖石一样。