ExcelVBA 　エラー処理

::: note warn
自分用のメモなので、形は整ってないです。
:::
### エラー処理における、On Error の有効範囲とその動作について
```
Sub sample1()
  On Error GoTo Label01
  Call sample2
  Call sample3
  Exit Sub
Label01:
  MsgBox "Label01"
End Sub

Sub sample2()
  On Error GoTo Label02
  Dim i As Long
  i = "abc"
  On Error GoTo 0
  Exit Sub
Label02:
  MsgBox "Label02"
End Sub

Sub sample3()
  Dim i As Long
  i = "abc"
  Exit Sub
Label03:
  MsgBox "Label03"
End Sub

'On Errorの有効範囲は、On Error GoTo 0まで、もしくはそのプロシージャを抜ける前
'sample1を実行すると、sample2で発生したエラーはLabel02:でトラップされるが、
'sample3で発生したエラーはsample1まで伝播し、Label01:でトラップされる
'呼び出し元の呼び出し元へも、エラーは伝播する

'※On Error がないプロシージャーでのエラー時の動作
'・呼び出し元が無い場合はエラー停止
'・呼び出し元にOn Error が無い場合はエラー停止
'・呼び出し元にOn Error がある場合はその時点でプロシージャーを抜ける


'以下の例では、sample6で発生したエラーがsample4まで伝播している
Sub sample4()
  On Error GoTo Label04
  Call sample5
  Exit Sub
Label04:
  MsgBox "Label04"
End Sub

Sub sample5()
  Call sample6
End Sub

Sub sample6()
  Dim i As Long
  i = "ttt"
  Exit Sub
End Sub
```
### エラー処理の、On Error Resume Next　On Error GoTo 0と、プロシージャの呼び出し元のエラートラップの関係
```
On Error GoTo 0としても、呼び出し元のエラートラップが無効になるわけではないらしい

Sub aaa()

On Error GoTo EH1
Call bbb

Exit Sub
EH1:
  MsgBox "EH1"
End Sub


Sub bbb()
Dim long1 As Long

On Error Resume Next

long1 = 100 / 0  'このエラーは無視される
If Err.Number <> 0 Then MsgBox "エラー番号" & Err.Number
'エラー番号は取得できる

On Error GoTo 0
' これはOn Error Resume Nextの宣言を無効にするだけらしい。aaa()のエラートラップを無効にするわけではない

long1 = 100 / 0  ' このエラーはaaa()まで伝播する

End Sub
```
### エラー番号一覧　　Err.Number で取得できる
```
3	Return に対応する GoSub がありません。
5	プロシージャの呼び出し、または引数が不正です。
6	オーバーフローしました。
7	メモリが不足しています｡
9	インデックスが有効範囲にありません。
10	この配列は固定されているか、または一時的にロックされています。
11	0 で除算しました。
13	型が一致しません。
14	文字列領域が不足しています。
16	式が複雑すぎます。
17	要求された操作は実行できません。
18	ユーザーによる割り込みが発生しました。
20	エラーが発生していないときに Resume を実行することはできません。
28	スタック領域が不足しています。
35	Sub または Function が定義されていません。
47	DLL のクライアント アプリケーションの数が多すぎます。
48	DLL 読み込み時のエラーです。
49	DLL が正しく呼び出せません。
51	内部エラーです。
52	ファイル名または番号が不正です。
53	ファイルが見つかりません。
54	ファイル モードが不正です。
55	ファイルは既に開かれています。
57	デバイス I/O エラーです。
58	既に同名のファイルが存在しています。
59	レコード長が一致しません。
61	ディスクの空き容量が不足しています。
62	ファイルにこれ以上データがありません。
63	レコード番号が不正です。
67	ファイルが多すぎます。
68	デバイスが準備されていません。
70	書き込みできません。
71	ディスクが準備されていません。
74	ディスク名は変更できません。
75	パス名が無効です。
76	パスが見つかりません。
91	オブジェクト変数または With ブロック変数が設定されていません。
92	For ループが初期化されていません。
93	パターン文字列が不正です。
94	Null の使い方が不正です。
97	オブジェクトが定義クラスのインスタンスではない場合、このオブジェクトに関するフレンド関数は呼び出せません。
98	プロパティまたはメソッドの呼び出しの場合には、引数または戻り値としてプライベート オブジェクトへの参照を含めることはできません。
298	システムリソースまたは DLL をロードできません。
320	キャラクタ デバイスは使えません。
321	不正なファイル形式です。
322	必要な一時ファイルを作成できません。
325	リソース ファイルの形式が不正です。
327	データ値が見つかりません。
328	不正なパラメータです。配列に書き込めません。
335	システム レジストリにアクセスできません。
336	コンポーネントが正しく登録されていません。
337	コンポーネントが見つかりません。
338	コンポーネントが正常に実行されませんでした。
360	このオブジェクトは既にロードされています。
361	このオブジェクトは、ロードまたはアンロードすることはできません。
363	指定されたコントロールが見つかりません。
364	既にアンロードされています。
365	現在アンロードできません。
368	ファイルは古い形式で作成されています。このプログラムには新しい形式のファイルが必要です。
371	指定されたオブジェクトは、Show メソッドのオーナー フォームとして使用できません。
380	プロパティの値が不正です。
381	不正なプロパティ配列インデックスです。
382	実行時には値を設定できません。
383	値を設定できません。値の取得のみ可能なプロパティです。
385	プロパティ配列インデックスが必要です。
387	値を設定できません。
393	実行時には値を取得できません。
394	値を取得できません。値の設定のみ可能なプロパティです。
400	既にフォームは表示されています。モーダルにできません。
402	一番手前 (前面) のモーダル フォームを先に閉じてください。
419	オブジェクトを利用できません。
422	プロパティが見つかりません。
423	プロパティまたはメソッドが見つかりません。
424	オブジェクトが必要です。
425	オブジェクトの使い方が不正です
429	ActiveX コンポーネントはオブジェクトを作成できません。
430	クラスはオートメーションまたは予測したインターフェースをサポートしていません。
432	オートメーションの操作中にファイル名またはクラス名を見つけられませんでした。
438	オブジェクトは、このプロパティまたはメソッドをサポートしていません。
440	オートメーション エラーです。
442	リモート プロセス用のタイプ ライブラリまたはオブジェクト ライブラリへの参照は失われました。参照設定を解除して [OK] を押してください。
443	オートメーション オブジェクトには既定値がありません。
445	オブジェクトはこの動作をサポートしていません。
446	オブジェクトは名前付き引数をサポートしていません。
447	オブジェクトは現在の国別情報の設定をサポートしていません。
448	名前付き引数が見つかりません。
449	引数は省略できません。
450	引数の数が一致していません。または不正なプロパティを指定しています。
451	Property Let プロシージャが定義されておらず、Property Get プロシージャからオブジェクトが返されませんでした。
452	序数が不正です。
453	関数は指定された DLL には定義されていません。
454	コード リソースが見つかりません。
455	コード リソースのロック エラーです。
457	このキーは既にこのコレクションの要素に割り当てられています。
458	Visual Basic でサポートされていないオートメーションが変数で使用されています。
459	オブジェクトまたはクラスがこのイベント セットをサポートしていません。
460	クリップボードのデータ形式が不正です。
461	メソッドまたはデータ メンバーが見つかりません。
462	リモート サーバーがないか、使用できる状態ではありません。
463	ローカル マシンにクラスが登録されていません。
480	AutoRedraw イメージを作成できません。
481	ピクチャが不正です。
482	プリンター エラーです。
483	プリンタ ドライバは指定されたプロパティをサポートしていません。
484	システムからプリンタ情報を受けるときに問題が発生しました。プリンタが正しく設定されているかを確かめてください。
485	ピクチャの形式が不正です。
486	フォームのイメージをこのプリンターで印刷することはできません。
520	クリップボードを空にできません。
521	クリップボードを開けません。
735	一時ファイルに保存できません。
744	検索文字列が見つかりませんでした。
746	置換後の文字列が長すぎます。
1004	アプリケーション定義またはオブジェクト定義のエラーです。
31001	メモリが不足しています。
31004	オブジェクトがありません。
31018	クラスが設定されていません。
31027	オブジェクトをアクティブにできません。
31032	埋め込みオブジェクトが作成できません。
31036	ファイルへの書き込み中にエラーが発生しました。
31037	ファイルの読み込み中にエラーが発生しました。
-2147221080	オートメーション エラーです。
上記以外	アプリケーション定義またはオブジェクト定義のエラーです。
```
### エラーを発生させる　　Err.Raise
```
エラーを補足した後に、再度スローできるので、エラー処理には有効

Sub ttt()

'エラーを発生させる。以下の構文
' Err.Raise number, source, description, helpfile, helpcontext

' number:  エラー番号　0～512まではシステムで使われている。513～65535まではユーザが使える
'実際には、513以降にも使われている番号はあるらしい。513～519か、600番台を使うのがいい

On Error Resume Next

Err.Raise 11        '0で除算のエラーを発生させる
Call ShowErrorInfo  ' エラー情報を取得
Err.Clear           '一旦エラーをクリア


Err.Raise 513, "独自エラー513"
' ユーザ定義のエラー、513番を発生させる
' "独自エラー513" はsourceになる
Call ShowErrorInfo
Err.Clear


Err.Raise 514, "独自エラー514", "ABCDE"
' ユーザ定義のエラー、513番を発生させる
' "ABCDE" はdescriptionになる
Call ShowErrorInfo
Err.Clear


Err.Raise vbObjectError + 513, "ユーザ定義エラー513"
' vbObjectError + 513 で、クラス モジュール内で独自のエラーコードに設定するらしいが、よくわからない
Call ShowErrorInfo

On Error GoTo 0

End Sub


Sub ShowErrorInfo()

Debug.Print "*******************"
Debug.Print Err.Number
Debug.Print "---------------"
Debug.Print Err.Description
Debug.Print "---------------"
Debug.Print Err.Source
Debug.Print "*******************"
Debug.Print ""

End Sub


'ttt()の実行結果は以下
'*******************
'11
'---------------
'0 で除算しました｡
'---------------
'VBAProject
'*******************
'
'*******************
'513
'---------------
'アプリケーション定義またはオブジェクト定義のエラーです｡
'---------------
'独自エラー513
'*******************
'
'*******************
'514
'---------------
'ABCDE
'---------------
'独自エラー514
'*******************
'
'*******************
'-2147220991
'---------------
'オートメーション エラーです｡
'イベントはどのサブスクライバーも呼び出すことができませんでした
'---------------
'ユーザ定義エラー513
'*******************

'Err.Raise 513, "独自エラー513"　の形でスローして、Err.NumberとErr.sourceで情報を取得すればいいのかな
```
### 親・子・孫プロシージャの関係で、どのレベルでエラーが発生したかを、親で判断する例
```
各レベルでエラー番号の範囲を決めて、Err.Raiseで再スローしている　　これがいいのかは自信無いが

Sub Parent()  '親

Dim arrErrors() As String
Dim lng1 As Long
Dim i As Long

On Error GoTo ParentEH

ReDim arrErrors(0)
lng1 = -250  'この数値を変えて、エラーの発生するレベルを調整

If lng1 < -200 Then
  Err.Raise 13  '13番は適当
End If


lng1 = 0
Call Child(arrErrors, lng1)
lng1 = -150
Call Child(arrErrors, lng1)

If arrErrors(0) <> "" Then
  i = 0
  Do While arrErrors(i) <> ""
    Debug.Print arrErrors(i)
    i = i + 1
  Loop
End If

On Error GoTo 0
Exit Sub


ParentEH:
If Err.Number >= 650 And Err.Number <= 699 Then
'GrandChildのレベルで発生したエラーの場合
  MsgBox "GarandChildレベルでエラー発生"
  
ElseIf Err.Number >= 630 And Err.Number <= 649 Then
'Childのレベルで発生したエラーの場合
  MsgBox "Childレベルでエラー発生"
  
Else
  arrErrors(UBound(arrErrors)) = "Parentでエラー発生。エラー番号: " & Err.Number
  ReDim Preserve arrErrors(UBound(arrErrors) + 1)
  MsgBox "Parentでエラー発生"
End If

Resume Next  'エラーが発生した次のステップまで戻る

End Sub



Sub Child(ByRef arrErrors() As String, ByVal lng1 As Long) ' 子

On Error GoTo ChildEH

If lng1 < -100 Then
  Err.Raise 6  '6番は適当
End If

Call GrandChild(arrErrors, lng1)

On Error GoTo 0
Exit Sub


ChildEH:
If Err.Number >= 650 And Err.Number <= 699 Then
  Err.Raise Err.Number
  'GrandChildのレベルで発生したエラーは、そのままの番号でスロー
Else
  arrErrors(UBound(arrErrors)) = "Childでエラー発生。エラー番号: " & Err.Number
  ReDim Preserve arrErrors(UBound(arrErrors) + 1)
  Err.Raise 630
  'Childのレベルでは、630～649のエラーをスローすることにする
End If


End Sub



Sub GrandChild(ByRef arrErrors() As String, ByVal lng1 As Long) ' 孫

On Error GoTo GrandChildEH
  
lng1 = 100 / lng1

On Error GoTo 0
Exit Sub

GrandChildEH:
arrErrors(UBound(arrErrors)) = "GrandChildでエラー発生。エラー番号: " & Err.Number
ReDim Preserve arrErrors(UBound(arrErrors) + 1)
Err.Raise 650
'GrandChildのレベルでは、650～699のエラーをスローすることにする

End Sub
```
### 	On Error GoTo～、On Error Resume Nextは、その後にOn Error GoTo～、On Error Resume Nextを記述すれば上書きされるらしい
```
On Error GoTo 0を毎回記述する必要は無いか

Sub aaa()

On Error GoTo EH1

On Error Resume Next
'この時点で、On Error GoTo EH1 は無効になり、エラーは無視して進むようになるらしい
'On Error GoTo 0 を記述する必要は無い
Err.Raise 514

On Error GoTo EH1
'この時点で、On Error Resume Next は無効になり、エラーはトラップされるようになる
Err.Raise 515

On Error GoTo 0

Exit Sub

EH1:
  MsgBox "エラー発生。エラー番号: " & Err.Number

End Sub
```
### エラー処理　基本
```
Sub aaa()
' 単純なエラー処理の例

    On Error GoTo ErrorHandler   ' エラーが発生した場合は、"ErrorHandler"へ移動
    
    ThisWorkbook.Charts(1).Activate
    MsgBox ActiveSheet.Name

    On Error GoTo 0   ' エラーのトラップを無効にする
    
    Exit Sub
    ' 必須。これが無いとプロシージャを抜けないので、ErrorHandlerの処理に移動してしまう
    
    
ErrorHandler:   ' エラー発生時の移動先

    MsgBox "グラフシートがありません"
    
    ' このままプロシージャを抜ける
    
End Sub

Sub bbb()
' エラー時の処理実行後、本処理に復帰する例

    On Error GoTo ErrorHandler1
    
    Dim long1 As Long
    long1 = 0
    MsgBox 100 / long1
    
    On Error GoTo 0
    
ReturnPoint:   ' エラー処理からの復帰ポイント

    On Error GoTo ErrorHandler2
    
    Worksheets("存在しないシート").Activate
    
    On Error GoTo 0
    
    Exit Sub
       
ErrorHandler1:
    MsgBox "0で除算した可能性があります"
    Resume ReturnPoint    ' ReturnPoint:に戻る
    
ErrorHandler2:
    MsgBox "存在しないシート名が指定されました"
    Resume Next   ' エラーの発生した処理の、次の処理に戻る
    ' ※ Resume を指定すると、エラーの発生した処理に戻るが、またそこでエラーが発生して永久ループの可能性あり
End Sub
```
### エラーが発生しても無視して処理を続行する
```
On Error Resume Next   ' エラーが発生した場合でも、無視して処理を続行する

MsgBox 100 / 0
Cells(-100, 2.5).Select

If Err.number <> 0 Then
' エラーが発生していなければ、Err.numberは0になる

    MsgBox "何らかのエラーが発生しています"
End If

On Error GoTo 0   ' このステートメントでErr.numberはクリア(0)になってしまうので注意
```
### エラーの情報を取得する
```
Sub sss()

  On Error GoTo ErrorHandler
  Cells(-1, -200).Select
  On Error GoTo 0
  Exit Sub
  
ErrorHandler:

  Debug.Print Err.Number        ' エラー番号。エラーがない場合は0
  Debug.Print Err.Description   ' エラーの説明文
  Debug.Print Err.Source        ' エラーの発生元のオブジェクト名、またはアプリケーションの名前
  Debug.Print Err.HelpContext   ' ヘルプファイルのトピックに対応するコンテキスト番号
  Debug.Print Err.HelpFile      ' ヘルプファイルへの絶対パス
  Debug.Print Err.LastDllError  ' 最後にダイナミックリンクライブラリ(DLL)を呼び出したときのエラーコード
  
  Err.Clear     ' エラー内容のクリア
  Err.Raise 11  ' 0で除算したエラーを発生させる

End Sub
```
### エラーの内容をクリアする
```
Sub aaa()
    On Error Resume Next
    
    MsgBox 100 / 0
    
    If Err.number <> 0 Then
        MsgBox "エラークリア前のエラー番号： " & Err.number & vbCrLf & "エラークリア前のエラー説明: " & Err.Description

        Err.Clear  ' エラー内容のクリア
        
        MsgBox "エラークリア後のエラー番号： " & Err.number & vbCrLf & "エラークリア後のエラー説明: " & Err.Description
        
    End If
    
    On Error GoTo 0   ' このステートメントでもエラーの内容はクリアされる
End Sub
```
### エラーハンドラーの部分では、On Error Resume Nextを指定してもエラーが無視されない?　多分これは仕様
```
「ツール」→「オプション」→「全般」で、エラートラップに関する設定有りだが、「エラー処理対象外～」が本来のはず

Sub aaa()

  On Error GoTo ErrHandler
  Err.Raise 514
  
  MsgBox "完了"
  Exit Sub


ErrHandler:

  On Error Resume Next
  Err.Raise 514  'エラーは無視するはずだが、ここで止まってしまう
  MsgBox "エラー処理完了"

'「ツール」→「オプション」→「全般」で、エラートラップに関する設定有り
'「エラー処理対象外～」にしないと、そもそもエラー処理がまともに実行されない

End Sub
```