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ジェネレータ & パーサー
‘ ===========================================================================
”’
”’
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
”’
”’
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
”’
”’
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という枯れた、しかし強力なプラットフォームを真に支配するために、この現代的でエレガントなアーキテクチャを今すぐあなたのプロジェクトに導入してください。
