VBAで実現する高精度ストップウォッチの設計と実装
Excel VBAにおいて時間を計測する際、多くの初心者が陥る罠が「Waitメソッド」や「Sleep関数」の誤用です。これらは処理を停止させるためのものであり、経過時間を正確に計測する目的には適していません。実務レベルで1/100秒単位の精度を確保したストップウォッチを構築するためには、Windows APIの「QueryPerformanceCounter」を活用するか、あるいはVBA標準の「Timer関数」を適切に制御する手法が求められます。
本稿では、VBAのTimer関数を最大限に活用し、画面描画の負荷を抑えつつ、正確な1/100秒計測を実現する「ストップウォッチ改」の実装方法を解説します。
詳細解説:Timer関数の特性と課題
VBAのTimer関数は、午前0時からの経過秒数を単精度浮動小数点型(Single)で返します。この関数の分解能はOSの環境に依存しますが、一般的に約1/100秒から1/64秒程度の精度を持っています。
しかし、単にループ内でTimerを監視するだけでは、以下の問題が発生します。
1. CPU負荷の増大:無限ループに近い状態になり、PC全体の動作が重くなる。
2. 画面描画の遅延:DoEventsを適切に配置しないと、ExcelのUIがフリーズする。
3. 浮動小数点誤差:長時間計測した場合、微小な誤差が蓄積する。
これを解決するためには、計測開始時の時刻を記録し、現在の時刻との差分を計算し続けるというロジックが必要です。また、画面更新(セルへの書き込み)は非常に重い処理であるため、更新頻度を制御する工夫が不可欠です。
サンプルコード:高精度ストップウォッチの実装
以下のコードは、標準モジュールに記述してください。シート上に「開始」「停止」「リセット」のボタンを配置し、それぞれにマクロを割り当てる想定です。
Option Explicit
' 計測状態を保持するモジュールレベル変数
Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Private m_StartTime As Double
Private m_IsRunning As Boolean
Private m_ElapsedTime As Double
' ストップウォッチ開始
Public Sub StartStopwatch()
If m_IsRunning Then Exit Sub
m_StartTime = Timer - m_ElapsedTime
m_IsRunning = True
Call UpdateDisplay
End Sub
' ストップウォッチ停止
Public Sub StopStopwatch()
m_IsRunning = False
End Sub
' ストップウォッチリセット
Public Sub ResetStopwatch()
m_IsRunning = False
m_ElapsedTime = 0
Range("A1").Value = "00:00.00"
End Sub
' 画面更新ループ
Private Sub UpdateDisplay()
Dim currentTime As Double
Do While m_IsRunning
' 現在の経過時間を計算
m_ElapsedTime = Timer - m_StartTime
' セルへの表示(mm:ss.00形式)
' Timer関数の値を加工して表示
Range("A1").Value = Format(m_ElapsedTime / 86400, "nn:ss") & "." & _
Format(Int((m_ElapsedTime - Int(m_ElapsedTime)) * 100), "00")
' UIのフリーズを防ぐ
DoEvents
' CPU負荷低減のための微小待機(10ミリ秒)
Sleep 10
Loop
End Sub
実務アドバイス:プロフェッショナルな設計への昇華
上記のコードは基本的な動作を保証しますが、実務でさらに品質を高めるためには以下の観点を考慮する必要があります。
1. 精度と負荷のトレードオフ
Sleep関数の引数を小さくすれば精度は上がりますが、CPU負荷が高まります。1/100秒計測であれば10ミリ秒のSleepで十分です。これ以上精度を求める場合は、VBAではなくC#などで作成したDLLを呼び出すか、別の計測手法を検討すべきです。
2. セル書き込みの最適化
セル(Range)へのアクセスは非常に低速です。もし計測値を別の場所でも計算に使用する場合は、セルに書き込む回数を減らし、変数内での計算を優先してください。今回のサンプルでは視認性を優先していますが、ログを取るような処理であれば、配列に溜め込んでから一括出力するのが定石です。
3. 誤操作防止のUI設計
ストップウォッチが動作中に「開始」ボタンを連打されると、複数のループが同時起動し、暴走する恐れがあります。これを防ぐために、開始ボタンを押した瞬間にボタンのEnabledプロパティをFalseにする、あるいはグローバル変数で制御するなどの排他制御を必ず実装してください。
4. 日付跨ぎへの対応
Timer関数は0時を過ぎると0に戻ります。そのため、長時間(深夜0時をまたぐ可能性のある)計測を行う場合は、Timer関数単体ではなく、Now関数やWindows APIのGetTickCountを使用する設計へ切り替える必要があります。今回のコードは「短時間の計測」に特化した実装です。
まとめ:VBAにおける時間計測の極意
VBAでストップウォッチを作成する際、最も重要なのは「計測のロジック」と「UIの更新」を明確に分離することです。Timer関数は非常に便利ですが、その特性を正しく理解し、OSのスケジューリング(DoEvents)と適切に協調させることで、安定したツールを作成できます。
プロフェッショナルなエンジニアは、単に「動くもの」を作るのではなく、「リソースを無駄にせず、かつ期待される精度を安定して提供できるもの」を作ります。本稿のサンプルコードをベースに、ご自身の業務環境に合わせて調整を行ってください。特に、計測値の精度がクリティカルな業務で使用する場合は、必ず事前に実機での検証を行い、Timer関数の挙動が環境によって左右されないか確認することをお勧めします。
VBAは、工夫次第で非常に強力なツールへと進化します。このストップウォッチのロジックを応用し、処理時間の計測やタスク管理など、業務効率化の幅を広げていってください。
