エラー | Excel作業をVBAで効率化 https://vbabeginner.net いつものExcel作業はVBAを使えば数秒で終わるかもしれませんよ Sun, 10 Nov 2024 14:07:51 +0000 ja hourly 1 https://wordpress.org/?v=6.6.2 https://vbabeginner.net/wp-content/uploads/2019/02/favicon-150x150.png エラー | Excel作業をVBAで効率化 https://vbabeginner.net 32 32 VBAのFor Eachでの実行時エラー424の対応方法 https://vbabeginner.net/for-each-424-error/ Mon, 29 May 2023 14:37:36 +0000 https://vbabeginner.net/?p=6744 For Eachで実行時エラー424が発生する理由

まず最初に実行時エラー424の基本的な対応方法については「エラー424対応方法(オブジェクトが必要です)」に記載しています。ここではFor Eachを使ってエラーが発生した場合について対応方法を書いています。

ループ処理の書き方にはいくつかの方法がありますが、その1つがFor Eachです。

For Eachは配列や複数データを持つオブジェクトなどの「データの集まり」を1件ずつ取得しますが、その1件のデータを格納する変数が必要になります。このループごと変数に格納する際に実行時エラー424が発生することがあります。

これは、格納先のデータ型が格納元の「データの集まり」が持つデータ型と異なることが原因です。

以下のコードは実行時エラー424が発生します。詳細は後述します。

実行時エラー424の原因

エラーが発生するサンプルコードで、なぜ実行時エラー424が発生するのかを説明します。

なお、以下のコードを動かすにはDictionaryオブジェクトを利用しているため、事前にVBA画面→ツールメニュー→参照設定、を選択し、参照設定ダイアログで「Microsoft Scripting Runtime」にチェックを付ける必要があります。

Sub ForEach424Error()
    Dim dic     As New Dictionary   '// ファイルパスマップ
    
    '// テスト用にファイルパスをマップに追加
    Call dic.Add("C:\a.txt", "111")
    Call dic.Add("C:\b.txt", "222")
    Call dic.Add("C:\c.txt", "333")
    Call dic.Add("C:\d.txt", "444")
    
    Dim oFile   As File             '// Fileオブジェクト
    
    '// マップを全件ループ
    For Each oFile In dic.Items     '// ★ここで実行時エラー424が発生
        '// ファイル名を出力
        Debug.Print oFile.Name
    Next
End Sub

このコードは、格納元であるDictionaryオブジェクト(変数dic)に4件のデータを追加し、それをFor Eachループで回して、oFile変数にループ1回ごとに格納したい、という内容です。

DictionaryオブジェクトのItemsプロパティをFor Eachで回すと、Dictionaryオブジェクトのキーが返されます。このコードの場合、「”C:\a.txt”」「”C:\b.txt”」「”C:\c.txt”」「”C:\d.txt”」の4件が4回のループそれぞれで取得できます。

この「”C:\a.txt”」などはファイルパスを表していますが、ファイルパスである前に文字列のデータです。

そのため、VBAの実行時に「文字列をFileデータ型にコピーしようとしてもできないからエラーです。ファイルパスなのかもしれんけど、正しいファイルパスかどうかまだわからんしそれ以前の問題やし」という意味のエラーとして実行時エラー424が発生します。

「Fileオブジェクト = 文字列」という代入がダメ、というわけです。

「Set oFile = “abc”」と書いてOKか?と言われると、”abc”は全然ファイルじゃないしそりゃダメよね、って理屈です。

でも、言いたいことは分かります。「ファイルパスなんだからFileオブジェクトに格納してよ」と。これは別の書き方で解消しなければなりません。それを後述します。

ファイルパス文字列をFileオブジェクトに入れる方法

上のコードの場合の解消方法は、FileSystemObjectのGetFileメソッドです。GetFileメソッドは文字列をFileオブジェクトに変換します。

この方法で実行時エラー424を解消したコードが以下になります。

For EachのループではDictionaryのItemsプロパティではなくKeysプロパティを使って、キー文字列を取得します。このとき、キーが文字列かどうか分からないため、格納する変数のデータ型はVariant型でなければなりません。String型にしたいところですが、Dictionaryのキーが文字列かどうかはこの時点では確証が得られないためエラーになります。

あとは取得したキー文字列であるsPath変数のファイルパスが実際に存在するかをFileSystemObject.FileExistsメソッドで判定します。存在していれば、FileSystemObject.GetFileメソッドでFileオブジェクトに変換しています。

これでデータ型の不整合が解消され、実行時エラー424が出なくなります。

なお、GetFileメソッドについての詳細は「FileSystemObjectのGetFileメソッド」をご参照ください。

Sub ForEach424ErrorFix()
    Dim dic     As New Dictionary   '// ファイルパスマップ
    
    '// テスト用にファイルパスをマップに追加
    Call dic.Add("C:\a.txt", "111")
    Call dic.Add("C:\b.txt", "222")
    Call dic.Add("C:\c.txt", "333")
    Call dic.Add("C:\d.txt", "444")
    
    Dim oFile   As File                 '// Fileオブジェクト
    Dim fs      As New FileSystemObject '// FileSystemObject(Fileオブジェクト取得用)
    Dim sPath   As Variant              '// ファイルパス文字列
    
    '// マップを全件ループ
    For Each sPath In dic.Keys
        '// ファイルが存在する場合
        If fs.FileExists(sPath) = True Then
            '// FileSystemObjectのGetFileメソッドでファイルパスをFileオブジェクトの変換
            Set oFile = fs.GetFile(sPath)
            
            '// ファイル名を出力
            Debug.Print oFile.Name
        End If
    Next
