リスト1●既存のファイルを開いて,データをワークシートにコピーするプロシジャ
リスト1●既存のファイルを開いて,データをワークシートにコピーするプロシジャ
[画像のクリックで拡大表示]
図1●リスト1では,「ファイルを開く」ダイアログボックスで[キャンセル]ボタンをクリックすると実行時エラーとなり,このメッセージが表示される
図1●リスト1では,「ファイルを開く」ダイアログボックスで[キャンセル]ボタンをクリックすると実行時エラーとなり,このメッセージが表示される
[画像のクリックで拡大表示]
リスト2●リスト1をエラー・トラップを使って書き直したもの
リスト2●リスト1をエラー・トラップを使って書き直したもの
[画像のクリックで拡大表示]

 今月から,Microsoft Excelをこよなく愛する方々に向けて,VBAに関する悩みや疑問を解決するExcel/VBAクリニックを開設することになりました。私が当クリニックの院長,ドクター古庄です。どうぞよろしく。さて,初回のお悩みはどんなことでしょうか?

今月の相談

「職場で使うためのVBAプログラムを作っています。でも,ユーザーが勝手な操作をしてプログラムが止まったり,暴走したりすることがあり,そのたびに私のせいにされるのがとても不愉快です。ユーザーに勝手なことをさせない方法はないでしょうか?」

 ほー,なるほど。プログラマなら一度ぐらいはこうした悩みや怒りを感じたことがあるでしょう。私もプログラミングを始めたころは,よくユーザーに泣かされたものです。1~3を選ぶ場面で4を押す人,手当たり次第にキーボードを押す人,しまいにはPCのリセットボタンや電源ボタンを押す人--もうありとあらゆるトラブルに泣かされました。

 結論から言えば,ユーザーに勝手な操作をさせないことは不可能でしょう。操作可能なメニューやボタンなどを極力減らすといった具合に,ユーザー・インタフェースを工夫することで対処できる場合もありますが,それがすべてのユーザーにとって使い勝手が良いとは限りません。したがってプログラマは,ユーザー・インタフェースを工夫すると同時に,ユーザーが予想外の操作をしたときに,プログラムが止まったり,暴走したりしないようにしておく必要があります。

 それにはいくつかの方法がありますが,今回はエラー・トラップを紹介しましょう。エラー・トラップは,実行時にエラーが生じた際に,処理を中断することなく続けられるようにする機能です。あらかじめエラーが発生しそうなところにトラップ(罠)を仕掛けることで,エラーが発生したときにプログラムを中断することなく,用意したエラー処理(例えば,正常に終了するなど)を実行できます。

 プログラムをリリースする前に,プログラムを知らない誰かに試しに操作してもらうと,ビックリするほどバグやエラーが見つかるものです。「インフルエンザの予防にはワクチンを!バグの予防にはエラー・トラップを!」です。

実行時エラーをユーザーに見せない

 簡単なサンプル・プログラムで説明します。リスト1[拡大表示]は,すでにデータが入力されているワークブックを開き,そのデータを自分のワークシートにコピーするマクロです。このマクロの中で,データが入力されているファイルを開き,オブジェクト型の変数に代入するステートメントがあります(1)。

リスト1●既存のファイルを開いて,データをワークシートにコピーするプロシジャ

Sub DataTransfer_non_Etrap()
    Dim myDataBook As Workbook
    Dim myRow As Long
    Dim myRes As Integer

    Set myDataBook = Workbooks.Open( _
        Application.GetOpenFilename( _
        "Excel ファイル(*.xls), *.xls"))

    myRow = ThisWorkbook.Worksheets(1). _
        Cells(Rows.Count, 2).End(xlUp).Row + 1

    ThisWorkbook.Worksheets(1).Cells(myRow, 2).Value = _
        myDataBook.Worksheets(1).Cells(2, 2).Value
    ~ 略~

    myDataBook.Close
    Set myDataBook = Nothing
