- Office VBA開發經典:中級進階卷
- 劉永富 劉行
- 5495字
- 2019-11-22 18:29:03
1.3 使用文件系統對象
FSO(FileSystemObject)不僅可以像使用傳統文件操作語句那樣實現文件的創建、改變、移動和刪除,而且可以檢測是否存在指定的文件夾,如果存在,那么這個文件夾又位于磁盤上的什么位置。更令人高興的是,FSO對象模型還可以獲取關于文件和文件夾的信息,如名稱、創建日期或修改日期等以及系統中使用的驅動器的信息,如驅動器的種類是CD-ROM還是可移動磁盤,當前磁盤的剩余空間還有多少。
FSO對象本身不屬于VBA對象,要在VBA中使用FSO操作文件和路徑,可以用前期綁定,也可以用后期綁定。
1.3.1 前期綁定
Office VBA不僅可以使用VBA本身的對象、成員,而且可以引入外部對象,例如在VBA中使用FSO、字典、正則表達式,以及后面講到的操作其他Office組件,其實都是在VBA工程中引入了外部對象。
所謂的前期綁定,就是在編寫程序之前,把外部對象庫加入工程的引用(References)中。這樣做的好處是,在寫代碼的時候,這些相關的對象后面輸入小數點,可以自動列出成員,而且在聲明變量時,也可以直接指定變量的類型。
采用前期綁定方式,可以使用New關鍵字或GetObject函數創建一個新的對象。
下面介紹一下采用前期綁定方式,向VBA工程引入FSO對象的步驟。
單擊VBA編輯器的菜單【工具/引用】,彈出工程的引用對話框。在對話框中勾選“Microsoft Scripting Runtime”,單擊“確定”按鈕關閉對話框,如圖1-23所示。

圖1-23 添加外部引用
“Microsoft Scripting Runtime”這個外部引用位于路徑“C:\Windows\System32\scrrun.dll”這個動態鏈接庫中,每個Windows系統都有這個文件。
VBA工程一旦引入了這個外部引用,就可以使用FSO對象模型,以及后面要講到的字典(Dictionary)對象。
下面通過一個VBA過程來測試一下。

當輸入Scripting后面的小數點時,會自動彈出FSO相關的成員,這就是前期綁定的特點。運行上述過程,會執行復制文件操作。
1.3.2 后期綁定
后期綁定,就是程序中用到的外部對象,不往工程中添加引用,而是在需要該外部對象的地方,使用CreateObject函數來創建對象。針對后期綁定,由于VBA的工程沒有添加對象庫的引用,自然就不會自動列出成員,聲明這方面的變量時,只能聲明為Object或Variant類型。
下面新建一個工作簿,打開VBA編輯器在標準模塊中直接書寫一個過程。

書寫上述代碼時的感受就是不彈出成員,也沒有任何語法提示。但是上述過程可以正常執行,實現文件的移動或重命名。
在實際編程過程中,前期綁定和后期綁定的代碼通過改寫,就可以轉換,但是對于剛剛學習一個新對象庫,推薦使用前期綁定方式。因為使用這種方式可以快速了解新對象的模型結構和語法特征。
1.3.3 FSO對象模型
FSO對象主要包括:Drive(分區、磁盤驅動器)、Folder(文件夾、路徑)、File(文件)、TextStream(文本文件),以及FileSystemObject這五類對象。
1.3.4 遍歷磁盤分區
FSO對象模型中的Drive對象可以表達一個分區。FSO.Drives是一個集合對象,用來返回所有分區。
下面的過程遍歷計算機的所有分區的名稱、總大小、可用空間、已用空間。其中已用空間是用總大小減去可用空間得到的。

運行上述過程,立即窗口的結果如圖1-24所示。
在計算機的資源管理器中查看E:盤的屬性,可以看到E:盤的總大小和可用空間與VBA運行結果是一致的,如圖1-25所示。

圖1-24 遍歷磁盤分區

圖1-25 核對磁盤分區大小
如果分區大小改寫為更大容量單位的,首先要了解如下換算關系。
1GB=1024MB
1MB=1024KB
1KB=1024B,B表示字節
可以看出1GB=10243B
下面的過程單獨查看E:盤的總大小和可用空間,用GB表示。

上述程序的運行結果如圖1-26所示。

圖1-26 查看磁盤分區屬性
1.3.5 操作文件夾
FSO對象模型的Folder對象表示一個文件夾,或者稱為一個路徑。Folder對象本身有大量的屬性、成員和方法可以使用。
要表達一個文件夾,只能使用FSO.GetFolder("文件夾路徑")的方式。
下面的過程查看一個文件夾的總大小、子文件夾的個數和文件的個數。

