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

スポンサーリンク

PowerPoint VBAを掌握する:WindowSelectionChangeイベントによるリアルタイム校正アシスタントの構築

PowerPointを用いた資料作成において、表記揺れや文字数オーバー、社内ガイドライン違反のチェックは、常に人間の「目視」という脆弱なプロセスに依存してきました。これを自動化しようと、多くの開発者が「ボタンを押して一括スキャンするマクロ」を作成しますが、それはすでに手遅れ(スライドが完成した後)です。

真の業務効率化とは、「ユーザーが文字を入力・選択しているその瞬間」に、バックグラウンドで静かに、かつ超高速に校正を完了させることに他なりません。

今回は、PowerPoint VBAのイベント駆動プログラミングの極致である `Application.WindowSelectionChange` イベントを使用し、選択された `TextRange` の状態をリアルタイムに監視・校正してステータスバーに警告を表示する、極めて堅牢な「リアルタイム校正アシスタント」の設計と実装を解説します。

1. リアルタイム監視における設計思想とアーキテクチャ

PowerPointには、Excelの `Worksheet_Change` のような「セルの値が変わった」ことを直接検知するシートレベルのイベントが存在しません。テキストの変更をリアルタイムに拾うための唯一の現実的なアプローチが、「選択範囲の変更(`WindowSelectionChange`)」をトリガーとし、選択されているテキスト(`TextRange`)を動的に解析する手法です。

しかし、このイベントはユーザーのあらゆる操作(マウスドラッグ、キーボードの矢印キー移動、スライド切り替え)でミリ秒単位で連打されます。設計を誤れば、PowerPointは極端に重くなり、最悪の場合クラッシュします。

プロフェッショナルなツールとして成立させるためには、以下の3つの鉄則を守らねばなりません。

鉄則1:COMオブジェクトの生存期間(ライフサイクル)の厳密な管理

イベントを補足するための `WithEvents` を宣言したクラスインスタンスは、グローバル変数(または専用のマネージャークラス)で常時メモリ上に生存させ続ける必要があります。VBAの実行エラーでこのインスタンスが破棄(初期化)された瞬間、監視は静かに停止します。予期せぬエラー発生時でも即座に自己修復、あるいは安全に再初期化できる設計が必要です。

鉄則2:イベントハンドラ内の徹底的な軽量化(アーリーエグジット)

イベントが発火するたびに重い処理(外部ファイルの読み込みや正規表現オブジェクトの毎回生成など)を行ってはなりません。
1. 選択されたオブジェクトが「テキスト」であるか否かを、最速で判定して抜ける(Early Exit)。
2. 校正エンジン(辞書パターンなど)は、クラス初期化時に一度だけメモリ(Dictionaryオブジェクト等)にキャッシュする。

鉄則3:ステータスバー(StatusBar)のUXコントロール

PowerPointの `Application.StatusBar` は便利ですが、OSやOfficeのバージョンによって挙動が繊細です。また、警告がないときは速やかに標準表示に戻さなければ、ユーザーに「古い警告」を表示し続け、かえって混乱を招きます。

2. 堅牢なプロダクションコード実装

それでは、コピペで動作し、そのまま実務に耐えうる堅牢なコードを実装しましょう。
このシステムは、以下の3つのモジュールで構成されます。

1. `clsAppEvents`(クラスモジュール):PowerPointのイベントを捕捉・フィルタリングする。
2. `modProofreader`(標準モジュール):具体的な校正ルールと解析処理を担うエンジン。
3. `modInitializer`(標準モジュール):システムの起動・停止、およびグローバルインスタンスの管理。

2.1. クラスモジュール:`clsAppEvents`

クラスモジュールを挿入し、オブジェクト名を `clsAppEvents` に変更して以下のコードを貼り付けてください。

‘—————————————————————————————–
‘ クラスモジュール: clsAppEvents
‘ 役割: PowerPoint全体のイベントを監視し、選択範囲がテキストの場合に校正エンジンを呼び出す
‘—————————————————————————————–
Option Explicit

‘ PowerPointアプリケーションのイベントを捕捉するための宣言
Public WithEvents App As PowerPoint.Application

‘ 最後に処理したTextFrameのIDと選択テキストの文字数をキャッシュ(同一箇所での連続実行を抑制)
Private m_LastSelectedText As String
Private m_LastStart As Long
Private m_LastLength As Long

Private Sub Class_Initialize()
m_LastSelectedText = “”
m_LastStart = 0
m_LastLength = 0
End Sub

