Office VBA开发经典:中级进阶卷
上QQ阅读APP看书,第一时间看更新

1.1 使用传统方式

使用传统方式可以访问文件和路径,对文本文件和二进制文件进行读写。最常用的函数和命令如下。

 Dir:用于列举路径下的文件和子文件夹名称。

 GetAttr和SetAttr:获取和设置属性。

 FileCopy、Name、MkDir等:对文件和路径复制、移动等。

 Open...Write...Close:对文本文件、二进制文件进行打开、读写、关闭。

1.1.1 获取文件或路径的属性

右击文件、文件夹,在弹出菜单中选择属性命令,打开属性窗口后,可以设置只读属性和隐藏属性等。

GetAttr函数用来获取和判断文件或路径的属性,该函数的参数是一个路径字符串,返回值是由多个2的整数幂的组合相加的总和,如表1-1所示。

表1-1 文件、路径的属性常量

这里假定磁盘下的TE.txt文本文件已设置为“只读”并且“隐藏”,如图1-1所示。

图1-1 查看文件属性

此时,GetAttr("C:\temp\abcd\TE.txt")会返回一个整数35。其实,35=32(vbArchive)+2(vbHidden)+1(vbReadOnly)。

因此,把GetAttr函数的计算结果拆分为多个枚举常量值之和,就可以得知该文件的属性。

下面的过程用来把任何一个正整数拆分为多个2的乘方。

运行上述过程,可以看到13被拆分为8+4+1。根据这个思路,可以设计一个用来判断文件是否被设置为只读的自定义函数。

这个函数的原理就是把GetAttr的结果拆分为多个数字,拆分的过程中,看看是否有一个拆分恰好等于枚举常量vbReadOnly,如果有就提前退出函数,返回True。

运行Debug.Print IsReadOnly("C:\temp\abcd\TE.txt"),在立即窗口返回结果True,表明这是一个只读文件。

同理,把上述函数中的ReadOnly这个单词替换为Hidden,就形成了可以判断文件或路径是否设置了隐藏属性。

这里假定C:盘下的Build文件夹被设置了隐藏属性,那么Debug.Print IsHidden("C:\Build\")返回结果True。

下面的函数可以判断一个路径是否为文件夹。

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函数用的筛选常量

如果不规定Attributes属性,则默认为vbNormal。

代码分析:如果计算机中不存在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所示。

图1-2 文件夹中的内容

可以看到有7个子文件夹,4个文件。运行如下的过程,打印出所有的子文件夹名称和文件名称。

上述程序的打印结果如图1-3所示。

可以看出,第一行打印出一个小数点,第二行打印出两个小数点,从第三行起才是正式的内容。

如果把代码中的Path = Dir(parent, vbDirectory)修改为Path = Dir(parent),则只遍历文件,不遍历子文件夹。

那么如何只遍历子文件夹呢?这就需要在循环体中加入If语句来选择性地遍历。

图1-3 遍历子文件夹和文件

上述过程中,用集合Col来装载所有的文件和子文件夹的名称,最后,遍历Col的时候,首先过滤出所有的子文件夹,然后排除小数点,最后输出纯粹的子文件夹,共7个,如图1-4所示。

如果要遍历C:\Ctex下面的所有扩展名为.txt的文本文件,代码可以修改为如下形式。

图1-4 只列举子文件夹名称

注意,Dir函数中用到了通配符,*.txt可以匹配所有的文本文件,如图1-5所示。

图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所示的代码连续两次删除同一个文件,第一句不会出错,但是运行到第二句时弹出“文件未找到”的错误。

图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。

图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关闭文件。

下面讲述一下导出数据到文本文件中的方法。

上述过程把三个字符串写入文本文件中,使用Print语句写入时,在末尾自动换行,如图1-8所示。

使用Print在同一行输出多个字符串时,每个字符串之间用半角分号隔开。

上述程序的运行结果如图1-9所示。

图1-8 向文件写入内容

图1-9 同一行输出多个结果

除了使用Print语句输出外,还可以使用Write语句输出内容到文本文件。

程序的运行结果如图1-10所示。

可以看出文本文件中的内容都带有双引号,这和Print语句有很大不同。

如果把Open "C:\temp\abc.txt" For Output As #1这句中的Output换成Append,则每次写入文件时,不删除文件原有内容。请读者自行测试。

接下来讲述如何从已有文本文件中读取内容,供程序调用。

读入文件内容涉及的常用术语有:

 v=Input(c,fileNum),表示从文件当前位置读取c个字符,赋给字符串变量v。

图1-10 使用Write输出内容

 Seek fileNum, c,把当前位置重设为c,c的最小值是1。

 LOF(fileNum),返回文件的长度,也就是文件中字符总数。

 EOF(fileNum),返回一个布尔值,当读取到文件尾部,返回True。经常使用EOF来判断是否读取完成。

现在假设文本文件auto.txt中的内容如图1-11所示。

图1-11 文本文件内容

代码分析: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所示。

图1-12 从文件中读取字符

根据这个特点,可以把文本文件中的所有字符分发到字符串数组中。

代码分析:上述过程,打开文本文件后,根据文件字符总数重新定义数组的上下界,使得数组能恰好容纳文本中的字符,然后使用For循环,遍历文本文件中的每个字符,并分发到数组的每个元素。

运行到Stop那句,通过本地窗口可以清晰地看到数组s的各元素取值情况,如图1-13所示。

图1-13 本地窗口查看数组

可以看出,每个元素恰好取得文件中的一个字符。最后通过Join把数组用*重新连接并输出,如图1-14所示。

图1-14 数组连接为字符串

此外,还可以使用Line Input语句,每次读取一整行。

假设文件b.txt中有4行古诗,下面用Line Input读取内容。

代码分析:本例直接把读取到的每行打印到立即窗口,因此可以使用Do循环,利用EOF函数来判断是否读到文件尾部,如果到了尾部,就结束循环。

上述程序的运行结果如图1-15所示。

图1-15 使用Line Input读取内容

如果要一次性读取文本文件的所有内容,可以使用下面的自定义函数。

运行下面的Test过程,即可把文件中的所有内容打印到立即窗口。

上述代码的源文件为“实例文档01.xlsm”。