Excel VBAでSolverの実行中を判定して処理をスキップする方法|高速化・安定運用の実践ガイド

Excel の Solver は強力ですが、実行中にワークシート全体の再計算やイベントが多発して処理が重くなる、という悩みを抱えがちです。そこで本稿では「Solver が動いている間だけ特定の処理をスキップする」ための実用的な回避策を、コピペで使える VBA コードとともに体系的に解説します。マクロでのラップ、Excel 設定の判定、フラグセル、オンタイマー監視まで、現場で役立つ設計と運用を一気に整えましょう。

目次

問題の本質:Solver 実行状態は標準 API で取得できない

前提として、Windows 版 Excel の標準機能や Solver アドインには「いま Solver が実行中か」を直接返すプロパティ/イベントはありません。SolverSolve は同期(ブロッキング)呼び出しのため、実行中に状態を問い合わせる API も用意されていません。一方で Windows 版 Solver は各イテレーションでシート全体を再計算するため、UDF・イベント・画面描画が繰り返しトリガーされ、重い処理が二次災害的にボトルネック化しがちです。

方針:直接検知を諦め、「確実に止める/避ける」で設計する

純正 API に頼らず、以下のいずれか(または併用)で「Solver の最中だけは重い処理を走らせない」設計に切り替えます。

#アプローチ長所短所・留意点
Aマクロで Solver をラップ
例:RunSolver → 事前に SuspendForSolver(計算=手動、イベント/画面更新オフ)、終了後 ResumeAfterSolver で復元
・確実にフラグを管理できる
・ユーザー操作を変えずにボタンに割り当て可
・必ずマクロ経由で Solver を起動する運用が必要
BExcel 設定の状態を判定してスキップ
If Application.Calculation = xlCalculationManual And Not Application.EnableEvents Then Exit Sub
・コードが簡潔
・総当たりループなどにも応用可
・普段から「計算=手動」を使うユーザー環境だと誤判定の恐れ
Cフラグセル方式
Solver 起動前に “Running”、終了後に “Idle” を書く
・VBA でも手動 Solver でも使える・フラグ更新を忘れると破綻
Dオンタイマー監視方式
Application.OnTime で 1 秒ごとに目的セルを監視して値変化が続く間を “Running”、停止後 n 秒で “Idle”
・ユーザー操作不要
・手動 Solver にも対応
・コード量が増える
・大規模ブックでは監視負荷が増える

推奨:まずは A(マクロラップ)を基本にし、重いイベント側では B(設定判定)のガードを入れるのがシンプルかつ堅牢。どうしてもマクロ経由で起動させられない運用なら D を併用します。

完成コード(遅延バインド版):参照設定なしで安全に動くラッパー

Solver 参照(VBE > ツール > 参照設定)を追加しなくても動く「遅延バインド版」の完成形です。既存ブックにそのまま貼り付けできます。

Module M_SolverGuard_LateBind

'======================
' Module: M_SolverGuard_LateBind
'======================
Option Explicit

'--- 状態コンテキスト(復元用)
Private Type TAppCtx
Calc As XlCalculation
Events As Boolean
Screen As Boolean
Status As Variant
Interrupt As XlCalculationInterruptKey
End Type

Private gCtx As TAppCtx
Private gInSolver As Boolean         ' 自前フラグ(A)
Private gTouched As Boolean          ' 自分が設定を変更したかの印
Private Const FLAG_NAME As String = "SolverStatus" '(C)で使う名前付きセル(任意)

'=== 公開 API: これをボタンやリボンに割り当て ===
Public Sub RunSolver_LateBind()
On Error GoTo EH

```
SuspendForSolver
WriteFlag "Running"              '(C)

' --- ここで必要なら制約や目的セルを設定 ---
' 例:
' Application.Run "Solver.xlam!SolverReset"
' Application.Run "Solver.xlam!SolverOk", Range("Target"), 1, , Range("ByChange")
' Application.Run "Solver.xlam!SolverAdd", Range("C1"), 3, 0    ' >= 0 など

' Solver 実行(UserFinish:=True でダイアログ無し)
Application.Run "Solver.xlam!SolverSolve", True
```

