Mutable_Yunの業務改善ブログ

業務改善や生産性向上のブログです。自動化の手段として、VBAやRPAの勉強に役立つ解説をしています。

VBAで複数のモジュールを使いこなす② イベント処理のためのシートモジュールとブックモジュール 

みなさんはシートモジュールやブックモジュールを使いこなすことはできていますか。今回はシートモジュール、ブックモジュールの意義とイベントプロシジャについて解説します。

目次

シートモジュールとブックモジュールとは

まずは表示⇒プロジェクトエクスプローラーでプロジェクトエクスプローラーを開きましょう。

プロジェクトエクスプローラを開くとVBAProject(Book1)と書いてあり、その下にMicrosoft Excel Obujetctsというフォルダの絵があります。
それを開いてやるとSheet1(Sheet1)とThisWorkbookというモジュールが表示されます。
f:id:mutable_yun:20190924034054p:plain

上の図のように私の環境ではatpvbean.xlsと言うのが表示されていますが、これは無視して下さい。今回の記事には関係ありません。


今開いているブックにはSheet1のみが存在します。Sheet1(Sheet1)はこのシートオブジェクトに付属しています。

シートモジュールはシートに付属しているため、シートそのものと切り離すことはできません。

試しに新しくシートを追加してみます。

Sub シート追加するだけ()

    Sheets.Add
    
End Sub

結果

ワークシートを追加するとシートモジュールも自動で挿入される
シートを追加するとSheet2(Sheet2)というシートモジュールが追加された

シートを追加すると同時にSheet2(Sheet2)と言うモジュールが追加されました。



次にSheet2のシート名を変更してみます。

Sub シート追加するだけ()

    Sheets(2).Name = "Hello"
    
End Sub

結果

シート名が変更されるとシートモジュールの名前も変わった
シート名が変更されるとシートモジュールの名前も変わった

シートモジュールの名前がSheet2(Hello)に変わりました。( )の
中がシート名になっていることがわかりました。

これらのシートモジュールはこのシート特有の作業を行うときに使います。

例えば、このシートだけはユーザーが勝手にシートの中身を変更することができない、とか、このシートを開く度に一旦保存する、と言った具合です。

その証拠に、シートを指定せずに単にセルに書き込んだ時の挙動が標準モジュールと異なります。

Sub 別シートがアクティブでもSheet1に書き込まれる()

    Sheets("Hello").Activate
    Cells(1, 1) = "こんにちは"
    
End Sub

シートモジュールはそれぞれのシートに紐付いており、そのシートに対するプロシジャを記述する為にあるので、(そのシートクラスのシートオブジェクトのメソッドやプロパティを操作するためにあるので)、別のシートがアクティブでもそのシートモジュールが属するシートのセルの値が書き換わります。

そしてこのシート特有作業の中でも特に、マクロが外部からの何かのトリガーを持って実行されることをイベントといいます。

イベントについては、次項で詳しく解説したいと思います。

ThisWorkbookと書かれているブックモジュールも同様の考え方で、このブック特有の挙動を記述するためのモジュールです。

イベントとは

イベントとはアプリケーションが外部から何かしらの影響を受けることをトリガーにしてプログラムが実行されることです。

VBAではエクセルアプリケーションがエクセルブックやエクセルシートに何かしらのアクションがあったときに発動することをします。

例えばワークシートのRange(のCellsプロパティ)に何かが書き込まれたり、あるワークブックが開かれたり、閉じたりする事がトリガーになり得ます。

シートモジュールやブックモジュールと言ったオブジェクトに付帯するモジュールは、それ特有の動作をすると言うことでしたが、イベントがまさにこれに当たります。

「その」ブックが開いたとき、という感じですね。

私見ですが、感覚的にはオブジェクトに付帯したモジュールの9割以上はイベントの為に使われていると思います。

よく使うイベントプロシジャ

早速イベントプロシジャを作成してみます。イベントプロシジャはSubプロシジャに記述する手順が決まっていて、自由にSubプロシジャを記述することができません。