End Sub

実行時エラー424はコピー元と先のデータ型が違う

上のコードはFor Eachの行でFileオブジェクトに文字列を代入しようとして実行時エラー424になる例ですが、別のデータ型の場合も同様です。

違うデータ型同士の代入は、同じデータ型になるような変換をしてあげる必要があります。

Fileオブジェクトに文字列のファイルパスを設定したい場合はFileSystemObject.GetFileメソッド、というように、他のデータ型でも文字列から変換する関数が大抵用意されています

例えば、日付を表すDate型に文字列の日付”yyyy/mm/dd”を格納したい場合はCDate関数を使う、などです。

ちなみ、Date型と日付文字列の変換については以下をご参照ください。
・「VBAで日付(Date型)から文字列に変換する
・「VBAで文字列から日付(Date型)に変換する

実行時エラー424が発生した場合は、そのような変換関数がないか探してみてください

]]>
エラー対応方法(エラー1004:ブックが開けない) https://vbabeginner.net/error-1004-book-cannot-be-opened/ Sun, 05 Sep 2021 02:34:49 +0000 https://vbabeginner.net/?p=6225 エラー内容

実行時エラー1004はいろんな種類のエラーで表示される「その他雑多エラー」のエラー番号です。エラーメッセージを見ないと何のエラーなのか分かりません。

その実行時エラー1004の中で、ブックを開くコードで「Workbook.Open()」メソッドを使った際に「申し訳ございません。が見つかりません。名前が変更されたか、移動や削除がおこなわれた可能性があります。」のようなメッセージが表示されることがあります。

原因はエラーメッセージの通りで、そのブックが存在しないため開くことが出来ない、というエラーです。

でも、ちゃんとコード書いてるのになー、ってことでハマることがあります。その解決方法を紹介します。

解決方法

まず、エラーメッセージのファイル名部分を見ます。そこにファイル名がどのように書いてあるかどうかで解決方法は少し変わります。

  1. ファイル名が何も書いてない場合
    「申し訳ございません。が見つかりません。名前が変更されたか、移動や削除がおこなわれた可能性があります。」
  2. ファイル名だけが書いてある場合
    「申し訳ございません。aaa.xlsxが見つかりません。名前が変更されたか、移動や削除がおこなわれた可能性があります。」
  3. ファイルパスが書いてある場合
    「申し訳ございません。C:\test\aaa.xlsxが見つかりません。名前が変更されたか、移動や削除がおこなわれた可能性があります。」

1. エラーメッセージにファイル名が無い場合

エラーメッセージにファイル名が書いていない、ということは、ほとんどの場合はファイル名がOpenメソッドに渡されていないためです。

以下のようなコードを書いている場合に起こります。

Sub Err1004_BookOpenError()
    Dim wb  As Workbook     '// ブック
    Dim sPath   As String   '// ブックパス
    
    'sPath = "C:\aaaa.xlsx"
    
    Set wb = Workbooks.Open(Filename:=sPath)
End Sub

原因は、ファイルパスを設定する変数であるsPathが5行目でコメントアウトされていて、Workbooks.Openメソッドに渡す時点では空になっているため、エラーになります。

コメントアウトを外せばファイルパスは指定されているため、そのファイルが存在していればブックが開きます。

あとは、Workbooks.Openで使うべき変数を別の変数に間違えていることもよくあります。

2. エラーメッセージにファイル名だけが表示されている場合

エラーメッセージにファイル名だけが書いてある場合は、ほとんどの場合はフォルダパスの書き忘れが原因です。

以下のようなコードを書いている場合に起こります。

Sub Err1004_BookOpenError()
    Dim wb  As Workbook     '// ブック
    Dim sPath   As String   '// ブックパス
    
    sPath = "aaaa.xlsx"                 '// パスを指定しない場合はカレントフォルダのaaaa.xlsxを開こうとする
    '// sPath = "C:\test\aaaa.xlsx"     '// パスを指定した方がよい
    
    Set wb = Workbooks.Open(Filename:=sPath)
End Sub

フォルダパスを書かない場合は、カレントフォルダにある対象の名前のブックが開く対象になります。ただ、ほとんどの場合は「カレントフォルダにあるブックを開く」なんてことはしないので、エクスプローラでブックのパスを確認してパスをちゃんと付けてあげれば解決します。

パスをくっつける場合の注意点として、フォルダパスとファイル名の間に「\」のパス区切りを付けるのを忘れないようにする必要があります。後述する「ActiveWorkbook.Path」は右端に「\」が付かないので、対応が必要になります。

なお、カレントフォルダを知りたい場合は「CurDir関数」で取得できます。

Sub GetCurDir()
    Dim sDir    As String
    
    sDir = CurDir     '// C:\Users\user\Documents など、その時点のカレントフォルダを返す
    Debug.Print sDir
End Sub

また、マクロを動かしているブックのパスであれば「ActiveWorkbook.Path」で取得できます。「ActiveWorkbook.Path」で取得したパスの右端にはパス区切りの「\」が付いていないので、手で付けてあげる必要があります。