Cleanup:
WriteFlag "Idle"                 '(C)
ResumeAfterSolver
Exit Sub

EH:
' 必ず復元する(途中停止・エラーでも原状回復)
ResumeAfterSolver
Resume Cleanup
End Sub

'=== イベント側・UDF側から呼ぶ「多層ガード」 ===
Public Function IsSolverLikelyRunning() As Boolean
' 1) 自前フラグ(最も確実)
If gInSolver Then
IsSolverLikelyRunning = True
Exit Function
End If

```
' 2) 設定ヒューリスティック(B)
If Application.Calculation = xlCalculationManual _
   And Not Application.EnableEvents _
   And gTouched Then
    IsSolverLikelyRunning = True
    Exit Function
End If

' 3) フラグセル(C)
On Error Resume Next
Dim nm As Name
Set nm = ThisWorkbook.Names(FLAG_NAME)
If Not nm Is Nothing Then
    IsSolverLikelyRunning = (LCase$(CStr(nm.RefersToRange.Value2)) = "running")
End If
On Error GoTo 0
```

End Function

'=== 事前停止(A) ===
Public Sub SuspendForSolver()
With Application
gCtx.Calc = .Calculation
gCtx.Events = .EnableEvents
gCtx.Screen = .ScreenUpdating
gCtx.Status = .StatusBar
gCtx.Interrupt = .CalculationInterruptKey

```
    .Calculation = xlCalculationManual
    .EnableEvents = False
    .ScreenUpdating = False
    .DisplayStatusBar = True
    .StatusBar = "Solver 実行中..."
    .CalculationInterruptKey = xlNoKey
End With
gInSolver = True
gTouched = True
```

End Sub

'=== 事後復元(A) ===
Public Sub ResumeAfterSolver()
On Error Resume Next ' 環境差異に配慮
With Application
.Calculation = gCtx.Calc
.EnableEvents = gCtx.Events
.ScreenUpdating = gCtx.Screen
.StatusBar = gCtx.Status
.CalculationInterruptKey = gCtx.Interrupt
End With
gInSolver = False
gTouched = False

```
' 自動計算に戻したら最終再計算を一度だけ
If gCtx.Calc = xlCalculationAutomatic Then Application.Calculate
```

End Sub

'=== フラグセル書き込み(C) ===
Private Sub WriteFlag(ByVal stateText As String)
On Error Resume Next
Dim nm As Name
Set nm = ThisWorkbook.Names(FLAG_NAME)
If Not nm Is Nothing Then nm.RefersToRange.Value2 = stateText
On Error GoTo 0
End Sub 

使い方(最短)

  1. VBE(Alt + F11)で標準モジュールに上記コードを貼り付ける。
  2. 必要に応じて Solver.xlam!SolverOkSolverAddRunSolver_LateBind のコメント部分に記述して、モデルをセットアップする。
  3. クイックアクセスツールバー(QAT)やリボンのカスタムボタンに RunSolver_LateBind を割り当てる。

以後、ユーザーはそのボタンで Solver を起動するだけ。実行中はイベント・画面更新が確実に止まり、終了後は必ず復元されます。

イベント/UDF 側の「すり抜け防止」:即時スキップ判定の定型

重い処理が置かれがちな Worksheet_ChangeWorksheet_CalculateWorkbook_SheetChange などのイベントハンドラ、および UDF の先頭には、次の 1 行を置いてください。

If IsSolverLikelyRunning() Then Exit Sub

UDF の場合は関数の定義に合わせて「軽量モード」に切り替えます。

Public Function HeavyUdf(ByVal x As Double) As Double
    If IsSolverLikelyRunning() Then
        ' Solver 中は軽量処理(例:入力をそのまま返す/キャッシュを返す)
        HeavyUdf = x
        Exit Function
    End If

