【テクニカル・上級編】【プロフェッショナル】Application.WindowSelectionChangeイベントによるリアルタイム校正:選択された「TextRange」の文字数や禁則処理を監視しステータスバーで警告する編集アシスタント – PowerPoint VBA解析バイブル

スポンサーリンク

【プロフェッショナル】Application.WindowSelectionChangeによるリアルタイム校正:VBAとWin32 APIで構築する極限の編集アシスタント

Microsoft PowerPointには、Excelのような手軽な`Application.StatusBar`プロパティが存在しません。しかし、大企業の大規模な提案書作成や、厳格なIR資料の作成現場において、「スライド内の文字数オーバー」や「不適切な表現(NGワード)の混入」をリアルタイムに検知し、ユーザーに警告する仕組みは極めて高い価値を持ちます。

本稿では、PowerPoint VBAのイベントモデルの深淵である`Application.WindowSelectionChange`イベントをハックし、Win32 APIを用いたステータスバー領域へのダイレクトメッセージ描画と、正規表現を用いた超高速な校正エンジンを融合させた、実務に耐えうるエンタープライズ向けリアルタイム校正システムの構築手法を解説します。

1. リアルタイム監視が直面するアーキテクチャ上の課題

PowerPointにおけるリアルタイム監視システムの構築には、ExcelやWordの開発にはない特有の技術的障壁が存在します。

PowerPointにおけるUI制御の制約

PowerPointのVBAオブジェクトモデルには、ユーザーインターフェースに即時アクセスするための簡易なAPIが不足しています。特に「ステータスバーへのテキスト出力」はVBA標準機能からは直接制御できません。これを解決するために、本アーキテクチャではWin32 API(`FindWindowEx`および`SendMessage`)を用いてPowerPointの内部ウィンドウハンドル(HWND)を走査し、ステータスバーオブジェクトを直接制御します。

イベントの「再入(Re-entrancy)」とパフォーマンスの限界

`WindowSelectionChange`は、ユーザーがマウスでテキストを選択した瞬間だけでなく、キーボードでテキストを1文字入力・削除し、カーソル(選択範囲)が移動するたびに秒間数十回の頻度で呼び出される可能性があります。
このイベントハンドラ内で重い処理(毎回COMオブジェクトを生成する、または非効率な文字列スキャンを行うなど)を実行すると、ユーザーのタイピングに致命的な遅延(レイテンシ)が発生します。

これを回避するために、以下の設計パターンを徹底します。

  • シングルトン・パターンによる正規表現エンジンの永続化: 判定のたびに`New RegExp`を行わず、クラスの初期化時に一度だけインスタンス化してメモリ上に保持します。
  • COM参照の即時解放: スライド、シェイプ、テキストレンジなどのCOMオブジェクトは、スコープを抜けたら即座に`Set = Nothing`で明示的に解放し、ガベージコレクションのオーバーヘッドを最小化します。

2. システムアーキテクチャとWin32 APIによるハック

PowerPointのウィンドウ構造はバージョン(SDI: Single Document Interface化された2013以降)によって変化しますが、基本的にはメインウィンドウ(`PPTFrameClass`)の下位にステータスバー領域が存在します。

Windows APIを使用して、このステータスバーのハンドルを特定し、メッセージを送信する仕組みを設計します。

[PowerPoint Main Window (PPTFrameClass)]

├── [Document Window (mdiClass)]

└── [Status Bar (msotb / MsoWorkArea)] <-- ここをWin32 APIで捕捉して書き換える ---

3. 実装コード

このシステムは、以下の3つのコンポーネントで構成されます。

1. `mdlWin32API` (標準モジュール): ウィンドウ制御用のWin32 API宣言とラッパー関数。
2. `clsAppWatcher` (クラスモジュール): PowerPointのイベントを監視し、校正ロジックを呼び出すコア。
3. `mdlController` (標準モジュール): システムの起動・停止、およびグローバルなインスタンス管理。

1. `mdlWin32API`(標準モジュール)

Windows APIを安全に呼び出すための宣言です。Officeの32bit/64bit両環境に対応(`VBA7` / `Win64`)しています。

Option Explicit