Sub GetActiveWorkbookPath()
    Dim sBookName   As String       '// ブック名(ファイル名)
    Dim sDir        As String       '// フォルダパス
    Dim sPath       As String       '// ファイルパス
    Dim wb          As Workbook     '// ワークブックオブジェクト
    
    '// ブック名を指定
    sBookName = "abc.xlsx"
    '// アクティブブックのパスをフォルダパスとして指定
    sDir = ActiveWorkbook.Parent
    '// フォルダパスとファイル名を\でつなげる
    sPath = sDir & "\" & sBookName
    '// \\を\に変換
    sPath = Replace(sPath, "\\", "\")
End Sub

3. エラーメッセージにファイルパスとファイル名が表示されている場合

エラーメッセージにファイルパスが書いてある場合は、2通り考えられます。1つ目はフォルダパスとファイル名がつながっていること(\が付いてない)2つ目はフォルダパスかファイル名のどちらかがやはり間違っていることです。どちらもよくあります。

どちらの場合も「パスは合ってる!」という思い込みでちゃんと確認しないで発見が遅れがちですが、ちゃんと見てみると「あ、やっちゃってた」というのが大体オチです。

まず、エクスプローラーでパスをコピペしてメモ帳でもテキストエディタでもいいので貼ってみてください。そして、VBAのパスも同様にテキストエディタに貼り付けて、比較してみてください。ほとんど場合はこれで間違いが発見できます

ActiveWorkbook.Pathでフォルダパスを取得したり、他からフォルダパスを渡されている場合に、右端に「\」のパス区切りがついていないことがあります。こういう場合はちゃんと付けてあげる必要があります。

なお、「\」が付いているかどうかわからない場合は以下のようにフォルダパスとファイル名を連結させる方法もあります。やってることは強制的にフォルダパスに「\」を付けて、「\\」のように重複していれば「\」に変換する方法です。上のサンプルコードのReplace関数での方法がそれになります。

sPath = Replace(sPath, “\\”, “\”)

]]>
エラー対応方法(既にある名前が含まれています) https://vbabeginner.net/error-includes-existing-name/ Tue, 30 Jul 2019 16:51:28 +0000 https://vbabeginner.net/?p=4384 参照先が見つからない名前の定義

シートをコピーすると、「既にある名前 ‘???’ が含まれています。この名前を使用しますか?」というメッセージが出ることがあります。

このメッセージは、Excel2002含めそれ以前の古いバージョンのExcelで作成されたブックに名前の定義が設定されている場合にシートのコピーを行うと発生します。そのため、拡張子が.xlsのブックを使っている場合がほとんどだと思われます。

名前の定義が重複することが問題の原因のため、解決方法は名前の定義を削除することです。数式タブ→名前の管理、から「名前の定義」ダイアログを開き、不要な定義を削除すれば基本的にはエラーは出なくなります。

ただ、名前の定義をVBAで非表示にされている場合は「名前の定義」ダイアログには表示されないため問題が解消しません。名前付きセルを使いたがる人に限って非表示にしたりするので迷惑極まりないです。

そこで、自分で名前の定義を確認して手で削除したい場合に非表示になっている名前の定義を表示するマクロと、そういうことはいいので一括で名前の定義を削除したい場合のマクロを紹介します。

非表示になっている名前の定義を表示する

名前の定義はNameオブジェクトで管理されています。NameオブジェクトのVisibleプロパティがFalseに設定されていると非表示になります。

以下のマクロはアクティブブックに含まれる全ての名前の定義のVisibleをTrueにします。

Sub VisibleName()
    Dim n   As Name
    
    For Each n In ActiveWorkbook.Names
        n.Visible = True
    Next
End Sub

あとは「名前の定義」ダイアログで不要な定義を削除してください。

一括で名前の定義を削除する

以下のマクロはアクティブブックの名前の定義を全て削除します。ただし、印刷範囲を指定する「Print_Area」とタイトル行と列の「Print_Titles」は削除せずに残します。

NameオブジェクトのDeleteメソッドで名前の定義を削除しますが、名前に不正文字が入っているとDeleteメソッドは1004エラーになります。

これはVBAでは回避できず、NameプロパティをVBAのコードで書き換えても正常に処理は通過しますが書き換えは行われません。名前の書き換えは「名前の定義」ダイアログでしか受け付けてもらえない仕様です。

Win32APIを使って「名前の定義」ダイアログを表示させてキーイベントで他の名前に書き換えることも可能ですが、このエラーに遭遇することはそこまでないと思われ、そこまでするとコードも長くなり面倒なのでここでは行いません。

不正文字の名前ののエラーが発生した場合はメッセージを表示し、手で「名前の定義」ダイアログで削除するように促すようにしています。

Sub DeleteNameAll()
    On Error Resume Next
    
    Dim n       As Name     '// Nameオブジェクト
    Dim er()                '// エラー発生分のNameオブジェクト
    Dim sMsg                '// 出力メッセージ
    Dim sName               '// Nameオブジェクトの名前
    
    ReDim er(0)
    
    '// ブックの名前の定義を全てループ
    For Each n In ActiveWorkbook.Names
        sName = n.Name
        
        '// 印刷範囲と印刷タイトルの定義の場合
        If (InStr(1, sName, "Print_Area") > 0) Or (InStr(1, sName, "Print_Titles") > 0) Then
            '// 次のループへ
            GoTo CONTINUE
        End If
        
        '// 名前の定義を削除
        n.Delete
        
        '// エラー発生分
        If Err.Number <> 0 Then
            '// エラー発生分のNameオブジェクトをコピー
            ReDim Preserve er(UBound(er) + 1)
            er(UBound(er) - 1) = sName
        End If
        
CONTINUE:
    Next
    
    If er(0) <> "" Then
        ReDim Preserve er(UBound(er) - 1)
        
        For Each sName In er
            sMsg = sMsg & sName & " "
        Next
    End If
    
    If sMsg <> "" Then
        sMsg = "エラー分は名前の定義ダイアログから削除してください。" & vbCr & sMsg
        Call MsgBox(sMsg, vbOKOnly, "エラー")
    End If
    