```
' 通常時の重い計算(例)
HeavyUdf = ExpensiveCompute(x)
```

End Function 

参照設定あり版(早期バインド):開発効率を重視する場合

Solver への参照を設定しているプロジェクトでは、オブジェクトブラウザで定義が見え、入力補完が効きます。早期バインド版はこうなります。

Module M_SolverGuard_Ref

'======================
' Module: M_SolverGuard_Ref
' 前提:VBE「ツール > 参照設定」で「Solver」を有効化
'======================
Option Explicit

Private gCalcState As XlCalculation
Private gEventState As Boolean
Private gScreenState As Boolean
Private gInSolver As Boolean

Public Sub RunSolver_Ref()
On Error GoTo EH

```
SuspendForSolver_Ref

' 必要ならセットアップ
' SolverReset
' SolverOk SetCell:=Range("C5"), MaxMinVal:=1, ValueOf:=0, ByChange:=Range("D2:D10")
' SolverAdd CellRef:=Range("C1"), Relation:=3, FormulaText:=0

SolverSolve UserFinish:=True
```

Cleanup:
ResumeAfterSolver_Ref
Exit Sub

EH:
ResumeAfterSolver_Ref
Resume Cleanup
End Sub

Private Sub SuspendForSolver_Ref()
With Application
gCalcState = .Calculation
gEventState = .EnableEvents
gScreenState = .ScreenUpdating

```
    .Calculation = xlCalculationManual
    .EnableEvents = False
    .ScreenUpdating = False
End With
gInSolver = True
```

End Sub

Private Sub ResumeAfterSolver_Ref()
With Application
.Calculation = gCalcState
.EnableEvents = gEventState
.ScreenUpdating = gScreenState
End With
gInSolver = False
If gCalcState = xlCalculationAutomatic Then Application.Calculate
End Sub

Public Function IsSolverRunning_Ref() As Boolean
IsSolverRunning_Ref = gInSolver
End Function 

フラグセル(C):手動 Solver でも使える「見える化」

ユーザーが Solver ダイアログから手動で実行する文化が根強いチームでは、フラグセルの導入が有効です。手順

  1. 管理用シートにセル(例:Control!B2)を用意し、名前SolverStatus を定義。
  2. Solver を起動する直前にそのセルへ "Running"、終了後に "Idle" と入力する運用にする(マクロ版は自動更新)。
  3. 重い処理側の先頭で次を判定:If [SolverStatus] = "Running" Then Exit Sub

条件付き書式で「Running のとき赤帯」などの視覚化を加えると、運用ミスの早期発見にも役立ちます。

オンタイマー監視(D):セル変化を“鼓動”として捉える

Solver 実行中は目的セル(または目的関数の出力)が頻繁に変化します。この「変化が続く間」を “Running” とみなし、数秒間変化が止まったら “Idle” とする監視器の例です。

'======================
' Module: M_SolverMonitor
'======================
Option Explicit

Private gMonitorOn As Boolean
Private gLastValue As Variant
Private gNoChangeTicks As Long

Private Const TARGET As String = "Sheet1!D10"  ' 監視対象セル(調整してください)
Private Const INTERVAL As String = "00:00:01"  ' 1秒ごと

Public Sub StartSolverMonitor()
gMonitorOn = True
On Error Resume Next
gLastValue = Evaluate(TARGET)
On Error GoTo 0
gNoChangeTicks = 0
ScheduleNext
End Sub

Public Sub StopSolverMonitor()
gMonitorOn = False
End Sub

Private Sub ScheduleNext()
If Not gMonitorOn Then Exit Sub
Application.OnTime Now + TimeValue(INTERVAL), "SolverMonitorTick", , True
End Sub

Public Sub SolverMonitorTick()
If Not gMonitorOn Then Exit Sub

