ExcelHome技术论坛

 找回密码
 免费注册

QQ登录

只需一步,快速开始

快捷登录

搜索
EH技术汇-专业的职场技能充电站 妙哉!函数段子手趣味讲函数 Excel服务器-会Excel,做管理系统 效率神器,一键搞定繁琐工作
HR薪酬管理数字化实战 Excel 2021函数公式学习大典 Excel数据透视表实战秘技 打造核心竞争力的职场宝典
让更多数据处理,一键完成 数据工作者的案头书 免费直播课集锦 ExcelHome出品 - VBA代码宝免费下载
用ChatGPT与VBA一键搞定Excel WPS表格从入门到精通 Excel VBA经典代码实践指南
查看: 24227|回复: 35

WINDOWS API入门实例:枚举EXCEL子窗口的API函数

  [复制链接]

TA的精华主题

TA的得分主题

发表于 2014-4-17 23:43 | 显示全部楼层 |阅读模式
本帖已被收录到知识树中,索引项:Windows API应用
本帖最后由 cbtaja 于 2014-4-18 18:25 编辑

API枚举.gif
即使只是用EXCEL VBA做一些简单的插件开发,当为了实现一些对系统层面的操作需求时,也都非常有可能要用到WINDOWS API函数功能。

说到WINDOWS API函数,初学时会觉得它有一种神秘魔力,但实际上对于有一定VBA编程经验的人来说,API入门也并不是想象中的那么难。

对于windows操作系统来说,最常见的对象就是窗口了,因而操作窗口就成了API入门必由之路了。
比如,我们看到QQ面板有这样的功能:当把它拖到离屏幕边缘较近距离时,它就自动吸附到边缘并隐藏;而当鼠标再次接近到它所隐身的屏幕边缘时,QQ面板又能自动跳出来。这是怎么实现的呢?
实际上,它就是把窗体的移动、最大化、最小化这些通常的手动操作,变成了在一定条件下的自动动作。在程序编写上,它就是跟踪鼠标运动轨迹、获取鼠标运动时的动态坐标,并与QQ面板自身所隐藏位置的坐标相比较,一旦两者的距离小于预设范围时,程序就将QQ面板的窗口自动展开(最大化)、移动出来。只要了解了这个原理,就可以查找出具有相应功能的API函数,来实现鼠标运动轨迹的跟踪、窗口的自动移动和隐藏,以及对鼠标动作做出特殊响应等功能。

那么,要操作窗口,首先,就要能区分不同的窗口,以及找到符合条件的窗口、获得对这些窗口的控制权力,然后才能对窗口做出相应的处理或响应。
为了能区分每个不同的窗口,在每个窗口生成之时,就都会同时生成一个唯一的标志号,如同身份证号一样,该标志号称之为Handle,它其实是一个指针,指向存储了这个窗口的基本信息的特定内存空间的地址信息,我们就是通过它来区分不同窗口的。
在获得Handle之后,我们就可以顺藤摸瓜而得到该窗口的信息,而后可以对这些具有特定结构的信息进行读取、利用,也可以对这些信息进行重构,包括修改、添加以及删除等操作,从而影响窗口的外在表现,比如使它的状态、位置、大小、内容等发生变化。
对窗口进行查找的API函数主要有:①、查找顶级窗口的findwindow函数,②、查找“子”窗口的findwindowEx,③、获得当前接受鼠标输入的窗口的函数GetCapture,④取得当前接受键盘输入的窗口的GetFocus函数,⑤、以屏幕上某个点上的坐标确定的窗口进行操作的函数。
下面是以几个最常用的API函数来做的API入门实例:①窗口的查找函数、②窗口各种信息的引用函数。

评分

5

查看全部评分

TA的精华主题

TA的得分主题

 楼主| 发表于 2014-4-17 23:44 | 显示全部楼层