If VBA7 Then
‘ 64bit/32bit共通(Office 2010以降)
Private Declare PtrSafe Function FindWindowExLib Lib “user32” Alias “FindWindowExA” ( _
ByVal hWnd1 As LongPtr, _
ByVal hWnd2 As LongPtr, _
ByVal lpsz1 As String, _
ByVal lpsz2 As String) As LongPtr

Private Declare PtrSafe Function SendMessageLib Lib “user32” Alias “SendMessageW” ( _
ByVal hWnd As LongPtr, _
ByVal Msg As Long, _
ByVal wParam As LongPtr, _
ByRef lParam As Any) As LongPtr

Private Declare PtrSafe Function GetAncestor Lib “user32” ( _
ByVal hWnd As LongPtr, _
ByVal gaFlags As Long) As LongPtr
Else
‘ レガシー32bit環境用
Private Declare Function FindWindowExLib Lib “user32” Alias “FindWindowExA” ( _
ByVal hWnd1 As Long, _
ByVal hWnd2 As Long, _
ByVal lpsz1 As String, _
ByVal lpsz2 As String) As Long

Private Declare Function SendMessageLib Lib “user32” Alias “SendMessageW” ( _
ByVal hWnd As Long, _
ByVal Msg As Long, _
ByVal wParam As Long, _
ByRef lParam As Any) As Long

Private Declare Function GetAncestor Lib “user32” ( _
ByVal hWnd As Long, _
ByVal gaFlags As Long) As Long
End If

Private Const WM_SETTEXT As Long = &HC
Private Const GA_ROOT As Long = 2

‘ メインウィンドウからステータスバーを走査してテキストを設定する
If VBA7 Then
Public Sub SetPPTStatusBarText(ByVal app As PowerPoint.Application, ByVal text As String)
Else
Public Sub SetPPTStatusBarText(ByVal app As PowerPoint.Application, ByVal text As String)
End If
Dim hMainWnd As LongPtr
Dim hStatusBar As LongPtr

‘ アクティブウィンドウから親ウィンドウハンドルを取得
On Error GoTo ErrorHandler
If app.ActiveWindow Is Nothing Then Exit Sub

‘ PowerPointのウィンドウハンドルを間接的に取得
‘ (PowerPoint 2013以降のSDI環境に対応するため、ActiveWindowのHWNDを起点とする)
hMainWnd = GetAncestor(app.ActiveWindow.HWND, GA_ROOT)
If hMainWnd = 0 Then Exit Sub

‘ PowerPointのステータスバー(クラス名: “msotb” または “MsoCommandBar” の一部)を探索
‘ ※バージョンや環境によって異なるため、広くマッチング
hStatusBar = FindWindowExLib(hMainWnd, 0, “MsoWorkArea”, vbNullString)
hStatusBar = FindWindowExLib(hStatusBar, 0, “msotb”, “Status Bar”)

If hStatusBar = 0 Then
‘ フォールバック: 直接 msotb を検索
hStatusBar = FindWindowExLib(hMainWnd, 0, “msotb”, vbNullString)
End If

If hStatusBar <> 0 Then
‘ Unicode(W)版のSendMessageでステータスバーにテキストを送信
Dim buf() As Byte
buf = text & vbNullChar
SendMessageLib hStatusBar, WM_SETTEXT, 0, buf(0)
Else
‘ APIでステータスバーが取得できない場合はスライドショー用のステータス表示等に代替
‘ もしくはデバッグ出力
Debug.Print “StatusBar HWND Not Found. Msg: ” & text
End If
Exit Sub

ErrorHandler:
‘ エラー発生時はプロセスのクラッシュを防ぐため静かに処理を抜ける
Err.Clear
End Sub

2. `clsAppWatcher`(クラスモジュール)

このクラスが監視システムの心臓部です。永続化された正規表現オブジェクト(RegExp)を保持し、高速にマッチングを行います。

Option Explicit

‘ イベントを捕捉するための宣言
Private WithEvents m_App As PowerPoint.Application

‘ 高速判定用の正規表現オブジェクトのキャッシュ
Private m_RegExpNG As Object
Private m_MaxCharLimit As Long

