マクロのある暮らし(7回目) - エラーに対処しつつブックを安全に利用する
こんにちは!なるーらぼです!今回は珍しく続き物です。
前回はこちら。
それではおさらいからいきましょう。
おさらい
前回は別のブックを2つ開いて、片方から片方へデータを移して別名保存する方法をご覧いただきました。最終的なコードを再掲しておきます。
Sub note_mu()
Const soldname As String = "売上データ.xlsx"
Const tempname As String = "領収証ひな形.xlsx"
Const mydocuments As String = "\Documents\"
Dim userdoc As String
Dim soldbook As Workbook
Dim tempbook As Workbook
userdoc = Environ("UserProfile") & mydocuments
Set soldbook = Workbooks.Open(userdoc & soldname)
Set tempbook = Workbooks.Open(userdoc & tempname)
'' 売上データのレイアウト
'' A:領収番号、B:名前、C:金額、D:日付、E:発行済み
Dim dataline As Variant
dataline = soldbook.Worksheets(1).Range("A2:E2")
With tempbook.Worksheets(1)
.Range("E2").Value = dataline(1, 1)
.Range("C4").Value = dataline(1, 2)
.Range("C6").Value = dataline(1, 3)
.Range("C11").Value = Format(dataline(1, 4), "yyyy年mm月dd日")
End With
tempbook.SaveCopyAs userdoc & dataline(1, 1) & "_" & dataline(1, 2) & ".xlsx"
soldbook.Close False
tempbook.Close False
Set soldbook = Nothing
Set tempbook = Nothing
End Sub
長いですね。では今回はこれを複数行データに対応できるようにするところから行きます。複数行に対応するためにはデータがある間は繰り返しを行ってひな形へのデータ写しと別名保存を行います。データがなくなったら処理を終了するようにします。
Sub note_mu()
Const soldname As String = "売上データ.xlsx"
Const tempname As String = "領収証ひな形.xlsx"
Const mydocuments As String = "\Documents\"
Dim userdoc As String
Dim soldbook As Workbook
Dim tempbook As Workbook
userdoc = Environ("UserProfile") & mydocuments
Set soldbook = Workbooks.Open(userdoc & soldname)
Set tempbook = Workbooks.Open(userdoc & tempname)
'' 売上データのレイアウト
'' A:領収番号、B:名前、C:金額、D:日付、E:発行済み
Const startcol As Integer = 1
Const endcol As Integer = 5
Const startrow As Integer = 2
Dim dataline As Variant
Dim soldsheet As Worksheet
Dim row As Integer
Dim cols As Variant
'' 長いので短縮のためにオブジェクトをつかんでおく
Set soldsheet = soldbook.Worksheets(1)
'' 開始行
row = startrow
Do
dataline = soldsheet.Range( _
soldsheet.Cells(row, startcol), _
soldsheet.Cells(row, endcol))
With tempbook.Worksheets(1)
.Range("E2").Value = dataline(1, 1)
.Range("C4").Value = dataline(1, 2)
.Range("C6").Value = dataline(1, 3)
.Range("C11").Value = Format(dataline(1, 4), "yyyy年mm月dd日")
End With
tempbook.SaveCopyAs _
userdoc & dataline(1, 1) & "_" & dataline(1, 2) & ".xlsx"
row = row + 1 '' 次の行へ
Loop Until WorksheetFunction.CountA( _
soldsheet.Range( _
soldsheet.Cells(row, startcol), _
soldsheet.Cells(row, endcol))) = 0
soldbook.Close False
tempbook.Close False
Set soldsheet = Nothing
Set soldbook = Nothing
Set tempbook = Nothing
End Sub
な、長い…ほんとうはサブルーチンに分割していったほうが読みやすさも向上していいのですが、バラバラにしてしまうと説明がややこしくなるので敢えてこのままいきたいと思います(キリッ
少し解説をしますと、Rangeメソッドに渡すセルの範囲を文字列の「A2:E2」という形式からCellsメソッドでFrom-Toにした形式に変更しています。
これで行数のカウンタを1ずつ足しこんでいくだけで範囲が変わっていきます。
さらに横着をしまして、データがなくなったことをワークシート関数の「CountA関数」でチェックしています。指定範囲の値が入ったセルの数をカウントしてくれるので、これがゼロを返すようになったら「データなし」とみなしてもいいでしょう、ということです。
エラーへの対処をしよう
こうしたマクロで無関係の別のファイルを操作することになると、問題になるのが「想定していない状況」によるエラーの発生です。
例えば「同じフォルダ内に保存していくとわけがわからなくなるので別のフォルダにしようぜ、例えば領収書発行、とか」といったような話が上がってくることはありますよね:)
そうしたときに保存のところを次のようにしたとします。
tempbook.SaveCopyAs _
userdoc & "\領収書発行\" _
& dataline(1, 1) & "_" & dataline(1, 2) & ".xlsx"
これで動かすと何が起きるでしょうか?
「ホッワーーーイッッッ!!!!!」ってなります。それにこのまま終了してください。ひな形も売り上げデータもブックが開いたままです…
エラーが発生したときも安全にブックだけは閉じておきたいですよね。
そうしたときにはVBAではエラーが発生したら制御を捕まえて特定の行へジャンプさせる機能を持っています。「On Error Goto ****」という書き方です。マクロに書いてあるのを見かけたことがあるでしょう。
これはエラーが発生したらコード内に書いたラベルへジャンプさせることができます。ただし、わかってくれるのは同じサブルーチン内のラベルです。ここで捕捉さえできれば、「Err」というエラー情報を含んだオブジェクトからエラー番号やエラーの内容をある程度(全部、とは言いません…)教えてもらうことができます。
なお、よくネット上の記事にあるのはサブルーチンの先頭に「On Error Goto」を書いてあるものがありますが、ラベルはわかるのであればいくつ書いてもいいですし、エラーの内容によってラベルを使い分けてもいいです。
先ほどのコードだと、次の箇所は保存に失敗するエラーが発生しやすそうですよね。いまは固定の場所になっていますが、もしも保存先のフォルダ名をセルなどのユーザー入力から受け取るようになったら発生確率は急激に上がります。
ではこの付近にだけエラーを捕まえるところを指定しておきます。
On Error GoTo SaveFile_Failure
tempbook.SaveCopyAs _
userdoc & "\領収書発行\" _
& dataline(1, 1) & "_" & dataline(1, 2) & ".xlsx"
On Error GoTo 0
ラベルはサブルーチンの終わり付近に設置して、「SaveFile_Failure」なんかにしておきますか。
SaveFile_Failure:
If Err.Number <> 0 Then
MsgBox "ブックが保存できませんでした。" & vbCrLf _
& "(" & Err.Number & ")" _
& Err.Description, vbExclamation,
ThisWorkbook.Name
End If
soldbook.Close False
tempbook.Close False
Set soldsheet = Nothing
Set soldbook = Nothing
Set tempbook = Nothing
これで動作させるとメッセージが表示されますね。そしてブックはすべて閉じられます。
ブックが開いているときなどもラベルをつけてジャンプさせてもいいですよ。ジャンプさせるには悪名高い「Goto」文を使います。できれば下方向への移動にのみ使うことを強くお勧めします。
ブックを開く前のエラーにも対処しておこう
現段階ではブックを開く前にエラーが発生しても対処することができません。だって、ブック保存のところにしかトラップを仕掛けていませんから。ですから、もしちょっと手が当たって「売上」の「上」という字を削除してしまったことに気づかず実行するとこのようになります。
ではエラーを捕まえる部分を少し修正してみます。
OpenFile_Failure:
If Err.Number <> 0 Then
MsgBox "ブックを開くことができませんでした。" & vbCrLf _
& "(" & Err.Number & ")" & Err.Description, _
vbExclamation, ThisWorkbook.Name
End If
SaveFile_Failure:
さきほどのラベルより上に持ってきました。これで実行してみるとメッセージボックスが表示されますが…
あれ!?保存できなかったっていうエラーも表示された…それはそうですね。だって続けて「SaveFile_Failure」ラベルがありますし、その中ではエラーになったときに表示するメッセージも用意されています。それに、エラー発生を番号がゼロ以外であることで判断していますので、もちろんエラーはあります!!ですから2回表示されて当然です。しかもそれもOKをクリックするとさらに意味のわからないエラーが…では「SaveFile_Failure」ラベルのブロックより下にさらに「Normal_End」というラベルを用意して、そこへジャンプさせるようにしましょう。
OpenFile_Failure:
If Err.Number <> 0 Then
MsgBox "ブックを開くことができませんでした。" & vbCrLf _
& "(" & Err.Number & ")" & Err.Description, _
vbExclamation, ThisWorkbook.Name
End If
GoTo Normal_End
SaveFile_Failure:
If Err.Number <> 0 Then
MsgBox "ブックが保存できませんでした。" & vbCrLf _
& "(" & Err.Number & ")" & Err.Description, _
vbExclamation, ThisWorkbook.Name
End If
Normal_End:
よし、これでOK!と思って実行してみるのですが、やはり意味のわからないエラーが表示されますよね…
このエラーの正体はなんなのでしょう?では「デバッグ」のほうをクリックしてみてください。答えがわかります。
ブックを閉じるところ・・・?もう、お分かりでしょうか。
そうです、まだ開いていないブックを閉じようとしているのでエラーが発生しているのです。原因が分かってしまえば対処は簡単です。「使わない」のところへラベルを移動させて、「Normal_End」ラベルがあった場所には別の「Close_Books」ラベルを配置します。これで、とりあえず開いたブックを閉じたくなったらここへジャンプさせればいいのですから。
Close_Books:
soldbook.Close False
tempbook.Close False
Normal_End:
Set soldsheet = Nothing
Set soldbook = Nothing
Set tempbook = Nothing
これで不用意に閉じられなくなるのでエラーは発生しなくなります。
最後に
今回は前回のコードを引き続きでエラーへの対処についてみてきました。
実際にはサブルーチンに分割してひとつひとつのサブルーチンで行うことをできるだけ小さくするとエラーへの対処もやりやすくなりますし、エラーの原因特定も早くすることができます。
そうしたサブルーチンは値を真偽値で返すようにするとサブルーチンを利用しているコードから「うまくいったのか」わかりやすくなります。エラーが起きたら偽(False)を返すようにするとIf文で制御しやすくなりますので。
ではまた。
この記事が気に入ったらサポートをしてみませんか?