本帖最后由 cbtaja 于 2014-4-18 15:27 编辑
  1. Public Declare Function getClassName Lib "user32" Alias "GetClassNameA" (ByVal Hwnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long
  2. Public Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
  3. Public Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal hWnd1 As Long, ByVal hWnd2 As Long, ByVal lpsz1 As String, ByVal lpsz2 As String) As Long
  4. Public Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal Hwnd As Long, ByVal lpString As String, ByVal cch As Long) As Long '获得窗口的标题(对于控件,则是其Caption)

  5. Public Function getClasName(ByVal Hwnd As Long) '获得窗口的类名
  6.     Dim lpClassName As String * 256, retval As Long
  7.     retval = getClassName(Hwnd, lpClassName, 255)
  8.     If retval <> 0 Then getClasName = Left$(lpClassName, retval)
  9. End Function
  10. Public Function GetWindwowCaption(ByVal Hwnd As Long) As String '获得窗口或控件的标题(如果它们存在)
  11. Dim cnt As Long, rttitle As String * 256
  12.     cnt = GetWindowText(Hwnd, rttitle, 255)
  13.     If cnt <> 0 Then GetWindwowCaption = Left$(rttitle, cnt)
  14. End Function
  15. Public Function EnumChildWind(ByVal hwndParent As Long) As Long '枚举子窗口并返回子窗口的总个数(为自定义函数)
  16. EnumChildWind = -1
  17. Do
  18.     hwdChild = FindWindowEx(hwndParent, hwdChild, vbNullString, vbNullString) '水平滚动条的句柄
  19.     TheClassName = getClasName(hwdChild)
  20.     rttitle = GetWindwowCaption(hwdChild)
  21.     Debug.Print hwdChild, TheClassName, rttitle '输出子窗口的句柄、类名以及窗口标题(后两项需调用其它API函数及相应自定义函数)
  22.     EnumChildWind = EnumChildWind + 1
  23. Loop Until hwdChild = 0
  24. End Function

  25. Sub 枚举EXCEL活动窗口及子窗口()

  26.     Dim hWndWin As Long
  27.     Dim rttitle As String, TheClassName As String
  28.    
  29.     hWndWin = Application.Hwnd '得到EXCEL主程序的句柄。直接从Application属性中取得
  30.     rttitle = GetWindwowCaption(hWndWin): Debug.Print rttitle
  31.    
  32.    
  33.     hWndWin = FindWindowEx(hWndWin, 0&, "XLDESK", vbNullString) '找到EXCEL程序的工作区窗口的句柄
  34.     rttitle = GetWindwowCaption(hWndWin): Debug.Print rttitle '
  35.    
  36.     hWndWin = FindWindowEx(hWndWin, 0&, vbNullString, vbNullString) '找到活动工作表窗口的句柄
  37.     TheClassName = getClasName(hWndWin) '得到相应的类名
  38.     rttitle = GetWindwowCaption(hWndWin) '
  39.     Debug.Print hWndWin, TheClassName, rttitle '在立即窗口输出活动工作表窗口的句柄、类名、窗口标题
  40.    
  41.     Call EnumChildWind(hWndWin) ' 枚举活动工作表窗口的子窗口
  42. End Sub
复制代码
http://club.excelhome.net/forum.php?mod=attachment&aid=MTU3NzM3MXxjNzQ4ZDlhMHwxMzk3ODA1ODQ2fDkxODkxMnwxMTEzNjQz

评分

3

查看全部评分

TA的精华主题

TA的得分主题

发表于 2014-4-18 09:54 | 显示全部楼层
[广告] Excel易用宝 - 提升Excel的操作效率 · Excel / WPS表格插件       ★免费下载 ★       ★ 使用帮助
标记备用着 用的时候不多

TA的精华主题

TA的得分主题

 楼主| 发表于 2014-4-18 15:21 | 显示全部楼层
本帖最后由 cbtaja 于 2014-4-18 18:00 编辑

继续

枚举子窗口,最恰当的方法仍然是使用API函数,就是 EnumChildWindows 函数,它的声明语句如下:

  1. Public Declare Function EnumChildWindows Lib "user32" (ByVal hWndParent As Long, ByVal lpEnumFunc As Long, ByVal lParam As Long) As Long
复制代码
由于这个函数采用回调方法,对于没有接触C语言的同学来说,试图直接去理解会有些困难,我这里根据自身的体会,用通俗的方式的说明一下。
一、EnumChildWindows函数的基本功能:
        对于给定的参数:“父窗口”的句柄hWndParent,EnumChildWindows逐一枚举其子窗口的句柄,并把所得的子窗口句柄传递给函数lpEnumFunc 使用,并等待这个函数的返回结果;如果这个函数返回非零值,则EnumChildWindows继续进行枚举,重复上一步骤,直到lpEnumFunc 返回的值为零、或者所有的子窗口都已经枚举完毕为止。