‘—————————————————————————————–
‘ イベントハンドラ: WindowSelectionChange
‘ ユーザーがスライド上の選択範囲を変更した瞬間に発火
‘—————————————————————————————–
Private Sub App_WindowSelectionChange(ByVal Sel As PowerPoint.Selection)
On Error GoTo ErrorHandler

‘ 1. 最速の Early Exit 判定
‘ 選択タイプがテキスト(ppSelectionText)以外、または選択オブジェクトが不正な場合は即終了
If Sel Is Nothing Then Exit Sub
If Sel.Type <> ppSelectionText Then
‘ テキスト選択が解除された場合は、ステータスバーをクリア
PowerPoint.Application.StatusBar = “”
Exit Sub
End If

‘ 2. TextRangeオブジェクトの安全な取得
Dim targetRange As PowerPoint.TextRange
Set targetRange = Sel.TextRange

If targetRange Is Nothing Then Exit Sub

‘ 3. 同一選択状態での無駄な再処理を防止(チャタリング・パフォーマンス対策)
If m_LastSelectedText = targetRange.Text And _
m_LastStart = targetRange.Start And _
m_LastLength = targetRange.Length Then
Exit Sub
End If

‘ キャッシュの更新
m_LastSelectedText = targetRange.Text
m_LastStart = targetRange.Start
m_LastLength = targetRange.Length

‘ 4. 校正エンジンの実行
Call modProofreader.AnalyzeText(targetRange)

Exit Sub

ErrorHandler:
‘ 開発中のデバッグおよび本番環境でのクラッシュ防止の妥協点
‘ イベント内での予期せぬエラーはサイレントに処理し、ユーザーのPowerPoint操作を阻害しない
Debug.Print “Error in WindowSelectionChange: ” & Err.Description
End Sub

2.2. 標準モジュール:`modProofreader`

標準モジュールを挿入し、オブジェクト名を `modProofreader` に変更して以下のコードを貼り付けてください。ここにすべての「校正ルール」を集約します。

‘—————————————————————————————–
‘ 標準モジュール: modProofreader
‘ 役割: テキストの解析、社内ガイドライン検証、及びユーザーへの警告表示
‘—————————————————————————————–
Option Explicit

‘ 校正設定用の定数
Private Const MAX_CHAR_COUNT As Long = 50 ‘ 1スライド内の1プレースホルダーにおける推奨最大文字数
Private Const WARN_COLOR_BG As Long = &H0000FF ‘ 将来的にシェイプの色を変える場合等の拡張用

‘—————————————————————————————–
‘ メイン解析ルーチン
‘—————————————————————————————–
Public Sub AnalyzeText(ByRef rng As PowerPoint.TextRange)
Dim textContent As String
textContent = rng.Text

‘ 空文字の場合は処理しない
If Len(Trim(textContent)) = 0 Then
PowerPoint.Application.StatusBar = “”
Exit Sub
End If

Dim alertMessage As String
alertMessage = “”

‘ — ルール1: 文字数制限チェック —
‘ 選択されているテキストが属する「親コンテナ(テキストフレーム全体)」の文字数をチェック
Dim totalLength As Long
On Error Resume Next
totalLength = rng.Parent.Parent.TextFrame.TextRange.Length
On Error GoTo 0

If totalLength > MAX_CHAR_COUNT Then
alertMessage = “⚠️【文字数超過】現在のテキストボックス内が ” & totalLength & “文字 です(推奨 ” & MAX_CHAR_COUNT & “文字以下) ”
End If

‘ — ルール2: 禁止ワード・表記揺れチェック —
Dim wordAlert As String
wordAlert = CheckForbiddenWords(textContent)

‘ — ルール3: 禁則処理・句読点チェック —
Dim punctuationAlert As String
punctuationAlert = CheckPunctuation(textContent)

‘ — 警告文の統合と表示 —
Dim finalMessage As String
finalMessage = “”

If alertMessage <> “” Then finalMessage = finalMessage & alertMessage & ” | ”
If wordAlert <> “” Then finalMessage = finalMessage & wordAlert & ” | ”
If punctuationAlert <> “” Then finalMessage = finalMessage & punctuationAlert & ” | ”

If finalMessage <> “” Then
‘ 末尾の区切り文字をカット
If Right(finalMessage, 3) = ” | ” Then
finalMessage = Left(finalMessage, Len(finalMessage) – 3)
End If
‘ ステータスバーに目立つように表示
PowerPoint.Application.StatusBar = “📢 校正アシスタント: ” & finalMessage
Else
‘ すべてのチェックをクリアした場合、ステータスバーをクリア、または安全宣言
PowerPoint.Application.StatusBar = “✅ 校正クリア”
End If
End Sub

