Access VBAの暗黒面:SetFocusエラーを「制御」し、堅牢なUIを構築する極限の設計論
Accessという環境は、長年現場を支えてきた「老練な戦士」であると同時に、扱いを誤れば即座に実行時エラーという名の地雷を踏む繊細なプラットフォームだ。
特に、UI操作の要である`Control.SetFocus`。多くの初学者はこれを単純なメソッドと見なすが、中堅以上のエンジニアであれば、これがどれほど「不安定な足場」であるかを知っているはずだ。非表示(Visible = False)、使用不可(Enabled = False)、あるいは親フォームがフォーカスを持っていない状態――これらの条件下で`SetFocus`を叩けば、システムは容赦なく停止する。
今日は、場当たり的な`On Error Resume Next`で誤魔化すような無様なコードとは決別し、オブジェクトの状態を厳密に管理する「プロフェッショナルな実装」を伝授する。
—
1. なぜ「SetFocus」は失敗するのか
Accessのオブジェクトモデルにおいて、フォーカスとは「単一のUI資源」を占有する排他制御だ。以下の条件を一つでも満たさないコントロールにフォーカスを強制しようとすれば、エラー番号2110や2164がシステムを襲う。
- Visible = True であること
- Enabled = True であること
- 親フォームがアクティブ であること
これらを逐一`If`文で判定するのはナンセンスだ。我々が求めるのは、いかなる文脈でも安全にフォーカスを奪取する「抽象化されたガード」である。
—
2. 堅牢性を極めた「SafeSetFocus」の実装
以下に、オブジェクトの状態を再帰的にチェックし、安全にフォーカスを移動させるための汎用関数を提示する。
‘ @brief 安全にフォーカスを移動する汎用プロシージャ
‘ @param ctrl 対象のコントロールオブジェクト
‘ @return 成功時はTrue、失敗時はFalse
Public Function SafeSetFocus(ByRef ctrl As Object) As Boolean
‘ オブジェクトが有効か、インターフェースを保持しているかを確認
If ctrl Is Nothing Then Exit Function
On Error GoTo Err_Handler
‘ 1. 親フォームのアクティブ化(これが漏れるとSetFocusは機能しない)
If Not ctrl.Parent.Form.ActiveControl Is Nothing Then
If ctrl.Parent.Name <> Screen.ActiveForm.Name Then
DoCmd.SelectObject acForm, ctrl.Parent.Name
End If
End If
‘ 2. 可視性および使用可否の厳密なチェック
‘ プロパティが存在しないオブジェクトへのアクセスはエラーになるため、On Errorで逃げる
If ctrl.Visible And ctrl.Enabled Then
ctrl.SetFocus
SafeSetFocus = True
End If
Exit Function
Err_Handler:
‘ ログ出力やトレースが必要な場合はここに記述
SafeSetFocus = False
End Function
—
3. シニアエンジニアが意識すべき「メモリとパフォーマンス」の視点
上記コードをさらに洗練させるためには、以下の「アーキテクトの視点」を忘れてはならない。
オブジェクトの明示的解放(Nothingへの帰還)
VBAのガベージコレクションを信用してはならない。特にAccessのモジュール間でオブジェクトを参照渡しする場合、循環参照がメモリリークを引き起こす。`Set obj = Nothing` を徹底することは、長期稼働する業務システムにおいて「メモリの断片化」を防ぐ唯一の手段だ。
APIによる低レイヤー制御の是非
もし、どうしてもフォーカスが制御できない「難解なActiveXコントロール」に遭遇した場合、Windows API(`SetFocus`関数など)を直接叩く手法もある。しかし、それはAccessのイベントループ(`GotFocus`/`LostFocus`)を破壊する危険がある。
「APIで解決するのは、Accessの標準機能が完全に論理破綻した時のみ」というのが、私の長年の教訓だ。
—
4. 保守性を高めるための設計思想
システム管理者が最も恐れるのは、仕様変更時に「どこでフォーカスが制御されているか」がブラックボックス化することだ。
- 集中管理: `SetFocus`をコードのあちこちに散らばらせるな。専用のモジュール(例: `modUIUtility`)に集約し、すべてのUI遷移をこの関数経由で行うよう強制せよ。
- エラーの可視化: 本番環境では`Resume Next`で隠蔽しても良いが、開発環境では`Debug.Assert`を使用して、フォーカス移動に失敗した瞬間にIDEを停止させる設計にすべきだ。
—
結論:コードは「対話」である
Access VBAはレガシーと言われるが、それは「制御の難しさ」を理解していない者が放つ言葉だ。オブジェクトのライフサイクルを理解し、状態遷移を予測してコードを書くことは、まさにシステムと対話することに他ならない。
今回提示した`SafeSetFocus`は、君たちの書くシステムから「予期せぬ実行時エラー」を排除する強力な武器となるはずだ。
明日からの開発において、`SetFocus`を呼ぶとき、一度立ち止まって考えてみてほしい。「このコントロールは、本当にフォーカスを受ける準備ができているか?」と。その問いこそが、真のエンジニアへの入り口だ。