‘ クラス初期化
Private Sub Class_Initialize()
Set m_RegExpNG = CreateObject(“VBScript.RegExp”)
With m_RegExpNG
.Global = True
.IgnoreCase = True
‘ 校正ルール:社外秘情報、不適切表現、機種依存文字などのパターンを設定
‘ 例: 「(丸暗記)」「NGワード」「絶対」「100%」などを検知
.Pattern = “(㊙|マル秘|極秘|社外秘|絶対|100%|バグ|瑕疵)”
End With

‘ 1つのテキストボックス内の推奨最大文字数
m_MaxCharLimit = 250
End Sub

‘ クラス破棄(メモリ解放の徹底)
Private Sub Class_Terminate()
Set m_RegExpNG = Nothing
Set m_App = Nothing
End Sub

‘ 監視対象のApplicationを設定するプロパティ
Public Property Set WatcherApp(ByVal objApp As PowerPoint.Application)
Set m_App = objApp
End Property

‘ Selection(選択範囲)の変更イベントをキャッチ
Private Sub m_App_WindowSelectionChange(ByVal Sel As PowerPoint.Selection)
On Error GoTo ErrorHandler

‘ テキストが選択されている、またはテキストボックス内にカーソルがあるか
If Sel.Type = ppSelectionText Then
Dim oTextRange As PowerPoint.TextRange
Set oTextRange = Sel.TextRange

‘ 選択されているテキスト、あるいはカーソルがある段落全体のテキストを取得
Dim targetText As String
targetText = oTextRange.Text

‘ 選択部分が空(単なるカーソル点滅)の場合は、親のShape全体の文字数を検査
If Len(targetText) = 0 Then
Dim oShape As PowerPoint.Shape
‘ テキストフレーム経由で親Shapeを取得
Set oShape = oTextRange.Parent.Parent
If oShape.HasTextFrame Then
targetText = oShape.TextFrame.TextRange.Text
End If
Set oShape = Nothing
End If

‘ 校正処理の実行
Call ExecuteProofreading(targetText)

‘ COMオブジェクトの明示的解放
Set oTextRange = Nothing
Else
‘ テキスト選択以外の場合は、ステータスバーを初期状態に戻す
Call mdlWin32API.SetPPTStatusBarText(m_App, “編集アシスタント: 待機中”)
End If
Exit Sub

ErrorHandler:
‘ リアルタイム処理のため、エラーによるダイアログ表示はユーザー体験を著しく損ねる
‘ エラーはログ出力に留め、処理を継続させる
Debug.Print “Error in WindowSelectionChange: ” & Err.Description
Err.Clear
End Sub

‘ 校正エンジン(超高速判定)
Private Sub ExecuteProofreading(ByRef text As String)
Dim charCount As Long
charCount = Len(text)

Dim isNGFound As Boolean
Dim warningMsg As String

‘ 1. NGワードチェック(正規表現)
If m_RegExpNG.Test(text) Then
isNGFound = True
Dim matches As Object
Set matches = m_RegExpNG.Execute(text)
warningMsg = “⚠️ 警告: 禁止ワード「” & matches(0).value & “」を検知しました。”
Set matches = Nothing
End If

‘ 2. 文字数超過チェック
If charCount > m_MaxCharLimit Then
If isNGFound Then
warningMsg = warningMsg & ” | ” & “⚠️ 文字数超過 (” & charCount & “/” & m_MaxCharLimit & “文字)”
Else
warningMsg = “⚠️ 文字数超過: このテキストブロックは長すぎます (” & charCount & “/” & m_MaxCharLimit & “文字)”
End If
End If

‘ 3. ステータスバーへの動的フィードバック
If isNGFound Or (charCount > m_MaxCharLimit) Then
Call mdlWin32API.SetPPTStatusBarText(m_App, warningMsg)
Else
Call mdlWin32API.SetPPTStatusBarText(m_App, “🟢 校正クリア (文字数: ” & charCount & “文字)”)
End If
End Sub

3. `mdlController`(標準モジュール)

このモジュールでアドイン起動時または手動実行時にクラスをインスタンス化し、グローバル変数に保持することで、PowerPointのライフサイクル全体を通じて監視を維持します。

Option Explicit

‘ クラスインスタンスをグローバル(モジュールレベル)に保持し、ガベージコレクションを防ぐ
Private m_Watcher As clsAppWatcher

