【実務・中級編】フォームの「OpenArgs」をJSON形式で受け渡し、画面間の複雑なパラメータ制御を実現する – Access VBA解析バイブル

スポンサーリンク

Access VBAの限界を突破する:画面間遷移で「OpenArgs」にJSON構造体を渡す極限のパラメータ制御

Access VBAを用いて業務システムを構築する際、避けて通れないのが画面(フォーム)間のパラメータ受け渡しです。

Accessが標準で提供する `DoCmd.OpenForm` の `OpenArgs` 引数は、悲しいかな単一の文字列しか受け取ることができません。

「複数のパラメータを渡したい」「遷移元の状態によって起動モードを細かく制御したい」

こうした要件に対し、多くの開発者が以下のような「アンチパターン」に手を染め、後にスパゲティコードとバグの温床に苦しむことになります。

  • アンチパターン1:カンマやスラッシュによる「オレオレ区切り文字列」
  • `”123,True,2023-10-01″` のように連結し、受け手側で `Split` する手法。パラメータの追加や順序変更が発生した瞬間にコードが崩壊します。また、データ自体にカンマが含まれていた場合のバグ制御が極めて困難です。
  • アンチパターン2:グローバル変数(TempVarsを含む)の乱用
  • 「渡す側」がグローバル変数に値を退避させ、「受け取る側」がそれを参照する手法。オブジェクトの結合度が極限まで高まり、どの画面がどの変数を書き換えたのか追跡不能になります。また、実行時エラーによる「変数の初期化(ロスト)」でシステムが即死します。

本記事では、これらの悪習を完全に断ち切ります。
現代のWeb開発では標準となっているJSON(JavaScript Object Notation)構造体を `OpenArgs` に適用し、64bit環境でも完全動作する堅牢でポータブルな画面間パラメータ制御アーキテクチャを提示します。

1. なぜJSONなのか? 設計思想とメリット

JSONを採用する理由は、単に「流行りだから」ではありません。システムアーキテクチャの観点から、圧倒的な合理性があるからです。

1. 自己組織化(セルフ・ドキュメンテーション)

  • データ自体がキーと値のペア(`”ID”: 1024, “ReadOnly”: true`)で構成されるため、デバッグ時の視認性が劇的に向上します。

2. スキーマの柔軟性

  • パラメータが増減しても、既存のパース処理(受信側)を破壊しません。必要なキーだけを抽出すればよいため、画面間の結合度を極限まで下げられます(疎結合化)。

3. データ型の維持

  • 文字列、数値、真偽値(Boolean)、Nullを明確に区別してカプセル化できます。

64bit Accessにおける「JSONライブラリ選定」の罠

VBAでJSONを扱う際、かつては `MSScriptControl.ScriptControl`(JScriptのエンジンを借りる手法)が多用されました。しかし、これは64bit版Officeでは動作しません。

今回は、外部DLLやActiveXコントロールに一切依存せず、VBA標準の文字列処理と `Scripting.Dictionary` のみで完結する軽量かつ堅牢なJSONパーサー/シリアライザーを実装します。これにより、環境依存による配布トラブルをゼロにします。

2. 実装:JSONシリアライズ・デシリアライズモジュール

まずは、パラメータをJSON文字列に変換し、またそれを解析して値を取り出すためのコアモジュールを作成します。
標準モジュール名を `mod_JSONParser` として以下のコードを貼り付けてください。

Attribute VB_Name = “mod_JSONParser”
Option Compare Database
Option Explicit

‘ ===========================================================================
‘ モジュール名: mod_JSONParser
‘ 目的: 外部ライブラリ非依存、64bit対応の超軽量JSONジェネレータ & パーサー
‘ ===========================================================================

”’

”’ Dictionaryオブジェクトから簡易的なJSON文字列を生成します(シリアライズ)
”’

Public Function SerializeJSON(ByRef params As Object) As String
If params Is Nothing Then
SerializeJSON = “{}”
Exit Function
End If

Dim keys As Variant
keys = params.keys

Dim jsonParts() As String
ReDim jsonParts(0 To params.Count – 1)

