WPF WebView2でPDFの現在ページ番号を取得する方法|Edge内蔵PDFビューアの限界と代替案

WPFアプリでWebView2を使ってPDFを表示すると手軽ですが、Edge内蔵PDFビューアはページ番号などの情報をアプリ側へ公開しません。本記事では、取得できない理由と、現在ページ・最終ページ判定を実現する現実的な代替案を整理します。

目次

WPFのWebView2でPDFが表示できる仕組みを先に整理

WPFで使うWebView2は、アプリ内に「Microsoft Edge(Chromium)のレンダリングエンジン」を埋め込み、Webコンテンツを表示するコントロールです。PDFをfile:///で開くと、HTMLとして描画しているわけではなく、Edge側のPDF表示機能(内蔵PDFビューア)へ処理が委譲され、専用のUIでレンダリングされます。

この「委譲」の性質が重要で、WPFアプリ側が自由に触れるのは“WebView2として公開されているAPI”までです。PDFビューアが内部で持っている状態(総ページ数、現在ページ、ズーム、スクロール位置など)は、WebView2の公開APIとしては提供されていません。

結論:WebView2+Edge内蔵PDFビューアのまま「現在ページ番号」「最終ページ判定」は基本的に取得できない

先に結論を明確にすると、Edge内蔵PDFビューアでPDFを表示している状態では、アプリ側から「現在何ページ目を見ているか」「最後のページに到達しているか」を公式に取得する方法はありません。Microsoft Q&Aでも同様の質問に対して、組み込みビューアから直接取得できない旨の回答がされています。

  • WebView2でExecuteScriptAsyncは実行できても、PDFビューアのページ情報へアクセスできない
  • DOM要素を探索してもnullになったり、期待した構造が取得できない
  • 将来のEdge/WebView2更新で壊れる前提のハックを除けば、運用に耐える実装は難しい

参考として、WebView2 SDKのリリースノートでもPDFに関するAPI制約が言及されています。たとえばFind APIはWebコンテンツの検索を制御できますが、PDFを表示している場合は一致箇所の移動などが制限される既知の問題が記載されています。PDFが“通常のWebページ”と同じように扱えない領域が残っていることが読み取れます。

なぜJavaScript注入で取れないのか(よくある誤解ポイント)

「WebView2はJavaScriptを流し込めるのだから、PDFビューアの画面要素(ページ番号の入力欄など)をDOMで読めるのでは?」という発想は自然です。しかし、実際には次の理由でうまくいきません。

期待現実結果
PDF画面はHTMLなのでDOMを触れるPDF表示は内蔵ビューア(特権的なコンポーネント)に委譲されるページ番号UIのDOMへ到達できない
document.querySelectorでページ番号要素を取得ExecuteScriptAsyncで見える範囲が限定されるnullが返る/期待と違うオブジェクトになる
DevToolsで動くJSはアプリからも動くDevToolsは内部コンテキストも扱える場合があるアプリ側のJS注入と条件が一致しない

実際、WebView2Feedback(GitHub)では「PDFを表示中にExecuteScriptAsyncでDOMを取ろうとしてもnullになる」「現在ページを取得できない」といった声が継続的に上がっています。これは“やり方が悪い”というより、ビューアがそのように設計されているためです。

要件を言語化すると設計が決まりやすい

「現在ページ番号」「最終ページ判定」と一口に言っても、背景の要件で最適解が変わります。まずは、あなたのアプリが何のために必要としているのかを言語化すると、その後の判断が一気に楽になります。

よくある目的必要な精度おすすめ方針
利用規約・同意書を最後まで読んだ判定「最後まで到達」だけで良い場合が多いPDFのままに固執せず、HTML化や自前ビューア検討
前回開いていたページへ復帰(しおり)ページ番号の正確な復元が必要pdf.jsまたは自前レンダリング(Windows.Data.Pdf等)
学習教材・マニュアルの進捗管理ページ+滞在時間など複合指標が欲しい自前レンダリング+ログ設計が現実的

回避策の全体像:ページ情報が必要なら「表示方式を変える」のが本筋

Edge内蔵PDFビューアを“そのまま”使い続ける限り、ページ状態はブラックボックスです。ページ番号や最終ページ判定がプロダクト要件として重要なら、最終的には次のいずれかに寄せる判断になります。

  • WebView2上で自前レンダリング(pdf.js):Web技術でPDFを描画し、イベント・状態をアプリが管理する
  • Windows標準API(Windows.Data.Pdf)で自前ビューア:WPF側でページ単位に描画し、ページ管理も自分で行う
  • PDFコントロール(商用/OSS):機能要件が多い場合に時間を買う

回避策1:pdf.jsで「現在ページ」「総ページ数」「最終ページ判定」をWebView2から取得する

