カテゴリー名: [ExcelVBAとOLE ワークシートの操作]
イベントプロシージャは、何らかの出来事(イベント)が発生した時に呼び出される手続き(プロシージャ)のことです。
イベントプロシージャには、ワークシートに関するものとワークブックに関するものがあります。ここではワークシート関連を取り上げます。
イベント(出来事)には、たとえば「該当のワークシートがアクティブになったとき」とか、「ワークシートの選択セルが変更されたとき」などいくつかあります。
Excelのユーザーがマウスやキーボードを操作すると、操作の種類によってはイベントが発生することになります。
イベントが発生した場合に、それに応じて実行されるプロシージャをシートモジュールに登録しておくと、どのタイミングで発生するか分からないイベントに対応できるようになります。
ワークシートのイベントプロシージャは、標準モジュールやクラスモジュールとは違うところに組み込みます。シートモジュールというコンポーネントに組み込みます。
新規のワークブックには Sheet1, Sheet2, Sheet3 の三つのワークシートがあります。それぞれにシートモジュールが用意されています。つまり、シートモジュールが三つあるわけです。
ワークシートのイベントプロシージャを組み込むときは、この三つのうちのどれに組み込むかを決める必要があります。
ここでは、VBScript(または JScript)によってシートモジュールを登録する方法を採ります。VBEを立ち上げてコードを入力する方法ではありません。
そのため、Excelのマクロセキュリティの設定をあらかじめ変更しておく必要があります。ExcelをGUI操作して、マクロのセキュリティに関して次の設定変更を行います。
イベントプロシージャは、全自動一括処理で扱っても意味がないので、「OLEを利用するVBScript」は掲げません。その代わりといっては何ですが、簡単なイベントプロシージャの例をいくつか取り上げようと思います。
Worksheet_Activateというプロシージャ名は、該当のワークシートがアクティブシートになった時に実行されるものです。該当のワークシートというのは、イベントプロシージャが組み込まれているワークシートです。
まず、イベントプロシージャとしては最も簡単と思われるもの =
Sheet2がアクティブシートになった時に、そのA1セルに現在時刻を書き込む、
という処理を取り上げます。
Sheet2がアクティブシートになる度にA1セルに書き込みが行われるので、アクティブシートになった時点の時刻がA1セルに表示されることになります。
プロシージャ部分だけ掲げると次のとおり。
Worksheet_Activateというプロシージャ名は、規定の名前です。
標準モジュールの場合は Macro1 とか SetValue など自分の好みの名前をつけることができますが、イベントプロシージャの場合は使える名前が決まっています。
その辺の事情を詳しく知りたい方は、たとえば下のサイトを参照して下さい。
エクセルExcel大事典 VBAマクロ イベントプロシージャ Open Target Cancel EnableEvents Volatile
次に、イベントプロシージャを組み込む VBScript を掲げます。
1' Sheet2へのイベントプロシージャの組み込み: Worksheet_Activate 2Option Explicit 3Dim FSO, BookPath 4Dim EXLapp, WBobj, CPobj 5Const xlWorkbookNormal = -4143 6 7Set FSO = CreateObject("Scripting.FileSystemObject") 8BookPath = FSO.GetAbsolutePathName("Book1.xls") 9If (FSO.FileExists(BookPath) = True) Then FSO.DeleteFile(BookPath) 10Set EXLapp = CreateObject("Excel.Application") ' Excelの起動 11EXLapp.Visible = True ' Excelを見える状態に 12Set WBobj = EXLapp.Workbooks.Add() ' Workbookの新規作成 13Set CPobj = WBobj.VBProject.VBComponents("Sheet2") ' シートモジュールの選択 14CPobj.CodeModule.AddFromFile(FSO.GetAbsolutePathName("event01.txt")) 15WBobj.SaveAs BookPath, xlWorkbookNormal 16EXLapp.quit
上のVBScriptを実行して生成される Book1.xls をExcelで開いてGUI操作するとき、
Sheet2をアクティブシートにする度に、そのA1セルを書き換えて現在時刻を表示します。
イベントプロシージャの組み込みを行っているのは下の2行です。
Set CPobj = WBobj.VBProject.VBComponents("Sheet2")
CPobj.CodeModule.AddFromFile(FSO.GetAbsolutePathName("event01.txt"))
上の "Sheet2"
を "Sheet3"
とすれば、Sheet3にプロシージャを組み込むことになります。
同じ処理をする JScript は下のとおり。
1// Sheet2へのイベントプロシージャの組み込み: Worksheet_Activate 2var fso, exlApp, wb, cp; 3var xlWorkbookNormal = -4143; 4 5fso = WScript.CreateObject("Scripting.FileSystemObject"); 6var bookPath = fso.GetAbsolutePathName("Book1.xls"); 7if (fso.FileExists(bookPath)) fso.DeleteFile(bookPath); 8exlApp = WScript.CreateObject("Excel.Application"); // Excelの起動 9exlApp.Visible = true; // Excelを見える状態に 10wb = exlApp.Workbooks.Add(); // Workbookの新規作成 11cp = wb.VBProject.VBComponents("Sheet2"); // シートモジュールの選択 12cp.CodeModule.AddFromFile(fso.GetAbsolutePathName("event01.txt")); 13wb.SaveAs(bookPath, xlWorkbookNormal); // ブックの保存 14exlApp.Quit(); // Excel終了
SelectionChangeは、該当のワークシート内において選択セルが変更された時呼び出されるプロシージャです。
カーソルを動かして、注目のセルをA1からB1に移したような場合にSelectionChangeが呼び出されます。
下のプロシージャをシートモジュールSheet1に登録したとしましょう。
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Target.value = 123
End Sub
この場合、選択セルをA1からB1に移すと、B1に123が書き込まれます。更にC1に移れば、C1にも123が書き込まれます。
この 123 の書き込みは、あくまでSheet1においてです。Sheet2, Sheet3 では発生しません。
Worksheet_SelectionChange() のパラメータ Target は、イベントプロシージャが呼び出された段階での注目セルを示します。
なので、Target.Value = 123
とすれば、注目セルに 123 が書き込まれます。
以下のサンプルでは、多少のゲーム性を持たせるため次のような仕様にしてみます。
VBAにおいてRnd関数は、0以上・1未満の乱数を返します。これに30を掛け算すると、0以上・30未満の数を得ることができます。
Int() は、与えられた数値の小数部分を切り捨てて整数部分を返します。
If文が入れ子になったりして少しごちゃごちゃしますが、イベントプロシージャの例を掲げます。
1' Sheet1で注目セルを変更したとき、乱数を書き込む 2Private Sub Worksheet_SelectionChange(ByVal Target As Range) 3 Randomize ' 乱数の初期化 4 If Target.Value = "" Then ' セルが空欄なら 5 Target.value = Int(Rnd*30)+1 ' 30以下の乱数 6 If Target.Value > 25 Then ' 乱数が25を超えた 7 MsgBox Target.Value & ">25 Over!" 8 Worksheets("Sheet2").UsedRange.Clear ' Sheet2の全クリア 9 Range(Cells(1,1), Cells.SpecialCells(xlCellTypeLastCell)).Copy _ 10 Worksheets("Sheet2").Range("A1") ' Sheet1→Sheet2のコピー 11 Worksheets("Sheet2").Activate ' Sheet2をアクティブに 12 End If 13 End If 14End Sub 15 16Private Sub Worksheet_Activate() 17 ActiveSheet.UsedRange.Clear ' シートの全クリア 18 Application.EnableEvents = False ' イベントプロシージャを無効化 19 Range("A1").Select 20 Application.EnableEvents = True ' イベントプロシージャを有効化 21End Sub
上のイベントプロシージャをワークブックに組み込む VBScript, JScript は、先の event01.vbs, event01.js とほとんど同じです。
シートモジュールを Sheet2 から Sheet1 に変更することと、
プロシージャが書かれているファイル名を event01.txt から event02.txt に変更するだけです。
なので、組み込むための VBScript, JScript は省略します(zip圧縮ファイルには同梱されています)。
以下、event02.txt について少し解説を加えます。
Randomizeは、乱数の初期化を行うものです。
これを記述しないと、Rndがいつも同じ乱数を発生させます。同じ値を同じ順序で発生させます。
UsedRangeは、該当のワークシートの中で使用済みの領域全体を指し示します。
より正確には、使用済み領域を矩形化した領域全体です。
UsedRange.Clear
とすれば、シートの使用済みの領域が全部クリアされます。
つまり、シート全体がまっさらな状態になります。
Cells.SpecialCells(xlCellTypeLastCell) というのは、ワークシートの中の使用済み領域全体の一番右下のセルを指し示します。矩形化した領域の右下です。
Range(Cells(1,1), Cells.SpecialCells(xlCellTypeLastCell))
は、A1セルから一番右下までの領域全体を示します。
前述の UsedRange は、A1セルを始点とするとは限りません。B2セルが始点ということもあります。
サンプルのイベントプロシージャでは、A1セルから一番右下までをSheet2にコピーしています。ワークシートそのもののコピーではなく、あくまでシートの中身のコピーです。
Application.EnableEvents は、イベントプロシージャの呼び出しを有効にしたり無効にしたりするのに用います。
サンプルでは、Worksheet_SelectionChange の他に Worksheet_Activate も一緒に組み込んでいます。
Worksheet_Activate は、Sheet1がアクティブになったときに呼び出されますが、その中で次の処理を行っています。
2番目の処理のとき、何も工夫しないでいると、
イベントプロシージャの Worksheet_SelectionChange が呼び出されてしまいます。
イベントプロシージャは、キー操作・マウス操作に反応するだけでなく、該当の操作と同等の処理がマクロ(プロシージャ)の中で実行されると、それにも反応します。
そのため、マクロ内でSelectメソッドが用いられると Worksheet_SelectionChange が呼び出されます。
今回、そうした呼び出しが発生すると困るので、Application.EnableEvents = False
としてイベントプロシージャの呼び出しを一時的に無効化しています。
Application.EnableEvents = True
とすれば、イベントプロシージャの呼び出しが再び有効化されます。
Worksheet_Changeは、セルに書き込まれている中身が変化すると呼び出されます。
空欄だったA1に ABC と書き込むと Worksheet_Change が呼び出されます。
逆に、ABCと書かれていたセルをクリアして空欄にしたときも Worksheet_Change が呼び出されます。
前の項で取り上げた Worksheet_SelectionChange は、セルの中身に関係なく、選択セルを移すと呼び出されますが、Worksheet_Change の方は、選択セルが移ったかどうかに関係なく、中身が変化すると呼び出されます。
より正確には、セルに何か書き込みがあると呼び出されるようです。結果的に中身が同じになったとしても(つまり実質的に変化しなかったとしても)、書き込みが行われると Worksheet_Change が呼び出されるようです。
以下では一種の数当ての例を掲げます。
プロシージャ内で、最下行・1列のセルに正解となる数値を書き込む訳ですが、セルに書き込みを行う時に Worksheet_Change が呼び出されるので、いわばプロシージャ内から同じプロシージャを呼び出すことになってしまいます。そうすると、その後の処理に不都合が生じます。
そこで、Application.EnableEvents の値を調整して、最下行・1列への正解の書き込みの時は Worksheet_Change がよびだされないようにしています。
1' Sheet1において任意のセルに書き込んだ数が正解の数ならメッセージを出す 2Private Sub Worksheet_Change(ByVal Target As Range) 3 If Cells(ActiveSheet.Rows.Count, 1).Value = "" Then ' 正解の数を設定 4 Randomize 5 Application.EnableEvents = False 6 Cells(ActiveSheet.Rows.Count, 1).value = Int(Rnd*20)+1 7 Application.EnableEvents = True 8 End If 9 If Str(Target.Value) = Str(Cells(ActiveSheet.Rows.Count, 1).Value) Then 10 MsgBox Target.Value & " Hit!" 11 End If 12End Sub 13 14Private Sub Worksheet_Activate() 15 Application.EnableEvents = False 16 ActiveSheet.UsedRange.Clear 17 Application.EnableEvents = True 18End Sub
zip圧縮ファイルに同梱されている event03.vbs または event03.js を実行すると、上のイベントプロシージャが Sheet1 に組み込まれます。
〜 以上 〜