Dim i As Long
Dim key As Variant
Dim val As Variant
Dim valStr As String

For i = 0 To params.Count – 1
key = keys(i)
val = params(key)

‘ データ型に応じた適切なJSONエスケープ・フォーマット処理
Select Case VarType(val)
Case vbString
‘ ダブルクォーテーションのエスケープ
valStr = “””” & Replace(val, “”””, “\”””) & “”””
Case vbBoolean
valStr = IIf(val, “true”, “false”)
Case vbDate
valStr = “””” & Format$(val, “yyyy-mm-dd hh:nn:ss”) & “”””
Case vbNull, vbEmpty
valStr = “null”
Case vbInteger, vbLong, vbSingle, vbDouble, vbCurrency
valStr = Str(val)
‘ Str関数による先頭の半角スペースをトリム
valStr = Trim$(valStr)
Case Else
valStr = “””” & Replace(CStr(val), “”””, “\”””) & “”””
End Select

jsonParts(i) = “””” & key & “””:” & valStr
Next i

SerializeJSON = “{” & Join(jsonParts, “,”) & “}”
End Function

”’

”’ 簡易的なJSON文字列を解析し、Dictionaryオブジェクトに変換します(デシリアライズ)
”’

Public Function DeserializeJSON(ByVal json As String) As Object
Dim dict As Object
Set dict = CreateObject(“Scripting.Dictionary”)
dict.CompareMode = 1 ‘ 大文字小文字を区別しない

json = Trim$(json)

‘ 空、またはJSONとして不適合な文字列のガード処理
If Len(json) < 2 Or Left$(json, 1) <> “{” Or Right$(json, 1) <> “}” Then
Set DeserializeJSON = dict
Exit Function
End If

‘ 外側の括弧を剥ぎ取る
json = Mid$(json, 2, Len(json) – 2)

Dim pairs() As String
pairs = Split(json, “,”)

Dim i As Long
For i = LBound(pairs) To UBound(pairs)
Dim pair As String
pair = Trim$(pairs(i))

Dim colonPos As Long
colonPos = InStr(pair, “:”)

If colonPos > 0 Then
Dim rawKey As String
Dim rawVal As String

rawKey = Trim$(Left$(pair, colonPos – 1))
rawVal = Trim$(Mid$(pair, colonPos + 1))

‘ キーのクォーテーションを剥ぎ取る
If Left$(rawKey, 1) = “””” And Right$(rawKey, 1) = “””” Then
rawKey = Mid$(rawKey, 2, Len(rawKey) – 2)
End If

‘ 値のデシリアライズ(型復元)
Dim cleanVal As Variant
cleanVal = ParseJSONValue(rawVal)

If dict.Exists(rawKey) Then
dict(rawKey) = cleanVal
Else
dict.Add rawKey, cleanVal
End If
End If
Next i

Set DeserializeJSON = dict
End Function

”’

”’ JSONの値セグメントから、VBAの適切なデータ型にキャストして返却します
”’

Private Function ParseJSONValue(ByVal valStr As String) As Variant
valStr = Trim$(valStr)

‘ Nullの処理
If LCase$(valStr) = “null” Or valStr = “” Then
ParseJSONValue = Null
Exit Function
End If

‘ Booleanの処理
If LCase$(valStr) = “true” Then
ParseJSONValue = True
Exit Function
End If
If LCase$(valStr) = “false” Then
ParseJSONValue = False
Exit Function
End If

‘ 文字列の処理
If Left$(valStr, 1) = “””” And Right$(valStr, 1) = “””” Then
Dim s As String
s = Mid$(valStr, 2, Len(valStr) – 2)
‘ エスケープされたダブルクォーテーションを復元
s = Replace(s, “\”””, “”””)
ParseJSONValue = s
Exit Function
End If

‘ 数値の処理
If IsNumeric(valStr) Then
If InStr(valStr, “.”) > 0 Then
ParseJSONValue = CDbl(valStr)
Else
ParseJSONValue = CLng(valStr)
End If
Exit Function
End If