pdf.jsはブラウザ上でPDFを表示するためのJavaScriptライブラリです。Edge内蔵ビューアを使わず、あなたのHTML/JSでPDFを描画するため、ページ番号やイベントを自由に扱えます。Microsoft Q&Aでも代替案としてpdf.jsが挙げられています。

実装イメージ

  • WPF:WebView2にローカルのHTML(pdf.jsを含む)を読み込む
  • JavaScript:PDFを読み込み、現在ページと総ページ数を管理する
  • 連携:window.chrome.webview.postMessageでWPFへページ情報を通知する

ローカルHTMLを安全に読み込むコツ(file:///より仮想ホストが扱いやすい)

pdf.jsをローカル配置して読み込む場合、file:///での読み込みは制約が出やすい(オリジンがファイルパスごとに分かれる、セキュアコンテキストが必要なAPIが使えない等)ため、WebView2の「仮想ホスト名マッピング」を使うと運用が安定します。

WPF側の例(仮想ホストでローカルフォルダを公開)

await webView.EnsureCoreWebView2Async();

var assetsFolder = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "PdfViewerAssets");
webView.CoreWebView2.SetVirtualHostNameToFolderMapping(
"appassets.local",
assetsFolder,
Microsoft.Web.WebView2.Core.CoreWebView2HostResourceAccessKind.Allow);

// 例:viewer.html(pdf.js同梱)を表示
webView.CoreWebView2.Navigate("[https://appassets.local/viewer.html](https://appassets.local/viewer.html)");

この方式だと、HTML/JS/CSSをアプリに同梱しやすく、相対パスの解決も安定します(Webの“サイト”として扱えるため)。

JavaScript側の例(ページ変更をWPFへ通知)

pdf.jsの公式ビューア(viewer.html)をそのまま使う場合でも、イベントをフックして現在ページを取れます。最小構成のイメージとして、ページ変更時にWPFへ通知する例を示します(実際はpdf.jsのバージョンに合わせて調整してください)。

// 例:ページ変更を検知してWPFへ通知する(概念例)
function reportPageState(currentPage, totalPages) {
  if (window.chrome && window.chrome.webview) {
    window.chrome.webview.postMessage({
      type: "pdfPageState",
      currentPage: currentPage,
      totalPages: totalPages,
      isLast: currentPage === totalPages
    });
  }
}

// ページ変更イベントのどこかで呼ぶ(viewer構成により取得方法は変わる)
reportPageState(currentPageNumber, totalPages);

WPFで受け取って「最終ページ表示中」を判定する

webView.CoreWebView2.WebMessageReceived += (s, e) =>
{
    var json = e.WebMessageAsJson;
    // JSONをパースして currentPage / totalPages を取得
    // isLast = (currentPage == totalPages) で判定
};

pdf.js採用のメリット・デメリット

観点メリットデメリット
ページ情報現在ページ・総ページ数・ズーム等を完全に制御できる実装は“ビューアを作る”方向になる
運用Edge更新で壊れにくい(自分のJSが主導)pdf.js更新に追従する必要がある
要件「ページ取得が必要」という要件を素直に満たせる「サードパーティ禁止」だと採用できない場合がある

回避策2:Windows.Data.PdfでWPF側に「ページ単位のPDFビューア」を作り、状態を管理する

「どうしてもサードパーティを入れたくない」「Edge内蔵ビューアはブラックボックスで困る」という場合、Windows標準のWinRT APIであるWindows.Data.Pdfを使って、PDFをページごとに画像としてレンダリングし、WPF側で表示・管理する方法があります。Microsoft Q&Aでもこの方向性が提案されています。

Windows.Data.Pdf.PdfDocumentはPDFを読み込み、PdfPage.RenderToStreamAsyncでページをレンダリングできます。これにより、

  • 総ページ数(pdfDoc.PageCount)が取れる
  • 今表示しているページ(あなたのUIが表示しているページ)をアプリが保持できる
  • 最終ページ判定(CurrentPage == PageCount)が素直に書ける

導入のポイント:デスクトップアプリからWinRT APIを呼べるようにする

WPF(.NET)からWinRT APIを使うには、プロジェクト設定や参照が必要です。Microsoft Learnには「デスクトップアプリでWinRT APIを呼び出す」ためのガイドがあります。

最小イメージ:PDFを開いてページを画像化する(概念コード)

WPFでの画像表示まで含めると実装は少し長くなりますが、流れはシンプルです。

  1. PDFをPdfDocument.LoadFromFileAsyncで読み込む
  2. pdfDoc.GetPage(n)でページを取得
  3. RenderToStreamAsyncで画像としてレンダリング
  4. WPFのImageにバインドして表示
// 概念例(例外処理・Dispose・UI更新は省略)
var file = await Windows.Storage.StorageFile.GetFileFromPathAsync(pdfPath);
var pdfDoc = await Windows.Data.Pdf.PdfDocument.LoadFromFileAsync(file);

uint pageCount = pdfDoc.PageCount;

