ねらい:Excelの「ウォッチャー」で変化を検知し、素早く安全に処理する
ウォッチャーは「何かが起きたら見張って反応する仕組み」です。Excel VBAでは、シートの値変更、タイマー、ファイル更新、計算完了、進捗閾値など、様々な“変化”を監視して自動で動かせます。ポイントは、監視は軽く、本処理は別の入口に分離し、暴走(再帰・多重起動)を防ぐこと。初心者でも貼って動かせるテンプレートと、実務で通用する設計の勘所をかみ砕いて解説します。
重要ポイントの深掘り
ウォッチャーは“常に回り続けるもの”ではなく、“イベントや一定間隔で軽く見て必要なら本処理を呼ぶもの”です。監視ロジックは最小限、重い処理は標準モジュールのSubへ委譲し、EnableEventsや排他ロックで暴走・二重起動を必ず抑えます。
シート変更ウォッチャー:セル変更を検知して受付だけ行う
目的と基本設計
Worksheet_Changeイベントを使い、対象列の変更だけを受け付けます。イベント内では検証・軽い通知・フラグ立てまでに留め、重い集計や書き出しは入口Subへ渡します。再帰発火防止のため、セルを書き換える箇所はEnableEventsで挟みます。
例題:Score列の入力検証+集計更新をトリガー
' CScoreWatcher.cls(対象シートのウォッチャー)
Option Explicit
Public WithEvents Ws As Worksheet
Public Sub Init(ByVal target As Worksheet)
Set Ws = target
End Sub
Private Sub Ws_Change(ByVal Target As Range)
If Intersect(Target, Ws.Range("D:D")) Is Nothing Then Exit Sub ' D列のみ監視
If Target.CountLarge > 1 Then Exit Sub ' 範囲変更は対象外
If Not IsNumeric(Target.Value) Or Target.Value < 0 Or Target.Value > 100 Then
Application.EnableEvents = False
Target.Value = ""
Application.EnableEvents = True
MsgBox "Scoreは0~100の数値です。", vbExclamation
Exit Sub
End If
Run_RefreshSummary ' 本処理へ委譲(配列I/Oで高速)
End Sub
VB' ThisWorkbook(ウォッチャーの寿命を確保)
Option Explicit
Private gWatchers As Collection
Private Sub Workbook_Open()
Set gWatchers = New Collection
AttachWatcher Worksheets("Input")
AttachWatcher Worksheets("Input2")
End Sub
Private Sub AttachWatcher(ByVal ws As Worksheet)
Dim w As New CScoreWatcher
w.Init ws
gWatchers.Add w ' コレクション保持で破棄されないようにする
End Sub
VB' ModService(本処理:配列I/Oで集計更新)
Option Explicit
Public Sub Run_RefreshSummary()
On Error GoTo EH
AppEnter "集計更新"
Dim arr As Variant: arr = Worksheets("Input").Range("A1").CurrentRegion.Value
Dim rows As Long: rows = UBound(arr, 1)
Dim idx As Long: idx = IndexByHeader(arr, "Score")
Dim sum As Double, i As Long
For i = 2 To rows: sum = sum + CDbl(arr(i, idx)): Next
Worksheets("Summary").Range("B2").Value = sum
AppLeave: Exit Sub
EH:
AppLeave: MsgBox "失敗: " & Err.Description, vbExclamation
End Sub
Public Function IndexByHeader(ByVal data As Variant, ByVal name As String) As Long
Dim j As Long
For j = 1 To UBound(data, 2)
If StrComp(CStr(data(1, j)), name, vbTextCompare) = 0 Then IndexByHeader = j: Exit Function
Next
Err.Raise 9100, , "ヘッダーがありません: " & name
End Function
Public Sub AppEnter(Optional ByVal status As String = "")
Application.ScreenUpdating = False
Application.EnableEvents = False
Application.Calculation = xlCalculationManual
If Len(status) > 0 Then Application.StatusBar = status
End Sub
Public Sub AppLeave()
Application.StatusBar = False
Application.Calculation = xlCalculationAutomatic
Application.EnableEvents = True
Application.ScreenUpdating = True
End Sub
VB重要ポイントの深掘り
イベント内は“受付専用”に徹することが安定運用の鍵です。セル書き換え時はEnableEventsで再帰を止め、重い処理は別Subに逃がす。ウォッチャーの寿命はコレクション保持で確保し、Workbook_Openで一度だけ登録します。
タイマーウォッチャー:一定間隔で軽く監視し、必要時のみ動く
目的と基本設計
Application.OnTimeで定期的に監視を走らせます。毎回フル処理はせず、軽い条件チェック(更新時刻・フラグ・未処理件数など)で必要なときだけ本処理を呼びます。キャンセル可能なフラグを用意し、停止手段を確保します。
例題:未処理フラグが立っている時だけ集計を再実行
' ModTimerWatcher.bas
Option Explicit
Private gNextTime As Date
Private gStop As Boolean
Public Sub StartWatcher(ByVal seconds As Double)
gStop = False
ScheduleNext seconds
End Sub
Public Sub StopWatcher()
gStop = True
End Sub
Private Sub ScheduleNext(ByVal seconds As Double)
gNextTime = Now + seconds / 86400# ' 秒→日
Application.OnTime gNextTime, "TickWatcher", , True
End Sub
Public Sub TickWatcher()
If gStop Then Exit Sub
If NeedsProcess() Then
Run_RefreshSummary
End If
ScheduleNext 5 ' 次回を5秒後に予約
End Sub
Private Function NeedsProcess() As Boolean
Dim flag As Variant: flag = Worksheets("Input").Range("Z1").Value
NeedsProcess = (CStr(flag) = "PENDING")
End Function
VB重要ポイントの深掘り
タイマーは「疑似非同期」。UIが固まりにくく、監視負荷も軽くできます。OnTimeは予約とキャンセルの扱いに注意し、停止フラグで安全に止められるようにします。毎回の本処理は配列I/Oと開始・終了枠で囲み、安定性を確保します。
ファイルウォッチャー:外部ファイルの更新を検知して取り込む
目的と基本設計
CSVや他ブックの更新時刻を定期的にチェックし、変更があったら取り込むウォッチャーです。FileSystemObjectで最終更新日時を取得し、前回記録との差分で判定します。ネットワーク遅延に備えて、リトライやフォルダ保証を併用します。
例題:CSV更新を検知して自動インポート
' ModFileWatcher.bas
Option Explicit
Private gLastStamp As Date
Public Sub StartCsvWatcher(ByVal csvPath As String, ByVal seconds As Double)
If gLastStamp = 0 Then gLastStamp = GetStamp(csvPath)
StartWatcher seconds ' タイマーウォッチャーを流用(TickWatcherが呼ばれる)
End Sub
Public Function NeedsProcess() As Boolean
Dim path As String: path = GetConfigString("CSV_PATH")
Dim cur As Date: cur = GetStamp(path)
If cur > gLastStamp Then
gLastStamp = cur
NeedsProcess = True
Else
NeedsProcess = False
End If
End Function
Private Function GetStamp(ByVal path As String) As Date
Dim fso As Object: Set fso = CreateObject("Scripting.FileSystemObject")
If fso.FileExists(path) Then GetStamp = fso.GetFile(path).DateLastModified
End Function
VB' ModConfig.bas(CSVパスなどをConfigから読む)
Option Explicit
Private Function ConfigSheet() As Worksheet: Set ConfigSheet = ThisWorkbook.Worksheets("Config"): End Function
Public Function GetConfigString(ByVal key As String) As String
Dim ws As Worksheet: Set ws = ConfigSheet()
Dim last As Long: last = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row
Dim r As Long
For r = 2 To last
If StrComp(CStr(ws.Cells(r, "A").Value), key, vbTextCompare) = 0 Then
GetConfigString = Trim$(CStr(ws.Cells(r, "B").Value))
Exit Function
End If
Next
Err.Raise 900, , "Configキーが見つかりません: " & key
End Function
VB重要ポイントの深掘り
ファイル監視は「最終更新日時」を使えば十分な場面が多いです。ネットワーク共有では遅延やロックに注意し、取り込み側はSaveCopyAsやリトライ戦略を用いると堅牢になります。監視は軽く、本処理は配列I/Oで確実に。
計算完了ウォッチャー:再計算タイミングで処理を連動させる
目的と基本設計
大量の数式があるブックで、計算完了のタイミングに合わせて集計やエクスポートを連動させます。Application_SheetCalculateやWorksheet_Calculateを使います。頻発しやすいので、簡易的なクールダウン(一定時間の間隔保証)を入れます。
例題:計算完了から2秒以内の連続発火を抑制して集計
' CCalcWatcher.cls
Option Explicit
Public WithEvents App As Application
Private lastFire As Date
Private Sub Class_Initialize()
Set App = Application
End Sub
Private Sub App_SheetCalculate(ByVal Sh As Object)
If Now - lastFire < 2 / 86400# Then Exit Sub ' 2秒クールダウン
lastFire = Now
Run_RefreshSummary
End Sub
VB' ThisWorkbook(寿命確保)
Option Explicit
Private gCalc As CCalcWatcher
Private Sub Workbook_Open()
Set gCalc = New CCalcWatcher
End Sub
VB重要ポイントの深掘り
計算完了イベントは短時間に多重発火しがちです。クールダウンで回数を間引き、処理は入口Subへ。必要ならタイマーウォッチャーへ渡して“次回にまとめる(OnTime予約)”と、UIの体感がさらに安定します。
進捗ウォッチャー:閾値到達時だけUI更新する
目的と基本設計
長ループの中で毎回UI更新すると激遅になります。進捗ウォッチャーは「1〜5%刻み」を基準に、到達時だけUIへ反映します。関数に切り出して“呼ぶだけ”にすると、どの処理でも再利用できます。
例題:1%刻みでステータスバー更新
' ModProgressWatcher.bas
Option Explicit
Public Sub TickProgress(ByVal cur As Long, ByVal total As Long, Optional ByVal label As String = "進捗")
If total <= 0 Then Exit Sub
Dim stepN As Long: stepN = Application.WorksheetFunction.Max(1, total \ 100)
If cur Mod stepN = 0 Then
Application.StatusBar = label & " " & Format(cur / total, "0%") & " (" & cur & "/" & total & ")"
DoEvents
End If
End Sub
VB' 使い方(任意のループで呼ぶ)
Dim i As Long
For i = 1 To total
' ...処理...
TickProgress i, total, "集計"
Next
Application.StatusBar = False
VB重要ポイントの深掘り
進捗は「安心感のためのUI」。呼びすぎると逆に遅くなります。更新間隔を関数に固定し、どの処理でも同じルールで呼ぶと、体感と速度のバランスが安定します。
排他ウォッチャー:二重起動を防ぐ“実行中フラグ”
目的と基本設計
ボタン連打や複数人運用時の二重起動を防ぐため、実行ロックを入れます。名前定義や隠しセルに“今走っている”印を残し、入口でチェックしてスキップ。終了時に解除します。
例題:名前定義 nm_Lock を使った簡易ロック
' ModLockWatcher.bas
Option Explicit
Public Function TryLock() As Boolean
On Error Resume Next
ThisWorkbook.Names("nm_Lock").RefersToRange.Value = Environ$("UserName") & "@" & Format(Now, "yyyy-mm-dd HH:NN:SS")
TryLock = (Err.Number = 0)
Err.Clear
On Error GoTo 0
End Function
Public Sub Unlock()
On Error Resume Next
ThisWorkbook.Names("nm_Lock").RefersToRange.Value = ""
On Error GoTo 0
End Sub
VB' 入口で使用
Sub Run_SafeProcess()
If Not TryLock() Then
MsgBox "別の処理が実行中です。しばらくしてから再試行してください。", vbExclamation
Exit Sub
End If
On Error GoTo EH
AppEnter "実行"
' ...本処理...
AppLeave: Unlock: Exit Sub
EH:
AppLeave: Unlock
MsgBox "失敗: " & Err.Description, vbExclamation
End Sub
VB重要ポイントの深掘り
ウォッチャーは“起動頻度が高い”ので、二重起動対策は必須です。TryLock/Unlockを入口に組み込むと、事故の大半を予防できます。失敗時でもUnlockが確実に走るよう、Finally相当の枠で包みます。
導入手順と設計の勘所
最初に「受付と本処理の境界」を作る
イベントやタイマーのウォッチャーは受付専用、本処理は標準モジュールへ分離。開始・終了枠、配列I/O、進捗間引きの三点セットを本処理に常備します。
次に「寿命と登録の仕組み」を整える
ウォッチャークラスはコレクションやモジュール変数で保持し、Workbook_Openで一回だけAttach。Workbook_BeforeCloseで必要なら参照を解除してクリーンにします。
監視対象を「軽い条件」に絞る
毎回フル走査はせず、対象列、未処理フラグ、更新時刻など軽い条件で判定。必要時のみ入口を呼ぶと、安定性と速度が両立します。
まとめ:ウォッチャーは“軽く見張って、必要時だけ動く”のが正解
イベント、タイマー、ファイル、計算、進捗、排他——Excelでもウォッチャーは十分実用的です。受付は軽く、本処理は配列I/O+開始・終了枠で確実に。寿命と再帰制御を押さえれば、長期運用でも暴走しません。
