在Excel中應用VBA批量導入數據
<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
馬維峯
1. 問題由來
當一個漂亮MM向你請教如何錄製並修改一個宏,把她每次的實驗數據(幾十個數據文件)導入Excel時,你感慨道:“很多Excel專家會錄製一個宏來解決問題,然後每次使用的時候修改代碼並粘貼到需要的地方,對於一個合格的程序員,這是最要命的事情。”
漂亮MM打斷並告訴你,她不是程序員,也不想做程序員,然後命令你開始工作。
2. 通過錄制宏導入數據
對於這個無法拒絕的MM,你只好垂頭喪氣的開始面對要解決的問題,想着MM幾年<?xml:namespace prefix = st1 ns = "urn:schemas-microsoft-com:office:smarttags" />後博士畢業,年薪至少5萬,幹個3、5年,年薪10萬,還有項目提成,平時吃飯、打車、買可樂都可以報銷,當然不會像你放棄了自己的專業,做了一個爲生計奔波的程序員。
數據文件是儀器生成一系列文本格式的數據文件,格式完全一樣,目的是要把每個數據文件導入到Excel中作爲一條記錄,也就是一行。那麼,你想,可以用VBA寫一個程序,然後定位到需要的位置,讀入需要的信息就可以了[①]。你打開Excel,打開VBA編輯器,準備開始寫代碼。
“開始錄宏吧”,MM提醒了心不在焉的你,你沉默了0.1秒,默唸了一下VBA的信條:“萬不得已不要寫代碼,儘量使用Office的功能”。於是你啓動Excel打開這個文本文件,按照彈出的文本文件導入嚮導對話框的步驟,使用固定列寬導入了需要的數據。數據包括2部分,第一部分是文件頭,包括一些數據信息,後面是按行放置的數據,包括結果和誤差,MM要的是後邊的數據,要把每行的數據和誤差放置到相鄰的兩列(見下圖)。
<?xml:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" /><?xml:namespace prefix = w ns = "urn:schemas-microsoft-com:office:word" />
圖 1數據文件部分和需要在Excel中的結果數據
明白了問題,一切就好辦了,打開Excel,然後開始錄製宏:首先打開文件,通過導入文本文件嚮導,讀入數據,將特定單元格的數據拷貝到一個目標Excel文件中,然後關閉這個文本文件,停止錄製宏。
錄製的宏很長,大概包括2部分。第一部分是一句打開文件,格式轉換的操作,後邊一部分是激活不同的文件,拷貝和粘貼不同的Range。你刪除掉剛纔拷貝進來的數據,運行了一下這個宏,很好,需要的數據進來了。
3. 修改宏導入成批數據
MM提醒你,這個她也能做到,但怎麼樣把所有的數據文件都導入進來。你看看她帶來的文件,文件名是“r20041124001357.txt”、“r20041124001358.txt”、“r20041124001359.txt”、“r20041124001360.txt”之類,大概是時間加序列吧。
你想,嗯,寫一個循環就可以了,你打開了剛纔錄製的宏,檢查了一下MM帶來的文件,文件名最後2位從46到89,你可以寫一個i從1到44的循環,把讀入文件部分的文件名改爲:
"r200411240013" & ( i + 45 ) & ".txt"
把粘貼目的地(range)表示行數的數字用i替換。
OK,你按下了執行按鈕,每次關閉文件的時候,有一個討厭的是否保存文件的對話框跳出來,其他好像一切正常,還好,點擊了44次鼠標後[②],MM得到了需要的數據。
4. 修改VBA代碼實現一個可通用的宏
4.1. 指定要導入的文件
當MM向你請教如何更改循環以導入不同的文件的數據時,你程序員的劣根性又開始衝動,你想通過一個打開文件對話框來指定需要的文件。你覺得實現應該不復雜,通過一個打開文件對話框,選擇一系列文件,然後將文件全路徑存入一個集合或數組,然後循環讀出這些文件就可以了。
你先創建了一個窗體,然後放置了一個按鈕,將CommonDialog控件引入工程,添加到窗體,在按鈕的點擊事件里加入如下代碼:
Dim strFiles As String, i As Long
With CommonDialog1
.Flags = &H200& Or &H80000 '可以選擇多個文件
.ShowOpen
If .FileName <> "" Then
strFiles = .FileName
End If
End With
'分割返回值,返回值爲以ASCII碼爲0的分割的字符串
'字符串第一個爲路徑,之後爲單個文件名
Files = Split(strFiles, Chr(0))
For i = 1 To UBound(Files) Step 1
Files(i) = Files(0) & "/" & Files(i) '連接路徑和文件名,組成文件數組
Next i
代碼不多,最後的文件列表保存在Files數組裏。因爲第一次使用CommonDialog控件打開多個文件,查找出多個文件的分割符號是ASCII碼爲0的字符費了你不少時間。你開始查了文檔,沒有得到信息;將FileName屬性用Msgbox輸出只有路徑,在調試狀態跟蹤時是一個怪字符分割的;你開始想想應該是Tab或者回車之類的,然後使用這些字符用Split函數分割,沒有成功;只好測試了,你將所有字符使用ASC函數輸出,發現原來是ASCII碼爲0的字符。你想,微軟的文檔向來不錯,爲什麼這個在幫助裏沒有呢?
後面的部分就簡單了。
For i = 1 To UBound(Files) Step 1
strFilename = Files(i)
DoImport strFilename
Next i
把原來的宏修改後保存在DoImport這個過程裏,傳入文件名即可導入這個文件,循環導入所有文件就可以了。雖然程序功能複雜了,但代碼似乎要有條理了。
4.2. 指定要導入的位置
聰明絕頂的MM很高興,馬上又舉一反三,提出應該可以指定從第幾行開始導入。你腦子轉了一下,認爲這個需求屬於合理需求[③],不能不予理會。
給窗體加一個RefEdit,點擊開始的區域後返回的將是一個引用位置的字符串,使用Range函數得到該區域的引用對象(Range對象),然後就可以得到其開始行數:
Range(Me.RefEdit1.Value).Row
重構一下DoImport這個過程,增加一個mRow參數,將導入的數據全部寫到第mRow行。上面的調用過程就變成了:
dim mRow as long
mRow = Range(Me.RefEdit1.Value).Row
For i = 1 To UBound(Files) Step 1
strFilename = Files(i)
DoImport strFilename, mRow
mRow = mRow + 1
Next i
你終於鬆了一口氣,臉上又浮現出了賊賊的笑容。MM也答應要請你吃飯,不過你知道兌現的可能性不大,最後還可能是MM請客你掏錢,不過不要緊,程序員的最起碼的風度和尊嚴還是要維護的。
4.3. 修改導入規則
你忽然覺得靈感一現,甚至想做一個可以導入各種格式文件的通用模塊,然後作爲加載宏發佈,於是很多實驗室都開始用你的程序,你開始狂收註冊費,Gates也坐不住了,要把你的程序買了OEM在Excel裏……
這時,呆在一旁的MM敲了一下你的頭,把手舞足蹈的你拉回了現實。
5. 總結
晚上,你打開了日誌,寫道“替Girl friend MM解決問題一個”。
你想,今天的問題很簡單,不過,解決的問題好像很管用,這大概就是所謂的VBA之道吧,以前似乎見過一個VB之道的帖子,你決定回頭也寫一個VBA之道。那麼,第一條應該是:
錄製宏,但要修改它!
(2004-11-24 凌晨)