.NET MAUI で業務アプリを作っていると、「画面はそのままに PDF だけ別ウィンドウで開きたい」「ページ遷移ではなくポップアップでプレビューしたい」という要件がよく出てきます。本記事では .NET 9 を前提に、Windows/macOS を軸にしつつ、Android も含めて PDF を別ウィンドウ/ポップアップ表示する実装パターンを整理します。
.NET MAUIでPDFを別ウィンドウ/ポップアップ表示する全体像
まずは「どのプラットフォームで」「どんな表示方法」が現実的なのかを俯瞰します。.NET MAUI は Window クラスによるマルチウィンドウ機能と、CommunityToolkit.Maui の Popup によるオーバーレイ表示の両方をサポートしており、デスクトップとモバイルでベストプラクティスが少し変わります。
| 目的 | プラットフォーム | 推奨手段 | 概要 |
|---|---|---|---|
| アプリとは別ウィンドウでPDFを開きたい | Windows / macOS / iPadOS | Window + WebView | MAUI の Window クラスで新規ウィンドウを作成し、その中に WebView を置く。 |
| 画面遷移せずに重ねて表示したい | すべてのプラットフォーム | CommunityToolkit.Maui Popup + WebView | Page 上にオーバーレイするポップアップを表示して、PDF を埋め込んだ WebView を載せる。 |
| Android で PDF をアプリ内表示したい | Android | WebView + pdf.js または既定PDFアプリに委譲 | 標準 WebView は PDF を直接レンダリングしないため、pdf.js をホストするか、Launcher.OpenAsync で OS に任せる。 |
| 端末の高機能PDFビューアを使いたい | すべてのプラットフォーム | Launcher.OpenAsync | アプリ外の既定 PDF アプリにファイルを渡し、閲覧・注釈・検索などを委譲する。 |
このあとで、
- Window を使った「別ウィンドウ表示」
- CommunityToolkit.Maui の Popup を使った「ポップアップ表示」
- Android 固有の pdf.js/既定アプリ対応
という流れで具体的なコードと設計のポイントを解説していきます。
.NET MAUIとPDF表示の基本戦略(WebView中心)
.NET MAUI には「PDFビューア」専用の標準コントロールは用意されていません。そのため、多くのケースでは次のいずれかの戦略を取ることになります。
- WebView に PDF を読み込む(ブラウザや WebView2 / WKWebView の PDF 機能に任せる)
- ネイティブ/サードパーティの PDF コンポーネントを導入する(Syncfusion 等の有償コンポーネントなど)
- 端末の既定 PDF アプリに委譲する(
Launcher.OpenAsync)
本記事では、標準機能だけで実現できる「WebView」と「既定 PDF アプリ」を中心に解説します。
WebViewでPDFを開いたときのプラットフォーム差
WebView に PDF を渡したときの挙動は OS によって少しずつ違います。
| プラットフォーム | 挙動の目安 | ポイント |
|---|---|---|
| Windows | WebView2(Edge)が PDF をネイティブ表示 | 拡大縮小・ページ移動・印刷など、Edge の PDF ビューア機能がそのまま使える。 |
| macOS / iOS / iPadOS | WKWebView が PDF を直接レンダリング | アプリバンドル内の PDF はパス(NSBundle)から URL を作って WebView に渡す形が一般的。 |
| Android | 標準 WebView では PDF は直接表示できないことが多い | pdf.js などの JavaScript ベースのビューアをホストするか、端末の PDF アプリに委譲する必要がある。 |
PDFのURL・ローカルパスの扱い方
WebView に渡す URL は、「リモートの HTTPS PDF」と「ローカルファイル(file://)」で扱いが変わります。
| ケース | 例 | WebViewへの設定例 |
|---|---|---|
| インターネット上の PDF | https://example.com/manual.pdf | new UrlWebViewSource { Url = "https://example.com/manual.pdf" } |
| アプリのキャッシュディレクトリ内 | /data/.../cache/manual.pdf | new UrlWebViewSource { Url = "file:///data/.../cache/manual.pdf" } |
| iOS / macOS のバンドルリソース | NSBundle.MainBundle.PathForResource("manual", "pdf") | 取得したパスをそのまま Url に渡す。 |
ネット上の PDF を扱う場合は、iOS/macOS の App Transport Security や Android のクリアテキスト制限を避けるため、基本的には HTTPS を利用するのがおすすめです。
Windowで別ウィンドウにPDFを表示する(.NET 9 / Windows・macOS)
本題の「別ウィンドウ表示」です。.NET MAUI では Window クラスを使って複数ウィンドウを生成し、Application.Current.OpenWindow で開くことができます。
.NET 9のCreateWindowとマルチウィンドウの基本
.NET 9 のテンプレートでは、App クラスで CreateWindow をオーバーライドして最初のウィンドウを返すスタイルが採用されています。
namespace MyMauiApp;
public partial class App : Application
{
public App()
{
InitializeComponent();
}
protected override Window CreateWindow(IActivationState? activationState)
{
// .NET 9 以降では CreateWindow をオーバーライドして
// 最初のウィンドウを生成する
return new Window(new AppShell());
}
}
追加のウィンドウは、どのページからでも次のように開けます。
var secondWindow = new Window(new MyPage());
Application.Current?.OpenWindow(secondWindow);
Multi-window は Windows では特別な設定なしで動作しますが、Android/iPadOS/Mac Catalyst では追加設定が必要であり、iPhone ではマルチウィンドウ自体がサポートされていません。
PDF専用ウィンドウを開くヘルパーの実装
PDF 表示用のウィンドウを開く処理をサービスとしてまとめると、どこからでも同じ UI で呼び出せて便利です。
public static class PdfWindowService
{
public static void Open(string pdfPathOrUrl)
{
if (Application.Current is null)
{
return;
}
var url = ToPdfUrl(pdfPathOrUrl);
var webView = new WebView
{
Source = new UrlWebViewSource
{
Url = url
}
};
var page = new ContentPage
{
Title = "PDF プレビュー",
Content = webView
};
var window = new Window(page);
#if WINDOWS || MACCATALYST
// デスクトップではウィンドウサイズを指定すると UX が安定する
window.Width = 900;
window.Height = 700;
#endif
Application.Current.OpenWindow(window);
}
static string ToPdfUrl(string pdfPathOrUrl)
{
// http(s) など既に完全な URL の場合はそのまま利用
if (Uri.IsWellFormedUriString(pdfPathOrUrl, UriKind.Absolute))
{
return pdfPathOrUrl;
}
// ローカルパスの場合は file:// を付与
if (pdfPathOrUrl.StartsWith("file://", StringComparison.OrdinalIgnoreCase))
{
return pdfPathOrUrl;
}
return $"file://{pdfPathOrUrl}";
}
}
ボタンからはシンプルに次のように呼び出せば OK です。
void OnOpenPdfWindowClicked(object sender, EventArgs e)
{
// 実際にはファイル選択ダイアログや設定値からパス/URL を取得する
var pdfPath = Path.Combine(FileSystem.AppDataDirectory, "sample.pdf");
PdfWindowService.Open(pdfPath);
}
ウィンドウ位置・サイズの調整(主にWindows)
Windows では Window.Width / Height / X / Y を設定することで、ウィンドウの大きさや位置をプログラムから制御できます。Mac Catalyst では直接のリサイズが制限されており、最小/最大サイズを一時的に変えるワークアラウンドがドキュメントに示されています。
簡単な例として、作成したウィンドウを画面中央に寄せたい場合は、デバイスのディスプレイ情報から座標を計算します。
void CenterWindow(Window window)
{
#if WINDOWS
var displayInfo = DeviceDisplay.Current.MainDisplayInfo;
var screenWidth = displayInfo.Width / displayInfo.Density;
var screenHeight = displayInfo.Height / displayInfo.Density;
window.X = (screenWidth - window.Width) / 2;
window.Y = (screenHeight - window.Height) / 2;
#endif
}
この CenterWindow を PdfWindowService.Open の中で呼び出せば、「常に中央に PDF ウィンドウを開く」といった UX を簡単に実現できます。
ローカルPDFの保存場所とパス生成パターン
Web API などから PDF をダウンロードして表示する場合、一度ローカルに保存してから file:// で読むほうが安定します。
async Task<string> DownloadPdfAsync(Stream pdfStream, string fileName)
{
var path = Path.Combine(FileSystem.AppDataDirectory, fileName);
using var fileStream = File.Open(path, FileMode.Create, FileAccess.Write);
await pdfStream.CopyToAsync(fileStream);
return path;
}
取得したパスはそのまま PdfWindowService.Open(path) に渡せば OK です。
CommunityToolkit.Mauiでポップアップ表示(モーダルオーバーレイ)
マルチウィンドウを使えない iPhone や、多くの Android 端末では「別ウィンドウ」の概念が限定的です。その場合、「画面遷移せずに重ねて表示」するポップアップ UI のほうが現実的です。.NET MAUI Community Toolkit の Popup 機能を使うと、任意の View をオーバーレイ表示できます。
CommunityToolkit.Mauiのセットアップ
まずは NuGet から CommunityToolkit.Maui を追加し、MauiProgram で登録します。
using CommunityToolkit.Maui;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.UseMauiCommunityToolkit(options =>
{
// Popup のデフォルト設定(任意)
options.SetPopupDefaults(new DefaultPopupSettings
{
CanBeDismissedByTappingOutsideOfPopup = true
});
});
// 省略: フォント・サービス登録など
return builder.Build();
}
}
ページ側では CommunityToolkit.Maui.Views を using して、拡張メソッド ShowPopupAsync を呼び出します。
WebView入りのPDFポップアップをその場で作る
「そこまで複雑な UI は要らない、WebView を 1 つ出せれば十分」というケースなら、ボタンクリックから直接 WebView を生成してポップアップできます。
using CommunityToolkit.Maui.Views;
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
async void OnOpenPdfPopupClicked(object sender, EventArgs e)
{
var pdfUrl = "https://example.com/sample.pdf";
var webView = new WebView
{
Source = new UrlWebViewSource
{
Url = pdfUrl
}
};
var content = new Grid
{
WidthRequest = 800,
HeightRequest = 600
};
content.Children.Add(webView);
var options = new PopupOptions
{
CanBeDismissedByTappingOutsideOfPopup = true
};
await this.ShowPopupAsync(content, options);
}
}
この方法はシンプルですが、
- 複数のページで同じ PDF ポップアップを使い回したい
- ヘッダーに「閉じる」ボタンやページ番号を乗せたい
といった場合には少し窮屈です。その場合は専用の ContentView を用意するのがおすすめです。
専用ContentViewで再利用可能なPDFポップアップを作る
まず、XAML で WebView を含む ContentView を定義します。
<ContentView
x:Class="MyApp.Views.PdfPopupView"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
<Grid
WidthRequest="800"
HeightRequest="600">
<WebView x:Name="PdfWebView" />
</Grid>
</ContentView>
コードビハインドでは、PDF の URL を受け取って WebView に渡すメソッドを用意します。
namespace MyApp.Views;
public partial class PdfPopupView : ContentView
{
public PdfPopupView()
{
InitializeComponent();
}
public void LoadPdf(string pdfUrlOrPath)
{
var url = pdfUrlOrPath;
if (!Uri.IsWellFormedUriString(pdfUrlOrPath, UriKind.Absolute)
&& !pdfUrlOrPath.StartsWith("file://", StringComparison.OrdinalIgnoreCase))
{
url = $"file://{pdfUrlOrPath}";
}
PdfWebView.Source = new UrlWebViewSource
{
Url = url
};
}
}
ページ側からは、作成した PdfPopupView を ShowPopupAsync で表示します。
using CommunityToolkit.Maui.Views;
using MyApp.Views;
public partial class MainPage : ContentPage
{
async void OnOpenPdfPopupClicked(object sender, EventArgs e)
{
var content = new PdfPopupView();
content.LoadPdf("https://example.com/sample.pdf");
await this.ShowPopupAsync(content, PopupOptions.Empty);
}
}
なお、Popup を表示すると、裏側のページには OnDisappearing / OnNavigatingFrom などのライフサイクルイベントが呼ばれる点に注意してください。ツールキットでは「直前が Popup だったか」を判定するヘルパーも用意されています。
Android固有のPDF対策:pdf.jsか既定アプリに委譲
Android WebViewはPDFが苦手
Android の標準 WebView は、HTML や画像は問題なく表示できますが、PDF をそのままレンダリングする機能は持っていません。そのため、pdf.js のような JavaScript ベースのビューアを WebView 内にホストするか、PDF ファイルを OS に渡して既定アプリで開く必要があります。
pdf.jsをWebViewでホストする場合の流れ
おおまかな手順は次のとおりです。
- pdf.js の配布物(
web/viewer.html一式)をダウンロードする - MAUI プロジェクトの
Resources/Raw/pdfjsなどに配置し、ビルドアクションを MauiAsset に設定する - WebView から
pdfjs/web/viewer.html?file=<PDFのURLまたはfile://パス>にナビゲートする
概念的なコードは次のようになります(実際のパスはプロジェクト構成に合わせて調整してください)。
public static class AndroidPdfWebViewFactory
{
public static WebView Create(string pdfUrlOrPath)
{
var url = pdfUrlOrPath;
if (!Uri.IsWellFormedUriString(pdfUrlOrPath, UriKind.Absolute)
&& !pdfUrlOrPath.StartsWith("file://", StringComparison.OrdinalIgnoreCase))
{
url = $"file://{pdfUrlOrPath}";
}
var escaped = Uri.EscapeDataString(url);
// pdfjs フォルダは Resources/Raw に配置し、MauiAsset にしておく
var viewerUrl = $"pdfjs/web/viewer.html?file={escaped}";
return new WebView
{
Source = new UrlWebViewSource
{
Url = viewerUrl
}
};
}
}
iOS のように PDF をアプリデータディレクトリに保存している場合、viewer.html(pdf.js)はバンドル内、PDF は AppDataDirectory のファイルという構成になり、URL の組み立てで悩みがちです。GitHub / Q&A でも同種の相談が多く、パスの組み合わせをデバッグしながら確認することが大切です。
Launcher.OpenAsyncで既定ビューアに任せる
「pdf.js を組み込むほどではない」「ユーザーは普段使っている PDF アプリの UI に慣れている」という場合は、端末既定アプリに任せるのが最短ルートです。
using Microsoft.Maui.ApplicationModel;
using Microsoft.Maui.Storage;
public static class SystemPdfViewer
{
public static async Task OpenAsync(string localPdfPath)
{
var file = new ReadOnlyFile(localPdfPath);
await Launcher.OpenAsync(new OpenFileRequest
{
File = file
});
}
}
この方法なら、注釈・検索・印刷など、端末にインストールされている PDF アプリの機能をほぼフル活用できます。一方で「アプリの外」に出てしまうため、ユーザーが戻ってこないリスクや、業務フローをアプリ内に完結させたい場合には不向きです。
マルチウィンドウのプラットフォーム設定
Windows / macOS だけであれば、マルチウィンドウはそのまま動作しますが、Android / iPadOS でも Window ベースの設計を取りたい場合、いくつかの追加設定が必要です。
Android: MainActivityのLaunchModeをMultipleに変更
Android でマルチウィンドウを有効にするには、Platforms/Android/MainActivity.cs の LaunchMode を Multiple に変更します。
using Android.App;
using Android.Content.PM;
using Android.OS;
namespace MyMauiApp;
[Activity(
Theme = "@style/Maui.SplashTheme",
MainLauncher = true,
LaunchMode = LaunchMode.Multiple,
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
public class MainActivity : MauiAppCompatActivity
{
}
それでもなお、Android のマルチウィンドウはデスクトップほど直感的に動かないケースがあり、実際に「Windows では動くのに Android では新しいウィンドウが出てこない」といった報告もあります。 モバイルでは基本的に「Popup で重ねる」という設計に寄せ、マルチウィンドウはデスクトップ/タブレット向けのオプション機能と割り切るのがおすすめです。
iPadOS / macOS: SceneDelegateとInfo.plist
iPadOS / Mac Catalyst でマルチウィンドウを有効にするには、
SceneDelegateクラス(MauiUISceneDelegateを継承)をPlatforms/iOSとPlatforms/MacCatalyst配下に追加するInfo.plistにUIApplicationSceneManifestを追加し、UIApplicationSupportsMultipleScenesをtrueにする
といった設定が必要になります。 iPhone はそもそもマルチウィンドウ非対応のため、ポップアップやページ遷移で代替する方針を取りましょう。
別ウィンドウとポップアップの使い分け指針
ここまでの内容を踏まえ、どのユースケースで Window/Popup/既定アプリを使うべきかを整理します。
| ユースケース | おすすめ手段 | 理由 |
|---|---|---|
| PCでマスタ画面を開きながらPDF仕様書を参照したい | Window + WebView | メイン画面と並べて表示しやすく、ドラッグで別モニタにも移動できる。 |
| 入力フォーム上で「契約書PDF」を確認してすぐ戻りたい | Popup + WebView | ページ遷移しないため、入力状態を維持したまま PDF を確認できる。 |
| 容量の大きな PDF のみ別アプリで開きたい | Launcher.OpenAsync | 読み込み性能やページ検索などを既定ビューアに任せられる。 |
| スマホアプリで「簡易プレビュー」だけ提供したい | Popup + WebView または 1 ページ遷移 | 画面が狭いので別ウィンドウよりも全画面またはモーダルのほうが見やすい。 |
サンプル設計:ボタン1つで「別ウィンドウ or ポップアップ」を切り替える
最後に、実プロジェクトで便利な「環境に応じて表示方法を切り替えるサービス」の一例を紹介します。
まず、PDF 表示のインターフェースを定義します。
public enum PdfViewerMode
{
NewWindow,
Popup,
SystemViewer
}
public interface IPdfViewerService
{
Task ShowAsync(string pdfPathOrUrl, PdfViewerMode mode);
}
実装では、プラットフォーム/モードに応じて Window・Popup・Launcher を使い分けます。
public class PdfViewerService : IPdfViewerService
{
readonly Page _page;
public PdfViewerService(Page page)
{
_page = page;
}
public async Task ShowAsync(string pdfPathOrUrl, PdfViewerMode mode)
{
#if WINDOWS || MACCATALYST
if (mode == PdfViewerMode.NewWindow)
{
PdfWindowService.Open(pdfPathOrUrl);
return;
}
#endif
switch (mode)
{
case PdfViewerMode.Popup:
await ShowPopupAsync(pdfPathOrUrl);
break;
case PdfViewerMode.SystemViewer:
await SystemPdfViewer.OpenAsync(pdfPathOrUrl);
break;
default:
// デフォルトはプラットフォームに応じたおすすめにフォールバック
#if WINDOWS || MACCATALYST
PdfWindowService.Open(pdfPathOrUrl);
#else
await ShowPopupAsync(pdfPathOrUrl);
#endif
break;
}
}
async Task ShowPopupAsync(string pdfPathOrUrl)
{
var content = new PdfPopupView();
content.LoadPdf(pdfPathOrUrl);
await _page.ShowPopupAsync(content, PopupOptions.Empty);
}
}
DI コンテナに IPdfViewerService を登録しておけば、ボタンクリック時のコードは非常にシンプルになります。
public partial class MainPage : ContentPage
{
readonly IPdfViewerService _pdfViewerService;
public MainPage(IPdfViewerService pdfViewerService)
{
InitializeComponent();
_pdfViewerService = pdfViewerService;
}
async void OnPreviewPdfClicked(object sender, EventArgs e)
{
var pdfPath = Path.Combine(FileSystem.AppDataDirectory, "contract.pdf");
#if WINDOWS || MACCATALYST
await _pdfViewerService.ShowAsync(pdfPath, PdfViewerMode.NewWindow);
#else
await _pdfViewerService.ShowAsync(pdfPath, PdfViewerMode.Popup);
#endif
}
}
このように「PDF の表示戦略」を 1 箇所に閉じ込めておくと、
- 後から pdf.js を導入したい
- Windows 版だけ専用 PDF コントロールに差し替えたい
- モバイルでは常に既定アプリに委譲したい
といった変更もサービスの実装を差し替えるだけで済み、アプリ全体の保守性が高まります。
まとめ
本記事のポイントを整理すると次の通りです。
- 表示手段は WebView が最も手軽。Windows / macOS / iOS 系は WebView に PDF の URL または
file://を渡せばそのまま表示できる。 - Android は標準 WebView が PDF を描画しないため、pdf.js をホストするか既定 PDF アプリに任せるのが現実解。
- 別ウィンドウ表示は .NET MAUI の
WindowクラスとApplication.Current.OpenWindowで実現でき、Windows / macOS / iPadOS で特に有効。 - ポップアップ表示は CommunityToolkit.Maui の
ShowPopupAsyncと任意の View(WebView を含む)を組み合わせるだけで実装でき、モバイル向け UX と相性が良い。 - .NET 9 では
CreateWindowを使ったウィンドウ生成が基本スタイルであり、マルチウィンドウや Window サイズ制御なども公式ドキュメントにまとめられている。
「Windows / macOS では Window で別ウィンドウ」「モバイルでは Popup」「Android だけ pdf.js または既定アプリ」という役割分担をベースに、この記事のスニペットを組み合わせれば、.NET 9 ベースの MAUI アプリで PDF を柔軟に表示できるようになるはずです。

コメント