決まり文句があって、それがイベントである事をエクセルアプリケーションに知らせています。

インターネット上には様々なイベントプロシジャの解説が載っているので、いろいろなイベントについてはそちらに譲ります。ここではいつものように具体的な使いどころとコツを解説していきます。

ちなみにネットで検索するコツは VBA (半角スペース) 下で解説するサブプロシジャの名前をコピペです。

よく使うイベント例
  • シートの値が変更されたら内容をチェックする。

まずは書き方から。

プロジェクトエクスプローラーが表示されていなければ、表示⇒プロジェクトエクスプローラーで表示させる。

その後、イベントプロシジャを作成したいシートをダブルクリックする。

f:id:mutable_yun:20190924185832p:plain
プロジェクトエクスプローラー

いつもSubプロシジャなどを記述している所の上にある2つのプルダウンリストの左のGeneralをWorksheetに変更する
f:id:mutable_yun:20190924185554p:plain

右のプルダウンリストからChangeを選択すると勝手に、Private Subプロシジャが選択される。

f:id:mutable_yun:20190924190059p:plain
Subプロシジャ作成完了

今までSubとしか書いてきませんでしたが、今回はPrivate Subとなっています。また、プロシジャ名の後に(ByVal Target As Range)と書いてあります。

PrivateもByValも大事な概念ですが、イベントプロシジャを使うだけなら、今は深入り不要です。

余談ですが、細かいことを一つずつ積み上げるより、やりたいことを最短で習得するのがプログラミング学習のコツの一つだと思います。後から「あ~そういうことなのね」と理解すれば、その時、本当に分かった、と言う事であり、これが最短の身に付ける方法だと思います。

周辺知識がない状態で、ByValとはに踏み入るのは遠回りです。いずれ通る道なので、その時振り返ればいいと思います。というわけで、詳しい解説は今回は省略して、イベントプロシジャの作成に取りかかっていきます。

Private Sub Worksheet_Change(ByVal Target As Range)

    If Not IsNumeric(Target) Then
        MsgBox "数値でない値が入力されました", vbExclamation
    End If

End Sub

自動的に挿入されたPrivate Sub Worksheet_Changeのカッコの中のTargetは、値が変更されたRangeを受け取る変数です。

IsNumeric関数は引数として受け取ったが数値ならTrue、数値でなければFalseを返します。
実験します。A1セルに文字を書き込みます。

f:id:mutable_yun:20190924190958p:plain
イベントプロシジャが実行されてメッセージボックスが表示された。

初めはこれだけでも感動するかもしれませんが実務的には物足りないですね。もう少し実務的に完成度を上げていきます。

このような売上リストがあると仮定します。

f:id:mutable_yun:20190924191344p:plain
売上リストサンプル

このリストのユーザーは売上数量に数量を入力することが仕事です。(それを自動化してあげたいですが、今回は解説用なので流して下さい)

<やりたいこと>

  • 入力ミス防止のために、売上数量が数値ではない場合に警告を出して、空白に戻す。

やはりメッセージを表示させるだけでなく、セルを入力前の状態に戻してあげた方が親切ですね。

あと、備考とかE列に書きたいかもしれないのでC列に記入されたときだけ発動するようにしましょう。

一番上の行はヘッダ部分で、後工程でアクセスかエクセルなど別のツールに取り込む予定なので、1行目も編集不可にしてしまいましょう。

やりたいことをまとめます。

  1. 1行目は編集不可
  2. C列だけイベント発動

 ⇒イベントのメインは数値でないときに、メッセージを出して、空白に戻す

Private Sub Worksheet_Change(ByVal Target As Range)

    If Target.Row = 1 Then
        'ここに「1.1行目は編集不可」の時の処理を記述する
    End If
    
    If Target.Column <> 3 Then
        'ここに「2.C列だけイベント発動」の時の処理を記述する
    End If
    
End Sub

