アプリからの印刷

このトピックでは、Windows アプリから印刷する方法について説明します。

より高度な機能については、「 印刷プレビュー UI をカスタマイズするを参照してください。

印刷登録

アプリに印刷を追加する最初の手順は、現在のウィンドウの PrintManager オブジェクトを取得して印刷に登録することです。 PrintManager クラスは、アプリの印刷フローを調整する役割を担います。 このクラスを使用するには、まず、現在アクティブなウィンドウに固有の PrintManager オブジェクトを返すメソッドを呼び出す必要があります。

アプリは、ユーザーが印刷できるようにしたいすべての画面でこれを行う必要があります。 印刷登録できるのは、ユーザーに表示される画面のみです。 アプリの 1 つの画面が印刷用に登録されている場合は、終了時に印刷の登録を解除する必要があります。 別の画面に置き換えられた場合、次の画面は開いたときに印刷に登録する必要があります。

ヒント

 アプリで複数のページからの印刷をサポートする必要がある場合は、この印刷コードを共通のヘルパー クラスに配置し、アプリ ページで再利用することができます。 これを行う方法の例については、UWP 印刷サンプルのPrintHelper クラスを参照してください。

ユーザーが印刷を開始したら、 PrintDocument を使用して、プリンターに送信するページを準備します。 PrintDocument 型は、 Microsoft.UI.Xaml.Printing 名前空間と、印刷用の XAML コンテンツの準備をサポートする他の型にあります。

PrintDocument クラスは、アプリと PrintManager 間の相互作用の多くを処理するために使用されますが、独自のいくつかのコールバックが公開されます。 登録時に、 PrintManagerPrintDocument のインスタンスを作成し、その印刷イベントのハンドラーを登録します。

この例では、ページの Loaded イベント ハンドラーから呼び出されるRegisterForPrinting メソッドで登録が実行されます。

using Microsoft.UI.Xaml.Printing;
using Windows.Graphics.Printing;

PrintDocument printDocument = null;
IPrintDocumentSource printDocumentSource = null;
List<UIElement> printPreviewPages = new List<UIElement>();

private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
    RegisterForPrinting();
}

private void RegisterForPrinting()
{
    var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(App.MainWindow);
    PrintManager printManager = PrintManagerInterop.GetForWindow(hWnd);
    printManager.PrintTaskRequested += PrintTask_Requested;

    printDocument = new PrintDocument();
    printDocumentSource = printDocument.DocumentSource;
    printDocument.Paginate += PrintDocument_Paginate;
    printDocument.GetPreviewPage += PrintDocument_GetPreviewPage;
    printDocument.AddPages += PrintDocument_AddPages;
}

警告

UWP 印刷の例では、OnNavigatedTo メソッドのオーバーライドから印刷を登録することをお勧めします。 UWP 以外のアプリでは、PrintManagerInterop.GetForWindow 呼び出しでウィンドウ ハンドルを使用する必要があるため、Loaded イベントを使用してウィンドウ ハンドルが nullされていないことを確認する必要があります。これは OnNavigatedTo の場合です。

ここでは、イベント ハンドラーは、 UnregisterForPrinting メソッドで登録解除されます。これは、 OnNavigatedFrom メソッドから呼び出されます。

protected override void OnNavigatedFrom(NavigationEventArgs e)
{
    base.OnNavigatedFrom(e);
    UnregisterForPrinting();
}


private void UnregisterForPrinting()
{
    if (printDocument == null)
    {
        return;
    }

    printDocument.Paginate -= PrintDocument_Paginate;
    printDocument.GetPreviewPage -= PrintDocument_GetPreviewPage;
    printDocument.AddPages -= PrintDocument_AddPages;

    var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(App.MainWindow);
    PrintManager printManager = PrintManagerInterop.GetForWindow(hWnd);
    printManager.PrintTaskRequested -= PrintTask_Requested;
}

Note

複数ページ アプリが存在しており、印刷を切断していない場合は、ユーザーがページから離れ、その後戻ってくると、例外がスローされます。

印刷ボタンを作成する

表示するアプリの画面に印刷ボタンを追加します。 印刷するコンテンツに干渉しないようにしてください。

<Button x:Name="InvokePrintingButton"
        Content="Print"
        Click="InvokePrintingButton_Click"/>

ボタンの Click イベント ハンドラーで、Windows 印刷 UI をユーザーに表示します。

var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(App.MainWindow);
await PrintManagerInterop.ShowPrintUIForWindowAsync(hWnd);

このメソッドは、適切な印刷ウィンドウを表示する非同期メソッドであるため、Click ハンドラーに async キーワードを追加する必要があります。 最初に IsSupported メソッドを呼び出して、印刷をサポートするデバイスでアプリが実行されていることを確認することをお勧めします (そうでない場合を処理します)。 その時点で他の理由で印刷を実行できない場合、メソッドは例外をスローします。 これらの例外をキャッチし、印刷を続行できないときにユーザーに知らせることをお勧めします。