End Sub

名前の定義は使わない方がいい?

私はこのエラーの原因になっている「名前付きセル」は一切利用しません。理由は、セルに名前を付けることの恩恵を感じたことは今まで一度もなく、むしろ迷惑を被った記憶しかないためです。

仕事ではどこかのだれかが作ったブックを使うことがありますが、名前の定義の参照先がその当人のPCや聞いたこともないファイルサーバのパスになっていたりします。当然機能しません。そして「名前の定義」ダイアログを表示しない限りこのことに気が付きません。

さらに頭に来るのが本件のエラーメッセージです。「はい」を押せば何度も何度も同じダイアログが表示され、「いいえ」を押せばどうでもいいのに名前の変更を要求されます。×ボタンを押せば全てから解放されるかと思いきや、「いいえ」と同じ動作をします。「すべてはい」ボタンや一発キャンセルの方法がなぜ無いのか本当に疑問です。

本などの説明では「名前を付けることで管理しやすくなる」とか「数式が管理しやすくなる」とか書いてありますが、使うならせめて自分しか使わないブックに限定すべきです。複数人が参照するようなブックだと現場は混乱しかねません。名前付きセルの機能自体がマイナーで多くの人が利用しないのですから。

]]>
VBAのエラー処理の使い分け(On Error・Resume) https://vbabeginner.net/proper-use-error-handling/ Wed, 21 Nov 2018 16:49:46 +0000 https://vbabeginner.net/?p=3709 エラー処理のステートメント

エラー処理のステートメントには6種類あります。

ステートメント 内容
On Error Goto 行ラベル エラーが発生した際の処理を用意しておき、実際にエラーが発生するとその処理に遷移する。
On Error Goto 0 エラーが発生すると、エラー発生源の処理を無効にして処理を中断する。
On Error Resume Next エラーが発生してもエラーを無視して処理を続ける。
Resume エラーが発生するとエラー時用の処理を実行して、エラーが発生した行に戻る。
Resume Next エラーが発生するとエラー時用の処理を実行して、エラーが発生した行の次の行に戻る。
Resume 行ラベル エラー発生時に指定した行ラベルに処理を移す。

 

エラー処理のステートメントを使う理由

VBAでのエラーには主に3つあります。

コンパイルエラー、実行時エラー、論理エラー、の3つです。

それらのエラーの中で、エラー処理のステートメントは2つ目の実行時エラーが発生したときにエラーをどう扱うかについて記述します。

On Error Gotoステートメント

On Error Gotoステートメントはエラーが発生すると行ラベルに処理が移動します。

行ラベルとは、エラー発生時に処理を移す位置の名前で、コロン(:)を付けることで行ラベルと認識されます。

「On Error Goto 」の後ろに同じ行ラベル名を書いておくと、エラー発生時に処理が移ります。

On Error Gotoステートメントは、エラーが発生する可能性がある行よりも前に書いておく必要があります。

以下の2つはどちらもSetステートメントの行でエラーが発生する処理ですが、On Error Gotoステートメントの位置が異なります。

1つ目のコードはエラー発生行より前にOn Error Gotoステートメントを書いているためエラーが発生すると行ラベルに処理が移りますが、2つ目のコードはエラー発生行より後に書いているため、行ラベルに処理が移らず、実行時エラーとしてエラーダイアログが表示されます。

Sub OnErrorGotoTest1()
    On Error GoTo ERR_LABEL
    
    Dim r   As Range
    Set r = Range("A1").Offset(-1, 0)   '// エラー発生時にERR_LABELに移る
    Exit Sub
ERR_LABEL:
    Debug.Print Err.Number & " "; Err.Description
End Sub

実行結果
1004 アプリケーション定義またはオブジェクト定義のエラーです。

Sub OnErrorGotoTest2()
    Dim r   As Range
    Set r = Range("A1").Offset(-1, 0)   '// エラー発生時にERR_LABELに移らない
    
    On Error GoTo ERR_LABEL
    Exit Sub
ERR_LABEL:
    Debug.Print Err.Number & " "; Err.Description
End Sub

エラー発生時に以下のダイアログが表示されます。

On Error Goto 0ステートメント

On Error Goto 0ステートメントには大きく分けると2つの機能があります。

1つはErrオブジェクトの情報をクリアです。

もう1つはエラー発生時に「On Error Goto 行ラベル」で行ラベルに処理が移らなくなります。

本などでは「エラー発生時の処理を無効にする」などとよく分からない説明がされていることがありますが、Errオブジェクトがクリアされて行ラベルにジャンプしなくなる、と考えた方が分かりやすいと思います。

以下のコードはエラー発生で行ラベルに処理が移動したあとにErrオブジェクトを使ってエラー情報を出力しています。

そのあとにOn Error Goto 0を実行すると、Errオブジェクトの内容がなくなり、エラー無しの0が出力されます。

Resume Nextでエラー発生行の次の行の7行目に移動します。

7行目は5行目と同じ処理のためまたエラーが発生しますが、「On Error Goto ERR_LABEL」のエラー処理が「On Error Goto 0」で無視されるようになったため、行ラベルに処理が移らず、エラーダイアログが表示されます。

処理の順番はコメントの番号の通りです。

Sub OnErrorGoto0Test()
    On Error GoTo ERR_LABEL
    
    Dim r   As Range
    Set r = Range("A1").Offset(-1, 0)   '// 1. エラー発生行(ERR_LABELへ遷移)
    
    Set r = Range("A1").Offset(-1, 0)   '// 6. エラー発生行(エラーダイアログ表示)
    
    Exit Sub
    