```
Dim v As Variant
On Error Resume Next
v = Evaluate(TARGET)
On Error GoTo 0

If VarType(v) <> VarType(gLastValue) Or v <> gLastValue Then
    Range("Control!B2").Value = "Running" ' 例:フラグセルへ書く
    gNoChangeTicks = 0
Else
    gNoChangeTicks = gNoChangeTicks + 1
    If gNoChangeTicks >= 3 Then           ' 3秒変化がなければ停止とみなす
        Range("Control!B2").Value = "Idle"
    End If
End If

gLastValue = v
ScheduleNext
```

End Sub 

タイマーは OnTime の性質上、ブックが最前面でなくても動きます。巨大ブックでは監視対象を「最終目的セル 1 つ」に絞ると負荷を抑えられます。

設定判定のみで捌く最小構成(B)

イベントが重く、今はとにかく止血したい 場面では以下の最小ガードが強力です。A を導入するまでの暫定策としても有用。

If Application.Calculation = xlCalculationManual _
   And Not Application.EnableEvents Then Exit Sub

この条件は「マクロ側で計算=手動&イベント停止に切り替える」という A の前提と相性が良く、誤判定を減らします。普段から「計算=手動」を好むユーザーが多い環境では、「自分が切り替えたか」 を示す自前フラグ(上記 gTouched など)と併用してください。

貼り付け・配線・UI の整備

  • 貼り付け位置:標準モジュール(挿入 > 標準モジュール)。
  • ボタン化:QAT またはリボンのカスタムボタンに RunSolver_LateBind を割り当て。従来のダイアログ起動の代わりに、ユーザーはこのボタンだけを使う運用に。
  • 手動派の救済:ダイアログで起動したいユーザー向けに、監視(D)を Workbook_Open で自動開始しておく。

堅牢性を高めるエラーハンドリング

  • 必ず復元:RunSolver には On Error GoTo EHCleanup ラベルを用意し、例外でも 復元 → フラグ更新 を保証。
  • Ctrl+Break の抑止:実行中は Application.CalculationInterruptKey = xlNoKey。強制停止の可能性を下げる(必要に応じて外す)。
  • 数式エラーの事前防御:零除算や範囲外などは IFIFERROR で潰しておくと、Solver の探索空間が安定しやすい。

高速化ベストプラクティス集(実地で効く項目)

  • 描画停止:Application.ScreenUpdating = False。ループや総当たりで 1〜2 桁の改善例が多い。
  • イベント停止:Application.EnableEvents = False。イベント嵐の根絶。
  • 自動計算の一時停止:Application.Calculation = xlCalculationManual → 復元時に一度だけ Calculate
  • ステータスバー利用:進捗や「Solver 実行中…」の表示でユーザー不安を軽減。
  • DisplayPageBreaks 無効:印刷プレビュー系のコストを削る(大規模シートで効く)。
  • 揮発関数の抑制:NOW/TODAY/RAND などをモデル外へ退避、または手動更新に。
  • UDF の軽量化:Solver 中はキャッシュ返却または計算を簡略化。
  • 範囲アクセスの集約:セル単位の Get/Set を避け、配列に一括ロード。
  • 構造の明確化:「Solver ゾーン」と「通常ゾーン」を関数で分け、再利用可能なユーティリティに。

計測テンプレート:効果を数字で把握する

Public Sub Bench(ByVal actionName As String, ByVal proc As String)
    Dim t0 As Double, ms As Double
    t0 = Timer
    Application.Run proc
    ms = (Timer - t0) * 1000
    Debug.Print actionName & ": " & Format(ms, "0.0") & " ms"
End Sub

' 使い方例:
' Call Bench("再計算", "DoRecalc") 

「ラップあり/なし」「イベント停止あり/なし」を切り替えて計測すると、ボトルネックが浮き彫りになります。