この例では、ボタン クリックのイベント ハンドラーに印刷ウィンドウが表示されます。 メソッドが例外をスローした場合 (その時点で印刷を実行できないため)、 ContentDialog コントロールは状況をユーザーに通知します。

private async void InvokePrintingButton_Click(object sender, RoutedEventArgs e)
{
    if (PrintManager.IsSupported())
    {
        try
        {
            // Show system print UI.
            var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(App.MainWindow);
            await Windows.Graphics.Printing.PrintManagerInterop.ShowPrintUIForWindowAsync(hWnd);
        }
        catch
        {
            // Printing cannot proceed at this time.
            ContentDialog noPrintingDialog = new ContentDialog()
            {
                Title = "Printing error",
                Content = "\nSorry, printing can' t proceed at this time.",
                PrimaryButtonText = "OK"
            };
            await noPrintingDialog.ShowAsync();
        }
    }
    else
    {
        // Printing is not supported on this device.
        ContentDialog noPrintingDialog = new ContentDialog()
        {
            Title = "Printing not supported",
            Content = "\nSorry, printing is not supported on this device.",
            PrimaryButtonText = "OK"
        };
        await noPrintingDialog.ShowAsync();
    }
}

アプリのコンテンツを書式設定する

ShowPrintUIForWindowAsyncが呼び出されると、PrintTaskRequested イベントが発生します。 PrintTaskRequested イベント ハンドラーでは、PrintTaskRequest.CreatePrintTask メソッドを呼び出して、PrintTask を作成します。 印刷ページのタイトルと、 PrintTaskSourceRequestedHandler デリゲートの名前を渡します。 タイトルが印刷プレビュー UI に表示されます。 PrintTaskSourceRequestedHandlerは、コンテンツを提供するPrintDocumentPrintTaskをリンクします。

この例では、エラーをキャッチするために完了ハンドラーも定義されています。 完了イベントを処理することをお勧めします。エラーが発生したかどうかをアプリがユーザーに知らせ、考えられる解決策を提供できるためです。 同様に、アプリは完了イベントを使用して、印刷ジョブが成功した後にユーザーが実行する後続の手順を示します。

private void PrintTask_Requested(PrintManager sender, PrintTaskRequestedEventArgs args)
{
    // Create the PrintTask.
    // Defines the title and delegate for PrintTaskSourceRequested.
    PrintTask printTask = args.Request.CreatePrintTask("WinUI 3 Printing example", PrintTaskSourceRequested);

    // Handle PrintTask.Completed to catch failed print jobs.
    printTask.Completed += PrintTask_Completed;

    DispatcherQueue.TryEnqueue(DispatcherQueuePriority.Normal, () =>
    {
        InvokePrintingButton.IsEnabled = false;
    });
}

private void PrintTaskSourceRequested(PrintTaskSourceRequestedArgs args)
{
    // Set the document source.
    args.SetSource(printDocumentSource);
}

private void PrintTask_Completed(PrintTask sender, PrintTaskCompletedEventArgs args)
{
    string StatusBlockText = string.Empty;

    // Notify the user if the print operation fails.
    if (args.Completion == PrintTaskCompletion.Failed)
    {
        StatusBlockText = "Failed to print.";
    }
    else if (args.Completion == PrintTaskCompletion.Canceled)
    {
        StatusBlockText = "Printing canceled.";
    }
    else
    {
        StatusBlockText = "Printing completed.";
    }


    DispatcherQueue.TryEnqueue(DispatcherQueuePriority.Normal, () =>
    {
        StatusBlock.Text = StatusBlockText;
        InvokePrintingButton.IsEnabled = true;
    });
}

印刷タスクが作成されると、 PrintManager は、 Paginate イベントを発生させることで、印刷プレビュー UI に表示する印刷ページのコレクションを要求します。 (これは、IPrintPreviewPageCollection インターフェイスの Paginate メソッドに対応します)。登録中に作成したイベント ハンドラーは、この時点で呼び出されます。

重要

 ユーザーが印刷設定を変更すると、改ページ位置イベント ハンドラーが再度呼び出され、コンテンツのリフローが可能になります。 ユーザー エクスペリエンスを最大限に高めるには、コンテンツをリフローする前に設定を確認し、ページ分割されたコンテンツが不要な場合は再初期化しないようにすることをお勧めします。

Paginate イベント ハンドラーで、印刷プレビュー UI に表示し、プリンターに送信するページを作成します。 印刷用にアプリのコンテンツを準備するために使用するコードは、アプリと印刷するコンテンツに固有です。