ERR_LABEL:
    '// 2. エラー発生時のエラー情報を出力
    Debug.Print Err.Number & " "; Err.Description
    
    '// 3. エラーを無効化(エラー情報が消去)
    On Error GoTo 0
    
    '// 4. エラー発生時のエラー情報は出力されず、0が出力される
    Debug.Print Err.Number & " "; Err.Description
    
    '// 5. エラー発生行の次の行に移動
    Resume Next
End Sub

実行結果
1004 アプリケーション定義またはオブジェクト定義のエラーです。
0

On Error Resume Nextステートメント

On Error Resume Nextステートメントは実行時エラーが発生してもエラーを無視して処理が継続します。

エラーを無視していてもErrオブジェクトにはエラー情報は登録されているため確認することは可能です。

以下のコードはエラーが発生しても処理が継続するサンプルです。

処理は継続しますがErrオブジェクトにはエラー情報が登録されています。

Sub OnErrorGotoResumeNextTest()
    On Error Resume Next
    
    Dim r   As Range
    Set r = Range("A1").Offset(-1, 0)   '// エラー発生が無視され処理が継続する
    
    Debug.Print Err.Number & " "; Err.Description
End Sub

実行結果
1004 アプリケーション定義またはオブジェクト定義のエラーです。

Resume、Resume Next、Resume 行ラベル

Resumeステートメントは「On Error Goto 行ラベル」でエラー発生時に行ラベルに処理が移ったあとの処理として利用します。

エラー処理が終わったあとにエラー発生行か、エラー発生行の次の行か、指定した行ラベルかに処理を移します。

それぞれの内容は最初に書いたとおりです。

ステートメント 内容
Resume エラーが発生するとエラー時用の処理を実行して、エラーが発生した行に戻る。
Resume Next エラーが発生するとエラー時用の処理を実行して、エラーが発生した行の次の行に戻る。
Resume 行ラベル エラー発生時に指定した行ラベルに処理を移す。

Sub ResumeTest1()
    On Error GoTo ERR_LABEL
    
    Dim r   As Range
    Set r = Range("A1").Offset(-1, 0)   '// エラー発生時にERR_LABELに移る
    
    Exit Sub
    
ERR_LABEL:
    Debug.Print Err.Number & " "; Err.Description
    
    Resume Next
End Sub

]]>
VBAで疑似的に例外エラーを発生させthrowする https://vbabeginner.net/pseudo-exception-error/ Mon, 19 Nov 2018 15:58:01 +0000 https://vbabeginner.net/?p=3707 Err.Raiseステートメント

C++やJava言語のようにtry-catchによる例外処理が必須な言語と比べるとVBAにはそこまで厳密な例外処理はまず要求されることはありません。

その理由で一番大きいのは、VBAはC++やJavaのように「絶対に異常終了してはいけない」とか「24時間常時稼働」などのような厳しい条件を付きつけられることがないことが挙げられます。

ちなみに、例外処理とは、ある関数内でなんらかの実行時エラーが発生した場合に、関数の呼び元でエラーを補足してもらい異常終了しないようにするための処理を言います。

VBAではC++やJavaのような厳しい条件を要求されることはほとんどないと思いますが、それでもある程度の性能を要求されることはあるでしょう。

そのような場合に、On Error Goto で「エラー発生しました。はい、異常終了で終わります」とするのではなく、エラーを事前に検知して安全に処理を継続させるために、疑似的に例外処理を行うことが可能です。

なお一般的には、例外が発生すると以降の処理は継続させずに終了させることが多いです。

VBAでは、関数内で実行時エラーとみなされる処理の個所でErr.Eraseステートメントを使うことで疑似的に例外を発生させ、呼び出し元の関数のOn Error GoToステートメントでエラーを補足することが出来ます。

構文

Err.Raise(Number As Long, [Source], [Description], [HelpFile], [HelpContext])

Err Errオブジェクトを指定します。
Number 0から65535の範囲でエラー番号を指定します。
Source省略可 エラーの発生原因のアプリケーションやオブジェクトなどを文字列で指定します。
Description省略可 エラーメッセージの文字列を指定します。省略した場合は引数Numberに紐付くエラーメッセージが利用されます。
HelpFile省略可 ヘルプファイルの絶対パスを指定します。
HelpContext省略可 ヘルプファイルのトピックのコンテキスト番号を指定します。

引数は多いですが、実際に使うときは引数Numberだけを指定することが多いと思います。

サンプルコード

以下のコードは2つの関数から成り立っています。

1つ目のGetAddress関数は引数がDate型でなければ実行時エラーを発生させますが、Date型の場合は年月日表記で日付を表示します。

2つ目のOutputDate関数はGetAddress関数を呼び出す関数で、Date型とString型の2つの引数を使ってGetAddress関数をコールします。

Sub GetAddress(dt)
    If VarType(dt) <> vbDate Then
        Call Err.Raise(50000, "GetAddress", "Date型の変数ではありません")
    End If
    
    Debug.Print Format(dt, "yyyy年mm月dd日")
End Sub

Sub OutputDate()
    On Error GoTo error_label
    
    Dim dt  As Date
    Dim s   As String
    
    dt = "2018/11/19"
    s = "abcde"
    
    Call GetAddress(dt) '// 2018年11月19日
    
    Call GetAddress(s)  '// 50000 Date型の変数ではありません
    Exit Sub
