2.1 Shell函数
Shell用于执行一个可执行文件,返回一个Variant (Double)类型的结果,如果成功,代表这个程序的任务ID,若不成功,则会返回0。
Shell的语法格式是:
Shell(PathName,WindowStyle)
具体的参数说明:
PathName:是一个字符串,必须包含一个可执行文件的名称,后面还可以加上一些命令参数,各个参数之间用空格隔开。
WindowStyle:枚举常量,用来规定窗口样式,如表2-1所示。
表2-1 Shell命令的窗口样式常量
需要注意的是,在VBA或VB6编程中,Shell函数是内置函数,不需要添加任何外部引用,也不需要创建对象。
Shell函数的功能可以这样理解,就是代替手工去打开某个应用程序或文档。
实际上,屏幕上面的窗口的根源绝大多数都来源于一个可执行文件(扩展名为.exe)。例如微软的Word,其窗口根本路径是Office安装路径下的WINWORD.EXE文件,当打开一个文本文件时,其根源文件是C:\Windows\System32\notepad.exe。那么Windows系统中的可执行文件,一部分是系统自带的,例如记事本、计算器、注册表编辑器等,它们都处于System32文件夹下,是系统内置可执行文件。而另一部分如QQ、Excel这些应用软件则是后期安装上的,根源文件往往位于用户指定的路径。
为了更好地理解Shell函数的原理,下面使用计算机的“运行”窗口完成一些动作。按下快捷键【Windows + R】,在屏幕左下角弹出“运行”窗口,如图2-1所示。
图2-1 运行窗口
在路径框中输入:C:\windows\System32\notepad.exe C:\temp\new.txt,单击“确定”按钮,就可以看到桌面自动弹出记事本,并能看到其中的内容。
我们仔细分析路径的构成,C:\windows\System32\notepad.exe其实就是系统文件记事本的根源文件,空格后面的路径是命令参数部分,也就是告诉记事本程序,要打开这个文件,而不是其他的文本文件。
针对以上实例,可以使用下面的Shell函数来达到同样目的。只需要把“运行”中的内容传递给Shell函数即可。
上述代码中的vbMaximizedFocus表示打开的记事本窗口取得焦点,并且最大化记事本窗口,如果改为vbHide,则打开的新窗口在屏幕上看不见,在某些场合下这样是很危险的。
对于上述过程,我们要认识到以下几点。
Shell函数的参数由可执行文件的路径以及命令参数路径构成。
可执行文件的路径,根据需要可以尽可能缩短简化。
命令参数不是必需的,如果不带命令参数,则默认只打开该应用程序。
由于上例中的notepad.exe处于系统文件夹中,系统的环境变量中一定有C:\windows\System32这个路径。因此,Shell函数还可以省略所在路径,以及后面的扩展名,从而简化为:Shell "notepad C:\temp\new.txt", vbMaximizedFocus。
如果不打开具体的文件,只打开记事本,则不需带命令参数。进一步简化为:
Shell "notepad"
根据以上心得,我们尝试用下面的代码打开一个Word文档。
Shell "WinWord C:\temp\公务员.docx"
代码中的WinWord是Offi ce安装文件夹中Word程序的主文件,如图2-2所示。
图2-2 Word的执行文件
2.1.1 System32中常用的可执行文件
Windows系统中,很多实用的工具都处于System32这个文件夹下,因此可以利用Shell函数调用这些可执行文件,完成一些特定的任务。比较常用的可执行文件如表2-2所示。
表2-2 常用的可执行文件
①这类文件有可能不在System32文件夹中。
2.1.2 执行DOS命令
DOS命令的可执行文件是cmd.exe,用Shell函数调用DOS命令后,会弹出黑屏窗口,当DOS命令执行完毕后,有可能自动退出黑屏,也有可能停留,这取决于cmd的参数。/c表示自动退出,/k表示停留。
下面的代码,首先切换磁盘分区到D盘,其次切换当前目录到Download文件夹,最后用Shell函数调用DOS窗口,用dir命令列举出该文件夹中的内容。
由于cmd的参数使用了/k,因此,当dir命令执行完后,黑屏仍然停留在屏幕上,如图2-3所示。
图2-3 Shell调用DOS命令
DOS命令众多,此处仅举几个典型的例子。
下面的代码用DOS命令自动把D:盘根目录下的VMware10.7z压缩包(该文件大约470MB)复制到D:\Download文件夹中,并试图把复制后的文件重命名。
运行上述过程,当运行到第二行时,弹出如下的运行时错误,如图2-4所示。
结束程序后,可以看到Download文件夹下确实多了一个文件。也就是说,复制操作成功地执行了,但是重命名失败。
导致操作失败的原因是,Shell函数是异步执行的,也就是说,复制文件的宿主程序是cmd文件,并不是VBA代码。因此VBA并不能监测到cmd那边复制操作是否已经完成。换句话说,在VBA中执行一句Shell语句几乎不花时间,而实际上的复制动作可能持续好几秒或好几分钟。由于VBA监测不到复制的状态,就立即运行后续的代码,进行重命名,出现运行时错误也就可以理解了。
图2-4 不可移动或重命名
2.1.3 认识Shell函数的异步
为了提高Shell函数的健壮性,利用Shell函数的返回值,再配合一些API函数,可以让程序在执行Shell函数的时候智能等待。也就是说,只有Shell函数中的动作已完成时,才往下继续执行代码,否则在空循环中等待。
在标准模块中写入如下代码。
代码分析:当文件正在复制时,代码中的ExitCode和常量STILL_ALIVE是相等的,都是非零值,此时跳不出Do循环体。
当复制动作完成时,ExitCode变为0,Do循环的条件不再成立,就跳出并执行后续重命名的代码。
运行上述CopyFile过程,即可进行先复制文件,然后重命名复制的文件,如图2-5所示。
图2-5 处理Shell的异步
因此,只要Shell函数中的新窗口不关闭,就不会跳出Do循环,读者可以把上述代码中的Pid = Shell("cmd.exe /c copy D:\VMware10.7z C:\lib\", vbNormalFocus),更改为Pid =Shell("notepad.exe", vbNormalFocus),再试一次,可以发现,弹出的记事本窗口如果不手工关闭,程序就阻滞在Do循环体中。
2.1.4 处理Shell函数中的空格
计算机的文件夹或文件名中经常包含空格,如果把包含空格的路径作为参数传递给Shell函数,该函数会以空格作为分隔符,把一个路径理解为多个参数的连接。
例如,试图用Word 2013打开C:\temp文件夹下的“公务员.docx”,如果写成如下形式。
Shell "C:\Program Files\Microsoft Office\Office15\WINWORD.EXE C:\temp\公 务 员.docx", vbNormalFocus
图2-6 文件或路径中包含空格
执行时,并不能打开名称或路径带空格的文件,而是弹出如图2-6所示错误对话框。
这是因为不仅Word的路径包含空格,而且计划打开的文档名称也有空格,Shell函数无法解释这些参数的含义,因此不能打开。
如果为Shell函数中各路径事先用双引号包起来,就不会有上述麻烦。
自定义函数AddQuote的作用是给路径两侧加上双引号,并且添补一个空格,以防止连在一起。
运行上面的“处理空格”过程,该文档可以正常地在Word中打开。
2.1.5 自动打开控制面板
控制面板的主文件是位于System32文件夹下的control.exe,因此只需要执行:
Shell "control.exe", vbNormalFocus
就可以打开控制面板的主页,如图2-7所示。
图2-7 自动打开控制面板
如果要直接打开控制面板中特定的一项,需要添加命令参数。
Shell "control.exe appwiz.cpl", vbNormalFocus
上述代码表示直接打开控制面板中的“卸载程序”,命令参数appwiz.cpl是Application Wizard的缩写,意思是程序向导,如图2-8所示。
因此,只需要把Shell函数命令参数中的cpl文件更改一下,就可以打开控制面板中的其他各项。常用的有如下这些。
卸载程序:Shell "Control.exe " & "appwiz.cpl", vbMaximizedFocus
显示属性:Shell "Control.exe " & "desk.cpl", vbMaximizedFocus
浏览器属性:Shell "Control.exe " & "inetcpl.cpl", vbMaximizedFocus
图2-8 控制面板中的项目
区域和语言:Shell "Control.exe " & "intl.cpl", vbMaximizedFocus
声音和音频:Shell "Control.exe " & "mmsys.cpl", vbMaximizedFocus
网络连接:Shell "Control.exe " & "ncpa.cpl", vbMaximizedFocus
用户账户:Shell "Control.exe " & "nusrmgr.cpl", vbMaximizedFocus
电源选项:Shell "Control.exe " & "powercfg.cpl", vbMaximizedFocus
计算机属性:Shell "Control.exe " & "sysdm.cpl", vbMaximizedFocus
日期和时间:Shell "Control.exe " & "timedate.cpl", vbMaximizedFocus
安全中心:Shell "Control.exe " & "wscui.cpl", vbMaximizedFocus
2.1.6 打开资源管理器
资源管理器是以树状结构显示计算机中的文件系统的窗口,在Windows XP系统中双击“我的电脑”,在Windows 7系统中双击“计算机”,或者按下快捷键【Windows + E】,都可以打开资源管理器。
资源管理器的主文件为explorer.exe。
在实际编程中,经常用到自动打开某个文件夹,或者自动选中文件、文件夹的操作。如下面的代码。
Shell "explorer.exe C:\temp\成绩表.pdf", vbNormalFocus
上述代码表示用系统默认程序打开指定的PDF文件,这相当于用鼠标双击了计算机中的“成绩表.pdf”文件,如果计算机中安装的是Adobe Acrobat,那么用该软件打开PDF文件。对于其他扩展名的文件也是如此,因此这个代码实用价值很高。
Shell "explorer.exe C:\temp\", vbNormalFocus
上述代码表示打开temp文件夹,相当于用鼠标双击了该文件夹,进入该文件夹内部。
针对以上两句代码,如果添加参数“/select,”,那么不是打开文件或文件夹,而是选中。
Shell "explorer.exe /select, C:\temp\成绩表.pdf", vbNormalFocus
上述代码表示显示资源管理器,并自动选中文件,如图2-9所示。
图2-9 在资源管理器中自动选中文件
Shell "explorer.exe /select, C:\temp\", vbNormalFocus
上述代码表示自动选中temp文件夹,而不打开。
此外,利用explorer还可以自动打开指定url的网页。
Shell "explorer.exe http://vba.mahoupao.net/forum.php", vbNormalFocus
上述代码会用计算机默认的网页浏览器打开一个论坛。
2.1.7 注册ocx文件和dll文件
ocx文件是指对象类别扩充组件。计算机中扩展名为.ocx的文件,不能直接双击执行,一般要把ocx控件插入到窗体中使用。例如在VBA的UserForm中,可以插入CommonDialog、DataGrid这些ActiveX控件,如图2-10所示。
图2-10 用户窗体中使用ocx控件
这些ocx文件大多数是微软公司开发的性能优良的控件,插入到窗体后,可以让编程更加简单,界面更加专业、美观。
此外,用户根据需要也可以自己制作专用的ocx控件。但是,ocx的移植,也就是说从一台计算机把ocx控件复制到另一台计算机,是不能直接使用的,必须在目标计算机上注册。因为未注册的ocx控件不会显示在“附加控件”对话框中,也就无法装入窗体。所谓注册,就是为该控件分配一个GUID,并保存于注册表中。反注册,就是从注册表中移除该控件的有关信息,使其无法在编程环境中访问。
注册和反注册文件,可以用Shell函数调用System32文件夹下的regsvr32.exe文件实现。
注册ocx文件的语法格式如下。
Shell "regsvr32.exe ocx文件的路径"
下面尝试注册一款笔者开发的TreeviewExplorer.ocx控件,并在VBA中运行。
Shell "regsvr32.exe E:\TreeviewExplorer\TreeviewExplorer.ocx"
执行上述过程会弹出注册成功的提示框,如图2-11所示。
如果要屏蔽注册成功的对话框,可以在regsvr32.exe后面添加/s参数,也就是改为如下形式。
图2-11 成功注册ocx控件
Shell "regsvr32.exe /s E:\TreeviewExplorer\ TreeviewExplorer.ocx"
那么,注册成功后,到底引起了哪些变化呢?
运行Shell "regedt32.exe" ,vbNormalFocus,自动打开注册表编辑器,在查找对话框中输入关键字,可以快速找到该控件的注册信息,如图2-12所示。
图2-12 ocx控件的信息写入注册表
可以看出注册信息位于:HKEY_CLASSES_ROOT\TreeviewExplorer.UC。
注册成功的控件,更重要的变化在于能够在各种窗体中使用该控件。下面是VBA的用户窗体中的“附加控件”对话框,在该项前面勾选,即可把自定义控件添加到“控件工具箱”中,从而可以插入窗体中,如图2-13所示。
图2-13 使用自定义控件
控件的反注册也很简单,只需要在上述注册的代码中插入一个/u参数,就是反注册。
Shell "regsvr32.exe /u E:\TreeviewExplorer\TreeviewExplorer.ocx"
如果不弹出提示对话框,更改为如下代码。
Shell "regsvr32.exe /s /u E:\TreeviewExplorer\TreeviewExplorer.ocx"
控件被反注册以后,注册表中会删除该控件的信息,在“附加控件”对话框中也找不到该控件。
dll文件,即动态链接库文件(Dynamic Link Library),也可以称为类库,与ocx文件一样,不能直接运行。dll文件中主要封装了一些函数和代码。Office的COM加载项就是一种扩展名为.dll的文件。
在VBA编程中,可以向VBA工程的引用中添加dll文件,从而使用dll文件中的功能和函数。
dll文件的注册、反注册方法和ocx的代码是一模一样的,只需要把ocx控件的路径更改为dll文件的路径即可。
OfficeDll是笔者开发的一款功能丰富的动态链接库,把该文件复制到目标计算机后,运行下面的代码进行注册。
Shell "regsvr32.exe E:\OfficeDll\OfficeDll.dll"
注册成功后,单击VBA编辑器的菜单【工具/引用】,在“可使用的引用”列表中可以看到该动态链接库,如图2-14所示。
添加引用成功后,编写代码,可以看到该类库的成员列表,如图2-15所示。
注意,如果同一台计算机中存在多个同名ocx或dll文件,以最近注册的为准。
注册和反注册的方法总结如下。
Shell "regsvr32.exe文件"表示注册一个文件,并弹出提示对话框。
Shell "regsvr32.exe /s文件"表示注册一个文件,不弹出提示对话框。
Shell "regsvr32.exe /u文件"表示取消注册一个文件,并弹出提示对话框。
Shell "regsvr32.exe /s /u文件"表示取消注册一个文件,不弹出提示对话框。
图2-14 动态链接库的注册和使用
图2-15 VBA中使用动态链接库
关于ocx控件和动态链接库的开发和应用,本书暂不讨论。
2.1.8 结束进程
在Windows系统中按下快捷键【Ctrl+Shift+Esc】可以弹出Windows任务管理器,通过Windows任务管理器可以强行结束一个进程,如图2-16所示。
图2-16 Windows任务管理器
假定现在屏幕上打开了计算器,进程列表中一定有calc.exe进程,然后在VBA中运行如下代码。
Shell "taskkill /f /im calc.exe", vbHide
可以看到计算器自动被终止。taskkill.exe也是System32文件夹下的一个可执行文件,专门用来终止进程。
2.1.9 自动关机
shutdown.exe是一个用于关机的系统文件,用Shell函数调用shutdown可以实现自动关机、取消关机、自动重启等操作。
运行上述Test1或Test2,会弹出提示框,如图2-17所示。
如果又不想关机或重启,那么需要运行Test3取消计划。计划被取消时,屏幕右下角弹出提示,如图2-18所示。
图2-17 计划关机
图2-18 取消计划
在日常办公中,一台计算机经常会连续工作好几天,此时,可以用Excel VBA设置一个计划,在未来某一天的某一时刻定时关机。
Application对象的OnTime方法可以在某一时刻准时执行某个过程,因此,运行下面的MySchedule过程,计算机不会发生任何变化,但是到了指定的时刻,会准时调用AutoShutdown过程,从而实现自动关机。
注意 运行MySchedule过程后,Excel要一直保持打开状态,如果退出Excel,计划无效。
以上内容的源代码文件为“实例文档06.xlsm”。