二、EnumChildWindows函数中的第三参数lParam的作用、意义
        这个第三参数lParam也是要传递给子函数lpEnumFunc使用的,它被作为子函数lpEnumFunc的第二参数的初始值,——因为它是以Byval方式传递的,在子函数中对lParam参数进行修改,并不会影响到EnumChildWindows自身内部的lParam值,因此主函数EnumChildWindows每次枚举后传递给子函数的,都是最开始的那一个值。这样一来,对于子函数来说,它的作用、意义不大;而它对于主函数来说,如前所述,更是没有任何作用。——因此,大多数情况下,如果子函数根本不需要用到这个参数时,我们可以简单将它置为0。
三、对EnumChildWindows函数的理解
       EnumChildWindows函数为一个回调函数,因而可以理解为一个嵌套了子函数的主函数。对于它的子函数,它除了要求必须有两个规定类型的参数来接受它所传递的信息、并要求返回一个long类型的值之外,再没有对子函数的行为作其它任何限定,编程人完全可以按自己的需要而另行编写它,这就赋予了编程极大的灵活性。比如,我们收到EnumChildWindows枚举出来的hwnd值后,可以把它存储在数组、列表框、字典对象、工作表的单元格区域等任何一个容器中,以便再予以筛选、利用。如果这些利用需要耗费较多的时间或者需要复杂的步骤的话,更应该先存储后处理,即异步进行。否则EnumChildWindows主函数在子函数的每个处理过程中都会处于等待状态,这种情况是较为不利的,应尽量予以避免。
        如上所述的原因,我们完全可以把EnumChildWindows函数改造成一个自定义过程。如我在2楼的代码,就利用FindWindowEx函数实现了枚举子窗口。同样地,我们也可以用EnumChildWindows来实现FindWindowEx的特定查找功能。我不确定这两个函数的是否存在相互依赖关系;假如有,——则哪个是基础的、哪个是衍生的呢?——那我猜测,由于枚举是最后的办法,或者说枚举是所谓的“没有办法的办法”,它可能要更基础一些吧,虽然,用到FindWindowEx的时候更多些。
四、把代码传上来,与大家共同探讨学习。以上如有谬误之处,还请不吝指正。
      另外,附件中还演示了多列式列表框的用法,此前我在网上搜索了很久也没有找到很好的实例,费了不少时间才弄清了基本注意事项,在附件的代码中有注释,供大家参考。

TA的精华主题

TA的得分主题

 楼主| 发表于 2014-4-18 15:24 | 显示全部楼层
[广告] VBA代码宝 - VBA编程加强工具 · VBA代码随查随用  · 内置多项VBA编程加强工具       ★ 免费下载 ★      ★使用手册
API函数入门实例.rar (16.07 KB, 下载次数: 1647)

补充内容 (2014-6-26 20:43):
附件中的代码有一个小错误,请坛友下载后自行修改(因为论坛设置: 上传的附件超过一定时间后不能被删除或更新):
窗体UserForm1中,过程Private Sub Listbox1_Click的代码中,有两处"Text + 1",应改为".Text"

TA的精华主题

TA的得分主题

发表于 2014-6-26 15:52 | 显示全部楼层
深奥晦涩,不过确实很有帮助!

TA的精华主题

TA的得分主题

 楼主| 发表于 2014-10-25 17:54 | 显示全部楼层
一点小资料:


64 位 Visual Basic for Applications 概述


Microsoft Visual Basic for Applications (VBA) 是 Microsoft Office 附带的 Visual Basic 版本。在 Microsoft Office 2010 中,VBA 包括可使 VBA 代码同时在 32 位和 64 位环境中正确运行的语言功能。


注意:默认情况下,Office 2010 安装 32 位版本。在安装过程中,您必须明确选择安装 64 位版本。



对于在 Office 2010 版本之前(VBA 版本 6 和更早版本)编写的 VBA 代码,需要修改为在 64 位 Office 版本中运行,否则在 64 位平台上运行时会导致错误。这是因为,VBA 版本 6 和更早版本完全以 32 位平台为目标,而且通常包含 Declare 语句,这些语句调用的 Windows API 使用 32 位数据类型的指针和句柄。因为 VBA 版本 6 和更早版本没有用于指针或句柄的特定数据类型,所以,它使用 Long 数据类型(一种 32 位 4 字节的数据类型)来引用指针和句柄。64 位环境中的指针和句柄为 8 字节 64 位数。这些 64 位数不能包含在 32 位数据类型中。