この例では、画面に表示されるページから画像とキャプションを印刷する 1 つの印刷ページを作成する基本的な手順を示します。

  • 印刷する UI 要素 (ページ) を保持するリストを作成します。
  • ページの改ページが発生するたびにページが重複しないように、プレビュー ページの一覧をクリアします。
  • プリンター ページのサイズを取得するには、 PrintPageDescription を使用します。
  • XAML コンテンツをプリンター ページに合わせて書式設定します。 印刷される各ページは、XAML UI 要素 (通常は他のコンテンツを含むコンテナー要素) です。 この例では、要素はコードで作成され、画面に表示される要素と同じデータを使用します。
  • 必要に応じて、コンテンツを追加のページにフローします。 この基本的な例では複数のページは表示されませんが、コンテンツをページに分割することは、Paginate イベントの重要な部分です。
  • 印刷するページの一覧に各ページを追加します。
  • PrintDocument でプレビュー ページの数を設定します。
List<UIElement> printPreviewPages = new List<UIElement>();

private void PrintDocument_Paginate(object sender, PaginateEventArgs e)
{
    // Clear the cache of preview pages.
    printPreviewPages.Clear();

    // Get the PrintTaskOptions.
    PrintTaskOptions printingOptions = ((PrintTaskOptions)e.PrintTaskOptions);
    // Get the page description to determine the size of the print page.
    PrintPageDescription pageDescription = printingOptions.GetPageDescription(0);

    // Create the print layout.
    StackPanel printLayout = new StackPanel();
    printLayout.Width = pageDescription.PageSize.Width;
    printLayout.Height = pageDescription.PageSize.Height;
    printLayout.BorderBrush = new Microsoft.UI.Xaml.Media.SolidColorBrush(Microsoft.UI.Colors.Black);
    printLayout.BorderThickness = new Thickness(48);

    Image printImage = new Image();
    printImage.Source = printContent.Source;

    printImage.Width = pageDescription.PageSize.Width / 2;
    printImage.Height = pageDescription.PageSize.Height / 2;

    TextBlock imageDescriptionText = new TextBlock();
    imageDescriptionText.Text = imageDescription.Text;
    imageDescriptionText.FontSize = 24;
    imageDescriptionText.HorizontalAlignment = HorizontalAlignment.Center;
    imageDescriptionText.Width = pageDescription.PageSize.Width / 2;
    imageDescriptionText.TextWrapping = TextWrapping.WrapWholeWords;

    printLayout.Children.Add(printImage);
    printLayout.Children.Add(imageDescriptionText);

    // Add the print layout to the list of preview pages.
    printPreviewPages.Add(printLayout);

    // Report the number of preview pages created.
    PrintDocument printDocument = (PrintDocument)sender;
    printDocument.SetPreviewPageCount(printPreviewPages.Count,
                                          PreviewPageCountType.Intermediate);
}

アプリ UI のスクリーンショットと、印刷プレビュー UI でのコンテンツの表示方法を次に示します。

印刷する画像とキャプションを示す、システム印刷プレビュー UI の横にあるアプリ UI のスクリーンショット。

印刷プレビュー ウィンドウに特定のページを表示する場合、 PrintManager は、 GetPreviewPage イベントを発生させます。 これは、IPrintPreviewPageCollection インターフェイスのMakePage メソッドに対応します。 登録中に作成したイベント ハンドラーは、この時点で呼び出されます。

GetPreviewPage イベント ハンドラーで、印刷ドキュメントの適切なページを設定します。

private void PrintDocument_GetPreviewPage(object sender, GetPreviewPageEventArgs e)
{
    PrintDocument printDocument = (PrintDocument)sender;
    printDocument.SetPreviewPage(e.PageNumber, printPreviewPages[e.PageNumber - 1]);
}

最後に、ユーザーが印刷ボタンをクリックすると、PrintManager は、IDocumentPageSource インターフェイスのMakeDocument メソッドを呼び出して、プリンターに送信するページの最終的なコレクションを要求します。 XAML では、これにより AddPages イベントが発生します。 登録中に作成したイベント ハンドラーは、この時点で呼び出されます。

AddPages イベント ハンドラーで、ページ コレクションからプリンターに送信する PrintDocument オブジェクトにページを追加します。 ユーザーが特定のページまたは印刷するページの範囲を指定した場合は、ここでその情報を使用して、プリンターに実際に送信されるページのみを追加します。

private void PrintDocument_AddPages(object sender, AddPagesEventArgs e)
{
    PrintDocument printDocument = (PrintDocument)sender;

    // Loop over all of the preview pages and add each one to be printed.
    for (int i = 0; i < printPreviewPages.Count; i++)
    {
        printDocument.AddPage(printPreviewPages[i]);
    }

    // Indicate that all of the print pages have been provided.
    printDocument.AddPagesComplete();
}