読んで字の如くDoEvents

#イマイチ理解できないDoEvents
`ExcelVBA`をそこそこ使いこなしている人でもイマイチ理解していないトピック。その一つが`DoEvents`だろう。
ネット上の数あるメジャーな解説サイトを調べてみたが、何だか良く理解できない。
その中で以下のような説明を見た。

>発生したイベントがオペレーティングシステムによって処理されるように制御を戻します。

この説明でもわかる人にはわかるのだろうが、自分の場合はこれだけではハッキリと理解できなかった。:sweat_smile:
それなら自分で説明を書いてみようと思い立ったのがこの記事である。

##無限ループから抜け出せ！
以下のマクロを実行したらどうなるかは、Doループを知っていればわかるだろう。

```vb:ExcelVBA
Sub Hoge_1()
    Do
        ' なんかの処理
    Loop
End Sub
```
これは無限ループである。ループからは一生抜けられない。これを実行するとマウスもキーボードも利かなくなり制御不能、終いにはExcel画面が真っ白になる。無限ループを一番簡単に書いたものである。

```vb:ExcelVBA
Sub Hoge_2()
    Do Until Range("A1").Value = 1
        ' なんかの処理
    Loop
End Sub
```
では上記はどうなるだろうか。
セル`A1`に`1`を入力すればループから抜けられるだろうと期待しだが、`Hoge_1`同様に画面が固まりセルに文字を入力することはできない。やはり無限ループである。

そこで、`DoEvents`を使ってみるとどうか。

```vb:ExcelVBA
Sub Hoge_3()
    Do Until Range("A1").Value = 1
        ' なんかの処理
        DoEvents
    Loop
    MsgBox "ループを抜けました。"
End Sub
```
ループ内に`DoEvents`をたった一行加えるだけで画面が固まらずマウスクリックやキー入力が普通にできるようになる。そしてセル`A1`に`1`を入力すると`MsgBox`が現れて処理が終了する。ループを抜けられたということである。
これは一体どういうことなのか？`DoEvents`は何をしているのだろう。:astonished::question:

##読んで字の如く - DoEvents
```vb:ExcelVBA
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`が現れる。
![09CCCFE9-B736-406F-B3CD-E86428FC2F41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294209/43b669b6-2cbf-4bd4-06fc-7a1ae3a78c12.png)
実は`DoEvents`が無くてもマクロ終了後にイベント処理は行われる。キモは`MsgBox`が出現する前に一連のイベント処理が行われるということだ。これは**本来後回しにされるイベント処理をマクロ実行中に行った**ということである。
つまり`DoEvents`がやっていることは、読んで字の如く「イベントを行う」と言うことである。

マクロ開始から`DoEvents`が呼ばれる時点までに溜まったイベント、あるいは`DoEvents`実行終了後に次の`DoEvents`を実行するまでに溜まった「イベントを行う」のである。もちろんイベントが溜まっていなければ何もせずに終了するだけなのでループ内で何回実行しても負荷は掛からない。:laughing:

##なんちゃって非同期処理
`ExcelVBA`は非同期処理ができないらしい。
例えば、処理が重たくて終了までに時間がかかって画面が固まってしまい、正常な処理中にもかかわらずハングアップしてしまったのかのように見える場合がある。
画面が固まることを防ぐため、重たい処理はC#（あるいはVB.net）などで書いたexeにさせるという方法がある。
exeを実行したら即終了であれば単にexeを呼び出すだけで良いが、exeの結果によって後続処理を変える必要がある場合、`DoEvents`が役に立つ。
以下はユーザーフォームに配置したボタンのクリックイベントプロシージャである。
`WshShell`クラスでexeを実行し、exe処理終了までテキストボックスに簡単なアニメーションを表示して処理中であることをユーザーに知らせている。

```vb:ExcelVBA

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
```

```C#:hoge.exe
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処理中はフラグを立てて二重起動を回避している。
他にもタイトルバー右上の :x: がクリックされたときのことや、その他のコントロールが配置されていた場合のときのことも考慮が必要になるが、それは省略。:stuck_out_tongue_winking_eye:
また、`rt`変数でexeの`int`の戻り値を得られ、exeが正常終了したかどうか確認できる。

exeを実行する為の`WshShell`クラスを使うには参照設定で『Windows Script Host Object Model』へのチェックが必要だ。

Excel内部の非同期ではないので微妙だが、なんちゃって非同期処理は`DoEvents`が役立つ一例である。

:laughing::v::laughing::v::laughing::v: -- END --