はい、これで大枠はできました。簡単ですね。.Rowは行番号を意味するRangeオブジェクトのプロパティでした。つまり選択されたのが何行目かを返します。

まず、一つ目のIfの中身を作成します。

Private Sub Worksheet_Change(ByVal Target As Range)
    
    If Target.Row = 1 Then
        MsgBox "1行目のヘッダ部分を変更することはできません", vbExclamation
        Application.EnableEvents = False ’ココが重要!
        Target.ClearContents
        Application.EnableEvents = True ’ココが重要!
        End
    End If
    
    If Target.Column = 3 Then
        'ここに「2.C列だけイベント発動」の時の処理を記述する
    End If
    
End Sub

ポイントはApplication.EnableEvents = False の所です。
これはアプリケーション、つまりエクセルさんにイベントを起こさないで下さい、とお願いしています。

どういうことかというと、

  1. 文字列が記入される
  2. イベントが発動する
  3. 元に戻す。つまり空白に戻す
  4. 空白に戻ったのでイベントが発動する
  5. 以降、空白に戻すというセルの値の変更が無限に繰り返される

と言う事になります。もちろんこれは望むところではありません。イベントは1回だけおきてほしいのです。

今回イベントがすでに発動しているので、Ifの中でイベントを起こさないで下さい、とお願いします。今実行しているイベントまで中断されるわけではありません。

そして、空白に戻すTarget.ClearContentsを実行した直後に、イベントが再度発生するように
Application.EnableEvents = Trueに戻しています。

Trueに戻さないと、このマクロと関係ない別ブックでもイベントが止まり続けてしまいます。アプリケーションに対する命令のためです。なので、Application.EnableEvents = Falseと、
Application.EnableEvents = Trueを先に書いてから、中にピンポイントでイベントを発生させたくない内容を記述するようにしましょう。

細かいところですが、このIfの中にEndステートメントを記述しています。これは、全てのプロシジャーを終わらせると言う意味です。このIfの中が実行された時点で、その後の「2.C列だけイベント発動」を行う必要がないからです。

次に同じ要領で「2.C列だけイベント発動」の内容を作成していきます。

Private Sub Worksheet_Change(ByVal Target As Range)
    
    If Target.Row = 1 Then
        MsgBox "1行目のヘッダ部分を変更することはできません", vbExclamation
        Application.EnableEvents = False
        Target.ClearContents
        Application.EnableEvents = True
        End
    End If
    
    If Target.Column = 3 Then
        If Not IsNumeric(Target) Then
            MsgBox "C列の値には数値を入力して下さい", vbExclamation
            Application.EnableEvents = False
            Target.ClearContents
            Application.EnableEvents = True
        End If
    End If
    
End Sub

できました。しかし、あと一歩、改善点があります。
ユーザーが自分で間違えた数字を入力した事に気づいて、DeleteかBack Spaceを押して空白に戻したときにもメッセージボックスが表示されてしまいます。

これを改善するにはどうしたらいいでしょうか。答えはTargetの値が空白でない値に変更されたときのみ2つめのIfが実行されるようにすれば良いです。

Private Sub Worksheet_Change(ByVal Target As Range)
    
    If Target.Row = 1 Then
        MsgBox "1行目のヘッダ部分を変更することはできません", vbExclamation
        Application.EnableEvents = False
        Target.ClearContents
        Application.EnableEvents = True
        End
    End If
    
    If Target.Column = 3 Then
        If Target <> "" Then  'ココを追記した
            If Not IsNumeric(Target) Then
                MsgBox "C列の値には数値を入力して下さい", vbExclamation
                Application.EnableEvents = False
                Target.ClearContents
                Application.EnableEvents = True
            End If
        End If  'ココを追記した
    End If
    
End Sub

できました。あとは実際の実務に合わせて、小数の値を指摘するなど、改造していくとより完成度が高まっていきます。

ユーザーにやさしいイベントプロシジャーを組んで、使いやすいマクロブックにしていきましょう。