‘—————————————————————————————–
‘ サブルーチン1: 表記揺れ・社内NGワードの高速走査
‘—————————————————————————————–
Private Function CheckForbiddenWords(ByVal txt As String) As String
‘ 実務では、この辞書をClass_Initialize等で一度だけDictionaryオブジェクトに読み込むのがベスト。
‘ ここではデモおよび即時動作のため、静的配列で高速に走査します。
Dim forbiddenPatterns As Variant
Dim replaceSuggestions As Variant

‘ NGワードと、その推奨代替案のペア
forbiddenPatterns = Array(“ソリューション”, “コミットする”, “デフォルト”, “バグ”)
replaceSuggestions = Array(“解決策”, “約束する/注力する”, “初期値”, “不具合”)

Dim i As Long
For i = LBound(forbiddenPatterns) To UBound(forbiddenPatterns)
If InStr(1, txt, forbiddenPatterns(i), vbTextCompare) > 0 Then
CheckForbiddenWords = “⚠️【表現】「” & forbiddenPatterns(i) & “」あり ➡️ 推奨:「” & replaceSuggestions(i) & “」”
Exit Function ‘ 最初に見つかった警告を優先して返す
End If
Next i

CheckForbiddenWords = “”
End Function

‘—————————————————————————————–
‘ サブルーチン2: 禁則処理および記号ルールの簡易チェック
‘—————————————————————————————–
Private Function CheckPunctuation(ByVal txt As String) As String
‘ 例: ビジネス文書において「!」や「?」の後に全角スペースがない場合に警告
‘ 例: 行頭に「、」や「。」が来そうな書き方の簡易検出(改行直後の句読点など)

If InStr(txt, vbCr) > 0 Or InStr(txt, vbLf) > 0 Then
‘ 改行コードの直後に句読点があるか
Dim lines() As String
lines = Split(Replace(txt, vbCrLf, vbLf), vbLf)
Dim j As Long
For j = 0 To UBound(lines)
If j > 0 Then
If Left(Trim(lines(j)), 1) = “、” Or Left(Trim(lines(j)), 1) = “。” Or _
Left(Trim(lines(j)), 1) = “,” Or Left(Trim(lines(j)), 1) = “.” Then
CheckPunctuation = “⚠️【禁則】行頭に句読点があります。”
Exit Function
End If
End If
Next j
End If

‘ 感嘆符の後にスペースがあるか(文字列の末尾にある場合を除く)
Dim regEx As Object
Set regEx = CreateObject(“VBScript.RegExp”)
With regEx
.Pattern = “[!?][^  \r\n]” ‘ 「!」または「?」の直後にスペースも改行もない文字が続く場合
.Global = True
If .Test(txt) Then
CheckPunctuation = “⚠️【記号】「!」や「?」の後には全角スペースを推奨します。”
Exit Function
End If
End With

CheckPunctuation = “”
End Function

2.3. 標準モジュール:`modInitializer`

標準モジュールを挿入し、オブジェクト名を `modInitializer` に変更して以下のコードを貼り付けてください。システムの司令塔となるモジュールです。

‘—————————————————————————————–
‘ 標準モジュール: modInitializer
‘ 役割: 監視用クラスインスタンスのライフサイクル管理、および手動起動/停止
‘—————————————————————————————–
Option Explicit

‘ 監視クラスのインスタンスをグローバル(モジュールレベル)で保持
‘ これにより、マクロ実行後もインスタンスがメモリ上に残り、イベントを捕捉し続ける
Private g_AppEvents As clsAppEvents

‘—————————————————————————————–
‘ システム起動:このマクロを実行するとリアルタイム校正が始まります
‘—————————————————————————————–
Public Sub StartProofreader()
‘ 二重起動を防止し、常にクリーンな状態から開始
Call StopProofreader

Set g_AppEvents = New clsAppEvents
Set g_AppEvents.App = PowerPoint.Application

PowerPoint.Application.StatusBar = “🟢 校正アシスタントが起動しました(選択テキストを監視中)”
Debug.Print “Proofreader started successfully.”
End Sub

‘—————————————————————————————–
‘ システム停止:監視を安全に終了し、メモリを解放します
‘—————————————————————————————–
Public Sub StopProofreader()
If Not g_AppEvents Is Nothing Then
Set g_AppEvents.App = Nothing
Set g_AppEvents = Nothing
End If

PowerPoint.Application.StatusBar = “🔴 校正アシスタントを停止しました”
Debug.Print “Proofreader stopped.”
End Sub

