今天继续讨论 VBA 的 Interaction 模块。昨天讨论了几个“多选一”的函数,今天来看几个与环境有关的函数与过程。Command 用于获取命令行上的参数。Command 返回子类型是 String 的 Variant 类型,Command$ 返回 String 类型。因为 VBA 是在 Excel 中执行的,无所谓命令行,因此 Command/Command$ 没有用处。Environ/Environ$ 用来获取环境变量,Environ 返回 Variant 类型,Environ$ 返回 String 类型。Environ( varexpr | number )其中,varexpr 是包含环境变量名的字符串表达式,number 是数值表达式,表示环境变量在环境变量表中的序号。Debug.Print Environ("SystemDrive") ' C:Debug.Print Environ(7) ' ComSpec=C:\Windows\system32\cmd.exe
Environ("SystemDrive") 返回系统环境中名为 SytemDrive 变量的值,而 Environ(7) 则返回系统环境变量表中序号为 7 的环境字符串。注:因为每个人的机器环境不一样,所以相同的序号可能具有不同的内容。下面我们写一段代码,利用 Environ 来枚举系统环境,如下图所示:在这段代码中,我们使用一个 Do ... Loop Until 循环,来枚举系统环境。循环的推出条件是 Environ 返回空字符串,表示已经没有更多的环境变量。因为 Path 和 PSModulePath 两个路径变量都是多个路径的组合,我们写了一个 ParsingPath 函数来解析这个路径组合,将其分拆成一个个的路径。ParsingPath 函数的核心是一段 Do ... Loop Until 循环。注意 Do ... Loop Until 的循环结束条件是 False,这意味着此循环将永不结束!不要怕(don't panic)!循环体内有个判断条件:If pos <= 0 Or IsNull(pos) Then在前一行语句 pos = InStr(p_pos, sp, ";") 中,如果 sp 中找不到分号(;),将返回 0。这意味着对字符串 sp 也就是路径表已经处理完毕,因此打印出最后一条路径后执行 Exit Do 跳出 Do ... Loop Until 循环。如果 sp 为 Null,则 InStr 返回 Null;如果 sp 是空字符串,则 InStr 返回 0;如果 sp 中不再含有分号,则 InStr 返回 0。在循环过程中,这些条件总有一个会满足,因此该 Do ... Loop Until 必会结束。Shell ( pathname [, windowstyle ] )其中,pathname 是外部命令的可执行文件路径名,譬如 C:\Windows\Notepad.exe。windowstyle 是可选参数,用来指定外部命令窗口的状态,其可取值如下表所示:Dim taskID As DoubletaskID = Shell("C:\Windows\Notepad.exe", vbNormalNoFocus)
如果执行成功,Shell 返回一个 Double 类型的数值,该数值表示 Shell 所执行任务的任务ID。如果执行不成功,Shell 将报告错误。注:关于 Shell 的返回值,VBA 的文档明确是任务 ID,不是 Windows 为执行该外部命令所创造的进程的进程 ID!在这个示例中,我们首先用 Shell 启动 Nodepad.exe,其返回值为 14480。但后等待 2 秒以确保 Notepad.exe 已启动,最后调用 WMI CIM 来寻找名为 Notepad 的进程,获得其进程ID 为 26328。AppActivate 用来激活某个应用窗口或进程。其一般格式为:AppActivate title [, wait ]其中,title 是窗口标题。AppActivate 用来激活(activate)具有此标题的窗口。所谓激活(activate),是指将窗口拉到最顶层,并具有焦点,成为当前活动窗口(active window)。VBA 文档中,关于 title 参数,还有一段话:The task ID returned by the Shell function can be used in place of title to activate an application.(Shell 函数返回的任务 ID 也可以用作为 title 参数来激活应用程序。)
经过多次测试,这段话是错的:AppActivate 不接受 Shell 的返回值作为 title 参数,报告 5 号错误:无效的过程调用或参数。见下图示例。
例1. AppActivate 不接受 Shell 返回值作为参数
图5 AppActivate 不接受 Shell 返回值为参数
经过多次测试,AppActivate 可接受的 title 参数为:
例2. AppActivate 接受窗口标题作为参数
图6 AppActivate 接受窗口表体作为参数
注意:空白 Notepad 的窗口标题具体取决于 Windows 当前的语言设置。对于英文 Windwos,窗口标题是 Untitled - Notepad。对于中文 Windows,窗口标题是 无标题 - 记事本。
例3. AppActivate 接受进程ID 作为参数
图7 AppActivate 接受进程ID 作为参数
由此例可以看出,获取一个应用的进程ID 比较麻烦,所以,使用 AppActivate 时,还是以窗口标题作为参数比较方便。
AppActivate 还有一个可选参数 wait,这是一个 Boolean 类型的参数,用来指示 AppActivate,在激活其他应用之前,当前的调用者是否需要获得焦点:
文章写完后,又有点不放心。难道微软的文档写作居然如此粗心?!决定按照文档中给出的例子再测试测试。结果如下:在这个测试中,分别测试了 Winword.exe、Notepad.exe、mspaint.exe。结果是:Winword 和 mspaint 都符合微软 VBA 文档中的描述,唯独 Notepad 不符!Shell 启动 Winword 时,返回的 Task Id 和 Winword.exe 的进程ID 是一样的,mspaint 也是如此。唯独 Notepad 的 Task Id 和进程ID 不一样!因此在使用 Shell 返回的 taskID 激活 Notepad 窗口时失败。此测试用例并没有推翻本文前面的结论,而是证明了 VBA 文档的描述是部分正确的。为什么 Notepad.exe 如此诡异?是否还有其他的应用和 Notepad.exe 一样?这个还需要再研究。