【実務・中級編】DoCmd.OpenFormの「WindowMode」を駆使した、ダイアログ形式でのデータ入力設計 – Access VBA解析バイブル

スポンサーリンク

Access VBAを掌握する極限の知見:`DoCmd.OpenForm` の神髄と、同期型ダイアログ設計の極意

こんにちは。開発プロジェクトの現場において、数々のAccessデスクトップアプリを「堅牢なエンタープライズシステム」へと昇華させてきたチーフアーキテクトの私だ。

Access VBAによる開発現場で、いまだにこんなコードを見かけることがある。

‘ 【悪手】非同期でフォームを開き、親側が勝手に後続処理を進めてしまう例
DoCmd.OpenForm “frmInput”
‘ ここで親側は入力完了を待たずに処理が進むため、データが空のまま進んでエラーになる
Call ProcessData

「画面を開いて、ユーザーに入力させて、その結果を受け取ってから次の処理に進む」

これは業務アプリケーションにおいて最も頻出する要件だ。しかし、Accessのオブジェクトモデルやイベントのライフサイクルを理解していないプログラマは、タイマーで無理やり待たせたり、グローバル変数を汚染させたりという悪夢のような実装に走りがちである。

今回は、`DoCmd.OpenForm` の第5引数である `WindowMode`(acDialog) を完全に掌握し、呼び出し元の処理を美しく同期停止させ、堅牢なデータ入力を実現するプロフェッショナルな設計手法を伝授しよう。

1. なぜ `acDialog` なのか? オブジェクトモデルの裏側

`DoCmd.OpenForm` に `acDialog` を指定した瞬間、Accessの内部挙動は劇的に変化する。

一般的に、フォームを `acNormal` などで開いた場合、VBAのコードはフォームの表示状態に関わらずそのまま次の行へ進行する(非同期)。しかし、`WindowMode:=acDialog` を指定すると、以下の3つの強烈な制約と特性が発動する。

1. コードの完全同期停止(Modal execution)
`OpenForm` を実行した行でVBAの実行が一時停止(サスペンド)する。ダイアログフォームが閉じられるか、非表示になるまで、呼び出し元のプロシージャは一歩も先に進まない。
2. モーダルウィンドウ化
ダイアログが開いている間は、親フォームや他のウィンドウへの操作がロックされる。
3. `Me.Visible = False` による隠蔽ハックの許容
これが実務で最も重要だ。`acDialog` フォームは、`DoCmd.Close` で完全に破棄しなくても、`Me.Visible = False` にしてメモリ上に生存させたまま呼び出し元に制御を返すことができる。これにより、ユーザーが入力したデータをフォームオブジェクトから直接回収するというエレガントな設計が可能になる。

2. 堅牢なダイアログ設計の全体像

今回構築するアーキテクチャのフローはこうだ。

1. 親フォーム(呼び出し元): `OpenForm` にて `acDialog` で子フォームを呼び出し、コードを一時停止する。
2. 子フォーム(入力用): ユーザーがデータを入力し、「登録」ボタンを押す。
3. 入力検証(Validation): 子フォーム内で厳格なバリデーションを行う。
4. データの受渡し: バリデーションを通過したら、フォームを「閉じる」のではなく `Me.Visible = False` にして非表示にする。
5. 親フォームの再開: 停止していた親フォームのコードが再開し、非表示になった子フォームのコントロールから入力値を取得する。処理が終わったら子フォームを明示的にアンロードする。

この設計であれば、グローバル変数を使う必要は一切ない。カプセル化された美しいデータフローが実現できる。

3. プロダクションコード実装例

そのまま現場に投入できる、洗練されたコードを提示しよう。

① 呼び出し元(親フォーム or 標準モジュール)のコード

‘ =========================================================================
‘ 呼び出し元プロシージャ
‘ =========================================================================
Public Sub OpenCustomerDialog()
On Error GoTo ErrorHandler

Dim frmName As String
frmName = “frmCustomerInput”

‘ 1. ダイアログモードでフォームを開く(ここでコードの実行が一時停止する)
DoCmd.OpenForm FormName:=frmName, _
WindowMode:=acDialog, _
OpenArgs:=”Mode:New” ‘ 必要に応じてパラメータを渡す

‘ — 【ここから下は、ダイアログフォームが閉じられるか非表示になるまで実行されない】 —

‘ 2. フォームが「キャンセル」されたか、「登録完了(非表示)」になったかを判定
If CurrentProject.AllForms(frmName).IsLoaded Then