よくある落とし穴と対処

  • ラップを通らずに実行してしまう:QAT/リボンのボタン配置を見直し、旧来のボタンは撤去。ガバナンス面では手順書とショート動画を用意。
  • 復元漏れ:復元は 1 箇所の関数に集約し、すべての出口がそこを通る構造に。
  • フラグセル運用ミス:条件付き書式で目立たせ、入力規則(Running/Idle しか入らない)を設定。
  • 誤判定(B):普段から「計算=手動」の人がいるなら、gTouched のような「自分が切り替えた印」を必ず併用。

設計を“プロダクト化”する:再利用と移植のコツ

  • 名前付けの一貫性:フラグセルは SolverStatus に統一。管理シート名も Control などに固定。
  • 設定を 1 モジュールに集約:対象セル・監視対象・制約条件など、冒頭の Const 群に。
  • 依存の分離:Solver セットアップ部分(SolverOk/SolverAdd)は別 Sub に分け、RunSolver から Application.Run で呼ぶ。

補足:Windows と Mac の挙動差に関する注意

Excel の再計算戦略はバージョンやプラットフォームで差があります。Windows 版は Solver 実行時に広範な再計算が走るため、A/B のような「イベント・描画の一時停止」の効果が大きいのが通例です。混在環境では、まず Windows 側でのガードを優先導入し、その後 Mac 側の要否を検討すると移行コストが最小化します。

ミニマム導入チェックリスト

  • 標準モジュールに M_SolverGuard_LateBind を貼り付けた。
  • QAT/リボンのボタンが RunSolver_LateBind を指している。
  • 重いイベント/UDF の先頭に If IsSolverLikelyRunning() Then Exit Sub を入れた。
  • (任意)SolverStatus を作り、可視化&運用ルールを定めた。
  • (任意)監視タイマー(D)を Workbook_Open で起動した。

まとめ

  • Solver 実行状態を返す純正 API は存在しない。
  • 現実解は 「マクロで包んでイベント・描画・自動計算を一時停止」 し、終了後に確実に復元する設計(A)。
  • 重い処理側では B(設定判定) を併用して“すり抜け”を防ぐ。
  • 手動運用が混在するなら C(フラグセル)D(オンタイマー) で検知・見える化。
  • これらをテンプレート化すれば、エラー回避と高速化を両立し、Solver モデルを安定運用できる。

付録:最小サンプル(丸ごとコピペ)

「止めて → 走らせ → 戻す」だけの極小版です。まずはこれで動作を体感してください。

Option Explicit

Private gCalc As XlCalculation
Private gEv As Boolean
Private gIn As Boolean

Public Sub RunSolverMini()
On Error GoTo EH
gCalc = Application.Calculation
gEv = Application.EnableEvents
Application.Calculation = xlCalculationManual
Application.EnableEvents = False
Application.ScreenUpdating = False
gIn = True

```
' 実行(ダイアログなし)
Application.Run "Solver.xlam!SolverSolve", True
```

FIN:
Application.Calculation = gCalc
Application.EnableEvents = gEv
Application.ScreenUpdating = True
gIn = False
If gCalc = xlCalculationAutomatic Then Application.Calculate
Exit Sub
EH:
Resume FIN
End Sub

Public Function IsSolverRunningMini() As Boolean
If gIn Then IsSolverRunningMini = True: Exit Function
If Application.Calculation = xlCalculationManual _
And Not Application.EnableEvents Then IsSolverRunningMini = True
End Function 

運用メモ:チームで徹底するコツ

  • ボタン一本化:「Solver はこのボタンのみ」を徹底。ダイアログ直起動は原則禁止。
  • 命名規約:監視セル/名前/モジュール名はテンプレートに固定し、複数ブックでも同じ作法に。
  • 変更履歴:復元漏れや誤判定はレビューで検出しやすい。RunSolver の差分だけを見る。

以上の設計・テンプレートを採用することで「Solver 実行中かどうかを判定できない」という制約を、設計力でカバーできます。現場の Excel を落とさず、速く、安定して回し切りましょう。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次