‘—————————————————————————————–
‘ 状態確認用(デバッグ等で活用)
‘—————————————————————————————–
Public Function IsRunning() As Boolean
IsRunning = Not (g_AppEvents Is Nothing)
End Function

3. 開発リーダーが解説する極限の最適化ポイント

このコードがなぜ「動き」、なぜ「落ちない」のか。その裏にある設計意図を解説します。

① `WindowSelectionChange` のチャタリング防止(重要)

ユーザーがキーボードの「右矢印キー」を連打してテキスト内を移動するとき、移動するごとにイベントが高速で発火します。これに対し、毎回正規表現やループを回すのは愚の骨頂です。
`clsAppEvents` 内に以下のキャッシュを保持しています。

If m_LastSelectedText = targetRange.Text And _
m_LastStart = targetRange.Start And _
m_LastLength = targetRange.Length Then
Exit Sub
End If

「選択したテキストの内容」「選択開始位置(Start)」「選択の長さ(Length)」の3つがすべて前回と同じであれば、1ミリ秒前に処理したばかりなので1行も無駄な処理をせずに即座に処理をスキップします。これにより、カーソル移動による描画遅延(モタつき)を完璧に防いでいます。

② `On Error Resume Next` の正しい局所化

COMオブジェクトの操作にはエラーがつきものです。例えば、ユーザーがプレースホルダーを選択した直後、そのオブジェクトが「一時的に編集不可」な状態になっている一瞬があります。
このコードでは、エラーハンドラを以下のように制御しています。

  • 全体のエラーハンドリング:`clsAppEvents`全体を `On Error GoTo ErrorHandler` で包み、万が一予期せぬ例外(オブジェクトがすでに存在しないなど)が発生しても、PowerPoint自体を落とさずにデバッグウィンドウにエラーを吐くだけにして、ユーザーの作業を邪魔しません。
  • 局所的無視:`modProofreader` 内の親オブジェクトのLength取得時など、親が `TextFrame` を持たない特殊なシェイプである可能性が排除できない1行のみ `On Error Resume Next` を使い、直後に `On Error GoTo 0` でリセットしています。広範囲な `On Error Resume Next` はバグを隠蔽するため、プロのコードでは厳禁です。

4. 実務でのスケールアップ:データベース連携とアドイン化

このシステムを社内で本格展開するにあたり、避けて通れない「二つの壁」とその超え方を提示します。

壁1:社内表記ガイドライン(辞書ファイル)の同期

上記のコードでは、NGワードの配列(`forbiddenPatterns`)をコード内に直書き(ハードコーディング)しています。しかし、数千語に及ぶ表記揺れや、頻繁に変わる社内ガイドラインをマクロのコード内に書き込むのは保守性の観点から最悪です。

  • 解決策:I/Oの最小化とメモリキャッシュ

辞書データは、共有フォルダ上の「CSVファイル」または「SQLiteなどの軽量データベース」に格納します。
ただし、イベントハンドラが走るたびにファイルを開いてはなりません(確実にPowerPointがフリーズします)。
`clsAppEvents` の `Class_Initialize` 時、あるいは `modInitializer.StartProofreader` が呼ばれたその瞬間に、共有フォルダのCSVを一度だけ読み込み、メモリ上の `Dictionary` オブジェクトにハッシュマップとして格納してください。

壁2:ユーザーへの配布と自動起動

ユーザーに「マクロ有効プレゼンテーション(.pptm)」を毎回開かせ、手動でマクロを実行させる運用は現実的ではありません。

  • 解決策:アドイン(.ppam)化と `Auto_Open` の実装

1. この3つのモジュールを含むファイルを「PowerPointアドイン(.ppam)」形式で保存します。
2. `modInitializer` に `Auto_Open` マクロを定義し、その中で `StartProofreader` を呼び出します。

Public Sub Auto_Open()
Call StartProofreader
End Sub

3. 各クライアントマシンのPowerPointアドインとして登録すれば、ユーザーは意識することなく、PowerPointを起動した瞬間から常にステータスバーでリアルタイム校正の恩恵を受けることができます。

5. まとめ

PowerPoint VBAによるイベント駆動型開発は、ユーザー体験を劇的に向上させる強力な武器です。今回紹介したアーキテクチャは、ただ動作するだけでなく、「パフォーマンスを限界まで追求し、ユーザーの思考を1ミリ秒も止めない」という、プロフェッショナルなツール開発において最も重要な思想を具現化したものです。

手動での「コピペチェック」という虚無な時間からチームを解放するために、ぜひこの堅牢なリアルタイム校正アシスタントをあなたの組織に導入してください。

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