‘ 判別不能な場合はそのまま文字列として返却
ParseJSONValue = valStr
End Function

3. 実践:画面遷移での適用例

それでは、この `mod_JSONParser` を用いて、実際に「顧客一覧画面(遷移元)」から「顧客詳細画面(遷移先)」を特定のパラメータ付きで呼び出すシナリオを実装します。

① 呼び出し側(送信側):F_CustomerList

一覧画面のボタンをクリックした際、以下の処理でパラメータを「構造化」して詳細画面へ引き渡します。

Private Sub btnOpenDetail_Click()
On Error GoTo ErrorHandler

‘ 1. パラメータを格納するDictionaryの生成
Dim params As Object
Set params = CreateObject(“Scripting.Dictionary”)

‘ 2. 構造化データの格納(異なるデータ型を混在させる)
params.Add “CustomerID”, 99501 ‘ Long型:顧客ID
params.Add “Mode”, “EDIT” ‘ String型:処理モード
params.Add “IsReadOnly”, False ‘ Boolean型:編集可否
params.Add “OpenedBy”, “SysAdmin” ‘ String型:操作者
params.Add “LimitDate”, DateAdd(“d”, 7, Date)’ Date型:処理期限(一週間後)

‘ 3. JSON文字列へシリアライズ
Dim jsonArgs As String
jsonArgs = mod_JSONParser.SerializeJSON(params)

‘ デバッグ用(イミディエイトウィンドウに出力)
Debug.Print “送信JSON: ” & jsonArgs

‘ 4. 堅牢な画面遷移実行
DoCmd.OpenForm FormName:=”F_CustomerDetail”, _
View:=acNormal, _
OpenArgs:=jsonArgs

Exit Sub

ErrorHandler:
MsgBox “画面遷移中に予期せぬエラーが発生しました: ” & Err.Description, vbCritical, “エラー”
End Sub

② 受け取り側(受信側):F_CustomerDetail

遷移先である詳細画面では、フォーム開閉時のライフサイクルを考慮し、`Form_Open`(起動可否判定・初期バリデーション)`Form_Load`(UIやレコードソースへのデータ反映) を厳密に使い分けます。

これができていない開発者が非常に多いのですが、`Form_Open` イベントは `Cancel = True` を返すことで画面の起動自体を安全にロールバックできる唯一の場所です。

Option Compare Database
Option Explicit

‘ フォームクラスモジュール変数としてパラメータ保持用Dictionaryを宣言
‘ これにより、フォーム内のすべてのプライベートメソッドからパラメータへ定数時間(O(1))でアクセス可能となる
Private m_args As Object

Private Sub Form_Open(Cancel As Integer)
On Error GoTo ErrorHandler

‘ 1. OpenArgsの存在チェック(ガードクローズ)
If IsNull(Me.OpenArgs) Or Trim$(Me.OpenArgs) = “” Then
MsgBox “不正なアクセスです。引数が指定されていません。”, vbExclamation, “起動エラー”
Cancel = True ‘ フォーム起動をキャンセル
Exit Sub
End If

‘ 2. デシリアライズ(JSON文字列からDictionaryオブジェクトの復元)
Set m_args = mod_JSONParser.DeserializeJSON(Me.OpenArgs)

‘ 3. 必須パラメータのバリデーションチェック
If Not m_args.Exists(“CustomerID”) Or Not m_args.Exists(“Mode”) Then
MsgBox “必要なパラメータ(CustomerID または Mode)が不足しています。”, vbCritical, “パラメータエラー”
Cancel = True ‘ フォーム起動をキャンセル
Exit Sub
End If

‘ 4. 読み取り専用モードの早期適用(UI描画前の制御)
If m_args(“IsReadOnly”) = True Then
Me.AllowEdits = False
Me.AllowDeletions = False
End If

Exit Sub

ErrorHandler:
MsgBox “初期化中に致命的なエラーが発生しました: ” & Err.Description, vbCritical, “システムエラー”
Cancel = True
End Sub

Private Sub Form_Load()
On Error GoTo ErrorHandler

