【テクニカル・上級編】【プロフェッショナル】Application.ShowWindowsInTaskbarとWindows APIを組み合わせ、PowerPointの特定プレゼンテーションウィンドウを自作Excelフォーム内に「子ウィンドウ」として埋め込んでプレビュー表示する超上級テクニック – PowerPoint VBA解析バイブル

スポンサーリンク

【プロフェッショナル】ExcelフォームとPowerPointの融合:Windows APIと`ShowWindowsInTaskbar`による極限ウィンドウホスティング

数多のVBA開発者が、ExcelからPowerPointを自動制御するコードを書いてきた。しかし、その大半は「プレゼンテーションを別プロセスで起動し、スライドショーをフルスクリーンで走らせる」か、「生成した画像をExcelのシートに貼り付けてごまかす」という、実務の現場においてはあまりにプリミティブなアプローチにとどまっている。

真に洗練されたエンタープライズ・アプリケーションにおいて求められるのは、「Excelのユーザーフォーム(UserForm)内に、PowerPointの編集/プレビュー画面そのものをネイティブな子ウィンドウとして完璧にシームレスに埋め込む」という狂気的とも言える統合アプローチだ。

本稿では、PowerPointのオブジェクトモデルの深淵と、Windows API(User32.dll)の低レイヤー制御を極限まで同期させ、タスクバーの占有問題やプロセスのライフサイクルを完全に掌握する超上級テクニックを解き明かす。

1. アーキテクチャの全体像と直面する技術的壁

PowerPointのウィンドウ(ウィンドウハンドル:`HWND`)をExcelのUserForm内に埋め込む際、最大の障壁となるのは以下の3点である。

1. タスクバーの汚染問題:
標準状態では、PowerPointのプレゼンテーションを開くたびに独立したウィンドウがタスクバーに生成される。ユーザーフォーム内に閉じ込める場合、これらは完全にノイズでしかない。
2. ウィンドウ階層のミスマッチ(Parent-Child):
ExcelとPowerPointは完全に独立したプロセス空間で動作している。単にAPIの`SetParent`を叩くだけでは、スタイルの不整合や描画のクロップ崩壊を引き起こす。
3. ライフサイクルとメモリリークの管理:
COMオブジェクトの参照解放(Reference Counting)と、Win32ウィンドウハンドルの破壊タイミングが同期していない場合、Excelごと激しくクラッシュする。

これらを解決するための鍵が、PowerPointの`Application.ShowWindowsInTaskbar`プロパティの事前制御と、適切なAPI群によるウィンドウスタイルの動的書き換えである。

2. 実装コード:完全統合型UserFormモジュール

以下のコードは、ExcelのUserForm内にPowerPointの特定プレゼンテーションを「子ウィンドウ」として完全に埋め込み、フォームのサイズ変更(Resize)にも追従させる実用的な実装である。

標準モジュール / 宣言部(Win32 API & 定数)

Option Explicit

‘ ==============================================================================
‘ Win32 API Declarations & Constants for Window Hosting
‘ ==============================================================================
Public Declare PtrSafe Function SetParent Lib “user32” (ByVal hWndChild As LongPtr, ByVal hWndNewParent As LongPtr) As LongPtr
Public Declare PtrSafe Function SetWindowLongPtr Lib “user32” Alias “SetWindowLongPtrA” (ByVal hWnd As LongPtr, ByVal nIndex As Long, ByVal dwNewLong As LongPtr) As LongPtr
Public Declare PtrSafe Function GetWindowLongPtr Lib “user32” Alias “GetWindowLongPtrA” (ByVal hWnd As LongPtr, ByVal nIndex As Long) As LongPtr
Public Declare PtrSafe Function MoveWindow Lib “user32” (ByVal hWnd As LongPtr, ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal bRepaint As Long) As Long
Public Declare PtrSafe Function SendMessage Lib “user32” Alias “SendMessageA” (ByVal hWnd As LongPtr, ByVal wMsg As Long, ByVal wParam As LongPtr, ByVal lParam As LongPtr) As LongPtr

Public Const GWL_STYLE As Long = -16
Public Const WS_CHILD As Long = &H40000000
Public Const WS_POPUP As Long = &H80000000
Public Const WS_CAPTION As Long = &HC00000 ‘ タイトルバー
Public Const WS_THICKFRAME As Long = &H40000 ‘ サイズ変更枠
Public Const WS_MINIMIZEBOX As Long = &H20000
Public Const WS_MAXIMIZEBOX As Long = &H10000
Public Const WS_SYSMENU As Long = &H80000