運行結果如圖1-27所示。
在資源管理器中查看dist文件的屬性,對比后發現文件夾的大小和運行結果是一致的。但是,文件夾和文件的個數不一樣,這是因為FSO中的SubFolders和Files是文件夾直屬的文件夾和文件,不包括子文件夾以及子文件夾中的文件。
資源管理器中看到的則是該文件夾中包含的所有文件夾和文件(包括遞歸嵌套的子文件夾),如圖1-28所示。

圖1-27 查看文件夾的大小以及子文件夾和文件的數量

圖1-28 查看文件夾屬性
下面的過程列出了文件夾Folder對象的其他常用屬性。

運行上述程序,立即窗口的結果如圖1-29所示。

圖1-29 文件夾的有關屬性
文件夾Folder對象的常用方法主要有Copy、Move和Delete等,用于復制、移動、刪除文件夾。下面的過程首先創建一個空文件夾,然后重命名文件夾,最后刪除該文件夾。

需要注意的是,FSO對象模型中文件夾的Copy、Move、Delete方法對于非空文件夾同樣有效,也就是說,即使被操作的文件夾包含文件和子文件夾,也被一起復制、移動和刪除。
要獲取和返回一個文件夾Folder對象,除了上面介紹過的GetFolder、CreateFolder方法以外,還可以使用Folder對象的SubFolders、ParentFolder得到文件夾的子文件夾和父級文件夾。
1.3.6 文件夾拒絕訪問的問題
磁盤根目錄下除了包含正常的文件夾外,經常還包含一些隱藏的系統文件夾,當用FSO讀寫這些系統文件夾時,會彈出“拒絕訪問”的錯誤。
如果計算機的文件夾選項中設置了不顯示隱藏的文件和文件夾或驅動器,那么在資源管理器中看到的全是可以正常操作的文件夾,如圖1-30所示。

圖1-30 不顯示隱藏的文件夾
通過更改“文件夾選項”,切換到“查看”選項卡,找到“隱藏受保護的操作系統文件”,去掉勾選,并且選擇“顯示隱藏的文件、文件夾和驅動器”,如圖1-31所示。

圖1-31 顯示隱藏的文件、文件夾和驅動器
設置完畢后,A:盤根目錄下看到了隱藏的文件夾(圖標比較虛),如圖1-32所示。

圖1-32 顯示文件夾中隱藏的內容
這些隱藏文件夾大多數是系統文件夾,因此它們的屬性是由vbHidden+vbSystem+vbDirectory組合的,結果為22。正常文件夾的結果是16。
下面的程序遍歷A:盤根目錄下的所有子文件夾,如果不是隱藏文件夾,就打印其名稱、包含的子文件夾個數、屬性值。

以上代碼中,If判斷語句起到過濾文件夾的作用,不處理隱藏的文件夾。
運行上述程序,立即窗口的打印結果如圖1-33所示。
可以看出,只有3個文件夾是正常文件夾。
假設去掉上述代碼中的If判斷語句,再次運行上述程序,當遍歷到“System Volume Information”這個文件夾,訪問fd.SubFolders.Count屬性時,彈出如圖1-34所示運行時錯誤。

圖1-33 只列舉正常的文件夾

圖1-34 不可訪問系統文件夾
綜上所述,在處理文件夾時,需要考慮到該文件夾能否被訪問,必要時需要補充上述過濾條件。
1.3.7 操作文件
FSO對象模型中的File對象表示一個文件。與Folder對象類似,File對象也有很多的屬性和方法。
在下面的過程中,用GetFile方法獲取一個文件后,遍歷該文件的常用屬性。

上述程序的運行結果如圖1-35所示。

圖1-35 文件的屬性
文件對象File的常用方法有Copy、Move、Delete。下面通過一段代碼進行了解。

代碼分析:先把abc.xls復制到dist文件夾下,并修改名稱為xyz.xls。接著刪除xyz.xls,最后把abc.xls重命名為123.xls。
1.3.8 遍歷文件
文件夾對象下面有一個Files集合對象,表示該文件夾下的所有文件。可以據此來遍歷文件夾下的直屬文件。該方法無法遍歷包含在子文件夾中的文件。
下面的程序遍歷CTEX文件夾下的所有文件,并且打印每個文件的重要屬性。

上述程序的運行結果如圖1-36所示。

圖1-36 遍歷文件夾中的所有文件
在資源管理器中查看文件的屬性,發現和輸出結果一致,如圖1-37所示。

圖1-37 核對文件屬性
如果要選擇性地遍歷文件,例如只遍歷文件夾中的文本文件,只需要在For循環中嵌套If語句,判斷一下擴展名即可。