error_label:
    Debug.Print Err.Number & " " & Err.Description
End Sub

実行結果

2018年11月19日
50000 Date型の変数ではありません

Err.Raiseを使う用途やメリット

通常はErr.Raiseを使わずに、If文でデータに問題がないかを確認して、問題があれば以降の処理をしない、というロジックを書くことが多いと思います。

では、Err.Raiseを使った場合に利点があるのか、また、用途はあるのか、というと、一応あります。

1つは、関数の引数にエラー用の引数を用意しなくていい、という点があります。

もう1つは、呼び出し元でエラーハンドリング(上記サンプルのerror_labelラベル)の前と後で、正常系の処理と異常系の処理とで完全に分離させることが出来ます。

シート操作などの処理であれば異常系のことを考える必要はあまりないですが、VBAで通信プログラムなどをコーディングした場合にFTPやTCPでの通信エラーや、ファイル操作での書き込み失敗などの外部要因によるエラーが発生するような場合は、多くの場合異常系の判定を入れることになりますが、そういう場合にはErr.Raiseで疑似的な例外として扱って、以降の処理が継続できるようなロジックにした方がいい場合があります。

Excel内に閉じたVBA処理であればあまり必要性は感じませんが、Excelの外と処理するような場合にはErr.Raiseステートメントの利用を検討してもいいかもしれません。

]]>
エラー28対応方法(スタック領域が不足しています) https://vbabeginner.net/error-28-insufficient-stack-area/ Mon, 15 Jan 2018 18:09:45 +0000 http://vbabeginner.net/?p=2428 エラー28内容

エラー28(スタック領域が不足しています)は関数の呼び出し順序を覚えられない状態に陥ったことを表しています。

関数の呼び出し順序を記憶しているのがスタック領域です。

ほとんどの場合はプログラムの修正が必要です。

エラー原因

Microsoftのヘルプでは5つ程度のエラー原因を挙げていますが、このエラーが発生する一番の原因は「再帰呼び出しが多すぎる」という場合です。

再帰以外の原因でのエラーを発生させてみようと思い、わざとエラーになるようなコードを書いてみましたが再現しませんでした。

以下はMicrosoftのヘルプです。
https://msdn.microsoft.com/ja-jp/vba/language-reference-vba/articles/out-of-stack-space-error-28?f=255&MSPPError=-2147217396

再帰呼び出しとは、関数が自分自身を呼び出している状態を言います。単純な再帰呼び出しはこのような状態になります。このコードはエラー28が発生します。

Sub Recursion(i)
    i = i + 1
    Call Recursion(i)
End Sub

何階層も関数が自分自身を呼び出したとしても、最終的には一番最初に呼び出した処理に戻らなければなりません。

そのためどの関数がどういう順番で呼び出されたのかをスタック領域で管理するのですが、管理しきれなくなったのがエラーの原因です。

スタック領域は言語によってサイズが異なりますが、VBAの場合は上記のような単純な関数であれば関数の呼び出し階層が5000階層程度になるとエラーになります。

呼び出し元関数 1階層
  Recursion 2階層
    Recursion 3階層
      Recursion 4階層
        Recursion 5階層
          Recursion 6階層
            ・・・・
                 Recursion 4999階層
                   Recursion 5000階層 ←このあたりでエラー発生

エラー対応方法

エラー28を発生させないようにするには、再帰呼び出しをやめるのが一番です。

再帰をしない構成にプログラムを変える必要がありますが、基本的にはループ処理への書き換えになります。

元の再帰呼び出しの処理をループ内で処理するように変更します。ループ内で呼び出すのであれば階層呼び出しにならない構成にもできますので、本処理自体は関数化しておき、それをループで呼び出すようにするなどの対応を行います。

]]>
エラー94対応方法(Nullの使い方が不正です) https://vbabeginner.net/error-94-illegal-usage-of-null/ Sun, 26 Nov 2017 17:21:39 +0000 http://vbabeginner.net/?p=2059 エラー内容

エラー94(Nullの使い方が不正です)は、Null値がプログラム中に発生しているのに、結果的に無視していることを指します。

構文エラーのためコードの修正が必要です。

エラー原因

エラー94は、関数の戻り値などでNull値が発生しているのに、それを変数に格納できない場合に発生します。

Nullが格納できる変数はVariant型に限られますので、String型やInteger型などの変数にNullを代入しようとするとエラー94になります。

そのため、対応方法は以下のいずれかになります。

  1. Nullを格納する変数をVariant型にする。または型を省略して暗黙のVariant型にする。
  2. Nullの場合は変数に代入しないようにする。
  3. Nullを発生させる原因を特定し、Nullが発生しないようにする。

どの対応がよいのかは、Null値を必要とするかどうかやNullの発生原因に手を入れることができるかによって変わります。根本原因であるNullの発生を止めることが出来ればエラー処理は不要になりますが、なかなかそうもいかないことが多いと思います。

以下はエラー94が発生するコードです。

Sub Err94Test()
    Dim s As String
    
    s = Null
End Sub

 

エラーの対応方法

上に挙げた3つの対応方法について、それぞれコードで紹介します。

1. Nullを格納する変数をVariant型にする方法

Sub Err94Test1()
    Dim s As Variant
    'Dim s
    
    s = Null
End Sub

このように、Nullを代入する変数の型をVariant型にするとエラーは発生しなくなります。

なお、2行目のように「As Variant」と書いてもいいですし、3行目のように型を書かずに暗黙のVarinat型扱いにしてもいいです。

2. Nullの場合は変数に代入しないようにする方法