using var page = pdfDoc.GetPage(0);
using var ras = new Windows.Storage.Streams.InMemoryRandomAccessStream();
await page.RenderToStreamAsync(ras);

// ras からWPFのBitmapImageへ変換して表示する(変換処理は別メソッド化推奨)

「現在ページ」をどう決めるか(スクロール型UIの定番パターン)

自前ビューアにすると、現在ページ番号は「PDFビューア内部の値」ではなく、あなたのUIの状態になります。実務では次のどれかで決めるケースが多いです。

  • ページ送り型(前へ/次へボタン):現在ページ = いま表示しているページ番号
  • サムネイル+ページ選択:選択中のページ番号
  • 連続スクロール型:ScrollViewerのオフセットと各ページ画像の位置から“いま最も見えているページ”を計算

連続スクロール型の場合は、ScrollChangedイベントで「ビューポート中央に最も近いページ」を現在ページとするルールにすると、ユーザーの体感と一致しやすいです。ここまで自分で決められるようになると、最終ページ判定も確実にできます。

Windows.Data.Pdf方式のメリット・デメリット

観点メリットデメリット
ライセンスOS標準API中心で構成でき、サードパーティ制約に強い環境によっては参照設定やWinRT呼び出しで躓きやすい
実装自由度ページ管理・ログ・UIを完全にコントロールできる検索・テキスト選択・注釈など高度機能は自前実装が必要
性能必要ページだけレンダリングすれば高速化できる全ページ画像化はメモリを食うため、キャッシュ/遅延描画が必須

回避策3:PDFコントロールを採用する(工数を機能で買う)

「現在ページ取得」だけでなく、検索、しおり、注釈、印刷、テキスト選択、フォーム入力など要件が増えるほど、自前実装は急速に重くなります。機能要件が多い場合は、最初からPDFコントロール(商用含む)を検討する方が総コストが下がることもあります。

それでもEdge内蔵PDFビューアを使い続けたい場合に知っておくべき現実

「表示だけはEdgeのPDFビューアが一番綺麗」「実装を増やしたくない」という理由で内蔵ビューアを使い続けたいケースもあります。その場合、できること・できないことを割り切って、UI/要件側を調整するのが現実的です。

できること:指定ページを“開く”ことはできる場合がある

PDFのURLフラグメントにpage=Nを付けることで、特定ページを開ける場合があります(ただし、同一PDFでフラグメントだけ変えても反映されないケースがあり、再読み込みなど工夫が必要という報告があります)。

// 例:4ページ目を開く(環境により挙動差あり)
PdfWebView.CoreWebView2.Navigate($"file:///{pdfFilePath.Replace("\\", "/")}#toolbar=0&page=4");

できないこと:閲覧中のページ番号を“信頼できる形”で取得する

WebView2Feedbackでも「ページ番号の取得や操作をAPIで提供してほしい」という要望はありますが、優先度が低い(近い将来に対応できない)として扱われているものもあります。つまり、プロダクト要件として依存するとリスクになります。

最終手段:ハック的アプローチ(推奨しないが存在はする)

どうしてもページ番号が必要で、かつ実装変更が許されない場合、ショートカットキーでページ番号UIを呼び出してクリップボードから取得する、といった力技が紹介されることがあります。ただし、ユーザー操作の妨げ、キーボードレイアウト差、権限、将来のUI変更で壊れるなど運用リスクが高いため、製品実装としては基本的におすすめしません。

比較表:どの方式が「現在ページ取得・最終ページ判定」に強いか

方式現在ページ取得最終ページ判定実装コスト将来の安定性向いているケース
WebView2+Edge内蔵PDFビューア不可(公式APIなし)不可(同上)最小高い(表示は安定)/ただし拡張は不可「表示できれば良い」
pdf.js(WebView2上で自前表示)可(イベント・状態を管理)可(current==totalで判定)中中(pdf.js更新は必要だがブラックボックス依存が減る)ページ管理・しおり・進捗が必要
Windows.Data.Pdf(WPF自前ビューア)可(UI側で保持)可(同上)中〜高高(OS標準API中心)サードパーティ制約が強い/要件が限定的
PDFコントロール(商用/OSS)可(多くが対応)可(多くが対応)低〜中(導入は簡単)製品次第検索・注釈・フォーム等が必要

まとめ:要件が「ページ取得」なら、ブラックボックスを避ける設計に切り替える

WebView2でPDFが表示できるのは便利ですが、Edge内蔵PDFビューアは“表示専用に近い存在”で、ページ番号などの内部状態をアプリへ公開していません。したがって、現在ページ番号や最終ページ判定を信頼できる形で取得したいなら、pdf.jsで自前レンダリングするか、Windows.Data.PdfなどでWPF側のビューアとしてページ管理を自分で持つのが現実解です。

まずは「なぜページ番号が必要なのか」を整理し、必要な精度に対して最もリスクが少ない表示方式を選ぶことが、結果的に開発も運用も安定させます。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次