注意:只有在 64 位版本的 Microsoft Office 中运行 VBA 代码时,才需要修改 VBA 代码。



在 64 位 Office 中运行旧 VBA 代码的问题在于,将 64 位加载到 32 位数据类型中会截断 64 位数。这会导致内存溢出、代码中出现意外结果,并且可能导致应用程序故障。

为解决此问题,以使 VBA 代码能同时在 32 位和 64 位环境中正确运行,新版 VBA 中增加了几项语言功能。此文档底部的表总结了这些新的 VBA 语言功能。有三个重要的新增功能,分别是:LongPtr 类型别名、LongLong 数据类型和 PtrSafe 关键字。




LongPtr - 现在,VBA 包括一种可变类型别名:LongPtr。LongPtr 实际解析为哪种数据类型取决于它在哪种 Office 版本中运行:在 32 位版本的 Office 中 LongPtr 解析为 Long,在 64 位版本的 Office 中 LongPtr 解析为 LongLong。LongPtr 用于指针和句柄。
LongLong - LongLong 数据类型为有符号的 64 位整数,仅在 64 位版本的 Office 中可用。LongLong 用于 64 位整数。必须使用转换函数将 LongLong(包括 64 位平台上的 LongPtr)显式赋予较小的整型。不允许将 LongLong 隐式转换为较小的整数。
PtrSafe - PtrSafe 关键字声明 Declare 语句可以在 64 位版本的 Office 中安全运行。
现在,在 64 位版本的 Office 中运行时,所有 Declare 语句必须都包括 PtrSafe 关键字。必须理解,仅仅将 PtrSafe 关键字添加到 Declare 语句中只是表示 Declare 语句显式针对 64 位,而语句中需要存储 64 位(包括返回值和参数)的所有数据类型仍须经过修改才能保存 64 位数。



注意:带有 PtrSafe 关键字的 Declare 语句为建议的语法。包括 PtrSafe 的 Declare 语句可同时在 32 位和 64 位平台上的 VBA7 开发环境中正确工作。为确保在 VBA7 中以及更早版本中的向后兼容性,请使用下面的构造:


#If Vba7 Then
Declare PtrSafe Sub...
#Else
Declare Sub...
#EndIf


请考虑下列 Declare 语句示例。在 64 位版本的 Office 中运行未经修改的 Declare 语句会导致一个错误,该错误指出 Declare 语句未包括 PtrSafe 限定符。修改后的 VBA 示例中包含 PtrSafe 限定符,但请注意,返回值(指向活动窗口的一个指针)返回 Long 数据类型。在 64 位 Office 上,这是错误的,因为指针应为 64 位。PtrSafe 限定符告知编译器 Declare 语句针对 64 位,因此该语句可以正常执行。但是返回值没有更新为 64 位数据类型,因此被截断,从而返回错误的值。



未修改的旧 VBA Declare 语句示例:


Declare Function GetActiveWindow Lib "user32" () As Long



修改后的 VBA Declare 语句示例,其中包括 PtrSafe 限定符,但仍使用 32 位返回值:

Declare PtrSafe Function GetActiveWindow Lib "user32" () As Long



再次重申,您除了必须修改 Declare 语句来包括 PtrSafe 限定符外,还必须更新语句中所有需要保存 64 位数的变量,以便这些变量使用 64 位数据类型。

修改后的 VBA Declare 语句示例,其中包括 PtrSafe 关键字,并且更新为使用正确的 64 位 (LongPtr) 数据类型:

Declare PtrSafe Function GetActiveWindow Lib "user32" () As LongPtr



总而言之,对于要在 64 位版本的 Office 中运行的代码,您需要找到并修改所有现有 Declare 语句以使用 PtrSafe 限定符。同时,还需要找到并修改这些 Declare 语句内所有引用句柄或指针的数据类型以使用新的 64 位兼容的 LongPtr 类型别名,并且需要使用新的 LongLong 数据类型保存 64 位整数的类型。此外,还必须更新任何包含指针或句柄以及 64 位整数的用户定义类型 (UDT),使之使用 64 位数据类型,同时,必须验证所有变量赋值是否正确,以防止发生类型不匹配错误。