‘ Form_Openを通過しているため、m_argsは必ず安全に初期化されている
Dim customerId As Long
Dim mode As String
Dim openedBy As String
Dim limitDate As Variant

customerId = m_args(“CustomerID”)
mode = m_args(“Mode”)
openedBy = m_args(“OpenedBy”)
limitDate = m_args(“LimitDate”)

‘ UIコントロールへの安全なバインドと描画
Me.txtCustomerID = customerId
Me.lblMode.Caption = “現在のモード: ” & mode
Me.txtOpenedBy = openedBy

If Not IsNull(limitDate) Then
Me.txtLimitDate = CDate(limitDate)
End If

‘ データのフィルタリング処理を実行
Me.Filter = “CustomerID = ” & customerId
Me.FilterOn = True

Exit Sub

ErrorHandler:
MsgBox “データの読み込みに失敗しました: ” & Err.Description, vbCritical, “読込エラー”
End Sub

‘ フォームが閉じられる際、メモリリークを防ぐためにオブジェクトを明示的に解放する
Private Sub Form_Unload(Cancel As Integer)
Set m_args = Nothing
End Sub

4. プロフェッショナルが語る「極限の知見」と注意点

1. オブジェクトのライフサイクルをハックせよ

Access VBAにおいて、`Form_Open` と `Form_Load` の役割分担は、システムの堅牢性を決定づける生命線です。

  • `Form_Open`:データバインド(レコードセットの読み込み)が完了するに走る。ここでパラメータの妥当性を検証し、NGなら `Cancel = True` でフォームを開くこと自体を中止する。
  • `Form_Load`:レコードセットが結合され、UIコントロールがメモリ上に実体化したに走る。ここでパース済みのパラメータをテキストボックスやクエリフィルタに適用する。

この役割分担を怠り、`Form_Load` だけで検証と代入を同時に行うと、エラー発生時に「中途半端に開いてコントロールがエラー表示になった壊れたフォーム」が画面に残ることになります。

2. 「Null」と「型」に対する絶対の防御策

JSON文字列はテキストデータに過ぎません。これをデシリアライズする際、VBAの `Variant` 型の挙動を理解していないとバグを誘発します。
例えば、JSON側で `null` と定義された値は、VBA内では `Null`(`vbNull`)として復元されます。
これを、型宣言された変数(例:`Dim myStr As String`)に直接代入すると、「Invalid use of Null(Nullの使い方が不正です)」のエラーでプログラムが異常終了します。

回避策として、JSONから復元した値を変数へ代入する際は、以下のように `Nz` 関数を使用するか、一旦 `Variant` で受ける防御コードを徹底してください。

‘ 安全な代入パターン
Dim customerName As String
customerName = Nz(m_args(“CustomerName”), “名無し顧客”)

3. パフォーマンスに関する懸念への回答

「文字列のパース処理(JSON変換)を挟むことで、画面遷移のパフォーマンスが低下するのではないか?」

この懸念は、数万回ループを回すようなバッチ処理ならともかく、ユーザーの画面遷移(ミリ秒単位のインターフェース制御)においては、完全に考慮不要の「早期の最適化(Early Optimization)」です。
Access VBAにおけるボトルネックの99.9%は「データベースへのI/O(クエリ発行、インデックスの未整備)」です。メモリ上での高々数百文字の文字列パースは数マイクロ秒で完結するため、保守性と堅牢性を優先すべきです。

5. まとめ

グローバル変数や、アドホックな区切り文字によるパラメータの受け渡しは、数か月後の自分、あるいは後任のエンジニアに対する「技術的負債の押し付け」に他なりません。

本記事で紹介した JSONによる構造化OpenArgsパターン を導入することで、以下のメリットを手に入れることができます。

  • カプセル化の実現:画面が自律的に動き、外部環境(グローバル変数)に依存しない。
  • 変更への耐性:パラメータが追加されても、既存コードの修正が不要。
  • バグの早期検知:`Form_Open` による厳格な門前払い。

Accessという枯れた、しかし強力なプラットフォームを真に支配するために、この現代的でエレガントなアーキテクチャを今すぐあなたのプロジェクトに導入してください。

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