![Office VBA开发经典:中级进阶卷](https://wfqqreader-1252317822.image.myqcloud.com/cover/711/26542711/b_26542711.jpg)
1.1 使用传统方式
使用传统方式可以访问文件和路径,对文本文件和二进制文件进行读写。最常用的函数和命令如下。
Dir:用于列举路径下的文件和子文件夹名称。
GetAttr和SetAttr:获取和设置属性。
FileCopy、Name、MkDir等:对文件和路径复制、移动等。
Open...Write...Close:对文本文件、二进制文件进行打开、读写、关闭。
1.1.1 获取文件或路径的属性
右击文件、文件夹,在弹出菜单中选择属性命令,打开属性窗口后,可以设置只读属性和隐藏属性等。
GetAttr函数用来获取和判断文件或路径的属性,该函数的参数是一个路径字符串,返回值是由多个2的整数幂的组合相加的总和,如表1-1所示。
表1-1 文件、路径的属性常量
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/890.jpg?sign=1738885393-yyjVn7BcaV6d2OBp6iMK6y48s0SszhwC-0-ce5d1f28297de06cf9e1e97ae12ebd3f)
这里假定磁盘下的TE.txt文本文件已设置为“只读”并且“隐藏”,如图1-1所示。
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/186.jpg?sign=1738885393-nPGDMjBBXAi4nNmWUDNn802cNuPlQUfp-0-7d21d2220a57a49198f1862bcc14e71b)
图1-1 查看文件属性
此时,GetAttr("C:\temp\abcd\TE.txt")会返回一个整数35。其实,35=32(vbArchive)+2(vbHidden)+1(vbReadOnly)。
因此,把GetAttr函数的计算结果拆分为多个枚举常量值之和,就可以得知该文件的属性。
下面的过程用来把任何一个正整数拆分为多个2的乘方。
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/213.jpg?sign=1738885393-xU1Y7vuAuR2qlkmq3SS3ud0am662HiTZ-0-eaa7d3175b8a4f1e97cdbbef72aff8f8)
运行上述过程,可以看到13被拆分为8+4+1。根据这个思路,可以设计一个用来判断文件是否被设置为只读的自定义函数。
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/214.jpg?sign=1738885393-6y60c7qQuG4cBAGHEyhPdiPvRve1kDL8-0-b3fbd2408e3c0aea3dd2a3b2b0e1c7b2)
这个函数的原理就是把GetAttr的结果拆分为多个数字,拆分的过程中,看看是否有一个拆分恰好等于枚举常量vbReadOnly,如果有就提前退出函数,返回True。
运行Debug.Print IsReadOnly("C:\temp\abcd\TE.txt"),在立即窗口返回结果True,表明这是一个只读文件。
同理,把上述函数中的ReadOnly这个单词替换为Hidden,就形成了可以判断文件或路径是否设置了隐藏属性。
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/215.jpg?sign=1738885393-iWoQU2wUzXlp5SuSILhAFkFCOv3mecCL-0-29d78a5e9fc7588d6d2b0a2bd7c41665)
这里假定C:盘下的Build文件夹被设置了隐藏属性,那么Debug.Print IsHidden("C:\Build\")返回结果True。
下面的函数可以判断一个路径是否为文件夹。
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/246.jpg?sign=1738885393-TbZe3sHNSndsLfXwpNIJDV8nTQ9R78BQ-0-dd89894a72775dbc21d5c4d95b187606)
Debug.Print IsDirectory("C:\Build\")返回True。Debug.Print IsDirectory("C:\Build\Hello.csv")返回False。
1.1.2 设置文件或路径的属性
与GetAttr相对应的函数是SetAttr,该函数可以设置文件、路径的属性。
SetAttr "C:\Build\", vbHidden + vbReadOnly
这条代码把Build文件夹的属性设置为只读,并且隐藏。
SetAttr "C:\Build\", vbNormal
这句代码去掉只读和隐藏属性,恢复为正常属性。
1.1.3 判断文件或路径是否存在
使用Dir函数可以列举出当前路径下所有文件和子文件夹的名称,从而间接地判断一个文件或文件夹是否存在。
Dir函数的语法为:
Dir(PathName,Attributes)
PathName是一个用来描述文件、路径的字符串,可以使用*、?通配符。Attributes可以使用如表1-2所示的值。
表1-2 Dir函数用的筛选常量
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/893.jpg?sign=1738885393-BsN0AVopx0aeqM3oAW13thbJ8sP3KZ1u-0-abd92b532d7f3e2ebb451bc519408f93)
如果不规定Attributes属性,则默认为vbNormal。
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/275.jpg?sign=1738885393-TRleq0mEOiVDI7ftdCbe8qLYaVtZ20EY-0-13c29b6649f64b64ecb0191e13878995)
代码分析:如果计算机中不存在Test.txt这个文件,那么Dir函数会返回空字符串;如果文件存在,则返回第一个符合模式的文件名称(不包含所在路径),据此可以判断磁盘或文件夹中是否有某个文件。此外,还可以使用Dir函数判断是否有某磁盘分区,或者是否有某个文件夹。
如果上述过程中的Path赋值为Path="M:"或者Path="M:\",则可以用来判断是否存在M:盘。
如果要判断是否存在某文件夹(路径),结尾必须加反斜杠。例如Dir("C:\build")用来判断C:盘下是否有build这个文件,而Dir("C:\build\")用来判断C盘下是否有build文件夹。
1.1.4 遍历文件和子文件夹
利用Dir函数和不带参数的Dir,可以遍历一个路径下的所有文件和子文件夹的名称。现在假定C:\CTEX文件夹中的内容如图1-2所示。
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/273.jpg?sign=1738885393-plPnuQQTWuOH3uPQdGMbx5nHZDYOi69o-0-ef7659b306265b72eaed448be3ffd5c0)
图1-2 文件夹中的内容
可以看到有7个子文件夹,4个文件。运行如下的过程,打印出所有的子文件夹名称和文件名称。
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/274.jpg?sign=1738885393-G8MFm0ot4h1didbinaxi8u9Xj8cmV8pS-0-8636f40e90257d1c57b26006dd23a66c)
上述程序的打印结果如图1-3所示。
可以看出,第一行打印出一个小数点,第二行打印出两个小数点,从第三行起才是正式的内容。
如果把代码中的Path = Dir(parent, vbDirectory)修改为Path = Dir(parent),则只遍历文件,不遍历子文件夹。
那么如何只遍历子文件夹呢?这就需要在循环体中加入If语句来选择性地遍历。
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/299.jpg?sign=1738885393-WEJLCGZX5rS6g27ZfPwzAvCpZnmox5YZ-0-769a24c9ebc0c24881bc3b667fc0c2ba)
图1-3 遍历子文件夹和文件
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/301.jpg?sign=1738885393-pQGheK9GqRBvBtP3VFk7s8yPQ1ExWU7N-0-b6ac1fa824a637d72198f8a66041e016)
上述过程中,用集合Col来装载所有的文件和子文件夹的名称,最后,遍历Col的时候,首先过滤出所有的子文件夹,然后排除小数点,最后输出纯粹的子文件夹,共7个,如图1-4所示。
如果要遍历C:\Ctex下面的所有扩展名为.txt的文本文件,代码可以修改为如下形式。
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/300.jpg?sign=1738885393-TpgscDUxgGBCY2j1bGkRR04qddoib9ll-0-52e196500ec05c745e911c212b999649)
图1-4 只列举子文件夹名称
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/298.jpg?sign=1738885393-w8ZmfkZSTC2tpMfY6C0tfF6z6vsojv5Y-0-d2cbe7c0f83f07f4c8668d0afe356ef4)
注意,Dir函数中用到了通配符,*.txt可以匹配所有的文本文件,如图1-5所示。
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/317.jpg?sign=1738885393-ZEqXhe2Nn2loxxGIERNlrTzmZTyZYaxi-0-ebc9e10906bfde9d74ac22727ea0b9b0)
图1-5 只遍历文本文件
1.1.5 文件的复制、移动和删除
文件的复制、移动和删除操作,分别用FileCopy、Name As和Kill语句。
FileCopy的语法为:
FileCopy Source, Destination
Source表示原文件,Destination表示复制的目标。
例如FileCopy Source:="C:\temp\a.xlsx", Destination:="D:\dist\goal.xlsx",表示把文件C:\temp\a.xls复制到D:\dist文件夹下,并且重命名为goal.xlsx。
文件的移动操作就是文件的剪切,也可以理解为文件的重命名。与复制文件的区别是,原文件不在原位置了。
Name "C:\temp\a.xlsx" As "D:\dist\b.xlsx",就相当于把a文件从原位置剪切到D:\dist文件夹中,并且设置名称为b.xlsx。
注意 针对文件的移动操作,如果D:\dist\下面原先就有一个b.xlsx文件,那么运行上述的Name语句会导致出错。也就是说,必须保证目标文件夹中还没有这个文件,才能进行移动操作。
Kill语句用于删除文件,如果文件处于打开、占用状态,运行该语句会出错。另外,用Kill语句删除掉的文件,不能通过回收站还原,要谨慎操作。
图1-6所示的代码连续两次删除同一个文件,第一句不会出错,但是运行到第二句时弹出“文件未找到”的错误。
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/316.jpg?sign=1738885393-exoOdCliw1mtWDwoCttQjz8GHkjgw2ZP-0-76470e2490a8a9f7a8a353fc10b2c5b0)
图1-6 重复删除同一文件的错误
1.1.6 文件夹的创建和删除
文件夹的创建和删除分别用MkDir和RmDir语句,Mk是Make的缩写,Rm是Remove的缩写。
MkDir语句的语法很简单。
MkDir Path:="C:\temp\2017",会在temp文件夹下创建一个名为2017的文件夹。
RmDir语句用来删除一个空文件夹。
RmDir Path:="C:\temp\picture",表示删除picture文件夹,如果该文件夹不是空的,包含其他的文件和子文件夹,那么RmDir会提示错误,如图1-7所示。
也就是说,要删除一个文件夹,必须先把里面的内容清空后,才能使用RmDir语句删除。
文件夹的重命名也使用Name…As语句。例如Name "C:\temp\picture" As "C:\temp\pic",表示把文件夹picture重命名为pic。
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/338.jpg?sign=1738885393-aquu6xdBWJo4UJ4Ynx6YdJVDJUBsNbwg-0-41b6b6946701a322db66a75a38f4d57f)
图1-7 文件夹中有内容则不能删除
1.1.7 文本文件的读写
编程过程中,经常需要把程序运行的结果数据保存到文本文件,也需要从文本文件中读取数据供程序使用,这就涉及文本文件的读写操作了。
本节介绍一下用于文件读写的Open语句。
Open语句的语法如下。
Open textFile For mode As fileNum
参数textFile是一个表示文本文件路径的字符串。
参数mode表示Open语句的读写模式,使用最多的模式如下。
Append:追加模式。
Output:擦写模式。
Input:读取模式。
如果要把程序运行的结果输出到文本文件中,那么使用Append模式会把输出结果追加到文件已有内容之后,而使用Output模式,则会先清空文件原先的内容,再写入输出结果。
如果要从文本文件中读取内容,而不破坏文件,可以使用Input模式。
要注意的是,在使用Output或Append模式时,如果计算机中textFile文件不存在,则会自动创建一个文本文件;如果使用Input模式读取一个文本文件,文本文件不存在会导致出错。
参数fileNum是一个文件号,可以是#1到#511中的任何一个。读写文件操作结束后,一定要用Close fileNum关闭文件。
下面讲述一下导出数据到文本文件中的方法。
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/359.jpg?sign=1738885393-3o78KiDpQjjOBZfv9fcIo2Moc9eGEWbs-0-2d34da15ff0a6e91dda0f58e2c3e7da6)
上述过程把三个字符串写入文本文件中,使用Print语句写入时,在末尾自动换行,如图1-8所示。
使用Print在同一行输出多个字符串时,每个字符串之间用半角分号隔开。
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/360.jpg?sign=1738885393-xnGA8SVwLW5LZru9RtKN4ru4j27scsIO-0-15a4ac99f1c874081f03367e70f125bc)
上述程序的运行结果如图1-9所示。
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/356.jpg?sign=1738885393-vLHfQa5EqFMJrncJqPrfc2mer65cado7-0-852bdcb2acabf2251e2d4a1e2d3397be)
图1-8 向文件写入内容
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/357.jpg?sign=1738885393-icjEbQvfDF3dSpBcwx5kh3yJvIQfQTq5-0-902858fa745c40cb89a566652b95622f)
图1-9 同一行输出多个结果
除了使用Print语句输出外,还可以使用Write语句输出内容到文本文件。
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/361.jpg?sign=1738885393-BaAcBo4xUTVZw3JT5HHYIGZoJJNiKa4l-0-8894db29a2bc6d7c18fc0692a1c1841f)
程序的运行结果如图1-10所示。
可以看出文本文件中的内容都带有双引号,这和Print语句有很大不同。
如果把Open "C:\temp\abc.txt" For Output As #1这句中的Output换成Append,则每次写入文件时,不删除文件原有内容。请读者自行测试。
接下来讲述如何从已有文本文件中读取内容,供程序调用。
读入文件内容涉及的常用术语有:
v=Input(c,fileNum),表示从文件当前位置读取c个字符,赋给字符串变量v。
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/358.jpg?sign=1738885393-xqDkhttfaRpcECYH3a50Gm2xHTLr8DFA-0-1e0ef2ca705bcd6a03d833e443936f8e)
图1-10 使用Write输出内容
Seek fileNum, c,把当前位置重设为c,c的最小值是1。
LOF(fileNum),返回文件的长度,也就是文件中字符总数。
EOF(fileNum),返回一个布尔值,当读取到文件尾部,返回True。经常使用EOF来判断是否读取完成。
现在假设文本文件auto.txt中的内容如图1-11所示。
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/381.jpg?sign=1738885393-yEvQtoZFAvBR28KQjtvQil3SJ7QOuC4V-0-1efa0861b1167f008dbbff32339628a6)
图1-11 文本文件内容
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/382.jpg?sign=1738885393-ljmbgmmJNvU7sT2asQE3ORu2Xxj1lpqE-0-e422c3bc74a9bee2fc7c66cbf07a7317)
代码分析:a = Input(1, #1),表示从文件的开头处读取1个字符,赋给a,因此变量a的取值为字符串h。
b = Input(2, #1),表示从上次读取的位置起,读入2个字符赋给变量b,因此b的取值为el。以此类推。
Seek #1, 1表示把读取位置重设为1,也就是文件开头,接下来d = Input(3, #1)表示从文件开头处读取3个字符,因此d的取值为Hel。
上述程序的运行结果如图1-12所示。
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/380.jpg?sign=1738885393-DM4EkchY2p8cmlPxZThWoNVQ9aHhB6zs-0-0784936ebf06d042793cee88ab2ce01c)
图1-12 从文件中读取字符
根据这个特点,可以把文本文件中的所有字符分发到字符串数组中。
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/383.jpg?sign=1738885393-RE10KK83uiYIbPF6tc3tw33zy3Ny78a0-0-2901e5df65e04a3b42bace9ed6ce7dbd)
代码分析:上述过程,打开文本文件后,根据文件字符总数重新定义数组的上下界,使得数组能恰好容纳文本中的字符,然后使用For循环,遍历文本文件中的每个字符,并分发到数组的每个元素。
运行到Stop那句,通过本地窗口可以清晰地看到数组s的各元素取值情况,如图1-13所示。
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/404.jpg?sign=1738885393-j65u5e03ofEgfWqWCb9nLUNa8EaXXK7L-0-f9ef7f610eb20fc1722d3c7fe0d74486)
图1-13 本地窗口查看数组
可以看出,每个元素恰好取得文件中的一个字符。最后通过Join把数组用*重新连接并输出,如图1-14所示。
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/405.jpg?sign=1738885393-tk0bs1TFOxcfNMJLzwupzDyL2kSKw09J-0-39d31302c6dfe7308b7f57bb9b6cea20)
图1-14 数组连接为字符串
此外,还可以使用Line Input语句,每次读取一整行。
假设文件b.txt中有4行古诗,下面用Line Input读取内容。
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/407.jpg?sign=1738885393-CuazZNspgf54uJbFlmllyMAJvzZbbvyE-0-88d024076b1746f3acb14d3471927a56)
代码分析:本例直接把读取到的每行打印到立即窗口,因此可以使用Do循环,利用EOF函数来判断是否读到文件尾部,如果到了尾部,就结束循环。
上述程序的运行结果如图1-15所示。
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/406.jpg?sign=1738885393-lam6IzHusdFvJkEZXxMapPRvp6DrgdSu-0-8a63c1957ef89cb58fa7e7ad0cb42b17)
图1-15 使用Line Input读取内容
如果要一次性读取文本文件的所有内容,可以使用下面的自定义函数。
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/424.jpg?sign=1738885393-7GlkyjwIbx2EHYJIYpiagpD3ALq2T3jZ-0-97f36cd5f925581a26136dad4c75fb78)
运行下面的Test过程,即可把文件中的所有内容打印到立即窗口。
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/425.jpg?sign=1738885393-GLuREiiPRYNlmSs7GkO3wjDMotnxPCur-0-43803f7c0107bccf5f5bc5cbf0cb3d1d)
上述代码的源文件为“实例文档01.xlsm”。