‘ フォームが生きていて、かつ非表示(=正常に値が確定された)状態かチェック
If Forms(frmName).Visible = False Then

‘ 3. 子フォームのパブリックプロパティ(またはコントロール)から安全に値回収
Dim customerName As String
customerName = Forms(frmName).CtlCustomerName.Value

‘ 4. データベースへの書き込み処理(トランザクション制御)
Call SaveCustomerData(customerName)

MsgBox “データの登録が完了しました。”, vbInformation, “成功”
Else
‘ ユーザーがキャンセルしてフォームを完全に閉じた場合
MsgBox “処理がキャンセルされました。”, vbInformation, “キャンセル”
End If

‘ 5. メモリ上のオブジェクトを完全に破棄
DoCmd.Close acForm, frmName, acSaveNo
End If

Exit Sub

ErrorHandler:
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical, “致命的エラー”
If CurrentProject.AllForms(frmName).IsLoaded Then
DoCmd.Close acForm, frmName, acSaveNo
End If
End Sub

② ダイアログフォーム(子フォーム)のコード

次に、入力画面側の実装だ。ここでは「登録ボタン」「キャンセルボタン」の制御と、値の確定ロジックを記述する。

‘ =========================================================================
‘ ダイアログフォーム側モジュール (frmCustomerInput)
‘ =========================================================================
Option Compare Database
Option Explicit

‘ データの確定フラグ
Private m_IsConfirmed As Boolean

Private Sub Form_Load()
‘ 初期化時は未確定状態
m_IsConfirmed = False
End Sub

‘ 【登録ボタン】クリックイベント
Private Sub btnRegister_Click()
On Error GoTo ErrorHandler

‘ 1. 厳格なバリデーション
If Not ValidateInput() Then Exit Sub

‘ 2. 確定フラグを立てる
m_IsConfirmed = True

‘ 3. ★重要: フォームをアンロード(破棄)せず、非表示にして親へ制御を返す
Me.Visible = False

Exit Sub
ErrorHandler:
MsgBox “登録処理でエラーが発生しました。”, vbCritical
End Sub

‘ 【キャンセルボタン】クリックイベント
Private Sub btnCancel_Click()
‘ 完全にフォームを閉じる(=親側で IsLoaded が False、あるいは Visible チェックで弾かれる)
DoCmd.Close acForm, Me.Name, acSaveNo
End Sub

‘ バリデーションロジック
Private Function ValidateInput() As Boolean
ValidateInput = False

If IsNull(Me.CtlCustomerName) Or Trim(Me.CtlCustomerName) = “” Then
MsgBox “顧客名は必須入力です。”, vbExclamation, “入力エラー”
Me.CtlCustomerName.SetFocus
Exit Sub
End If

‘ その他の業務ルール検証をここに記述

ValidateInput = True
End Function

4. プロジェクトを崩壊させないための「実務の鉄則」

現場でこの設計を運用するにあたり、以下のアンチパターンに注意してほしい。

🚨 注意点1: エラーハンドリングの脱落による「永遠のフリーズ」

`acDialog` を使用している最中に、フォームのモジュール内でキャッチされない実行時エラーが発生すると、Access全体がモーダルロックされたような挙動に陥り、ユーザーが身動きできなくなることがある。必ずダイアログ側のプロシージャには `On Error GoTo` を網の目のように張り巡らせ、異常系では確実にフォームが閉じられるか、`Visible = False` に落ちる担保を作ること。

🚨 注意点2: データベース連携(トランザクション)の分離

サンプルコードの通り、ダイアログ側で直接データベースを書き換えるのではなく、「ダイアログは値の収集とバリデーションに徹し、実際のDB書き込みは親側のスコープ(トランザクション制御下)で行う」 のが美しい。万が一、書き込み時にエラーが起きた場合でも、ロールバックの制御が親側で一元管理できるためだ。

チーフアーキテクトからの総括

`DoCmd.OpenForm` の `acDialog` は、単なる画面表示のオプションではない。これは、「非同期が基本であるVBAのイベント駆動モデルにおいて、強烈な同期のコンテキストを作り出すためのアーキテクチャ上の武器」である。

グローバル変数やスパゲッティなイベント間通信に頼るコードは、今日で終わりにしよう。この堅牢な同期型ダイアログ設計をあなたのプロジェクトに導入すれば、バグの発生率は激減し、保守性は劇的に向上する。

プロとしての誇りを持った、美しいコードを書き続けたまえ。

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