‘ アシスタントの起動
Public Sub StartProofreadingAssistant()
If m_Watcher Is Nothing Then
Set m_Watcher = New clsAppWatcher
Set m_Watcher.WatcherApp = PowerPoint.Application
Call mdlWin32API.SetPPTStatusBarText(PowerPoint.Application, “🟢 リアルタイム校正アシスタントが起動しました”)
Else
MsgBox “アシスタントは既に稼働しています。”, vbInformation, “システム情報”
End If
End Sub

‘ アシスタントの停止
Public Sub StopProofreadingAssistant()
If Not m_Watcher Is Nothing Then
Set m_Watcher = Nothing
‘ ステータスバーをクリア
Call mdlWin32API.SetPPTStatusBarText(PowerPoint.Application, “”)
MsgBox “アシスタントを停止しました。”, vbInformation, “システム情報”
Else
MsgBox “アシスタントは稼働していません。”, vbInformation, “システム情報”
End If
End Sub

‘ PowerPointアドイン(.ppam)ロード時に自動起動させるためのエントリポイント
Public Sub Auto_Open()
Call StartProofreadingAssistant
End Sub

‘ アドインアンロード時に自動停止
Public Sub Auto_Close()
Call StopProofreadingAssistant
End Sub

4. 堅牢化とパフォーマンスの極限最適化

リアルタイム監視システムが実質的な「オーバーヘッド(動作の重さ)」としてユーザーに嫌われないために、アーキテクトが施すべき最適化手法を解説します。

1. ガベージコレクションとメモリ管理の徹底

VBAでは、オブジェクトの参照カウンタが0になった時点でメモリが解放されます。しかし、`WindowSelectionChange`のような超高頻度のイベント内では、解放処理が追いつかずに一時的にメモリ使用量(プライベートワーキングセット)が急増することがあります。

  • イベントハンドラ内のローカル変数(`oTextRange`, `oShape`, `matches`等)は、処理の最後に必ず `Set オブジェクト = Nothing` を明示的に記述します。
  • `String` 型の結合(`&` 演算子)は、数万回単位で繰り返されない限り高速ですが、結合処理が膨大になる場合は、あらかじめメモリ領域を確保する固定長文字列バッファやWin32の`SysAllocString`などの利用を検討します。本システムでは実用十分なレベルに最適化されています。

2. 正規表現オブジェクト(RegExp)の再利用

以下のような記述は絶対に避けてください

‘ アンチパターン
Private Sub m_App_WindowSelectionChange(…)
Dim reg As Object
Set reg = CreateObject(“VBScript.RegExp”) ‘ イベントのたびにCOM生成 (極めて重い)

End Sub

`CreateObject`はWindowsのレジストリを参照してCLSIDからCOMコンポーネントをインスタンス化するため、著しいディスクI/OおよびCPUオーバーヘッドを発生させます。本実装のように、`Class_Initialize`で一度だけ生成してメンバー変数に保持(キャッシュ)するのがプロフェッショナルの鉄則です。

5. 本番運用(アドイン化)への展開

開発したコードを実際の業務環境へデプロイするには、マクロ有効プレゼンテーション(`.pptm`)ではなく、PowerPointアドイン(`.ppam`)としてコンパイルして配布するのが最もスマートです。

手順

1. コードを含むVBAプロジェクトを整理し、不要なデバッグ用の`Debug.Print`をコメントアウトまたは削除します(I/O負荷軽減)。
2. PowerPointの「名前を付けて保存」から、ファイルの種類として PowerPointアドイン (.ppam) を選択して保存します。
3. 配布先PCのPowerPointで「開発」タブ -> 「PowerPointアドイン」から、作成した `.ppam` ファイルを登録します。
4. `Auto_Open` マクロにより、PowerPointが起動した瞬間に裏でWin32 APIと監視イベントが静かに立ち上がり、ユーザーに一切のストレスを与えることなく、全スライドの編集時にリアルタイム校正がバックグラウンドで開始されます。

この軽量かつ極めて強力な「編集アシスタント」は、レガシーと言われるVBAアーキテクチャが、設計次第で最新のWebアドイン(Officeアドイン)にも劣らないレスポンスと実用性を発揮できることの証明です。

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