End Sub

 処理的には,「ファイルを開く」ダイアログボックスを表示して,ユーザーに既存のExcelファイルを選択してもらう場面です。このマクロは,データ入力済みのファイルがあることが前提ですから,ダイアログボックスで[キャンセル]ボタンをクリックすることはないはずです。しかし,実際にはユーザーが何らかの理由で[キャンセル]ボタンをクリックすることは十分ありえます。するとどうなるでしょうか。

 図1[拡大表示]のようなエラー・メッセージが表示され,処理は中断します。VBAの知識を持たないユーザーがこのメッセージを見たら,きっとパニックになるでしょう(ここで電源ボタンを押して,すべてを見なかったことにするというユーザーに私は何度か出会ったことがあります)。

 エラー・トラップを使えば,こんな場面でユーザーに違和感を与えることなく処理を続けられます。リスト2[拡大表示]は,リスト1[拡大表示]をエラー・トラップを使って書き直したものです。これにより,ダイアログボックスで[キャンセル]ボタンをクリックすると,「処理を中断しますか」というメッセージを表示します([はい]をクリックするとマクロを終了します)。

リスト2●リスト1をエラー・トラップを使って書き直したもの

Sub DataTransefer()
    Dim myDataBook As Workbook
    Dim myRow As Long
    Dim myRes As Integer

    On Error GoTo ErrHandler1
        Set myDataBook = Workbooks.Open( _
        Application.GetOpenFilename( _
        "Excel ファイル(*.xls), *.xls"))
    On Error GoTo 0

    ~ 略~

Exit Sub

ErrHandler1:
    myRes = MsgBox("処理を中断しますか", _
        vbYesNo + vbQuestion)

    Select Case myRes
        Case vbNo
            Resume
    End Select
End Sub

エラー・トラップで使用するステートメント

 具体的に見ていきましょう。(1)でエラー・トラップを設定します。

 On Error GoTo 行ラベル(または行番号)
は,エラーが発生した場合に処理をジャンプさせる命令です。VBAのヘルプには「行番号,あるいは行ラベルを指定できる」と書いてありますが,行ラベルを記述してそこにジャンプさせるのが一般的です。ここでは(4)のErrHandler1という行ラベルを指定しています。(2)の
 On Error GoTo 0
は,エラー・トラップを解除するステートメントです。エラー・トラップはすべてのエラーを対象とします。したがって(2)のステートメントがないと,ほかのステートメントで発生した別のエラーに対しても同じ処理ルーチンにジャンプします。エラー・トラップを仕掛けたいステートメントの直後に,必ずこのステートメントを記述しましょう。

 なお,一つのエラー処理ルーチンで複数のエラーに対応することも可能です。その場合は,エラー番号による条件分岐で対処します。しかし,VBAが返すエラー番号は,あまり正確ではありません。使用する場合は十分注意してください。

 (3)はこのプロシジャを直ちに抜けるというステートメントです。エラーが発生しなかった場合に,通常処理の後にエラー処理ルーチンが続けて実行されないように,ラベルの直前に記述します。

 (4)のラベルから最後の「End Sub」までがエラー処理ルーチンです(5)。ここにエラーが発生したときに実行する処理を記述します。ここでは前述のように終了確認メッセージを表示して,処理を続けるか,終了するかを選択させます。Selectブロックにある
 Resume
は,エラーが発生したステートメントから処理を再開せよ,という命令です。つまり,今回の場合では「処理を中断しますか」のメッセージに対して,[いいえ]をクリックすると,再びダイアログボックスを開きます。

 エラー・トラップで使えるステートメントはこれらだけではありません。ほかに,「On Error Resume Next」(エラーを無視して,エラーが発生した次のステートメントから処理を続行:プロシジャの処理の中に記述)や「Resume Next」(エラーが発生したステートメントの次から処理を再開:エラー処理ルーチンの中に記述)などがあります。状況に応じて使い分けましょう。

 VBAのテクニックを駆使すれば,エラー・トラップを使わなくても,エラーの発生を回避できるケースもあります。しかし,簡単かつ確実にエラーを回避する方法があるのなら,それを使うのもプログラミングのテクニックです。実用的なプログラムを作るためには,エラー・トラップぐらいは覚えておきましょう。

古庄 潤