![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=1739238903-AyXKRPCyftjROHFt0z92tczcB1sossiQ-0-4aa2acb6b597512436a56efbd685dab3)
这里假定磁盘下的TE.txt文本文件已设置为“只读”并且“隐藏”,如图1-1所示。
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/186.jpg?sign=1739238903-101KuaK8uRkCjcOZRUCsOeaaozMtuBMr-0-5d8840a902e055131d2acc9f9382e22a)
图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=1739238903-SA6ceZnYj5qcCJO8B3phFMrni424hyN6-0-07aa5bcb263f8a1e863c86bd5e1a7dd3)
运行上述过程,可以看到13被拆分为8+4+1。根据这个思路,可以设计一个用来判断文件是否被设置为只读的自定义函数。
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/214.jpg?sign=1739238903-FXjMnyCneszk6wlbg4EIfyRbMmMEoomX-0-0048ee7520e04cf6e93f2c4a76a9e945)
这个函数的原理就是把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=1739238903-UBozhnE44dIhB4uZtlcannREmAwyaZfg-0-c4f77d3efbe75c458aeee834ba2ce628)
这里假定C:盘下的Build文件夹被设置了隐藏属性,那么Debug.Print IsHidden("C:\Build\")返回结果True。
下面的函数可以判断一个路径是否为文件夹。
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/246.jpg?sign=1739238903-Ych3RmBRYudt9ojCvbhPbcqCd3m7LBut-0-9c4720d3c0930551c1a4b6f26ee89b44)
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=1739238903-JwitRmDjqcNHn9notOS1SQgQ3f6wqvPV-0-570de1f50fe5c84b028eb2d71ebb0046)
如果不规定Attributes属性,则默认为vbNormal。
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/275.jpg?sign=1739238903-LZVYXjcOrP5mkwtEaLrqNeoPpbdpK0RF-0-8ca7ae345f0133493f1d27d6540616c2)
代码分析:如果计算机中不存在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=1739238903-5v3yApsJhIq6KZyW1rGAtx7o98f1nfmZ-0-ac067f05951f4ef74ea90b10fa500327)
图1-2 文件夹中的内容
可以看到有7个子文件夹,4个文件。运行如下的过程,打印出所有的子文件夹名称和文件名称。
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/274.jpg?sign=1739238903-OLeKbLt3XkC0X1sqoEjHL1EGfVDobQ7i-0-905d1222a641f348b72c5124747cf165)
上述程序的打印结果如图1-3所示。
可以看出,第一行打印出一个小数点,第二行打印出两个小数点,从第三行起才是正式的内容。
如果把代码中的Path = Dir(parent, vbDirectory)修改为Path = Dir(parent),则只遍历文件,不遍历子文件夹。
那么如何只遍历子文件夹呢?这就需要在循环体中加入If语句来选择性地遍历。
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/299.jpg?sign=1739238903-wxNmkFGs9NZjYxQAeuiHD1gsjlqlj4Iq-0-f7832a4f04466c8684f9bf293219205f)
图1-3 遍历子文件夹和文件
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/301.jpg?sign=1739238903-wKKpqfaOTeoDm4xNywEUhq9rMqq1eHH9-0-22328977e5d0840b7746cd39eb17fda7)
上述过程中,用集合Col来装载所有的文件和子文件夹的名称,最后,遍历Col的时候,首先过滤出所有的子文件夹,然后排除小数点,最后输出纯粹的子文件夹,共7个,如图1-4所示。
如果要遍历C:\Ctex下面的所有扩展名为.txt的文本文件,代码可以修改为如下形式。
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/300.jpg?sign=1739238903-QiFiVOiL9ISL6xQ0yEOHYiS3RxuWsHqH-0-7f11e4815b46b88abf012ccca982fa87)
图1-4 只列举子文件夹名称
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/298.jpg?sign=1739238903-lCzPWFxNm1BAPSTGQ4W1iEaQ4d7lghXo-0-a69f2597a256a83734b2cdd5d153cd80)
注意,Dir函数中用到了通配符,*.txt可以匹配所有的文本文件,如图1-5所示。
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/317.jpg?sign=1739238903-j00jV9Bh6714CQ13Xsd0HjsUuHo4d6Oh-0-6db397c543962e505258734b69f598ae)
图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=1739238903-YUkrYYvtYxJ1hx4keeINYUZrIKT051RW-0-23c82e5a9f9b9717dd47fa84b7b34e5a)
图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=1739238903-6MaxBdUVGuipze30unX8NL6IelynJnZO-0-ba445319308676e1882bcb91a1311173)
图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=1739238903-EDTscJsJbxS9wnD8IeTVfqcEp4VP7SEu-0-cd938f1edf6132db0170db044624ef2d)
上述过程把三个字符串写入文本文件中,使用Print语句写入时,在末尾自动换行,如图1-8所示。
使用Print在同一行输出多个字符串时,每个字符串之间用半角分号隔开。
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/360.jpg?sign=1739238903-eQK3y4oSjAEg93d3txkiR5I60kN4CrY9-0-4bbb56043d7a539067b8aa7f68b96b14)
上述程序的运行结果如图1-9所示。
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/356.jpg?sign=1739238903-GCfepYKssEcHWiXlgKOJtWtmOpFTkinq-0-2a5fc22753bcd2e0e5ecea609e1ead84)
图1-8 向文件写入内容
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/357.jpg?sign=1739238903-G0NNWvBwc2GFRojxkKt3EqakAZotM3Qs-0-4dbb172dc279e6092a1d65482c65bb7d)
图1-9 同一行输出多个结果
除了使用Print语句输出外,还可以使用Write语句输出内容到文本文件。
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/361.jpg?sign=1739238903-AmL7ZpcJlLznF27JqRMAx1h3Lo6V4gnl-0-4f7663a38cb3332cb236351f452b7570)
程序的运行结果如图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=1739238903-OuB0otW7XCafjYoOD3cH5yaAbUQjNqcd-0-054a6a3f296d9f7666756db3ee80dd3a)
图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=1739238903-IyvIw45eDgWLKVjbSrmcZjPWEriKx1qY-0-f5be3fe000e55280640c572fba190ce0)
图1-11 文本文件内容
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/382.jpg?sign=1739238903-b0ZVCn1g6Bb8Dt20Ug7HU6rrmfPTdmll-0-aae7913addd1a080c18cde426e0d6bc9)
代码分析: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=1739238903-AHnW0EAnH33mj3wNSqUSA7iBTjlJtZuu-0-78c00dcc8146b267b233095ff9f6e5b6)
图1-12 从文件中读取字符
根据这个特点,可以把文本文件中的所有字符分发到字符串数组中。
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/383.jpg?sign=1739238903-p5qWEriHPVczQdwHa7ue4LDdV1cu3Ocm-0-0709fce90cd73f2eb3819e4120d7fd15)
代码分析:上述过程,打开文本文件后,根据文件字符总数重新定义数组的上下界,使得数组能恰好容纳文本中的字符,然后使用For循环,遍历文本文件中的每个字符,并分发到数组的每个元素。
运行到Stop那句,通过本地窗口可以清晰地看到数组s的各元素取值情况,如图1-13所示。
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/404.jpg?sign=1739238903-y6S0Kj5avOZ7tkTk3N7x5EDN2aqahUgC-0-c18f13eba0713d5302a7870f702dc931)
图1-13 本地窗口查看数组
可以看出,每个元素恰好取得文件中的一个字符。最后通过Join把数组用*重新连接并输出,如图1-14所示。
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/405.jpg?sign=1739238903-BpEQQXtfyPccKxstg9XsNJACETXgIFoz-0-dcbd8cd84c5933ca95b6390a97c7740a)
图1-14 数组连接为字符串
此外,还可以使用Line Input语句,每次读取一整行。
假设文件b.txt中有4行古诗,下面用Line Input读取内容。
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/407.jpg?sign=1739238903-UL0EcwqZBK4wxfFSesP4KrtPwoxi5J6n-0-6dddd5ced3b3891ed671314129fe31d0)
代码分析:本例直接把读取到的每行打印到立即窗口,因此可以使用Do循环,利用EOF函数来判断是否读到文件尾部,如果到了尾部,就结束循环。
上述程序的运行结果如图1-15所示。
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/406.jpg?sign=1739238903-qWcXvlbvypcQikmdwqBf4YohkAxv0rmJ-0-446219b23c9a4744fe6f9f224c931185)
图1-15 使用Line Input读取内容
如果要一次性读取文本文件的所有内容,可以使用下面的自定义函数。
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/424.jpg?sign=1739238903-T0FAdD4FCeleGg4T9C0N05rfZBTjno2I-0-679a4625cb54eb7c7db01a0398a6fb76)
运行下面的Test过程,即可把文件中的所有内容打印到立即窗口。
![](https://epubservercos.yuewen.com/F986E7/15056702504171006/epubprivate/OEBPS/Images/425.jpg?sign=1739238903-Vfls4HnITOyC0YWR0Rtf4cqzPr0wuSMF-0-6d0b42279e2173a0d7843646f442334c)
上述代码的源文件为“实例文档01.xlsm”。