Excel VBA 逆引き集 | ウォッチャー

Excel VBA
スポンサーリンク
  1. ねらい:Excelの「ウォッチャー」で変化を検知し、素早く安全に処理する
    1. 重要ポイントの深掘り
  2. シート変更ウォッチャー:セル変更を検知して受付だけ行う
    1. 目的と基本設計
    2. 例題:Score列の入力検証+集計更新をトリガー
    3. 重要ポイントの深掘り
  3. タイマーウォッチャー:一定間隔で軽く監視し、必要時のみ動く
    1. 目的と基本設計
    2. 例題:未処理フラグが立っている時だけ集計を再実行
    3. 重要ポイントの深掘り
  4. ファイルウォッチャー:外部ファイルの更新を検知して取り込む
    1. 目的と基本設計
    2. 例題:CSV更新を検知して自動インポート
    3. 重要ポイントの深掘り
  5. 計算完了ウォッチャー:再計算タイミングで処理を連動させる
    1. 目的と基本設計
    2. 例題:計算完了から2秒以内の連続発火を抑制して集計
    3. 重要ポイントの深掘り
  6. 進捗ウォッチャー:閾値到達時だけUI更新する
    1. 目的と基本設計
    2. 例題:1%刻みでステータスバー更新
    3. 重要ポイントの深掘り
  7. 排他ウォッチャー:二重起動を防ぐ“実行中フラグ”
    1. 目的と基本設計
    2. 例題:名前定義 nm_Lock を使った簡易ロック
    3. 重要ポイントの深掘り
  8. 導入手順と設計の勘所
    1. 最初に「受付と本処理の境界」を作る
    2. 次に「寿命と登録の仕組み」を整える
    3. 監視対象を「軽い条件」に絞る
  9. まとめ:ウォッチャーは“軽く見張って、必要時だけ動く”のが正解

ねらい: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+開始・終了枠で確実に。寿命と再帰制御を押さえれば、長期運用でも暴走しません。

タイトルとURLをコピーしました