フォームは「情報の檻」であれ:DoCmd.OpenFormによる入力フローの極限制御
Access開発において、多くのエンジニアが陥る罠がある。それは「フォームを開くこと」を単なる画面遷移だと捉えることだ。
シニアエンジニア諸君、思い出してほしい。AccessはGUIを備えたデータベース・エンジンであると同時に、メモリ上で複雑なオブジェクトのライフサイクルが交錯する「非同期の迷宮」だ。`DoCmd.OpenForm`の`WindowMode`を制御することは、単にウィンドウの見た目を変えることではない。それは、ユーザーという予測不能な変数をシステムという閉鎖環境に閉じ込め、データの整合性を担保するための「強制執行」に他ならない。
1. WindowModeの「本質的」選択基準
`acDialog`か、それとも`acWindowNormal`か。この二択の境界線には、メモリ管理とイベント駆動の哲学が横たわっている。
- acDialog (排他的制御): 呼び出し元のコード実行を一時停止させ、当該フォームを閉じるまで制御を解放しない。これは「同期処理」の強制だ。入力の完遂が必須となるサブフォーム的役割には必須だが、多重起動すればスタックオーバーフローやメモリリークの温床となる。
- acWindowNormal (非同期制御): 呼び出し元はそのまま進行する。マルチタスクなワークフローには適するが、データ整合性維持のために`Form_Unload`や`Form_Close`で呼び出し元へイベントを伝播させる「疎結合な設計」が求められる。
2. 極限の制御:Windows APIを介したウィンドウの「幽閉」
`acDialog`だけでは足りない現場がある。例えば、入力中に親フォームを操作できないようにしつつ、特定のAPIでウィンドウのZオーダーや最前面表示を制御したい場合だ。
以下は、`WindowMode`を補完し、Windows APIを用いてウィンドウのスタイルを強制的に固定する実装例である。
‘ 標準モジュール:APIによるウィンドウ制御
If VBA7 Then
Private Declare PtrSafe Function SetWindowLong Lib “user32” Alias “SetWindowLongA” (ByVal hwnd As LongPtr, ByVal nIndex As Long, ByVal dwNewLong As LongPtr) As LongPtr
Private Declare PtrSafe Function GetWindowLong Lib “user32” Alias “GetWindowLongA” (ByVal hwnd As LongPtr, ByVal nIndex As Long) As LongPtr
Else
Private Declare Function SetWindowLong Lib “user32” Alias “SetWindowLongA” (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Private Declare Function GetWindowLong Lib “user32” Alias “GetWindowLongA” (ByVal hwnd As Long, ByVal nIndex As Long) As Long
End If
Private Const GWL_HWNDPARENT = -8
‘ フォームのOpenイベントで呼び出すことで、親フォームとの階層関係を固定する
Public Sub SetModalParent(ByVal targetForm As Form, ByVal parentForm As Form)
On Error Resume Next
‘ 親ウィンドウのハンドルを子フォームの親として再設定することで、
‘ acDialog以上の強力な「依存関係」を強制できる
SetWindowLong targetForm.hwnd, GWL_HWNDPARENT, parentForm.hwnd
End Sub
3. オブジェクトのライフサイクルと「ゴミ」の掃除
`DoCmd.OpenForm`を多用するシステムで最も恐ろしいのは、フォームが閉じられた後の「見えない残滓」だ。特に`acDialog`で呼び出したフォーム内で`CurrentDb`を安易に再生成し続けると、接続プールが枯渇する。
以下のパターンを徹底せよ。
1. フォーム変数による明示的参照:
`DoCmd.OpenForm`で開くのではなく、フォームをオブジェクト変数としてインスタンス化し、`Set Nothing`で確実に解放する。
2. レコードセットの明示的閉鎖:
フォームの`Close`イベントに、`Recordset`の`Close`と`Set Nothing`を必ず記述する。VBAのガベージコレクションを信じてはいけない。
‘ 呼び出し元の極限実装例
Public Sub OpenEntryForm()
Dim frm As Form_frmEntry
Set frm = New Form_frmEntry
‘ プロパティを直接設定することで、DoCmdのオーバーヘッドを回避
frm.RecordSource = “SELECT FROM T_Master WHERE ID = 1”
frm.Modal = True ‘ 強制モーダル化
frm.Visible = True
‘ ここで停止(モーダル相当の制御)
Do While frm.Visible
DoEvents
Loop
‘ 終了後のメモリ解放
Set frm = Nothing
End Sub
4. 伝説のエンジニアとしてのアドバイス
システム間連携において、フォームはデータの「入力口」であると同時に「バリデーションの境界線」でもある。`acDialog`で入力をロックし、そのフォームの`BeforeUpdate`イベントでトランザクションを制御する。これが、データ整合性を担保する唯一無二の王道だ。
レガシーなAccess環境において、「なんとなく」動いているコードほど危険なものはない。`WindowMode`ひとつとっても、それがメモリにどう影響し、ユーザーの操作をどう制約しているのかを言語化できるレベルで使いこなしてほしい。
技術は常に冷徹であるべきだ。感情でフォームをデザインするな。「制約」という名の設計図で、ユーザーの操作ミスを数学的に排除せよ。
それが、Accessの深淵を覗き込んだ者たちの責務である。
