在VBA(Visual Basic for Applications)中,當使用 For Each 迴圈遍歷集合時,控制變數的類型必須是 Variant 或 Object。若控制變數的類型不符合要求,將會出現「For Each 的控制變數必須是 Variant 或 Object」的錯誤訊息。
For Each 迴圈的設計目的是遍歷集合中的每一個元素,而這些元素可能是不同類型的物件。為了確保迴圈能夠靈活處理各種物件類型,VBA要求控制變數必須是 Variant 或 Object 類型。這樣可以避免在迭代過程中因為類型不匹配而導致的錯誤。
在您的程式碼中,控制變數 ws 被宣告為 Worksheet 類型:
Dim ws As Worksheet
而迴圈遍歷的是 wb.Sheets 集合。雖然在大多數情況下,Sheets 集合主要包含 Worksheet 物件,但實際上它也可能包含其他類型的工作表,如 Chart 物件等。因此,將 ws 宣告為 Worksheet 可能在某些情況下不夠靈活,從而引發錯誤。
為了解決這個錯誤,您需要將控制變數的類型改為 Variant 或 Object。這樣可以確保迴圈在遍歷不同類型的物件時不會出現問題。
這是最直接的解決方法,適用於大多數情況:
Dim ws As Variant
修改後的迴圈部分如下:
For Each ws In wb.Sheets
' 執行相關操作
Next ws
如果您希望更明確地表示 ws 是一個物件,可以將其宣告為 Object:
Dim ws As Object
這樣的宣告方式同樣能解決錯誤,並且在某些專案中,這樣的宣告可能更符合編碼習慣。
除了修正控制變數類型之外,還有一些最佳實踐可以幫助提升程式碼的穩健性和可維護性:
在程式碼的開頭加入 Option Explicit,可以強制所有變數必須被明確宣告。這有助於避免因為拼寫錯誤或未宣告變數而導致的潛在錯誤:
Option Explicit
這樣做可以讓VBA在編譯時檢查所有變數的使用,確保變數被正確宣告和使用。
為了提高程式碼的可讀性和可維護性,建議使用描述性變數名稱。例如,將 ws 改為 worksheet 或 currentSheet,這樣可以更直觀地了解變數的用途:
Dim currentSheet As Variant
這樣在迴圈中使用 currentSheet 作為控制變數,可以讓代碼更加清晰明瞭。
在迴圈內部,可以加入錯誤處理機制,以應對在遍歷過程中可能出現的意外情況。例如,使用 On Error Resume Next 來跳過錯誤,或者記錄錯誤訊息以便後續調查。
On Error Resume Next
For Each currentSheet In wb.Sheets
' 執行操作
If Err.Number <> 0 Then
' 處理錯誤
Err.Clear
End If
Next currentSheet
On Error GoTo 0
這樣可以確保程式在遇到問題時不會中斷,並且能夠妥善處理錯誤。
根據上述建議,以下是修改後的完整VBA程式碼:
Option Explicit
Sub 出入金統計()
Dim inputDate As String
Dim targetDate As String
Dim yearPrefix As String
Dim filePath As String
Dim statPath As String
Dim wb As Workbook
Dim ws As Variant ' 修改為 Variant 類型
Dim summary As String
Dim trusteeName As Variant
Dim trusteeStats As Variant
Dim i As Long
Dim huanNanStats As String
Dim outMoney As String
Dim inMoney As String
Dim fileNum As Integer
Dim yuanDaStats As String
' 預設日期為今天
targetDate = Format(Date, "yyyymmdd")
' 輸入日期
inputDate = InputBox("請輸入要統計的日期 (四碼或八碼數字):", "日期輸入", Format(Date, "mmdd"))
If inputDate = "" Then
MsgBox "未輸入日期,將以今天日期作為預設值: " & targetDate, vbInformation
Else
If Len(inputDate) = 4 Then
yearPrefix = Year(Date)
targetDate = yearPrefix & inputDate
ElseIf Len(inputDate) = 8 Then
targetDate = inputDate
Else
MsgBox "輸入日期格式錯誤!請輸入四碼 (MMDD) 或八碼 (YYYYMMDD)。", vbCritical
Exit Sub
End If
End If
' 建立檔案路徑
filePath = "G:\26_Investment Insurance Policy\1_Custody\6_基金下單表格\Fund 執行單 & Instruction\執行單\" & targetDate & "\Asia\" & targetDate & "內部執行單試算表-Asia-send.xlsm"
statPath = "G:\68_Vincent\試作工作表格紀錄\基金回盤上傳\出入金統計\" & targetDate & "_出入金統計.txt"
' 開啟檔案 (唯讀模式)
On Error Resume Next
Set wb = Workbooks.Open(filePath, ReadOnly:=True)
If wb Is Nothing Then
MsgBox "無法開啟檔案,請確認路徑是否正確: " & filePath, vbCritical
Exit Sub
End If
On Error GoTo 0
' 初始化統計資料
trusteeName = Array("復華投信", "富蘭克林華美投信", "凱基投信", "兆豐投信", "合庫投信", "霸菱投顧", "台新投信", "瀚亞投信", "統一投顧", "華南永昌投信", "中國信託投信", "聯博投信", "元大投信")
trusteeStats = Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
' 初始化華南(元大保銀)統計資料
huanNanStats = "【華南(元大保銀)出入金紀錄】" & vbCrLf & "出金:" & vbCrLf
outMoney = ""
inMoney = "入金:" & vbCrLf
' 統計分頁
For Each ws In wb.Sheets
For i = LBound(trusteeName) To UBound(trusteeName)
If InStr(ws.Name, "入金") > 0 Or InStr(ws.Name, "出金") > 0 Then
If ws.Range("A3").Value Like "*受託人:" & trusteeName(i) & "*" Then
If (IsNumeric(ws.Range("D8").Value) And ws.Range("D8").Value <> 0) Or _
(IsNumeric(ws.Range("E8").Value) And ws.Range("E8").Value <> 0) Then
trusteeStats(i) = trusteeStats(i) + 1
End If
' 統計華南(元大保銀)出入金紀錄
If ws.Range("A3").Value Like "*受託人:華南永昌投信*" And ws.Range("C3").Value Like "*保管銀行:元大銀行*" Then
If ws.Name Like "*出金*" Then
If IsNumeric(ws.Range("D8").Value) And ws.Range("D8").Value <> 0 Then
outMoney = outMoney & Format(ws.Range("D8").Value, "#,##0.00") & " 單位" & vbCrLf
End If
If IsNumeric(ws.Range("E8").Value) And ws.Range("E8").Value <> 0 Then
outMoney = outMoney & Format(ws.Range("E8").Value, "#,##0.00") & " 單位" & vbCrLf
End If
ElseIf ws.Name Like "*入金*" Then
If IsNumeric(ws.Range("E8").Value) And ws.Range("E8").Value <> 0 Then
inMoney = inMoney & Format(ws.Range("E8").Value, "#,##0.00") & " 元" & vbCrLf
End If
End If
End If
End If
End If
Next i
Next ws
' 統計元大申贖出入金紀錄
yuanDaStats = "【元大申贖出入金紀錄】" & vbCrLf
' 元大名家-累積(入金)
If Not IsError(Application.Worksheets("元大名家-累積(入金)").Range("D8").Value) Then
If Application.Worksheets("元大名家-累積(入金)").Range("D8").Value = 0 Then
yuanDaStats = yuanDaStats & "元大名家-累積(入金):無申贖" & vbCrLf
Else
yuanDaStats = yuanDaStats & "元大名家-累積(入金): " & Format(Application.Worksheets("元大名家-累積(入金)").Range("D8").Value, "#,##0.00") & " 元" & vbCrLf
End If
End If
' 元大名家-累積(出金)
If Not IsError(Application.Worksheets("元大名家-累積(出金)").Range("D8").Value) Then
If Application.Worksheets("元大名家-累積(出金)").Range("D8").Value = 0 Then
yuanDaStats = yuanDaStats & "元大名家-累積(出金):無申贖" & vbCrLf
Else
yuanDaStats = yuanDaStats & "元大名家-累積(出金): " & Format(Application.Worksheets("元大名家-累積(出金)").Range("D8").Value, "#,##0.00") & " 單位" & vbCrLf
End If
End If
' 元大名家-月撥回(入金)
If Not IsError(Application.Worksheets("元大名家-月撥回(入金)").Range("D8").Value) Then
If Application.Worksheets("元大名家-月撥回(入金)").Range("D8").Value = 0 Then
yuanDaStats = yuanDaStats & "元大名家-月撥回(入金):無申贖" & vbCrLf
Else
yuanDaStats = yuanDaStats & "元大名家-月撥回(入金): " & Format(Application.Worksheets("元大名家-月撥回(入金)").Range("D8").Value, "#,##0.00") & " 元" & vbCrLf
End If
End If
' 元大名家-月撥回(出金)
If Not IsError(Application.Worksheets("元大名家-月撥回(出金)").Range("D8").Value) Then
If Application.Worksheets("元大名家-月撥回(出金)").Range("D8").Value = 0 Then
yuanDaStats = yuanDaStats & "元大名家-月撥回(出金):無申贖" & vbCrLf
Else
yuanDaStats = yuanDaStats & "元大名家-月撥回(出金): " & Format(Application.Worksheets("元大名家-月撥回(出金)").Range("D8").Value, "#,##0.00") & " 單位" & vbCrLf
End If
End If
' 元大ETF精選-累積(入金)
If Not IsError(Application.Worksheets("元大ETF精選-累積(入金)").Range("D8").Value) Then
If Application.Worksheets("元大ETF精選-累積(入金)").Range("D8").Value = 0 Then
yuanDaStats = yuanDaStats & "元大ETF精選-累積(入金):無申贖" & vbCrLf
Else
yuanDaStats = yuanDaStats & "元大ETF精選-累積(入金): " & Format(Application.Worksheets("元大ETF精選-累積(入金)").Range("D8").Value, "#,##0.00") & " 元" & vbCrLf
End If
End If
' 元大ETF精選-累積(出金)
If Not IsError(Application.Worksheets("元大ETF精選-累積(出金)").Range("D8").Value) Then
If Application.Worksheets("元大ETF精選-累積(出金)").Range("D8").Value = 0 Then
yuanDaStats = yuanDaStats & "元大ETF精選-累積(出金):無申贖" & vbCrLf
Else
yuanDaStats = yuanDaStats & "元大ETF精選-累積(出金): " & Format(Application.Worksheets("元大ETF精選-累積(出金)").Range("D8").Value, "#,##0.00") & " 單位" & vbCrLf
End If
End If
' 元大ETF精選-月撥回(入金)
If Not IsError(Application.Worksheets("元大ETF精選-月撥回(入金)").Range("D8").Value) Then
If Application.Worksheets("元大ETF精選-月撥回(入金)").Range("D8").Value = 0 Then
yuanDaStats = yuanDaStats & "元大ETF精選-月撥回(入金):無申贖" & vbCrLf
Else
yuanDaStats = yuanDaStats & "元大ETF精選-月撥回(入金): " & Format(Application.Worksheets("元大ETF精選-月撥回(入金)").Range("D8").Value, "#,##0.00") & " 元" & vbCrLf
End If
End If
' 元大ETF精選-月撥回(出金)
If Not IsError(Application.Worksheets("元大ETF精選-月撥回(出金)").Range("D8").Value) Then
If Application.Worksheets("元大ETF精選-月撥回(出金)").Range("D8").Value = 0 Then
yuanDaStats = yuanDaStats & "元大ETF精選-月撥回(出金):無申贖" & vbCrLf
Else
yuanDaStats = yuanDaStats & "元大ETF精選-月撥回(出金): " & Format(Application.Worksheets("元大ETF精選-月撥回(出金)").Range("D8").Value, "#,##0.00") & " 單位" & vbCrLf
End If
End If
' 關閉檔案
wb.Close SaveChanges:=False
' 輸出結果
summary = "【出入金統計】" & vbCrLf
For i = LBound(trusteeName) To UBound(trusteeName)
summary = summary & trusteeName(i) & ": " & trusteeStats(i) & " 張" & vbCrLf
Next i
' 添加華南(元大保銀)出入金紀錄到總結
summary = summary & vbCrLf & huanNanStats
If outMoney <> "" Then
summary = summary & outMoney
Else
summary = summary & "無出金紀錄" & vbCrLf
End If
summary = summary & vbCrLf & inMoney
If inMoney = "入金:" & vbCrLf Then
summary = summary & "無入金紀錄" & vbCrLf
End If
' 添加元大申贖出入金紀錄到總結
summary = summary & vbCrLf & yuanDaStats ' 添加元大申贖出入金紀錄到摘要
' 儲存到記事本
fileNum = FreeFile
Open statPath For Output As #fileNum
Print #fileNum, summary
Close #fileNum
' 打開記事本顯示統計結果
Shell "notepad.exe " & statPath, vbNormalFocus
MsgBox "出入金統計已完成!結果已保存到: " & statPath, vbInformation
End Sub
在VBA程式碼的開頭使用 Option Explicit 能夠強制要求所有變數在使用前必須被明確宣告。這有助於防止因為拼寫錯誤或未宣告變數而導致的難以察覺的錯誤。同時,這也提升了程式碼的可讀性和可維護性。
在VBA中,適當的錯誤處理機制能夠確保程式在遇到異常狀況時不會意外中斷。使用 On Error Resume Next 和 On Error GoTo 0 可以在特定區域內忽略錯誤或重置錯誤處理狀態。此外,記錄錯誤訊息或提供友好的錯誤提示,有助於使用者了解發生了什麼問題。
選擇具有描述性的變數名稱能夠大大提升程式碼的可讀性。例如,將 ws 改為 currentSheet,能讓程式碼的意圖更加明確,讓其他開發人員更容易理解和維護。
將程式碼拆分成模組化的函數或子程序,能夠提升程式碼的可維護性和重用性。這樣可以避免重複代碼,提高開發效率,並且在需要調整某些功能時,只需要修改單一模組,而不必遍歷整個程式碼。
| 資源名稱 | 連結 |
|---|---|
| Microsoft Learn: For Each control variable must be Variant or Object | 閱讀更多 |
| Stack Overflow: Control variable must be Variant or Object | 閱讀更多 |
| Microsoft Tech Community: VBA Compile Error | 閱讀更多 |
VBA程式中出現「For Each 的控制變數必須是 Variant 或 Object」錯誤,主要是由於控制變數的類型不符合VBA的要求所致。通過正確宣告控制變數為 Variant 或 Object,可以有效解決此問題。此外,啟用 Option Explicit、採用描述性變數名稱以及加入適當的錯誤處理機制,都是提升VBA程式碼質量的重要步驟。希望本指南能夠幫助您順利修復錯誤,並撰寫出更為穩健和高效的VBA程式碼。