代碼分析:FSO的GetExtensionName可以返回指定文件的擴展名。
上述程序的運行結果如圖1-38所示。
需要注意的是:在用For Each循環遍歷文件夾中的所有文件時,盡量不要在遍歷的同時對文件進行重命名、刪除、復制、移動等操作,以免發生不可預料的結果。比較安全的做法是遍歷的時候可以先把所有文件名存儲到數組或字典中,后期對數組或字典進行操作。

圖1-38 只遍歷指定擴展名的文件
1.3.9 遍歷子文件夾
一個文件夾中可能包含多個子文件夾,在FSO對象模型中,SubFolders集合對象表示磁盤分區或者文件夾下面的所有子文件夾。
下面的代碼遍歷CTEX文件夾下的所有子文件夾,打印每個子文件夾的路徑,以及子文件夾中包含的文件總數。

上述程序的運行結果如圖1-39所示。

圖1-39 每個文件夾包含的內容
實際上,Windows的文件、路徑管理是一個樹狀結構,文件夾中可以包含子文件夾和文件,子文件夾也是一種文件夾,其中還能包含子文件夾和文件,從邏輯上講,子文件夾可以無限層嵌套。
如果要遍歷到某位置下的所有子文件夾和文件,需要用遞歸算法反復訪問SubFolders才能實現。
本書源代碼文件“實例文檔02.xlsm”中的UserForm1使用了Treeview控件結合遞歸算法來展示文件夾中的所有子文件夾和文件,如圖1-40所示。

圖1-40 遞歸遍歷文件管理系統
讀者可以下載源代碼文件自行研究。
下面的代碼使用遞歸算法遍歷任意路徑,并且把遍歷的結果發送到Excel單元格中。

運行代碼中的Traversal過程,從根目錄“E:\ExcelObject_VSTO_VBA”反復調用Recursion過程,如圖1-41所示。

圖1-41 遞歸遍歷的結果發送到單元格
上述代碼的源文件為“實例文檔03.xlsm”。
1.3.10 FSO的更多操作方式
前面介紹過的文件、文件夾的操作(Copy、Move、Delete)是以文件/文件夾對象為主體的。針對這種方式,一行代碼只能操作一個文件或路徑。
FSO允許以FSO對象作為主體,這種情形下,以通配符作為參數,從而達到一行代碼就可以批處理文件或路徑。常用操作有以下6個:
FSO.CopyFile
FSO.CopyFolder
FSO.MoveFile
FSO.MoveFoler
FSO.DeleteFile
FSO.DeleteFoler
文件或路徑中的通配符可以使用*來匹配任意多個字符,也可以使用?匹配任意一個字符。
假設C:\temp下面有大量的文件夾,下面的過程可以把p開頭的所有文件夾一次性刪除。

運行上述過程,以p開頭的文件夾就被刪除了,如圖1-42所示。

圖1-42 用通配符限定被處理的文件夾
下面再舉一個批量移動文件的實例。datas文件夾下有大量的記事本文件,下面的代碼可以把一位數命名的文件批量轉移到2018文件夾中。

代碼分析:?.txt只能匹配到9.txt等,但是不能匹配12.txt,也就是說?只能代表一個字符。因此,運行上述過程后,圖中框內的文件被批量轉移,如圖1-43所示。

圖1-43 用通配符限定文件
此外,使用FSO還可以快速獲取計算機中的特殊文件夾。

上述程序的運行結果如圖1-44所示。

圖1-44 獲取特殊文件夾
1.3.11 判斷是否存在
在利用FSO對計算機中的磁盤分區、文件夾、文件進行操作時,必須事先確保目標存在方可進行操作,因此,FSO提供了DriveExists、FolderExists、FileExists三個函數來快速判斷目標是否存在,這三個函數都返回布爾值,如果存在則返回True。
以下三行代碼分別判斷計算機中是否存在K盤、文件夾C:\temp,以及是否存在Test.Spec文件。

下面的代碼先判斷C:盤下是否有Download文件夾,如果沒有,則創建這個文件夾。

在日常辦公中,經常需要在成千上萬個文件中核對哪些文件存在,哪些文件沒有,現在假設C:\Example文件夾下有大量的壓縮包文件,理論上從1月1日到1月20日的文件名都有。現在需要核對哪些日期的文件不存在,如圖1-45所示。

圖1-45 文件夾中的內容
下面的過程,在日期中循環,通過變化的日期產生臨時的文件名,然后用FSO的FileExists來判斷該日期對應的文件是否存在,不存在的話就打印到立即窗口。

運行上述過程,文件夾中不存在的文件名打印在立即窗口中,如圖1-46所示。

