Project VBAを掌握する極限の知見:動的カスタムビュー生成・制御によるエンタープライズPMO自動化
Microsoft ProjectのVBA(Visual Basic for Applications)開発において、最も過小評価され、同時に最も強力なポテンシャルを秘めているのがViewオブジェクトの動的制御である。
数千行に及ぶWBS(Work Breakdown Structure)を抱える巨大なエンタープライズ環境において、進捗状況や担当者グループ、あるいはリスクマトリクスに応じたビューの切り替えを手動で行うことは、エンジニアリングの敗北を意味する。PMO(プロジェクトマネジメントオフィス)が求める「瞬時の情報可視化」をプログラムレベルで実現するためには、MS Project特有のオブジェクトモデルの挙動、そしてCOMコンポーネントのライフサイクルを完全に掌握しなければならない。
本稿では、Project VBAにおける「カスタムビューの動的生成・切り替え・破棄」の極限の知見を、実戦投入可能なコードとともに解き明かす。
—
1. MS Projectオブジェクトモデルの闇:View・Table・Filterの三位一体構造
Excel VBAであれば「セル範囲を指定して書式を変える」だけで済む話が、MS Projectでは全く通用しない。ProjectのUIは以下の3つの要素が複雑に絡み合って構成されている。
1. View(ビュー): ガントチャート、タスクシート、リソースusageなど、画面全体のレイアウト。
2. Table(テーブル): ビューの中に表示される「列(Field)」の定義群。
3. Filter(フィルタ) / Group(グループ): 表示するデータの絞り込みと階層化。
動的ビュー制御の最大の罠は、「Viewオブジェクト単体をいじっても、列やフィルタは連動して変わらない」という点にある。既存のビューを流用して場当たり的にプロパティを変更すると、グローバルテンプレート(`Global.mpr` または `Global.mpt`)を汚染し、他のプロジェクトを開いた際にUIが破損する致命的な不具合を引き起こす。
したがって、エンタープライズ環境における正しいアプローチは以下の通りとなる。
- 一時的なカスタムビューをプログラムから動的に生成する
- 必要なTableとFilterをプログラム側でアタッチする
- 処理完了後、あるいはセッション終了時に確実にメモリとオブジェクトを解放・削除する
—
2. 実装コード:動的カスタムビューの生成と切り替えエンジン
以下に、特定の担当者グループとクリティカルパスに焦点を当てたカスタムビューを動的に生成し、適用するための堅牢なVBAコードを示す。レガシー環境でのメモリリークを防ぐため、オブジェクト変数の明示的な解放(`Set … = Nothing`)を徹底している。
Option Explicit
‘ =========================================================================
‘ プロジェクト名: ProjectVBA_ViewEngine
‘ 概要: 指定された条件に基づくカスタムビューの動的生成と切り替え
‘ ターゲット: Microsoft Project 2016 / 2019 / 365 (Desktop)
‘ =========================================================================
Public Sub ApplyDynamicExecutiveView()
Dim prj As Project
Set prj = ActiveProject
Const TARGET_VIEW_NAME = “_AutoExec_Resource_View”
Const TARGET_TABLE_NAME = “_AutoExec_Table”
Const TARGET_FILTER_NAME = “_AutoExec_Filter”
On Error GoTo ErrorHandler
‘ 1. 既存の同名一時ビュー・テーブル・フィルタが存在する場合はクリーンアップ
Call CleanupExistingObjects(prj, TARGET_VIEW_NAME, TARGET_TABLE_NAME, TARGET_FILTER_NAME)
‘ 2. 動的フィルタの作成(例: 稼働率過負荷 または クリティカルタスク)
‘ ※ProjectのFilterCreateメソッド構文に準拠
Dim customFilter As Filter
‘ 構文: FilterCreate(Name, TaskOrResource, [FieldName], [Test], [Value], [Operation], [Value2])
FilterCreate Name:=TARGET_FILTER_NAME, _
TaskOrResource:=True, _
FieldName:=”Critical”, _
Test:=”equals”, _
Value:=”Yes”
‘ 3. 動的テーブルの作成と列(Field)の追加
Dim customTable As Table
Set customTable = Tables.Add(Name:=TARGET_TABLE_NAME)
With customTable.TableFields
.Add FieldName:=”Indicators”, Width:=4
.Add FieldName:=”Name”, Width:=30
.Add FieldName:=”Start”, Width:=15
.Add FieldName:=”Finish”, Width:=15
.Add FieldName:=”Resource Names”, Width:=20
.Add FieldName:=”Cost”, Width:=12
.Add FieldName:=”Work Variance”, Width:=12
End With
‘ 4. 複合カスタムビューの作成(タスクシートをベースにする)
‘ ViewsEx または Views コレクションを使用
Dim customView As View
Set customView = ViewExs.Add(Name:=TARGET_VIEW_NAME, Type:=pjViewTaskSheet)
‘ ビューに作成したテーブルとフィルタを割り当て
customView.Table = TARGET_TABLE_NAME
customView.Filter = TARGET_FILTER_NAME
‘ 5. ビューを画面に適用
ViewApply Name:=TARGET_VIEW_NAME
‘ 画面の再描画を強制してレンダリングの崩れを防ぐ
Calculate
MsgBox “動的ビュー [” & TARGET_VIEW_NAME & ” ] の生成と適用が完了しました。”, vbInformation, “PMO Automation Engine”
GoTo Finally
ErrorHandler:
MsgBox “致命的なエラーが発生しました: ” & Err.Description, vbCritical, “VBA Runtime Error”
Finally:
‘ オブジェクトのライフサイクル管理:メモリの明示的解放
Set customView = Nothing
Set customTable = Nothing
Set customFilter = Nothing
Set prj = Nothing
End Sub
‘ ————————————————————————-
‘ プライベートヘルパー: 既存の一時オブジェクトの安全な削除
‘ ————————————————————————-
Private Sub CleanupExistingObjects(ByRef prj As Project, ByVal vName As String, ByVal tName As String, ByVal fName As String)
On Error Resume Next
‘ ビューの削除
Dim v As View
For Each v In prj.Views
If v.Name = vName Then
v.Delete
Exit For
End If
Next v
‘ テーブルの削除
Dim t As Table
For Each t In prj.Tables
If t.Name = tName Then
t.Delete
Exit For
End If
Next t
‘ フィルタの削除
Dim f As Filter
For Each f In prj.Filters
If f.Name = fName Then
f.Delete
Exit For
End If
Next f
On Error GoTo 0
End Sub
—
3. シニアエンジニアが知るべき「メモリ最適化」と「COMの罠」
上記のコードは一見して正常に動作するように見えるが、MS ProjectのCOMアーキテクチャの特性を理解していないと、大規模プロジェクト(タスク数10,000件超)の運用時にメモリリークやHRESULT: 0x8001010A (RPC_E_SERVERCALL_RETRYLATER)といったサーバービジーエラーを引き起こす。
① グローバルテンプレート汚染の防止
`Views.Add` ではなく可能な限りプロジェクトスコープ内での操作に留めるべきだが、Project VBAの仕様上、ビューやテーブルはグローバル(またはプロジェクトファイル内)のコレクションに永続化される。
スクリプト実行終了時に不要となった一時ビューを残しておくと、プロジェクトファイルサイズが肥大化するだけでなく、次回の起動時にCOMオブジェクトの参照整合性が崩れる原因になる。そのため、処理の最初で同名オブジェクトを完全に削除(Purge)するイディオムが必須となる。
② 画面描画のロック(App.ScreenUpdatingの概念不在への対策)
Excel VBAにある `Application.ScreenUpdating = False` は、残念ながらMS ProjectのVBAオブジェクトモデルには存在しない(一部ウィンドウ操作のAPIを除く)。
そのため、動的ビューの生成と適用時に裏でUIが再描画され、パフォーマンスが著しく低下する。これを回避するためには、処理の前にプロジェクトの計算モードを手動に切り替えるか、無駄なイベントハンドラを挟まない設計にするのが極意である。
‘ 計算を手動モードに切り替えて高速化を図る場合
ActiveProject.Calculation = pjCalculationManual
‘ — 処理 —
ActiveProject.Calculation = pjCalculationAutomatic
—
4. システム間連携(API/外部DB)への発展
この動的ビュー生成エンジンの真価は、単なるUIの切り替えに留まらない。例えば、外部のERPやRedmine、JiraなどのAPIから「本日の要警戒タスクIDリスト」をJSON等で取得し、それをVBA側で動的フィルタの `Value` パラメータに動的に流し込むことで、「外部システムと完全同期したプロジェクト可視化ダッシュボード」をMS Projectクライアント上に一瞬で構築できる。
‘ 擬似コード: 外部APIからの動的フィルタ値の注入
Dim criticalIDs As String
criticalIDs = “105, 108, 112″ ‘ 外部連携モジュールから取得したID群
FilterCreate Name:=”_External_Sync_Filter”, _
TaskOrResource:=True, _
FieldName:=”ID”, _
Test:=”in”, _
Value:=criticalIDs
—
総括
Project VBAにおけるUIの自動化は、単なる「手作業の肩代わり」ではない。それは、複雑怪奇なMS Projectのオブジェクトモデルを調教し、プロジェクトマネジメントのデータインテグリティを極限まで高めるためのアーキテクチャ設計そのものである。
マジックナンバーやその場のノリで書かれたコードは、プロジェクトの規模が拡大した瞬間に破綻する。本稿で示したライフサイクル管理とクリーンアップのイディオムを武器に、真に堅牢なPMOオートメーション基盤を構築してほしい。
