Excel の Solver は強力ですが、実行中にワークシート全体の再計算やイベントが多発して処理が重くなる、という悩みを抱えがちです。そこで本稿では「Solver が動いている間だけ特定の処理をスキップする」ための実用的な回避策を、コピペで使える VBA コードとともに体系的に解説します。マクロでのラップ、Excel 設定の判定、フラグセル、オンタイマー監視まで、現場で役立つ設計と運用を一気に整えましょう。
問題の本質:Solver 実行状態は標準 API で取得できない
前提として、Windows 版 Excel の標準機能や Solver アドインには「いま Solver が実行中か」を直接返すプロパティ/イベントはありません。SolverSolve は同期(ブロッキング)呼び出しのため、実行中に状態を問い合わせる API も用意されていません。一方で Windows 版 Solver は各イテレーションでシート全体を再計算するため、UDF・イベント・画面描画が繰り返しトリガーされ、重い処理が二次災害的にボトルネック化しがちです。
方針:直接検知を諦め、「確実に止める/避ける」で設計する
純正 API に頼らず、以下のいずれか(または併用)で「Solver の最中だけは重い処理を走らせない」設計に切り替えます。
| # | アプローチ | 長所 | 短所・留意点 |
|---|---|---|---|
| A | マクロで Solver をラップ 例: RunSolver → 事前に SuspendForSolver(計算=手動、イベント/画面更新オフ)、終了後 ResumeAfterSolver で復元 | ・確実にフラグを管理できる ・ユーザー操作を変えずにボタンに割り当て可 | ・必ずマクロ経由で Solver を起動する運用が必要 |
| B | Excel 設定の状態を判定してスキップ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
使い方(最短)
- VBE(Alt + F11)で標準モジュールに上記コードを貼り付ける。
- 必要に応じて
Solver.xlam!SolverOkやSolverAddをRunSolver_LateBindのコメント部分に記述して、モデルをセットアップする。 - クイックアクセスツールバー(QAT)やリボンのカスタムボタンに
RunSolver_LateBindを割り当てる。
以後、ユーザーはそのボタンで Solver を起動するだけ。実行中はイベント・画面更新が確実に止まり、終了後は必ず復元されます。
イベント/UDF 側の「すり抜け防止」:即時スキップ判定の定型
重い処理が置かれがちな Worksheet_Change・Worksheet_Calculate・Workbook_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 ダイアログから手動で実行する文化が根強いチームでは、フラグセルの導入が有効です。手順:
- 管理用シートにセル(例:
Control!B2)を用意し、名前SolverStatusを定義。 - Solver を起動する直前にそのセルへ
"Running"、終了後に"Idle"と入力する運用にする(マクロ版は自動更新)。 - 重い処理側の先頭で次を判定:
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 EHとCleanupラベルを用意し、例外でも 復元 → フラグ更新 を保証。 - Ctrl+Break の抑止:実行中は
Application.CalculationInterruptKey = xlNoKey。強制停止の可能性を下げる(必要に応じて外す)。 - 数式エラーの事前防御:零除算や範囲外などは
IF/IFERRORで潰しておくと、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 を落とさず、速く、安定して回し切りましょう。

コメント