圖1-46 檢查哪些名稱的文件夾不存在
1.3.12 文本文件的讀寫
計算機中的文件大體可以分為文本文件和二進制文件兩大類,文本文件可以用Windows自帶的記事本軟件打開,二進制文件(圖片、Word文檔)則不能用記事本打開。
在編程過程中,經常需要對文本文件進行讀寫。下面介紹利用FSO中的TextStream對象操作文本文件。
對文件的讀寫操作分為以下三個環節。
打開文件(OpenTextStream)。
讀/寫(Read、Write、Append)。
關閉文件(Close)。
1. 打開文件
OpenTextStream函數返回一個TextStream文件對象,該函數有以下4個參數。
FileName:文件路徑。
IOMode:讀寫模式,有ForReading、ForWriting、ForAppending三種模式,含義分別是讀取、寫入和追加寫入。
Create:當文件不存在時,詢問是否創建。該參數默認值為False。
Format:編碼格式,取值必須是以下三者之一,TristateFalse(以ANSI格式打開)、TristateTrue(以Unicode格式打開)、TristateUseDefault(使用系統默認打開)。
2. 寫入文件
TextStream對象寫入文件的方法有如下三種。
Write:當前位置寫入一個字符串。
WriteLine:當前位置寫入一個字符串,并在后面自動加一個換行符。
WriteBlankLines:寫入多個空行。
以下代碼向記事本文件中自動寫入一些內容。

代碼分析:以上代碼用ANSI格式打開new.txt,如果路徑下不存在該記事本文件,則會自動創建一個空文件。
Write方法寫入內容后,繼續寫入的內容會緊跟其后寫入。代碼中的WriteBlankLines 3表示輸入三個換行符。寫入操作完畢后,別忘記用Close方法關閉文件。
上述程序的運行結果如圖1-47所示。

圖1-47 寫入文本文件
3. 讀取文件
TextStream對象用于讀取文本文件內容的方法有如下三種。
Read(i):在當前位置讀取i個字符。
ReadLine:讀取一整行。
ReadAll:讀取整個文件內容。
當一個文本文件被打開時,指針處于文本內容的起始位置,讀取過程中,指針(當前位置)隨之向右移動。
假定new.txt文件中有一首完整的二十四節氣歌,如圖1-48所示。
下面用Read和ReadLine方法來讀取一部分內容,賦給過程中的變量。

圖1-48 文本文件的內容

代碼分析:文件打開后,指針位于“春”之前,result(1)讀取5個字符,指針移動到“清”之后,result(2)從當前位置再讀取5個字符,也就是讀取“谷天”、回車符(vbCr)、換行符(vbLf)、“夏”,總計5個,如圖1-49所示。

圖1-49 文件讀取方法
result(3)從當前位置讀取到行尾。
在實際編程過程中,經常需要把記事本中的多行文本按行讀取,發送到單元格中,或者列表框控件等,此時使用ReadLine是最好的選擇。
如果要一次性讀取所有內容,發送給文本框控件,用ReadAll最省事。
下面兩個過程分別采用ReadLine、ReadAll方法從文本文件中讀取內容,發送到列表框、文本框中。

代碼分析:在使用TextStream對象的Read以及ReadLine方法時,一定要判斷是否已經讀取到文件末尾。當讀取到文件末尾時,TextStream對象的AtEndOfStream屬性會返回True,因此經常利用該屬性配合Do循環讀取所有內容。
上述程序中,左側是一個列表框控件,右側是一個文本框(MultiLine為True)。
上述程序的運行結果如圖1-50所示。

圖1-50 從文本文件讀取內容到控件
讀取文件的過程中,還可以使用Skip或SkipLine方法跳過字符或跳過行,相當于主動改變指針位置。
還是以二十四節氣歌為例,理解一下Skip的作用。

代碼分析:打開文件后,result(1)首先讀取前2個字符“春雨”,Skip 2表示跳過2個字符(“驚春”兩個字被跳過),接著result(2)讀取兩個字符,也就是讀取“清谷”兩個字。
下面的代碼演示了如何跳過整行。

運行上述過程,立即窗口的結果如圖1-51所示。
以上內容的源代碼文件為“實例文檔04.xlsm”。

圖1-51 跨行讀取內容
- 自己動手寫搜索引擎
- Learning PostgreSQL
- Maven Build Customization
- 架構不再難(全5冊)
- Scala Design Patterns
- Object-Oriented JavaScript(Second Edition)
- Python貝葉斯分析(第2版)
- JavaScript Concurrency
- LabVIEW入門與實戰開發100例(第4版)
- 從零開始學Unity游戲開發:場景+角色+腳本+交互+體驗+效果+發布
- Python面向對象編程(第4版)
- Python深度學習與項目實戰
- KnockoutJS Blueprints
- jQuery權威指南
- 深入理解C++11:C++11新特性解析與應用