Sub Err94Test2()
    Dim s As String
    Dim n As Variant
    
    n = Null
    
    If (IsNull(n) = False) Then
        s = n
    End If
End Sub

値を変数に代入する前に、IsNull関数でその値がNullかどうかをチェックします。

Nullでない場合のみ代入するようにするとエラーは発生しなくなります。

3. Nullが発生しないようにする

Nullが発生する条件は大きく2つです。

1つは実行中にExcelブックとは別のテキストデータやデータベースなどの外部データを利用していて値が取得できなかった際に発生します。この場合にNullが発生しないようにするのは外部データの設計を変更しなければならないことが考えられるため難しいかもしれません。

もう1つは範囲選択したセルの書式設定をまとめて取得した場合など、値が1つに限定されない場合です。この場合はセルの書式設定を見直すことでNullが発生することを回避できる場合があります。

前者の外部データについてはパターンが多いため省略します。後者のセルの書式設定の見直しについて説明します。

セルの書式設定の表示形式で、A1セルには数値の”0_ “、A2セルにはパーセンテージの”0%”を設定します。ここがエラーの根本原因になります。

そして、2つのセルの表示形式をまとめて取得します。

Sub Err94DiffSetting()
    Dim n
    
    '// A1とA2セルで異なる表示形式に設定する
    Range("A1").NumberFormat = "0_ "
    Range("A2").NumberFormat = "0%"
    
    '// 異なる表示形式のセル範囲の表示形式を取得するとNullが返却される
    n = Range("A1:A2").NumberFormat
    
    If (IsNull(n) = True) Then
        MsgBox "Nullです"
    Else
        MsgBox "Nullではありません"
    End If
End Sub

そうすると、2つのセルの表示形式が一致しないためNumberFormatプロパティはNullを返します。

ここでNullを発生させないようにするには、根本原因である5行目と6行目で表示形式を同じ設定に統一することで対応することができます。

]]>
エラー62対応方法(ファイルにこれ以上データがありません) https://vbabeginner.net/error-62-remedy-there-is-no-more-data-in-the-file/ Sat, 09 Sep 2017 18:21:03 +0000 http://vbabeginner.net/?p=1050 エラー内容

エラー62はテキストファイルなどの外部のファイルを読み込んでファイルの終端に到達したあとに、さらにファイルの内容を読み込もうとして発生するエラーです。

エラー原因

通常、ファイルの読み込み処理はファイルの先頭から実施します。

その読み込み処理で全てのデータの読み込みが行われるとEOFに達します。

ファイルの終端のことをEOFと言います。End Of Fileの略です。

EOFに達したかどうかはEOF関数で判定することができます。なお、バイナリモード(Binary)で開いた場合はEOF関数は利用できません。LOF関数とLoc関数で判定します。

EOFに達した場合、それ以降にはファイルデータはありません。

しかし、処理でさらに先のデータを取るようなコードを書いている場合にエラー62は発生します。

サンプルコード

通常ファイル

エラー62が発生する通常ファイルの場合のサンプルです。ファイルの文字コードはShift_JISです。14行目でエラーになります。

Sub Err62Test()
    Dim sPath   '// ファイルパス
    Dim sLine   '// 読み込み行
    
    sPath = "C:\web\testfile.txt"
    
    Open sPath For Input Access Read As #1
    
    Do While Not EOF(1)
        Line Input #1, sLine
        Debug.Print sLine
    Loop
    
    Line Input #1, sLine
    
    Close #1
End Sub

 

バイナリファイル

エラー62が発生するバイナリファイルのサンプルです。ここではファイルの文字コードをUTF8にしています。15行目でエラーになります。

バイナリファイルの場合はLOF関数(Length Of File)でファイルサイズを取得し、ループ内のLoc関数(Location)で現在のファイルの位置を取得しています。

Sub Err62BinaryTest()
    Dim sPath   '// ファイルパス
    Dim sChar   '// 読み込み文字
    Dim position
    
    sPath = "C:\web\textfile_utf8.txt"
    
    Open sPath For Binary Access Read As #1
    
    Do While position < LOF(1)
        Input #1, sChar
        position = Loc(1)
    Loop
    
    Input #1, sChar
    
    Close #1
End Sub

 

エラー対応方法

エラーの対応方法は、上記のサンプルのようにファイル読み込みはループ処理で行うようにします。

EOF関数やLOF関数で終端の判定を行い、それ以降では読み込みを行わないようなコードすることで対応できます。

上記のサンプルコードであれば、黄色の行の部分を削除することでエラーが発生しなくなります。

]]>
エラー6対応方法(オーバーフローしました) https://vbabeginner.net/error-6-response-method-overflow/ Thu, 07 Sep 2017 16:10:47 +0000 http://vbabeginner.net/?p=1039 エラー内容

エラー6(オーバーフローしました)は、数値型が許容できる桁の上限または下限を超えてしまうことを指します。

エラー原因

エラー6は以下の変数の型の設定可能範囲を超える値が設定された場合に発生します。

データ型の設定可能範囲とエラー発生サンプルコード

以下はエラー6が発生するデータ型の一覧です。ここに書かれていないデータ型(Boolean、Object、Variant)はエラー6が発生しません。

データ型 設定可能範囲 エラー発生サンプルコード
Byte(バイト型) 0~255
Sub Err6ByteTest()
    Dim b As Byte
    b = 333
End Sub
Integer(整数型) -32,768~32,767
Sub Err6IntegerTest()
    Dim i As Integer
    i = 45000
