.NET MAUIでPDFを別ウィンドウ・ポップアップ表示する方法【.NET 9対応】

.NET MAUI で業務アプリを作っていると、「画面はそのままに PDF だけ別ウィンドウで開きたい」「ページ遷移ではなくポップアップでプレビューしたい」という要件がよく出てきます。本記事では .NET 9 を前提に、Windows/macOS を軸にしつつ、Android も含めて PDF を別ウィンドウ/ポップアップ表示する実装パターンを整理します。

目次

.NET MAUIでPDFを別ウィンドウ/ポップアップ表示する全体像

まずは「どのプラットフォームで」「どんな表示方法」が現実的なのかを俯瞰します。.NET MAUI は Window クラスによるマルチウィンドウ機能と、CommunityToolkit.Maui の Popup によるオーバーレイ表示の両方をサポートしており、デスクトップとモバイルでベストプラクティスが少し変わります。

目的プラットフォーム推奨手段概要
アプリとは別ウィンドウでPDFを開きたいWindows / macOS / iPadOSWindow + WebViewMAUI の Window クラスで新規ウィンドウを作成し、その中に WebView を置く。
画面遷移せずに重ねて表示したいすべてのプラットフォームCommunityToolkit.Maui Popup + WebViewPage 上にオーバーレイするポップアップを表示して、PDF を埋め込んだ WebView を載せる。
Android で PDF をアプリ内表示したいAndroidWebView + 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 によって少しずつ違います。

プラットフォーム挙動の目安ポイント
WindowsWebView2(Edge)が PDF をネイティブ表示拡大縮小・ページ移動・印刷など、Edge の PDF ビューア機能がそのまま使える。
macOS / iOS / iPadOSWKWebView が PDF を直接レンダリングアプリバンドル内の PDF はパス(NSBundle)から URL を作って WebView に渡す形が一般的。
Android標準 WebView では PDF は直接表示できないことが多いpdf.js などの JavaScript ベースのビューアをホストするか、端末の PDF アプリに委譲する必要がある。

PDFのURL・ローカルパスの扱い方

WebView に渡す URL は、「リモートの HTTPS PDF」と「ローカルファイル(file://)」で扱いが変わります。

ケース例WebViewへの設定例
インターネット上の PDFhttps://example.com/manual.pdfnew UrlWebViewSource { Url = "https://example.com/manual.pdf" }
アプリのキャッシュディレクトリ内/data/.../cache/manual.pdfnew 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でホストする場合の流れ

おおまかな手順は次のとおりです。

  1. pdf.js の配布物(web/viewer.html 一式)をダウンロードする
  2. MAUI プロジェクトの Resources/Raw/pdfjs などに配置し、ビルドアクションを MauiAsset に設定する
  3. 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)
            &amp;&amp; !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 を柔軟に表示できるようになるはずです。

この記事を書いた人

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

コメント

コメントする

目次