#イマイチ理解できないDoEvents
ExcelVBA
をそこそこ使いこなしている人でもイマイチ理解していないトピック。その一つがDoEvents
だろう。
ネット上の数あるメジャーな解説サイトを調べてみたが、何だか良く理解できない。
その中で以下のような説明を見た。
発生したイベントがオペレーティングシステムによって処理されるように制御を戻します。
この説明でもわかる人にはわかるのだろうが、自分の場合はこれだけではハッキリと理解できなかった。
それなら自分で説明を書いてみようと思い立ったのがこの記事である。
##無限ループから抜け出せ!
以下のマクロを実行したらどうなるかは、Doループを知っていればわかるだろう。
Sub Hoge_1()
Do
' なんかの処理
Loop
End Sub
これは無限ループである。ループからは一生抜けられない。これを実行するとマウスもキーボードも利かなくなり制御不能、終いにはExcel画面が真っ白になる。無限ループを一番簡単に書いたものである。
Sub Hoge_2()
Do Until Range("A1").Value = 1
' なんかの処理
Loop
End Sub
では上記はどうなるだろうか。
セルA1
に1
を入力すればループから抜けられるだろうと期待しだが、Hoge_1
同様に画面が固まりセルに文字を入力することはできない。やはり無限ループである。
そこで、DoEvents
を使ってみるとどうか。
Sub Hoge_3()
Do Until Range("A1").Value = 1
' なんかの処理
DoEvents
Loop
MsgBox "ループを抜けました。"
End Sub
ループ内にDoEvents
をたった一行加えるだけで画面が固まらずマウスクリックやキー入力が普通にできるようになる。そしてセルA1
に1
を入力するとMsgBox
が現れて処理が終了する。ループを抜けられたということである。
これは一体どういうことなのか?DoEvents
は何をしているのだろう。
##読んで字の如く - DoEvents
Sub Hoge_4()
Application.Wait [Now()] + 10 / 86400
' 60秒 × 60分 × 24時間 = 86,400秒 (1日の秒数)
DoEvents
MsgBox "マクロ処理中のイベントを実行"
End Sub
上記ではApplication.Wait
で10秒待機してからDoEvents
を実行している。このマクロを実行してExcelワークシート内で作業をしてみよう。
マウスクリックやキー入力などの作業をしても画面が固まってしまい作業内容は無視されたかのように見える。
ここで構わず作業をしてみよう。
例えば、
イベント① ---> セルB3
をクリック
イベント② ---> セルB3
に9
を入力
イベント③ ---> セルB5
をクリック
イベント④ ---> セルB5
を黄色で塗りつぶす
を10秒間でやってみる。画面は固まって無反応だが、とにかくやってみよう。10秒後にDoEvents
が実行されると①〜④の溜まっていたイベントが処理され、MsgBox
が現れる。
実はDoEvents
が無くてもマクロ終了後にイベント処理は行われる。キモはMsgBox
が出現する前に一連のイベント処理が行われるということだ。これは本来後回しにされるイベント処理をマクロ実行中に行ったということである。
つまりDoEvents
がやっていることは、読んで字の如く「イベントを行う」と言うことである。
マクロ開始からDoEvents
が呼ばれる時点までに溜まったイベント、あるいはDoEvents
実行終了後に次のDoEvents
を実行するまでに溜まった「イベントを行う」のである。もちろんイベントが溜まっていなければ何もせずに終了するだけなのでループ内で何回実行しても負荷は掛からない。
##なんちゃって非同期処理
ExcelVBA
は非同期処理ができないらしい。
例えば、処理が重たくて終了までに時間がかかって画面が固まってしまい、正常な処理中にもかかわらずハングアップしてしまったのかのように見える場合がある。
画面が固まることを防ぐため、重たい処理はC#(あるいはVB.net)などで書いたexeにさせるという方法がある。
exeを実行したら即終了であれば単にexeを呼び出すだけで良いが、exeの結果によって後続処理を変える必要がある場合、DoEvents
が役に立つ。
以下はユーザーフォームに配置したボタンのクリックイベントプロシージャである。
WshShell
クラスでexeを実行し、exe処理終了までテキストボックスに簡単なアニメーションを表示して処理中であることをユーザーに知らせている。
Private eventFLg As Boolean
Private Sub CommandButton1_Click()
If eventFLg Then
MsgBox "処理中です。", vbInformation
Exit Sub
End If
eventFLg = True
Dim wsh As New WshShell
Dim rt As WshExec
Set rt = wsh.Exec("C:\hoge\hoge.exe")
Dim logStr As String
logStr = "-- START --" & vbCr
TextBox1.Value = logStr
Dim pos As Integer, anime As String
pos = 1
Do While rt.Status = 0
Application.Wait [Now()] + 2 / 864000
Select Case pos
Case 1
anime = ">>"
pos = pos + 1
Case 2 To 9
anime = " " & anime
pos = pos + 1
Case 10
anime = Replace(anime, ">>", "<<")
pos = pos + 1
Case 11 To 17
anime = Mid(anime, 3)
pos = pos + 1
Case 18
anime = "<<"
pos = 1
End Select
TextBox1.Value = logStr & vbCr & anime
DoEvents
Loop
TextBox1.Value = logStr & vbCr & "-- END --"
eventFLg = False
If rt = -1 Then
' exeでエラーがあったときの処理
' ...
Else
' exeが正常に終了したときの処理
' ...
End If
End Sub
static int Main(string[] args)
{
try
{
int result = 0;
// 重たい処理
// ...
return result;
}
catch (Exception ex)
{
// エラー処理
// ...
return -1;
}
}
Doループを抜ける条件であるrt.Status
はexeが実行中は0
である。ループの中でDoEvents
が繰り返し呼ばれる為、ユーザーフォーム上で起こったイベントは即座に反映され、画面が固まることはない。exeの終了とともにrt.Status
が0
でなくなり、ループを抜け、後続処理が行われる。
ユーザーフォーム上のボタンはDoループ中はクリックができてしまうのでexe処理中はフラグを立てて二重起動を回避している。
他にもタイトルバー右上の がクリックされたときのことや、その他のコントロールが配置されていた場合のときのことも考慮が必要になるが、それは省略。
また、rt
変数でexeのint
の戻り値を得られ、exeが正常終了したかどうか確認できる。
exeを実行する為のWshShell
クラスを使うには参照設定で『Windows Script Host Object Model』へのチェックが必要だ。
Excel内部の非同期ではないので微妙だが、なんちゃって非同期処理はDoEvents
が役立つ一例である。
-- END --