End Sub
Long(長整数型) -2,147,483,648~2,147,483,647
Sub Err6LongTest()
    Dim l As Long
    l = 1E+51
End Sub
Single(単精度浮動小数点数型) 負数:-3.402823E38~-1.401298E-45
正数:1.401298E-45~3.402823E38
Sub Err6SingleTest()
    Dim si As Single
    si = 1E+64
End Sub
Double(倍精度浮動小数点数型) 負数:-1.79769313486231E308~-4.94065645841247E-324
正数:4.94065645841247E-324~1.79769313486232E308
Sub Err6DoubleTest()
    Dim si As Single
    si = 1E+117
End Sub
Currency(通貨型) -922,337,203,685,477.5808~922,337,203,685,477.5807
Sub Err6CurrencyTest()
    Dim cu As Currency
    cu = 9.88325328532958E+17
End Sub
Date(日付型) 西暦100年1月1日~西暦9999年12月31日
Sub Err6DateTest()
    Dim da As Date
    da = ""2000/1/1""
    da = da - 900000
End Sub

なお、Booleanがエラー6を発生しない理由は、値の解釈の仕方が他のデータ型と違うためです。

BooleanはFalseとTrueを持ちますが、0以下の場合はFalse、1以上の場合はTrueと解釈されるため代入時にエラーになることがありません。Val(10000)*10000などの演算している場合も同様でエラーになりません。

演算結果でエラーになる場合

上記のデータ型の範囲にあるのにエラー6が発生することがあります。

それは、演算が行われている場合です。

Sub Err6IntegerCalCTest()
    Dim i As Long
    i = 400 * 400
End Sub

400×400の160000はLong型の範囲に入っていますがエラー6が発生します。

その理由は、VBAでは右辺の演算は各数値の型同士で演算され、その型が保持されるためです。

右辺の400 * 400は、Integer×Integerと解釈されます。そのため、その演算結果もIntegerと判定され、演算結果がIntegerの範囲外になるためエラーになります。

以下のようにCLng関数でLong型に変換するとエラーが回避できます。なお、3行目のように演算結果をCLng関数で変換しても変換前の時点でInteger型×Integer型と解釈されるためエラーになります。

Sub Err6IntegerCalCTest2()
    Dim i As Long
'   i = CLng(400 * 400)
    i = CLng(400) * 400
End Sub

 

エラー対応方法

エラー6の対応方法は、データ型の範囲外の値を設定しないようにするか、データ型をより範囲の広い型に変更します。

簡単なのは変数の型をVariant型もしくは型を省略(省略するとVariantと解釈される)することです。

]]>
エラー5対応方法(プロシージャの呼び出し、または引数が不正です) https://vbabeginner.net/error-5-response-method-procedure-call-or-argument-is-invalid/ Tue, 05 Sep 2017 16:10:29 +0000 http://vbabeginner.net/?p=1034 エラー内容

エラー5(プロシージャの呼び出し、または引数が不正です)は、関数を呼び出す際に、渡している引数がおかしいことを示すエラーです。

エラー原因

エラー5が発生する原因は、Mid関数などのVBA関数を使用した場合に引数に設定できる値に制限があるもので、その制限値を超えた値が設定されるためです。

自作関数では例外を発生させる実装は出来ませんが、VBA関数には引数に許容しない値が設定されると例外が発生するものがあります。

例えば、Mid関数は引数が3つありますが、文字列の検索開始位置である第二引数が1以上、または、開始位置からの検索文字数である第三引数が0以上でない場合は例外が発生し、エラー5を表示します。

以下は第二引数と第三引数が不正のため例外が発生し、エラー5が発生します。

Sub Err5Test()
    Dim s
    s = Mid("abc", 0, -1)
End Sub

同様に、Dir関数でもエラー5が発生します。

Dir関数の第一引数には検索するファイルやフォルダのパスを指定します。検索で見つからなかった場合は””を返します。検索で見つかった場合はそのファイル名やフォルダ名を返します。

2回目以降の検索で第一引数を省略すると、””が返却されるまで、前回設定した検索パスで検索が実行されます。””が返却された場合は再度検索パスを指定しなければなりません。

以下は””が返却されたあとに検索パスを指定していないためエラー5が発生します。

Sub Err5Test2()
    Dim sPath
    
    '// 存在しないファイルを検索すると、""が返却される
    sPath = Dir("C:\abcdefg\aaa.txt")
    
    '// ""返却後は再度Dir関数の引数が必要だが未指定だとエラー5が発生する
    sPath = Dir
End Sub

 

エラー対応方法

エラー5が起きるのはVBA関数の引数の設定が誤っていることがほとんどです。

使用するVBA関数の引数が許容している値を設定するように修正することで対応します。

以下はエラー5が発生する代表的なVBA関数を挙げています。引数の制限を超えていないかを確認して対応してください。

  • Mid関数:第二引数は1以上、第三引数は0以上が設定条件です。それ以外を指定するとエラー5が発生します。
  • Left関数:第二引数は0以上が設定条件です。それ以外を指定するとエラー5が発生します。
  • Right関数:第二引数は0以上が設定条件です。それ以外を指定するとエラー5が発生します。
  • Dir関数:2回目以降の検索で前回検索で””が返却されている場合に引数省略するとエラー5が発生します。
  • WeekDayName関数:引数に1から7の範囲が設定条件です。それ以外を指定するとエラー5が発生します。
  • DateDiff関数:第一引数は”yyyy”, “m”, “d”, “ww”, “w”, “q”, “y”, “h”, “n”, “s”のいずれかが設定条件です。それ以外を指定するとエラー5が発生します。
]]>