楼主 wise |
本文来自 接受网友的意见,写了一篇关于VB API的文章,希望大家能从中学到一些知识。 一、API是什么? 这个我本来不想说的,不过也许你知道其它人不知道,这里为了照顾一下新手,不得不说些废话,请大家谅解。 Win32 API即为Microsoft 32位平台的应用程序编程接口(Application Programming Inte**ce)。所有在Win32平台上运行的应用程序都可以调用这些函数。 使用Win32 API,应用程序可以充分挖掘Windows的32位操作系统的潜力。Mircrosoft的所有32位平台都支持统一的API,包括函数、结构、消息、宏及接口。使用 Win32 API不但可以开发出在各种平台上都能成功运行的应用程序,而且也可以充分利用每个平台特有的功能和属性。 以上为API的相关介绍,不过有些新手看了以后可能还是不怎么明白API到底有什么用?这里请不要着急,如果你有足够耐心的话,请慢慢往下看。 二、如何使用API? 估计这才是大家真正关心的,那么如何使用API呢?在了解API之前,先打开你的VB书,翻到过程函数这章来,在搞清楚API之前应该先搞懂过程函数是怎么一回事!如果你还不知道过程的工作方式,那么请先不要急着往下看,那样容易走很多弯路。 好了,当你理解了过程函数时,也就是你可以使用API的时候了,别把API看得太难,你就像使用过程函数一样使用API就可以了。首先,让我们看看一个简单的API,以下:
以上这个API的呢是起一个延时作用。你如果是刚接触API的话可能会感到API的书写及其复杂,而且会感到很不适应。其实这没什么的,慢慢习惯就好了。至于API这些复杂的书写你就不用操心了,在你安装VB的时候微软已经帮我们带上了API浏览器,这些全部都可以利用API浏览器帮我们自动生成。API浏览器的位置位于[开始菜单-程序-Microsoft Visual Basic 6.0 中文版-Microsoft Visual Basic 6.0 中文版工具-API 文本浏览器]。打开API浏览器,在最上面的一个文本框中输入Sleep,这时下面列表框中就会自动显示相应的API函数,然后点右边添加按钮即可,接着点击复制按钮,这时你就可以用Ctrl+V把声明的API添加到VB代码窗口中了。 这里我要说一下,有些新手可能还弄不明白。API的声明范围一般有两种模式,一种是Private(私有的),一种是Public(公用的)。一般Private是声明在类模块或窗体类中,Public声明在模块中。你在添加API的时候,添加按钮下面就有API的声明范围,可以根据自己的需要进行添加。这里我们一般选择私有的(Private)就可以了。 经过上面,我们知道如何添加API,接着我们分析一下API声明,这是你了解API必备的。首先看第一个单词Private,很显然,我上面刚刚讲过,这是申明一个私有的API变量。再看第二个Declare,这个单词帮我们告诉VB是在申明API函数,一般申明外在的API函数时都必须带上这个单词。第三个Sub,别告诉我你不知道什么意思?这就是我叫你先学习VB中过程函数的意思,这个说白了就是没有反回值,一般如果不是Sub而是Function都带有反回值的。第四个Lib,这个是告诉VB我们要声明哪一个DLL中的API函数,也就是告诉VB我们要申明第五个单词kernel32.dLL中的API,一般写DLL名称时都要用双引号括起来,如"user32"、"shell32.dll"等,至于后面的.dll这个可以带可不带。再来看第六个Alias,这个也是需要同后面一个一起用的,我们应该把第六个和第七个连起来一起看Alias "Sleep",这个意思表示将被调用的过程在DLL中还有另外的名称,这个是可选的。最后括号里面的,也就是和过程函数一样,你传入相应的值就可以了。 上面我们分析完API函数声明以后,接着我们就要自己动手写代码了。先把这个API复制到Form1代码窗口中,然后写如下代码:
解释一下,也就是在窗体启动时使用Sleep API进行延时2秒,后面的参数dwMilliseconds是表示你要延时的秒数,基本上和设置Timer中的秒数一样。你再看一下Sleep 2000的使用方式,是不是和使用VB过程函数一样呢?好了,我们的第一个VB API程序写完了,可以看到使用API并不是一件很难的事。 三、如何才能提升你对API的学习兴趣? API,我常把它看做成过程函数,不过每人都有每人的见解和理解方式,自己的理解方式只要可以帮助自己更好的学习和掌握API,也没必要一定要学习他人的。 1,自己做MsgBox 了解API参数的使用方法是很重要的,这里我们不用VB的MsgBox,直接使用API弹出MsgBox消息框。首先,打开API浏览器,选择MessageBox,大家可以用这个API和VB内置的MsgBox比较一下,其实MsgBox也就是MessageBox的缩写,只不过一个是API,一个是VB内置的,但两者都是通过API进行工作的。好了,选择私有声明方式,粘贴到VB代码编辑窗口中,然后新建一个CommandButton,写入以下代码:
先让我们来分析一下,首先看第一个参数Byval hWnd As Long,很显然这是一个长整形变量,所以我们这里需要传递的是数字,你可能会发现我们传递的并不是数字啊,而是 Me.hwnd??很奇怪是吗?如果你真的有此疑问说明你是真心想要学习好API的,现在就让我们来看看Me.hwnd到底是什么东西?以下摘自VB帮助文档: hWnd 属性:返回窗体或控件的句柄。 句 柄:是由操作环境定义的一个唯一的整数值,它被程序用来标识或者切换到对象,如窗体或控件等。 现在估计你差不多就已经明白了,我们调用的hwnd其实是一个句柄整数值,你可以用 Msgbox Me.hwnd 看一下就知道了。至于Me这是一个关键字,代表当前Form窗体对象。如:Me.Caption="标题"、Me.BackColor=vbRed等。 接上面的,首先我们传入了Me.hwnd,表示是当前窗口调用MessageBox,这里告诉大家一个技巧,也就是以后凡是看到Byval hwnd As Long,一般都是需要传入句柄的,至于传入哪个对象句柄,那就要看你是怎么实现的了。 ByVal lpText As String,这个是字符串变量,标识着叫我们需要传入字符串进去,可以看里面的变量字符lpText,属于文本的意思,也就是说是用来显示MsgBox中的消息文本的。 ByVal lpCaption As String,也是字符串变量,还是传入字符串进去。在看里面的变量字符lpCaption,其实就是显示MsgBox标题的。 ByVal wType As Long,这是一个整形变量,需要传递整形数字,还是看里面的变量字符wType,标识着显示MsgBox类型,这里可以像VB的MsgBox一样使用,如这里可以传入:vbYesNo,vbOkCancel等,如果忽略那就传入0即可。 好了,按F5启动程序,点击Command1,接着就会弹出一个消息框,这里我们制作以及分析MsgBox已经完成了。希望你能在这段学习到一些知识。 2,来点实用的吧 就拿隐藏Windows任务管理器来说吧,这里只能隐藏任务管理器中的窗口,不能隐藏进程。(问:有没有隐藏进程的?答:你想干什么?),当程序运行后你无法从任务管理器的窗口中关闭程序,只能从进程中进行终止。好了,还是老规矩,打开API浏览器,输入GetWindow和ShowWindow两个API,声明范围还是私有的,复制粘贴到Form代码窗口中,嗯,好了?别急,还是API浏览器,选择Combox中的常数,输入GW_OWNER和SW_HIDE这两个API常数,然后粘贴到代码窗口中,问我这两个是干什么的?那就接着往下看吧。写入以下代码:
又到了分析的时候了,这对刚入门的新手可谓是最激动的时候了。好了,还是老子,看看两个API的表面意思和传递值变量。 先看GetWindow,表面意思:获取窗口。传递值变量:hWnd整形句柄,wCmd整形命令值。 再看ShowWindow,表面意思:显示窗口。传递值变量:hWnd整形句柄,nCmdShow整形命令值。 然后是使用代码,先看lphWnd = GetWindow(Me.hwnd, GW_OWNER)这句,这句意思是获取当前窗口的所有者窗口句柄,可以看到GetWindow是Function过程函数,执行以后会返回相应的窗口句柄值,这个值为Long整形(同句柄)。接着调用ShowWindow lphWnd, SW_HIDE,这句意思是显示lphwnd这个句柄的窗口,关键一句是最后的SW_HIDE,这是API函数的常量。通过设置常量能让系统知道API到底应该怎么执行显示窗口,是显示?还是隐藏?Hide当然是隐藏的意思。好了,编译成Exe,运行后打开任务管理器,查看程序窗口,还有吗? 我又要说一下了,有些人可能不懂为什么要用GW_OWNER这些常量,这些到底有什么用?还有就是我怎么知道哪些API对应哪些的常量?其实这些常量你只要稍微注意一下就知道它们是怎么回事了,如在GetWindow中我使用GW_OWNER,在ShowWindow中我使用SW_HIDE这些常量都有一个共同的特点,就是他们都是以API的单词第一个字母为标准。如GetWindow相对应的常量就是Get(G)Window(W)=GW,ShowWindow相对应的常就是Show(S)Window(W)=SW,这些常量可以自己在VB的API浏览器中找找看。 |
2楼 wise |
一、API的类型结构。 API的类型结构与VB中差不多,一般声明时使用Type定义类型。如果你不懂的话麻烦你先打开你的VB入门教程书看看。好了,文字理论咋不多说,用实例证实理论(偶喜欢这句话)。 1,在Form窗体上用API画文字,最初了解类型结构填充
好了,咱们开始分析,首先看Form_Load里面的代码。 Dim lpRect As RECT。这一句是声明一个RECT自定义类型,这个类型可在VB浏览器的类型中找到。 Me.AutoRedraw = True。这一句其实就是设置当前窗体的AutoRedraw属性,设为True,表示持久输出图像。不懂查帮助。 Me.ScaleMode = 3。设置当前窗体的像素模式。至于为啥要设置为3,下面将会讲到。 With lpRect...End With。从With 到 End With为填充咱们声明的lpRect类型结构。具体看里面的。 .Left=0。这里相当于x坐标的初始值。 .Top=0。这里相当于y坐标的初始值。 .Right = Me.ScaleWidth。Right表示从Left开始的宽度。这里赋值为Me.ScaleWidth表示当前窗体像素的宽度。 .Bottom = Me.ScaleHeight。同上。Bottom表示从Top开始的高度。Me.ScaleHeight表示当前窗体像素的高度。 关键的就是下面一句,是它帮助我们在窗体中画出文字的。这里说一下为什么需要把当前窗体的坐标度量单位(ScaleMode)改成3(Pixel像素),因为Windows不管画什么都是以像素为单位,所以我们为了确定咱们所画的文本显示在正常位置,就必须使用像素为单位!明白了么??(问:还是不明白?答:默默无语两眼泪啊~~) DrawText Me.hdc, "这是文字效果", -1, lpRect, DT_CENTER 现在分析这条API。看看里面需要传入的参数:ByVal hdc As Long, ByVal lpStr As String, ByVal nCount As Long, lpRect As RECT, ByVal wFormat As Long ByVal hdc As Long。hdc为Long整形,所以应该传入数值,不过给大家一个技巧,以后凡是看到hdc这个变量字符,表示需要传入的为一个hdc句柄,注意可不是hwnd句柄哦!这个句柄窗体和Picture控件都有,大家注意看就是了。 ByVal lpStr As String。传入字符串。这里就是咱们需要画到窗体上的文本。 ByVal nCount As Long。传入数值。看看里面的声明字符nCount,可以看出来这与数目有关,与什么数目呢?当然是文本罗。也就是说要显示的字符个数,一般如果需要全部显示出来可设为-1即可,表示显示所有字符。 lpRect As RECT。一个Rect类型,这里需要使用Rect类型,上面我们已经在API浏览器找到了这个类型并声明了,所以只需要传入相应的类型声明就可以了。不过这次与我上一次讲的有所不同,上次是通过API类型结构获取相关的信息,而这次需要你把API类型结构填充好以后再传入进去。希望大家在这里注意下。可能有人要问了,那为什么这次就要这么做?我的回答是:因为API并不是万能的和灵活的,有的时候需要我们人为的设置一些参数来完成自己所需要这个API的功能,大家可以通过上面的结构填充就可知道,我们所要画文本的整个范围就是整个Form窗体的范围啊! ByVal wFormat As Long。传入数值,不过可以看看nFormat声明字符,可以想像这里可能需要传入这个API的使用格式,就如我们前面所讲的那样。现在打开API浏览器,在常数中找找,输入这个API的开头,Draw(D)Text(T)=DT_,可以看到有很多吧。具体这些都是什么意思可以查查相关的API资料。这里我们只传入了一个DT_CENTER常数,意思是居中显示。默认可以为0,因为这里为文字显示格式,所以你什么格式也不想要的话可设为0以后文字显示在左上角。 OK。F5运行之。窗体中间正常显示“这就是文字效果”的几个文字。 至此这里API相关结构的填充与获取我不想再多说了,不懂的话建议把我以前发的API入门帖子与这里的第一小节一起看看,或许你会明白些什么?? |
3楼 wise |
二、Windows 消息 可以说这节是本文中的重点,当你能理解该节中的所有内容时,我相信你已经可以用该方法写出不错的程序功能了,好了,不说多了,接着往下来看。 在了解消息之前,先让我们看看Windows 消息到底是什么? Windows系统是一个消息驱动的OS,所以操作都是基本消息驱动的,这就好比我用鼠标按下一个按钮,这时Windows会先发送该鼠标的左键按下消息,也就是WM_LBUTTONDOWN到你按下的那个CommandButton,这时就会激发按钮的CommandButton_MouseDown事件,然后松开鼠标,Windows这时会发送WM_LBUTTONUP消息,激发CommandButton_MouseUp事件,说明该鼠标按键已经弹起(松开),这时就会激发咱们VB中的 CommandButton_Click 事件,所以你点击某个按钮以后就会执行相应的操作。这里我不想说得太深太复杂,只是想以这种简单的理解方式让你明白Windows消息到底是什么,也许说得太含糊,但是对你第一次理解这东西已经足够了! 实践1: 现在就让我们以消息来写一个入门程序,先是打开VB(问:废话。答:……),然后打开API浏览器(问:早打开了。答:……),然后在API浏览器找到 SendMessage ,再然后在API 常数中找到 WM_CLOSE,好了,在VB工程中新建一个标准EXE,添加一个Command1按钮,写入以下代码: Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long Private Const WM_CLOSE = &H10 Private Sub Command1_Click() SendMessage Me.hwnd, WM_CLOSE, 0, ByVal 0& End Sub 好了,先看看 SendMessage 这个API,第一个参数,hWnd 为Long,所以传入一个对象的句柄,第二个参数 wMsg 同样为 Long,这里要说一下,在该API的第二个参数,大多数都是传入以 WM_ 为开关的常数,希望大家注意!第三个 wParam 也是 Long,该参数的意思大多数是取决于第二个参数 uMsg ,这里传入的常数是 WM_CLOSE,所以这里输入 0 即可,最后一个参数,lParam 为 Any,Any是什么意思这里我说一下,也就是是说明该参数可以指定为任何标准数据类型,允许将任意数据类型传递给该参数!希望你明白,该参数的值也是取决于 uMsg。 再看看 Command1_Click 事件中的代码,其中使用了SendMessage API,第一个是当前的窗口句柄,至于什么是句柄我在API初级入门时讲过,第二个为 WM_CLOSE 常数消息,也就是说给当前窗口发送关闭消息,第三、四个参数分别为0,具体参数我已经说过,取决于你在 uMsg 传入的常数。 现在 F5 运行点击Command1按钮试试。 小提示:注意这里的句柄当然也可以输入其它窗口的句柄,执行效果同上面一样,可以关闭你传递的那个句柄窗口!自己试一下,具体怎么获取窗口句柄我在VB初级入门时讲过,自己去试试吧! 实践2: 现在我们开始第二个消息程序,还是使用 SendMessage ,这次我们是要给一个按钮传递 Click(点击)事件,最近貌似有些人在问这个问题?现在就写出来大家实践实践。 新建一个标准EXE,添加两个 CommandButton 控件,分别为 Command1 和 Command2 ,然后输入以下代码: Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long Private Const BM_CLICK = &HF5 Private Sub Command1_Click() SendMessage Command2.hwnd, BM_CLICK, 0, ByVal 0& End Sub Private Sub Command2_Click() MsgBox "command2" End Sub 这里先看看 BM_CLICK 常数,这个常数好像在VB自带的API浏览器中没有,所以大家可以把该常数自行复制到各自的VB程序中,也可以新建一个记事本保存下来,也可以把它添加到VB的API浏览器文本中等等。 再来看看 Command1_Click 事件,可见调用了 SendMessage API 向 Command2 中发送了 BM_CLICK 消息,该消息为按钮点击消息,后面的参数取决于 wMsg,所以后面两个为0即可,现在说一个为什么最后一个参数是写成 Byval 0&,写成这样的原因主要是因为最后一个参数变量,大家可以看看最后的参数变量 lParam As Any ,Any 是什么意思我已经说过,0& 后面的 & 其实为 Long 的简写,说白了 & 就是 Long 的意思,当然你可以在你程序写在 Dim ABC& 声明一个 Long 类型,而 Byval 0& 代表着是以传值方式进行传递该参数。不明白意思不要紧,翻开你的VB书多看看过程这章。 最后一个 Command2_Click 的 MsgBox "command2" 自然是显示一个消息。 好了,F5 运行试试。 小提示:同样该 hWnd 参数可以传递其它按钮句柄,实现效果同上面一样。 现在让我们看看 GetWindowLong 和 SetWindowLong 这两个“超级”API,为什么说超级呢?因为我们实现子类化少不了它们啊! |
4楼 wise |
这次我专门为你开一个小灶,这是第一次,不过也是最后一次!好了,现在让我们看看如何向其它窗口的文本框传入字符串.还是老样子,新建一个标准EXE,接着新建两个Form窗体,分别为 Form1 和 Form2 ,现在我们在 Form1 中添加一个 CommandButton 按钮,然后再在 Form2 中添加一个 TextBox 文本控件和一个 CommandButton 按钮控件,现在在 Form1 中写入以下代码(一会下面有相关的源码下载): Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long Private Const WM_SETTEXT = &HC Private Sub Command1_Click() SendMessage Form2.Text1.hwnd, WM_SETTEXT, 0, ByVal "abcdefg" SendMessage Form2.Command1.hwnd, WM_SETTEXT, 0, ByVal "hehe" End Sub Private Sub Form_Load() Form2.Show End Sub 其它的我不再分析了,大家注意看就是了,现在让我们看看 WM_SETTEXT 这个常数,该常数所代表的意思是向某对象发送设置文本消息,具体可以参考MSDN,其它的参数还是老样子,第一个是对象句柄(也可以说控件句柄),第二个是你给某某对象发的消息,第三个 wParam 参数我上面说过,以 wMsg 的值为定,这里保留为 0,现在关键是看看最后一个,lParam ,该参数为一个 Any 类型,也就是说我们可以传递任何值给任何对象,也就是说我们 wMsg 参数传递的为 WM_SETTEXT 消息,第三个参数为 Long 长整形,所以无法传递字符串,只能看最后一个,而恰好最好一个是可以传递任何类型的参数,所以这里就把我们需要传递的字符串输入即可,注意 lParam 是缺省 ByRef 传递的,ByRef 与 Byval 不同的是,ByRef 是以地址传递,而 Byval 是以参数值传递,所以你把上面的 Byval 去掉看看会产生什么后果?也就是说你传递的字符是一个地址,但是地址必须是整形,所以你去掉以后将会发生重大问题,不信自己试试轻则达不到预想的效果,重则 VB IDE(编程环境) 直接崩溃! 好了, F5 运行试试,希望这下大家能明白其中的原因,下面将不会再多说! |
5楼 wise |
这篇文章之所以这么久才和大家见面,主要由于工作或学习时间太忙,一直想写但又一直没时间去写,拖来拖去,唉!~前些天碰见二哥跟我说VBGOOD还有很多网友等着我的API教程,至此我感到挺惭愧的,先前的API教程写得并不好,但是却得到了大家的肯定与认可,甚感欣慰!在此谢谢大家的支持和鼓励! 好了,废话不多说,接前次文章,此次"中级入门"主要是以消息和子类作讲解,顺带我还会介绍一些其它的知识(纯粹是个人的经历而言),Windows 是一个很丰富的平台,它的包函当然不止这些.在 Windows 编程中,如果你的程序有窗口(Window),那么就一定会和消息打交道! 网友提问时间:"那啥叫消息?" 举个例子,当你的程序运行时,假设这时你的程序带有一个窗口,那么此时会先创建窗口,这时会激发 WM_CREATE 消息,我上篇文章已经说过,在 Windows 系统中,所以的消息常量都是以 WM_ 为开头的,大家可以打开API 浏览器看看就知道了.再假设如果你用鼠标左键点击所创建的窗口,此时会激发 WM_LBUTTONDOWN 消息,在此我来帮大家分析下这些常量为什么会这么写,首先, WM_ 我们不用去管它,因为后面 LBUTTONDOWN 才是关键,如果你E文OK的话应该可以看得出来,咱们把它进行拆分以后就是这样的:L Button Down ,现在应该明白了吧? L 代表的鼠标左键 Left , Button 自然是按钮的意思, Down 表示鼠标按下的意思,既然这样,那么当鼠标松开时,自然会激发 WM_LBUTTONUP (Left Button Up)消息啦.当然,如果是右键点击的话,那么自然同理,WM_RBUTTONDOWN,折分出来就是: Right Button Down.(俺曰:明白否? 答:所有网友沉默中..)有人可能会问到,为什么要采用这种方式去做常量? 答:因为微软中所有的命名风格都是采用匈牙利命名法,大家可以到网上搜搜"关于匈牙利命名法". 接上面,再再假设如果你的程序要退出了,这时你程序肯定会先关闭窗口,释放相关的资源,然后退出,这时窗口会激发 WM_CLOSE 消息,至于这些个E文我都不用解释了吧? 从上面这些来看,在VB中我们窗口,控件等事件,几乎都是以消息驱动来完成的,所以说,如果你想编写好 Windows 窗口程序,对消息的理解不得忽视.在上一篇API 中级入门中,我给大家讲解了如何使用 SendMessage 等API函数给程序发送消息,如果你在前篇文章不明白为什么那么做,那么我将会在下面为你解答. 附注:前段时间有网友发伊妹儿跟我说不太明白 SendMessage 后面两个参数为什么要根据 Msg 而定义,可能前段时间写的不太详细,这里我再仔细的说明下,还是以实例的方法来解答. 附: 怎么分别 SendMessage 后面两个参数应该传什么值. 首先我们来看看 SendMessage 这个API Public Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long 很显然,这是一个发送消息的API,从字面上的意思我们都可以看出,如: Send = 发送的意思, Message = 自然是消息的意思了.我们说过 Windows 窗口程序大部分都是以消息进行处理的,窗口创建时系统就向程序发送 WM_CRATE 创建消息,窗口绘画时系统就给程序发送 WM_PAINT 消息,窗口点击时系统就给程序发送 WM_LBUTTONDOWN 消息,窗口关闭时系统就给程序发送 WM_CLOSE,消毁窗口时系统就会给程序发送 WM_DESTROY 消息,所以说,一个窗口程序是离不开消息的.当然,微软为了能让我们更好的控制窗口,给我们提供了很多窗口操作接口(这里指API),而 SendMessage 就是其中的一个,通过该函数可以向任何有窗口的程序发送任何消息,只要是所接收对象的窗口能处理的消息. 现在再来说说后面两个参数传递的具体定义,可以说它们完全根据 wMsg 参数而定,说到这里可能网友有些糊涂了,怎么根据 wMsg 参数而定?该怎么定义?咳~~大家别急哈,我会帮大家慢慢搞明白的. 首先,还是以一个例子作以说明,假设这里所有的API和常量你已声明: SendMessage Form1.hWnd, WM_CLOSE, 0, ByVal 0& 看看上面一段代码,它所执行的功能为关闭我们的 Form1 窗口. 第一个参数自然是句柄了,你要给哪个窗口发送消息,你就传递哪个窗口的句柄,这很容易明白是吧? 第二个参数我前面讲过,自然是给某个窗口发送的消息,这里是 WM_CLOSE 关闭消息,说白了就是向一个窗口发送关闭窗口消息. 现在来看看第三个参数,这里有些人可能搞不明白为什么就传0?怎么不传一,二,三或四呢?如果你有这个疑问可以尝试着把该参数换成100, 1000, 10000 都行,你看看会不会起什么作用?我们来看看 MSDN 的说法: wParam : This parameter is not used. 翻译过来就是:这个参数不被使用. 那为啥不被使用呢?很简单,你给一个窗口发送关闭消息,当接收窗口收到该消息时就会作出退出操作,而这时的窗口关闭只需要接收到 WM_CLOSE 消息即可,所以这时你传递任何值它都会被忽略掉不处理,所以说,你就算换在 1,2,3,4,5,6 我想都不会发生任何作用. 那这里可能就人要问了,那既然不用的话这个参数还有存在下去的必要吗?我的回答是:当然有必要!后面我会给大家解开这个困惑的. 再看看最后一个参数,这里也有网友向我询问过,问为什么要 ByVal 0& 这样传递呢? 首先我们看看当前API的最后声明是怎么样的: lParam As Any 可以看出 lParam 参数是以 ByRef 方法声明的,说白的这时所传递的值是一个地址,如果我们在参数前面加上 ByVal 的话VB就会默认向该参数以值的方式传递,说白了这时所传递的就是一个值.如果你还不明白什么是地址什么是值,可以到网上查查相关资料. 再看看后面为什么要加个 & ,这个符号在VB中如果以运算方式来看的话,它是一个链接符,用于链接两个字符串.如果以数据类型来看的话,它所代表的含意相当于 Long 变量.大家可以看看下面这个API声明方式: Public Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long 看出什么区别没有?最后一个参数是以 ByVal lParam As Long 方式声明的,所以如果使用以后就会是这样的: SendMessage Form1.hWnd, WM_CLOSE, 0, 0 现在你应该明白了?那么再看看这个参数为什么也要为0,我们还是先看看 MSDN 的说法: lParam : This parameter is not used. 还是那句老话:该参数不被使用.所以这里我们默认就以 0 作填充了,因为这个参数不管你传递什么值所接收窗口都不会处理这个值的.所以嘛,忽略,忽略掉!~~ 现在,我们再说说这两个参数存在下去的必要性,首先,我们看看原来的 WM_SETTEXT 消息(假设API都已声明): SendMessage Form1.hWnd, WM_SETTEXT, 0, ByVal "is demo" 前面两个参数我都不说了,来看看第三个参数为何也是零? 下面是MSDN的说法: wParam : This parameter is not used. 还是那种话:该参数不被使用.所以说咱们在这个参数传递什么值,窗口消息都不会处理的,所以填充0. 再看看最后一个参数,下面是MSDN的说法: lParam : Pointer to a null-terminated string that is the window text. 翻译过来可能就是: 该参数须传递一个以 NULL 为结尾的字符串指针.(俺E文不好,大大们觉得翻译有误还请多多包涵撒~~~).这里所指的 NULL 我们可以理解为VB中的 Chr(0). 那么这样看来,这个参数不再是零了,而是一个字符串指针.那么看看我们上面是怎么传递的: ByVal "is demo" 注意:这里参数默认是以 lParam As Any 方式声明的,不是以 ByVal lParam As Long. 看看上面的,如果你声明为 ByVal lParam As Long 整形那么应该怎么传递这个字符串呢(ps: 如果你会指针你当然也有办**可以用 StrPtr,超出本文范围,这里不作详解)?所以呢,咱们还是要保留该参数的默认声明方式为 Any,那么我们可以传递任何值给这个参数. 现在来看看为什么前面还要加个 ByVal, ByVal 的意思已经说过,是以值方式传递.这里之所有在传递字符串时需要用到 ByVal,那是因为VB中的字符串默认就是一个指针.注意了, VB中的字符串默认就是一个指针!!!而 Windows 操作系统中所有关于字符串的传递都是以指针方式进行的,不可能直接把几个字符串按值传递.所以这里传递的其实是 ByVal 字符串地址 现在,应该很清楚明白了?如果你把 ByVal 去掉的话,那么传递就是: 字符串地址的地址 这又是指向内存哪个地方的东西??明白了吧??不信你可以试试执行后的结果是不是预想的那样. 现在又有人要向我发飙了,问: 看了这么多示例?为什么 wParam 参数老为零? 答:哎呀呀呀~~~,你小子,了不起啊~~ 再来看看另一个示例(假设你已声明API): Private Sub Command1_Click() SendMessage Me.hwnd, WM_KEYDOWN, 65, ByVal 0& End Sub Private Sub Form_KeyDown(KeyCode As Integer, Shift As Integer) MsgBox Chr(KeyCode) End Sub 至于 Form_KeyDown 这里面的东西我就不说了,基本的东西了.只看 Command1_Click 中的代码,首先 WM_KEYDOWN 是按键消息,你可以拆分看看 Key Down,是不是?该消息说白了就是按键按下时所激发的消息. 最后一个参数我们不去看它,这里只作一个示例,有兴趣的朋友自己查MSDN吧,先来看看MSDN对了WM_KEYDOWN 消息的第三个参数的说明: wParam : Specifies the virtual-key code of the nonsystem key. 翻译过来就是:指定一个虚拟键码,非系统键.这里所指的当然是那些 A, B, C,D等等按键编码了.OK了,这里不在为0了,得传递点东西上面去了,这里我默认是传递的一个 65,大家可以用 Chr(65) 看看是什么键.然后把这个按键消息发送给我自己,当我收到以后,就会调用 MsgBox 得到传递过来的键值了. Now,大家应该明白了吧? ^_^ |
6楼 wise |
一,初识子类 当你还不碰过子类的时候,你看到这个标题,定会问:"啥叫子类?".因为你知道我定会为你解答.(阴险哪~~~),因为鄙人文才不好,不知如何以最详细最能理解的方式为你解答,所以到网上偷了一段,还请各位笑纳: 子类处理,是一种功能强大的技术,它的作用是对发送到窗口的消息进行处理,我们完全可以用自己定制的一个窗口函数替代它,并保留指向默认窗口函数的指针,当一个消息到达窗口时,自制的窗口函数会拦截它并进行识别处理,对不能识别或不需进行特别处理的消息,就通过指向默认窗口函数的指针传递给默认的窗口函数进行处理,这样便扩充了默认窗口函数的功能。这种用定制的窗口函数代替默认的窗口函数,拦截并处理到达窗口的消息的技术,我们就称之为“子类处理”,定制的函数我们称之为“回调函数”。 上面解释可能较复杂,但是我们现在也没必要完全搞懂它,因为有了实例这一切就自然明白了. 首先,在使用子类的时候,我们需要用到三个API函数,它们分别是 GetWindowLong, SetWindowLong, CallWindowProc(没吓到你吧?),这三个函数不难,可以说是很简单,有了它们,我们实现子类化成为可能,先来看看 GetWindowLong API函数的原型: Public Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long) As Long 前面的我都不说了,只看参数: ByVal hwnd As Long ,不用我说了吧?传句柄的(问:谁的句柄? 答:当然是你要子类的那个窗口的句柄啦) ByVal nIndex As Long ,咳咳,这个嘛,需要传递一个常量,至于传递什么样的常量,咱们用前段时间学习的API分析**分析下不就OK了么?(嘿~~聪明) 第二个参数具体传递什么样的参数,根据这个API的名称进行分析(问:为什么啊? 答:倒,API的常量一般都和API名字有很大关联的,难道还分析你的名字不成么?),首先是 Get = G, Window = W, Long = L,合成以后就是 GWL_ ,OK ,打开API 浏览器,转到常量,输入 GWL_ 四个字符看看?有没?没有我马上玩游戏去~~~~~~~~~~ Public Const GWL_EXSTYLE = (-20) Public Const GWL_HINSTANCE = (-6) Public Const GWL_HWNDPARENT = (-8) Public Const GWL_ID = (-12) Public Const GWL_STYLE = (-16) Public Const GWL_USERDATA = (-21) Public Const GWL_WNDPROC = (-4) 很多哈,但是实现子类的时候我们只需要一个,那就是 GWL_WNDPROC,至于其它的常量我建议大家看看MSDN,如果你看不懂的话可以参考下我以前发的关于一篇大量使用 GetWindowLong 和 SetWindowLong 两个API函数的文章,地址是: 然后,我们再看看 SetWindowLong 原型: Public Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long 参数: ByVal hwnd As Long ,句柄,还是句柄 ByVal nIndex As Long ,好奇的你可能已经发现了这个参数和 GetWindowLong 的第二个参数一样的耶?那么恭喜你,是一样的,包括传递的参数都是一样的,如果你不服气你去试下有没有 SWL_ 开头的常量.^_^ ByVal dwNewLong As Long ,这个嘛~~, 参数的名称已经说明这是一个 New(新的)Long(长整形),当然这里的长整形是指一个地址 Address , you know ? 前段时间我们知道了 Get 与 Set 的区别,那就是一个获取,一个设置,当然这个也不例外.再来看看 CallWindowProc 这个API的原型: Public Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long 参数: ByVal lpPrevWndFunc As Long ,这个,我先试着拆分看看意思, Prev 的意思可能是 Previous(原始的), Wnd 自然是 Window(窗口) 意思咯,最后一个 Func 应该是 Function(函数),看看前面的 lp ,如果你会匈牙利命名法的话可以很明显看出来,该参数应该传一个指针,当然VB默认不支持指针,所有的指针类型都是以Long代替(注:貌似在32位系统中指针本来就是一个四字节的整形变量).那么这个参数的意思就是需要传递一个原始窗口指针,至于为什么,在这会可能说不清楚,咱们接着往下看. ByVal hWnd As Long , 啥也不说了,句柄呗. ByVal Msg As Long, 消息撒.至于传什么,那就接着往下看 ByVal wParam As Long , 还是那句老话,根据 Msg 消息来定,前篇API教程中我们说过 SendMessage 后面二个参数的具体定义,本次这些也一样 ByVal lParam As Long , 同 wParam 解释一样 这个API的用途就是把已处理的消息按 lpPrevWndFunc(原窗口消息地址) 返回给该地址. 好了,这段算是介绍完了,可能你还处于云山雾里,不过别担心,我能让你吃亏吗?哈哈哈~~~~~ 二,回调函数 在调用过程时指定的自定义函数被称为回调函数。回调函数(通常简称为“回调”)能够对过程提供的数据执行指定的操作。回调函数的参数集必须具有规定的形式,这是由使用回调函数的 API 决定的。关于需要什么参数,如何调用它们,请参阅 API 文档。 从上面一段文字描述中我们可以看出,回调函数说白了就是一段自定义的过程函数,至于是什么样的自定义函数,那都是由API来决定的.那么有人会问:咱们这个实现子类化的回调函数是怎么定义的呢?问的好,当然,我还是那句老话,如果你有MSDN的话,建议看看.这里我们实现的子类的回调函数是下面这种样子: Function WindowProc(ByVal hwnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long 怎么样?(网友: 好复杂啊 答:不会吧?这都复杂?那我介绍一个容易记住的方法,把 SendMessage API中的几个参数拿过来用就行了,是不是发现它们都是一样的?然后把最后一个参数改成 Byval ???? Long 就行了),可见这个回调函数和 SendMessage 好像啊,几乎差不多,不过要注意的是: SendMessage 最后一个参数是 lParam As Any,当然 SendMessage 的最后一个参数也可以改成 Byval lParam As Long,当然这里是根据你的需要去定的. OK,该了解了我们都了解的差不多了?现在该是我们实际操作的时候了,打开VB6,新建标准EXE,然后新建一个标准模块. 注意哦:用VB进行子类是危险的,你要时刻记得保存你的VB文档,否则你写了半天的代码会因为突然崩溃而没保存,到那时就别怪我没提醒你哦.哈哈~~~~~~ 提示:在VB中进行子类时,默认只能在标准模块中进行. |
7楼 wise |
三,实际操作的机会来了 在 Form1 中的代码: Private Sub Form_Load() pWndProc = GetWindowLong(Me.hwnd, GWL_WNDPROC) SetWindowLong Me.hwnd, GWL_WNDPROC, AddressOf WindowProc End Sub Private Sub Form_Unload(Cancel As Integer) SetWindowLong Me.hwnd, GWL_WNDPROC, pWndProc End Sub 在 Module1 中的代码: Public Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long) As Long Public Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long Public Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal hwnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long Public Const GWL_WNDPROC = (-4) Public pWndProc As Long Public Function WindowProc(ByVal hwnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long Debug.Print Hex$(Msg) WindowProc = CallWindowProc(pWndProc, hwnd, Msg, wParam, lParam) End Function 就这么一点代码就能实现VB中的子类,可以说是比较简单的,先来看看 Module1 中的代码,至于API函数和常量的声明我就不说了. 首先我们声明了一个 Public pWndProc As Long 变量,这里主要是把它当然指针来使用,这里我们再看看 Form1 中的代码: pWndProc = GetWindowLong(Me.hwnd, GWL_WNDPROC) ,可见窗口初始时先获取当前窗口的消息地址,然后再使用 SetWindowLong Me.hwnd, GWL_WNDPROC, AddressOf WindowProc 把地址指向模块中的 WindowProc 回调函数,我们在分析 SetWindowLong, 前面两个参数我就不说了,后面一个用到了 Addressof 函数,这个函数的功能就是返回一个函数的地址.再看看 SetWindowLong 最后一个参数, dwNewLong 肯定与一个新的参数有关,而这里正是指向我们参数自定义的函数地址. 我们不可能 SetWindowLong Me.hwnd, GWL_WNDPROC, WindowProc 这样传递是吧?这样直接把函数当做一个参数是不对的,而第三个参数正好是一新地址,所以我们通过 AddressOf WindowProc 该回调函数地址设置成当前窗口的消息地址,这样我们就能通过 WindowProc 回调函数处理我们的消息了. 接着看 WindowProc 回调函数里的代码: Debug.Print Hex$(Msg) 这句意思是以十六进制的方式查看当前的消息情况.当然你可以运行该代码,然后你将鼠标移动到Form1窗体上,这时在立即窗口就会显示相应的消息数据. WindowProc = CallWindowProc(pWndProc, hwnd, Msg, wParam, lParam) 这句的意思是把不需要处理的消息返回给系统,前面我们分析过 CallWindowProc 函数,后面四个参数我就不多说了,它们都是按照回调函数中的参数原样返回就行了,主要看第一个参数. CallWindowProc 的第一个参数上面分析时说明为一个地址,这个地址必须是原有的.通过 Form_Load 中的代码我们就可以看出,先是通过 GetWindowLong 获取Form1窗口的默认消息地址,然后再通过 SetWindowLong 把消息的流通地址转到我们的回调函数中,然后通过回调函数处理,当不需要时需要再通过 CallWindowProc 返回给窗体默认的消息地址就行了. 再看 Form_Unload 中的代码,这是一个还原,主要是当我们退出时不在处理窗体默认的消息时应该返回给系统,这里是需要注意的,否则程序可能会出现异常. 说了这么多,可能有些人还不太怎么明白,不过不要紧,以后多多接触这方面的例子就自然会明白了.好了,我来拟个运行顺序,希望大家能够明白它们的运行机制. 首先,窗体加载时,我们使用 pWndProc = GetWindowLong(Me.hwnd, GWL_WNDPROC) 保存当前窗体的默认消息地址, 然后再通过 SetWindowLong Me.hwnd, GWL_WNDPROC, AddressOf WindowProc 把当前窗体的默认消息地址设置到我们的回调函数地址处,然后我们就可以通过回调消息来控制当前窗体的消息了 WindowProc = CallWindowProc(pWndProc, hwnd, Msg, wParam, lParam) 当消息不用时,我们就要把这个消息返还给系统.该参数只按原路照写就行了.这里切记,如果少了这个函数,程序接不到相应的消息会死掉的 SetWindowLong Me.hwnd, GWL_WNDPROC, pWndProc 最后不用时咱们就把地址返还给系统.当然有时缺少这一句不会出现什么问题,但是以良好的SDK编程规则来说,最好把这句写上. OK,这段似乎较复杂,不过不要紧,先苦后甜嘛,等你熟练使用子类的时候,你的Windows 编程功力就更上一层楼. 休闲时间广告: 您曾经是否为无法处理窗口的消息而烦恼?您曾经是否看到别人漂亮的自绘菜单而羡慕?您曾经是否因为自己的窗口功能太单一而忧郁?现在好了,赶快学习VB子类吧,有了它,一切都会好起来的.赶快拿起电话定购吧: 电话:110 上面广告纯属虚构,如有雷同,算我倒霉.其实子类的好处不止这些,当然也有很多很多,下面我会举一些例子来说明子类的好处. 四,理论,实操一起抓 1,让你发消息关不掉我 首先新建标准EXE,然后新建一个标准模块: 在 Form1 中加入以下代码 Private Sub Form_Load() pWndProc = GetWindowLong(Me.hwnd, GWL_WNDPROC) SetWindowLong Me.hwnd, GWL_WNDPROC, AddressOf WindowProc End Sub Private Sub Form_Unload(Cancel As Integer) SetWindowLong Me.hwnd, GWL_WNDPROC, pWndProc End Sub 然后再在 Module1 中加入以下代码: Public Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long) As Long Public Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long Public Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal hwnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long Public Const GWL_WNDPROC = (-4) Public Const WM_CLOSE = &H10 Public pWndProc As Long Public Function WindowProc(ByVal hwnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long If Msg = WM_CLOSE Then '处理 WM_CLOSE 窗口关闭消息 WindowProc = 1 Exit Function End If WindowProc = CallWindowProc(pWndProc, hwnd, Msg, wParam, lParam) End Function 其它代码我就不说了,主要是看回调函数中的那几段处理代码. 首先通过 Msg 参数判断当前激发的消息,先前说过,Windows 的所以消息都是以 WM_ 开头的,大家可以找找看.如果是我们要拦截的消息时,那么用下面代码 WindowProc = 1 Exit Function 两句搞定, WindowProc = 1 返回为 True,意思是该消息处理完成,然后 Exit Function 自然是退出该函数,这句代码的意思说白了就是不让执行下面的 CallWindowProc,如果你给CallWindowProc执行了,消息也就自然返回给程序了,当程序接到WM_CLOSE,自然就会退出了.所以说白了,子类拦截消息就是不让执行 CallWindowProc.应该很简单吧?? 好了,运行起来试试(别忘了保存)?如果你的VB崩溃了,说明你的代码有误,请仔细检查下.找一个利用 SendMessage 发送WM_CLOSE消息关闭窗口程序试试,发现无法正常关闭你的窗口吧?(注:别拿 Windows 任务管理器试,因为它不单单只是 SendMessage. 如果你实在拿不出什么工具来试,可以自己写个 SendMessage me.hWnd, WM_CLOSE, 0, 0 试试) |
8楼 nothingwmm |
哈哈,这个好贴,我以前听说过API,这里可以先初步了解一下API,版主些的很详细,领教了,终于看完了,累呀。这玩意儿都深入到Windows系统里去了,Excel都要深入到这种地步,就太复杂了些。哎,学海无涯。 |
9楼 xyh9999 |
下载下来好好学习. |
10楼 angel928 |
分享学习 |
11楼 YANG5555 |
看不懂,这就是差距! |
12楼 yaoandxu2011 |
这个是初级入门的帖吗? 我怎么觉得是提升的帖子 只得望帖兴叹:“差距啊!” |
13楼 水星钓鱼 |
感谢小7分享。 |