Application.OnTime で設定した無限実行を正しく制御する
昼ごはんにカレーうどんを食べました。
メチャおいしかったけど、胸やけがコワかったあてくしです
こんちくわ 壁|ω・)ノ
さて、今日の記事はApplication.OnTimeメソッドで作った無限実行は止める仕組みが必要。でも 止めるなら正しく止めましょう~なオハナシです。
でわいきます ̄▽ ̄)ノ
目次
背景
前回の記事では、指定した時間にマクロを起動させるApplication.OnTimeメソッド
を使って、学習時間を管理するマクロを紹介しました。
アルゴリズムのポイントはApplication.OnTimeで Nowから指定の時間後に自分自身を起動する
というものでした。ただし永久機関になってしまうので、ちゃんと止める仕組みを用意しましょうね~と書きました。
ところが、この止める仕組みとして前回紹介したプロシージャでは誤作動が起こる
ことがわかりました。なぜだろうかと頭をひねり続けて土曜日の時間を延々と過ごしたのですが、一つの結論と対策にたどり着いたので、修正コード
とともに紹介します。
Application.OnTime を使った時間更新アルゴリズム
例えば、10分間隔で表示時間を更新するなら、以下の様なコードが書けます。
このコードは以下のように動作して、A1セルの時間表示を更新していきます、
ただしコレはきちんと制御しないと、いつまでも時間更新が止まらないことになります。ところが、下の図のように、間に停止処理をただ挟み込むだけでは止めることができません。
Application.OnTimeメソッドが実行された瞬間に、次の実行予約が確定するためですね。
永久機関を制御するために
そこで、Application.OnTimeメソッドを実行するフラグを使った以下のような仕組みを考えました(前回の記事で紹介した仕組み)。
このコードでは以下のような動作を期待しました。
結果、これでうまく動作したので、前回はこのまま記事を公開したのでした。
不具合発見
作成したコードで本格運用を始めたのですが、ふとカウンターが変な動作をするに気が付きました。
まずは、下記Gifアニメのカウンターの動きをご覧ください。
なんか、なんかカウンターが不規則な動きをしてます。
しかも、どう見ても設定の時間間隔ではない。。
原因を特定するのに時間がかかりましたが(土曜日の日中の時間をほぼ丸ごと使いましたw)、結論としてはカウンターのON/OFFを短時間の間に繰り返したことが原因で起こる不具合だということがわかりました。
では、何が起こっていたのか。
図にすると以下のような感じです。
先のコードでは、停止ボタンポチー
後も発生するClockCounterの不要な予約起動そのものはスルーして、次の予約を発生させない
という仕組みでしたが、結果的にそれが不具合の原因になっていたんですね。
不具合の対策
実いうと停止ボタンポチー後に発生するClockCounterの不要な予約起動は避けられないと思っていました。でも、ちゃーんと制御するための仕組みが準備されていました。
先日の記事を思い出してみましょう。
(Applicationは省略できません)
引数 | 省略 | 説明 | 型 |
---|---|---|---|
EarliestTime | 否 | プロシージャを実行したい時刻 |
Date |
Procedure | 否 | 実行するプロシージャ名(ダブルクオーテーションで囲みます) (文字列を引数として渡す形での設定は可能だが、変数を渡す形での設定はできないよもよう) |
String |
LatestTime | 可 | (指定のプロシージャが実行できない状態のとき)実行できる状態になるまで待つ時刻 省略した場合は指定のプロシージャが実行できるまで待ちます |
Date |
Schedule | 可 | 設定済みのOnTimeメソッドの実行を取りやめるか否か | Boolean |
ということが書いてありましたね。
このうちの引数 [Schedule] のところを見ると、設定済みのOnTimeメソッドの実行を取りやめるか否か
と書いてあります(ハイ。私が書きました)。これをきちんと使わなければいけなかったんですね。
でわどのように記述するのか。
御覧のように、引数 Schedule=False を記述します。ただし、ここで気を付けないといけないのは、実行をスキャンセルする予約起動を正確に特定
しないといけないことです。つまり、起動するプロシージャ名だけでなく、起動時間の記述もそろえないといけない
のですね。
というわけで, ClockCounterのコードの群は以下のように作りました。
Now()+時間間隔
内容を変数reservedTimeに格納
しました。そして、その中身を停止用プロシージャでも利用できるよう`モジュールレベル変数として宣言しています。
これで、停止ボタンポチー
後に発生していた不要な予約起動も完全にキャンセルすることができるようになりました。
と、いうわけで前回掲載しましたコードは以下のように修正されました
Option Explicit Private Const timeCount = "0:00:10" '時間カウントの間隔を設定する Private reservedTime As Date Private isInitial As Boolean Public Sub TimerStart() isInitial = True MsgBox "始めます", vbInformation, "がんばれ~" Call ClockCounter End Sub Private Sub ClockCounter() With ws時間記録 '開始ボタンポチーした最初は時間の表示を更新しない If Not isInitial = True Then .Range("B2").Value = .Range("B2").Value + TimeValue(timeCount) .Range("C2").Value = .Range("C2").Value + TimeValue(timeCount) End If 'ClockCounterご本尊 reservedTime = Now() + TimeValue(timeCount) Application.OnTime reservedTime, "ClockCounter" isInitial = False End With End Sub Public Sub stopTimer() 'タイマーを止めるときは "今回の学習時間" 列のみ時間をリセットする Application.OnTime reservedTime, "ClockCounter", , False MsgBox "記録終了します", vbInformation, "終わったのかな? ん?" ws時間記録.Range("B2").Value = "0:00:00" End Sub
まとめ
今回の記事ではApplication.OnTimeメソッドの 自分予定呼び出しで作った無限実行を止める方法について書きました。前回の記事でもコードとしては掲載していたのですが、不具合が発生する不完全なものだということがわかりました。
Application.OnTimeメソッドには設定された予約実行を停止するための仕組みとして、引数 Schedule がちゃんと準備されており、それを使うことで不具合なくコードを実行することができるようになりました。
つまるところ、きちんと理解してから使えってことですねw
でわまた~  ̄▽ ̄)ノシ