‘ フォームのウィンドウハンドル取得用
If Win64 Then
Public Declare PtrSafe Function FindWindow Lib “user32” Alias “FindWindowA” (ByVal lpClassName As String, ByVal lpWindowName As String) As LongPtr
Else
Public Declare PtrSafe Function FindWindow Lib “user32” Alias “FindWindowA” (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
End If

ユーザーフォーム(`frmPPTViewer`)の実装

Option Explicit

Private pptApp As Object
Private pptPres As Object
Private pptHWnd As LongPtr
Private formHWnd As LongPtr

Private Sub UserForm_Initialize()
Dim targetPath As String
targetPath = ThisWorkbook.Path & “\sample.pptx” ‘ 読み込むプレゼンテーションのパス

On Error GoTo ErrorHandler

‘ 1. PowerPointアプリケーションのインスタンス化(バックグラウンド実行を意識)
Set pptApp = CreateObject(“PowerPoint.Application”)

‘ 【極限の知見】タスクバーへのウィンドウ表示を抑制する
‘ これにより、ホスト(Excel)の背後で無駄な独立ウィンドウが生成されるのを防ぐ
pptApp.ShowWindowsInTaskbar = False

‘ 2. プレゼンテーションのオープン
Set pptPres = pptApp.Presentations.Open(FileName:=targetPath, ReadOnly:=True, WithWindow:=True)

‘ 3. 開いたウィンドウのハンドル(HWND)を取得
‘ PowerPointウィンドウのタイトルやクラス名から特定するか、ActiveWindow.HWNDを使用
pptHWnd = pptPres.Windows(1.hWnd)

‘ 4. ExcelのUserForm自体のHWNDを取得 (クラス名は “ThunderDFrame”)
formHWnd = FindWindow(“ThunderDFrame”, Me.Caption)
If formHWnd = 0 Then Err.Raise 9999, , “Excel UserFormのハンドル取得に失敗しました。”

‘ 5. ウィンドウスタイルの書き換え (キャプションや境界線を削除し、子ウィンドウ化する)
Dim currentStyle As LongPtr
currentStyle = GetWindowLongPtr(pptHWnd, GWL_STYLE)

‘ 不要なスタイル(タイトルバー、システムメニュー、サイズ変更枠など)を除去し、WS_CHILDを付与
currentStyle = currentStyle And Not (WS_CAPTION Or WS_SYSMENU Or WS_THICKFRAME Or WS_POPUP)
currentStyle = currentStyle Or WS_CHILD

SetWindowLongPtr pptHWnd, GWL_STYLE, currentStyle

‘ 6. 親子関係の再構築 (PowerPointの親をExcelフォームに変更)
SetParent pptHWnd, formHWnd

‘ 7. フォーム内の指定エリアにサイズと位置をアライメント
Call ResizePPTWindow

Exit Sub

ErrorHandler:
MsgBox “初期化エラー: ” & Err.Description, vbCritical
Call TerminatePPT
End Sub

‘ フォームのサイズ変更イベントに連動させる
Private Sub UserForm_Resize()
If pptHWnd <> 0 Then
Call ResizePPTWindow
End If
End Sub

Private Sub ResizePPTWindow()
‘ フォーム内の余白を考慮してPowerPointウィンドウのサイズを追従させる
‘ 例として、フォーム全体(余白上下左右10ピクセル)にフィットさせる
Dim margin As Long
margin = 10

Dim w As Long, h As Long
w = Me.InsideWidth – (margin 2)
h = Me.InsideHeight – (margin 2)

‘ DPIスケーリングやポイント/ピクセル変換の差異を吸収してウィンドウ移動
MoveWindow pptHWnd, margin, margin, w, h, 1
End Sub

Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
‘ 終了時の確実なリソース解放
Call TerminatePPT
End Sub

Private Sub TerminatePPT()
On Error Resume Next
If Not pptPres Is Nothing Then
pptPres.Close
Set pptPres = Nothing
End If
If Not pptApp Is Nothing Then
pptApp.Quit
Set pptApp = Nothing
End If
pptHWnd = 0
End Sub

3. チーフアーキテクトが解説する「陥りがちな罠」と最適化の極意

このコードは表面上美しく動作するように見えるが、エンタープライズ環境で稼働させるためには、さらに深レイヤーの知識が必要となる。

A. オブジェクトのライフサイクルとプロセス残存問題

`pptApp.Quit` を呼び出したとしても、COMの参照カウント(Reference Count)が適切にデクリメントされていない場合、タスクマネージャーの裏で `POWERPNT.EXE` がゾンビプロセスとして残存し続ける。
これを防ぐためには、`Set pptPres = Nothing`、`Set pptApp = Nothing` の順序を厳守し、VBAのガベージコレクション(厳密にはCOMの解放フェーズ)を確実にトリガーさせること。また、エラーハンドラー内であっても必ずこの後始末を通る構造にしなければならない。

B. DPIスケーリング(高解像度モニター環境)の罠

現代のWindows環境では、4KディスプレイやSurfaceなどのスケーリング環境(125%, 150%, 200%など)が当たり前となっている。
VBAのUserFormが扱う単位は「ポイント(Points)」である一方、Win32 APIの`MoveWindow`が要求するのは「ピクセル(Pixels)」である。
上記コードで単純な `Me.InsideWidth` を渡すと、高DPI環境でPowerPointウィンドウが右側にはみ出したり、小さく表示される現象が発生する。
完全なプロフェッショナル環境を目指す場合は、`GetDpiForWindow` APIなどを併用し、ポイントとピクセルの換算係数を動的に算出して `MoveWindow` に渡すべきである。

C. `ShowWindowsInTaskbar = False` の真価

このプロパティを軽視してはならない。もしこれを `True`(デフォルト)のまま `SetParent` でExcelの子ウィンドウにすると、Windowsのシェル(Explorer)側でウィンドウの親子関係とタスクバーのタスク項目との不整合が生じ、タスクバーをクリックした際にフォーカスがロストしたり、最悪の場合はExcelプロセス全体がフリーズする致命的な競合を引き起こす。
「埋め込むウィンドウはタスクバーから隠す」——これがマルチプロセスGUI統合の鉄則である。

4. 結語:VBAの限界を超えるという選択

「VBAはレガシーな簡易言語である」という偏見は、オブジェクトモデルとWindowsアーキテクチャの境界線を理解していない者の言い訳に過ぎない。

今回解説した `Application.ShowWindowsInTaskbar` の制御と Win32 API の融合テクニックをマスターすれば、Excelを単なる表計算ソフトではなく、「複数の他社製アプリケーションを統合するリッチなリッチクライアント・シェル」へと変貌させることが可能になる。

極限まで最適化されたコードと、ハードウェア・OSの挙動を見通すアーキテクチャの視点。それこそが、真のプロフェッショナルエンジニアリングである。

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