编写可同时在 32 位和 64 位 Office 上运行的代码
若要编写可同时在 32 位和 64 位版本的 Office 上运行的代码,只需对所有指针和句柄值使用新的 LongPtr 类型别名来代替 Long 或 LongLong 即可。LongPtr 类型别名将解析为正确的 Long 或 LongLong 数据类型,具体取决于运行的是哪种 Office 版本。请注意,如果您需要执行不同的逻辑(例如,您需要在大型 Microsoft Excel 项目中使用 64 位值),可以使用 Win64 条件编译常量,如下面一节所示。

编写可同时在 Microsoft Office 2010(32 位或 64 位)以及以前版本的 Office 上运行的代码
若要编写可同时在新版本和旧版本的 Office 中运行的代码,可以使用新的 VBA7 与 Win64 条件编译器常量的组合。Vba7 条件编译器常量用来判断代码是否在 VB 编辑器版本 7(Office 2010 附带的 VBA 版本)中运行。Win64 条件编译常量用于判断正在运行哪种 Office 版本(32 位还是 64 位)。

#if Vba7 then
'  代码正在新 VBA7 编辑器中运行
#if Win64 then
'  代码正在 64 位版本的 Microsoft Office 中运行
#else
'  代码正在 32 位版本的 Microsoft Office 中运行
#end if
#else
' 代码正在 VBA 版本 6 或更早版本中运行
#end if



#If Vba7 Then
Declare PtrSafe Sub...
#Else
Declare Sub...
#EndIf





VBA7 语言更新总结
下表总结了新增的 VBA 语言功能,并给出每个新增功能的解释:



名称 类型 描述
PtrSafe  关键字 声明 Declare 语句针对 64 位系统。在 64 位上是必需的。
LongPtr 数据类型 该类型别名映射为 32 位系统上的 Long,或 64 位系统上的 LongLong。
LongLong 数据类型 8 字节的数据类型,只在 64 位系统上可用。数字类型。-9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 范围内的整数。LongLong 只是 64 位平台上的有效声明类型。此外,不能将 LongLong 隐式转换为较小的类型(例如,不能将 LongLong 赋予 Long)。这样做的目的是防止不慎将指针截断。允许显式强制转换,所以在上例中,可以将 CLng 应用于 LongLong,并将结果赋予 Long。(只在 64 位平台上有效。)
^ LongLong 类型声明字符 显式将文字值声明为 LongLong。声明大于最大 Long 值的 LongLong 文字时是必需的(否则它将隐式转换为 double)。
CLngPtr 类型转换函数 将简单表达式转换为 LongPtr。
CLngLng 类型转换函数 将简单表达式转换为 LongLong 数据类型。(只在 64 位平台上有效。)
vbLongLong VarType 常量 VarType 常量。
DefLngPtr DefType 语句 将一系列变量的默认数据类型设置为 LongPtr。
DefLngLng DefType 语句 将一系列变量的默认数据类型设置为 LongLong。

TA的精华主题

TA的得分主题

发表于 2015-8-22 17:37 | 显示全部楼层

TA的精华主题

TA的得分主题

发表于 2015-9-7 09:15 | 显示全部楼层
[广告] Excel易用宝 - 提升Excel的操作效率 · Excel / WPS表格插件       ★免费下载 ★       ★ 使用帮助
讲的太好了,能再基础一点吗?有点看不懂啊

TA的精华主题

TA的得分主题

发表于 2015-10-17 08:05 | 显示全部楼层
楼主,求教
如何通过窗体的句柄得到窗体对象啊?
比如有了hWnd,如何得到它的object。就像
Dim ie as object
set ie = object(hWnd)

然后对ie的操作就方便多了。
您需要登录后才可以回帖 登录 | 免费注册

本版积分规则

手机版|关于我们|联系我们|ExcelHome

GMT+8, 2024-12-4 03:00 , Processed in 0.049508 second(s), 10 queries , Gzip On, MemCache On.

Powered by Discuz! X3.4

© 1999-2023 Wooffice Inc.

沪公网安备 31011702000001号 沪ICP备11019229号-2

本论坛言论纯属发表者个人意见,任何违反国家相关法律的言论,本站将协助国家相关部门追究发言者责任!     本站特聘法律顾问:李志群律师

快速回复 返回顶部 返回列表