从应用打印Print from your app

重要的 APIImportant APIs

了解如何从通用 Windows 应用中打印文档。Learn how to print documents from a Universal Windows app. 本主题还介绍了如何打印特定页面。This topic also shows how to print specific pages. 有关对打印预览 UI 的更多高级更改,请参阅自定义打印预览 UIFor more advanced changes to the print preview UI, see Customize the print preview UI.

提示

 本主题中的大多数示例都基于 通用 Windows 平台 (uwp) 打印示例,这是 GitHub 上 通用 Windows 平台 (uwp) 应用示例 存储库的一部分。 Most of the examples in this topic are based on the Universal Windows Platform (UWP) print sample, which is part of the Universal Windows Platform (UWP) app samples repo on GitHub.

注册打印Register for printing

在你的应用中添加打印的第一步是注册打印合约。The first step to add printing to your app is to register for the Print contract. 你的应用必须在你希望你的用户能够从其打印的每个屏幕上执行此操作。Your app must do this on every screen from which you want your user to be able to print. 仅显示给用户的屏幕可以注册打印。Only the screen that is displayed to the user can be registered for printing. 如果你的应用的一个屏幕已注册打印,则该屏幕必须取消注册打印(当该屏幕存在时)。If one screen of your app has registered for printing, it must unregister for printing when it exits. 如果该屏幕替换为另一个屏幕,则当下一个屏幕打开时,它必须注册新的打印合约。If it is replaced by another screen, the next screen must register for a new Print contract when it opens.

提示

 如果你需要在应用中支持多个页面的打印,则可以将该打印代码放置在常用的帮助程序类中并且让你的应用重复使用它。 If you need to support printing from more than one page in your app, you can put this print code in a common helper class and have your app pages reuse it. 有关具体做法的示例,请参阅 UWP 打印示例中的 PrintHelper 类。For an example of how to do this, see the PrintHelper class in the UWP print sample.

首先,声明 PrintManagerPrintDocumentFirst, declare the PrintManager and PrintDocument. PrintManager 类型以及用于支持其他 Windows 打印功能的类型都位于 Windows.Graphics.Printing 命名空间中。The PrintManager type is in the Windows.Graphics.Printing namespace along with types to support other Windows printing functionality. PrintDocument 类型以及支持准备 XAML 内容以供打印的其他类型都位于 Windows.UI.Xaml.Printing 命名空间中。The PrintDocument type is in the Windows.UI.Xaml.Printing namespace along with other types that support preparing XAML content for printing. 你可以通过在页面中添加以下 usingImports 语句,使得编写打印代码更加容易。You can make it easier to write your printing code by adding the following using or Imports statements to your page.

using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

PrintDocument 类用于处理应用和 PrintManager 之间的大部分交互,但是它会公开其自身的多个回调。The PrintDocument class is used to handle much of the interaction between the app and the PrintManager, but it exposes several callbacks of its own. 在注册期间,创建 PrintManagerPrintDocument 的实例并为其打印事件注册处理程序。During registration, create instances of PrintManager and PrintDocument and register handlers for their printing events.

UWP 打印示例中,注册通过 RegisterForPrinting 方法来执行。In the UWP print sample, registration is performed by the RegisterForPrinting method.

public virtual void RegisterForPrinting()
{
   printDocument = new PrintDocument();
   printDocumentSource = printDocument.DocumentSource;
   printDocument.Paginate += CreatePrintPreviewPages;
   printDocument.GetPreviewPage += GetPrintPreviewPage;
   printDocument.AddPages += AddPrintPages;

   PrintManager printMan = PrintManager.GetForCurrentView();
   printMan.PrintTaskRequested += PrintTaskRequested;
}

当用户转到支持打印的页面时,它会在 OnNavigatedTo 方法中启动注册。When the user goes to a page that supports printing, it initiates the registration within the OnNavigatedTo method.

protected override void OnNavigatedTo(NavigationEventArgs e)
{
   // Initialize common helper class and register for printing
   printHelper = new PrintHelper(this);
   printHelper.RegisterForPrinting();

   // Initialize print content for this scenario
   printHelper.PreparePrintContent(new PageToPrint());

   // Tell the user how to print
   MainPage.Current.NotifyUser("Print contract registered with customization, use the Print button to print.", NotifyType.StatusMessage);
}

在此示例中,事件处理程序已在方法中取消注册 UnregisterForPrintingIn the sample, the event handlers are unregistered in the UnregisterForPrinting method.

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

    printDocument.Paginate -= CreatePrintPreviewPages;
    printDocument.GetPreviewPage -= GetPrintPreviewPage;
    printDocument.AddPages -= AddPrintPages;

    PrintManager printMan = PrintManager.GetForCurrentView();
    printMan.PrintTaskRequested -= PrintTaskRequested;
}

当用户离开支持打印的页面时,将在方法中取消注册事件处理程序 OnNavigatedFromWhen the user leaves a page that supports printing, the event handlers are unregistered within the OnNavigatedFrom method.

备注

如果有多页应用但不断开打印,则当用户离开页面并返回到该页面时,将引发异常。If you have a multiple-page app and don't disconnect printing, an exception is thrown when the user leaves the page and then returns to it.

protected override void OnNavigatedFrom(NavigationEventArgs e)
{
   if (printHelper != null)
   {
         printHelper.UnregisterForPrinting();
   }
}

创建打印按钮Create a print button

将打印按钮添加到你希望其在其中显示的应用屏幕。Add a print button to your app's screen where you'd like it to appear. 确保它不会干扰你要打印的内容。Make sure that it doesn't interfere with the content that you want to print.

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

下一步,将时间处理程序添加到应用的代码以处理 click 事件。Next, add an event handler to your app's code to handle the click event. 使用 ShowPrintUIAsync 方法开始从你的应用打印。Use the ShowPrintUIAsync method to start printing from your app. ShowPrintUIAsync 是显示相应打印窗口的异步方法。ShowPrintUIAsync is an asynchronous method that displays the appropriate printing window. 我们建议先调用 IsSupported 方法,以检查应用是否在支持打印的设备上运行(并处理不支持打印的情况)。We recommend calling the IsSupported method first in order to check that the app is being run on a device that supports printing (and handle the case in which it is not). 如果此时出于任何其他原因,无法执行打印,ShowPrintUIAsync 会引发异常。If printing can't be performed at that time for any other reason, ShowPrintUIAsync will throw an exception. 我们建议捕获这些异常,并让用户知道何时无法继续打印。We recommend catching these exceptions and letting the user know when printing can't proceed.

在此示例中,点击按钮将在事件处理程序中显示打印窗口。In this example, a print window is displayed in the event handler for a button click. 如果该方法引发异常(因为不能在该时间执行打印),则 ContentDialog 控件将通知用户该情况。If the method throws an exception (because printing can't be performed at that time), a ContentDialog control informs the user of the situation.

async private void OnPrintButtonClick(object sender, RoutedEventArgs e)
{
    if (Windows.Graphics.Printing.PrintManager.IsSupported())
    {
        try
        {
            // Show print UI
            await Windows.Graphics.Printing.PrintManager.ShowPrintUIAsync();

        }
        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();
    }
}

格式化应用的内容Format your app's content

调用 ShowPrintUIAsync 时会引发 PrintTaskRequested 事件。When ShowPrintUIAsync is called, the PrintTaskRequested event is raised. 此步骤中显示的 PrintTaskRequested 事件处理程序将通过调用 PrintTaskRequest.CreatePrintTask 方法创建 PrintTask,并传递打印页面的标题和 PrintTaskSourceRequestedHandler 委托的名称。The PrintTaskRequested event handler shown in this step creates a PrintTask by calling the PrintTaskRequest.CreatePrintTask method and passes the title for the print page and the name of a PrintTaskSourceRequestedHandler delegate. 请注意,在此示例中,PrintTaskSourceRequestedHandler 是以内联方式定义的。Notice that in this example, the PrintTaskSourceRequestedHandler is defined inline. PrintTaskSourceRequestedHandler 提供用于打印的格式化内容,并在以后对其进行介绍。The PrintTaskSourceRequestedHandler provides the formatted content for printing and is described later.

在此示例中,还会定义一个完成处理程序以捕获错误。In this example, a completion handler is also defined to catch errors. 最好对完成事件进行处理,因为,那样你的应用可以让用户知道是否发生了错误并提供可能的解决方案。It's a good idea to handle completion events because then your app can let the user know if an error occurred and provide possible solutions. 同样,你的应用可以使用完成事件来指示用户在打印作业成功之后要采取的后续步骤。Likewise, your app could use the completion event to indicate subsequent steps for the user to take after the print job is successful.

protected virtual void PrintTaskRequested(PrintManager sender, PrintTaskRequestedEventArgs e)
{
   PrintTask printTask = null;
   printTask = e.Request.CreatePrintTask("C# Printing SDK Sample", sourceRequested =>
   {
         // Print Task event handler is invoked when the print job is completed.
         printTask.Completed += async (s, args) =>
         {
            // Notify the user when the print operation fails.
            if (args.Completion == PrintTaskCompletion.Failed)
            {
               await scenarioPage.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
               {
                     MainPage.Current.NotifyUser("Failed to print.", NotifyType.ErrorMessage);
               });
            }
         };

         sourceRequested.SetSource(printDocumentSource);
   });
}

创建打印任务之后,PrintManager 将通过引发 Paginate 事件来请求要在打印预览 UI 中显示的打印页面集合。After the print task is created, the PrintManager requests a collection of print pages to show in the print preview UI by raising the Paginate event. 这与 IPrintPreviewPageCollection 接口的 Paginate 方法对应。This corresponds with the Paginate method of the IPrintPreviewPageCollection interface. 此时将调用在注册期间创建的事件处理程序。The event handler you created during registration will be called at this time.

重要

 如果用户更改打印设置,则将再次调用分页事件处理程序以允许你重排内容。 If the user changes print settings, the paginate event handler will be called again to allow you to reflow the content. 为了获得最佳的用户体验,我们建议在重新排列内容之前检查设置,以避免在不必要时重新初始化分页内容。For the best user experience, we recommend checking the settings before you reflow the content and avoid reinitializing the paginated content when it's not necessary.

Paginate 事件处理程序中(UWP 打印示例中的 CreatePrintPreviewPages 方法),创建要在打印预览 UI 中显示并发送到打印机的页面。In the Paginate event handler (the CreatePrintPreviewPages method in the UWP print sample), create the pages to show in the print preview UI and to send to the printer. 用于准备你的应用的内容以供打印的代码特定于你的应用以及你打印的内容。The code you use to prepare your app's content for printing is specific to your app and the content you print. 请参考 UWP 打印示例源代码,了解它如何格式化其内容以进行打印。Refer to the UWP print sample source code to see how it formats its content for printing.

protected virtual void CreatePrintPreviewPages(object sender, PaginateEventArgs e)
{
   // Clear the cache of preview pages
   printPreviewPages.Clear();

   // Clear the print canvas of preview pages
   PrintCanvas.Children.Clear();

   // This variable keeps track of the last RichTextBlockOverflow element that was added to a page which will be printed
   RichTextBlockOverflow lastRTBOOnPage;

   // Get the PrintTaskOptions
   PrintTaskOptions printingOptions = ((PrintTaskOptions)e.PrintTaskOptions);

   // Get the page description to deterimine how big the page is
   PrintPageDescription pageDescription = printingOptions.GetPageDescription(0);

   // We know there is at least one page to be printed. passing null as the first parameter to
   // AddOnePrintPreviewPage tells the function to add the first page.
   lastRTBOOnPage = AddOnePrintPreviewPage(null, pageDescription);

   // We know there are more pages to be added as long as the last RichTextBoxOverflow added to a print preview
   // page has extra content
   while (lastRTBOOnPage.HasOverflowContent && lastRTBOOnPage.Visibility == Windows.UI.Xaml.Visibility.Visible)
   {
         lastRTBOOnPage = AddOnePrintPreviewPage(lastRTBOOnPage, pageDescription);
   }

   if (PreviewPagesCreated != null)
   {
         PreviewPagesCreated.Invoke(printPreviewPages, null);
   }

   PrintDocument printDoc = (PrintDocument)sender;

   // Report the number of preview pages created
   printDoc.SetPreviewPageCount(printPreviewPages.Count, PreviewPageCountType.Intermediate);
}

要在打印预览窗口中显示特定页面时,PrintManager 将引发 GetPreviewPage 事件。When a particular page is to be shown in the print preview window, the PrintManager raises the GetPreviewPage event. 这与 IPrintPreviewPageCollection 接口的 MakePage 方法对应。This corresponds with the MakePage method of the IPrintPreviewPageCollection interface. 此时将调用在注册期间创建的事件处理程序。The event handler you created during registration will be called at this time.

GetPreviewPage 事件处理程序(UWP 打印示例中的 GetPrintPreviewPage 方法)中,在打印文档上设置相应的页面。In the GetPreviewPage event handler (the GetPrintPreviewPage method in the UWP print sample), set the appropriate page on the print document.

protected virtual void GetPrintPreviewPage(object sender, GetPreviewPageEventArgs e)
{
   PrintDocument printDoc = (PrintDocument)sender;
   printDoc.SetPreviewPage(e.PageNumber, printPreviewPages[e.PageNumber - 1]);
}

最后,在用户单击打印按钮后,PrintManager 会通过调用 IDocumentPageSource 接口的 MakeDocument 方法,请求要发送到打印机的页面的最终集合。Finally, once the user clicks the print button, the PrintManager requests the final collection of pages to send to the printer by calling the MakeDocument method of the IDocumentPageSource interface. 在 XAML 中,这会引发 AddPages 事件。In XAML, this raises the AddPages event. 此时将调用在注册期间创建的事件处理程序。The event handler you created during registration will be called at this time.

AddPages 事件处理程序(UWP 打印示例中的 AddPrintPages 方法)中,将页面集合中的页面添加到要发送到打印机的 PrintDocument 对象。In the AddPages event handler (the AddPrintPages method in the UWP print sample), add pages from the page collection to the PrintDocument object to be sent to the printer. 如果用户指定要打印的特殊页面或页面范围,则使用此处的信息以仅添加将实际发送到打印机的页面。If a user specifies particular pages or a range of pages to print, you use that information here to add only the pages that will actually be sent to the printer.

protected virtual void AddPrintPages(object sender, AddPagesEventArgs e)
{
   // Loop over all of the preview pages and add each one to  add each page to be printied
   for (int i = 0; i < printPreviewPages.Count; i++)
   {
         // We should have all pages ready at this point...
         printDocument.AddPage(printPreviewPages[i]);
   }

   PrintDocument printDoc = (PrintDocument)sender;

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

准备打印选项Prepare print options

下一步,准备打印选项。Next prepare print options. 例如,本部分将介绍如何设置页面范围选项来允许打印特定页面。As an example, this section will describe how to set the page range option to allow printing of specific pages. 有关更多高级选项,请参阅自定义打印预览 UIFor more advanced options, see Customize the print preview UI.

此步骤将创建一个新打印选项,定义该选项支持的一个值列表,然后将该选项添加到打印预览 UI 中。This step creates a new print option, defines a list of values that the option supports, and then adds the option to the print preview UI. 页面范围选项具有以下设置:The page range option has these settings:

选项名Option name 操作Action
打印全部Print all 打印文档中的所有页面。Print all pages in the document.
打印选择内容Print Selection 仅打印用户选择的内容。Print only the content the user selected.
打印范围Print Range 显示编辑控件,用户可以将要打印的页面输入到该控件中。Display an edit control into which the user can enter the pages to print.

首先,修改 PrintTaskRequested 事件处理程序以添加代码,从而获取一个 PrintTaskOptionDetails 对象。First, modify the PrintTaskRequested event handler to add the code to get a PrintTaskOptionDetails object.

PrintTaskOptionDetails printDetailedOptions = PrintTaskOptionDetails.GetFromPrintTaskOptions(printTask.Options);

清除在打印预览 UI 中显示的选项列表,并添加要在用户想要从应用打印时显示的选项。Clear the list of options that are shown in the print preview UI and add the options that you want to display when the user wants to print from the app.

备注

 这些选项按追加它们的相同顺序显示在打印预览 UI 中,第一个选项显示在窗口的顶部。 The options appear in the print preview UI in the same order they are appended, with the first option shown at the top of the window.

IList<string> displayedOptions = printDetailedOptions.DisplayedOptions;

displayedOptions.Clear();
displayedOptions.Add(Windows.Graphics.Printing.StandardPrintTaskOptions.Copies);
displayedOptions.Add(Windows.Graphics.Printing.StandardPrintTaskOptions.Orientation);
displayedOptions.Add(Windows.Graphics.Printing.StandardPrintTaskOptions.ColorMode);

创建新打印选项并初始化选项值列表。Create the new print option and initialize the list of option values.

// Create a new list option
PrintCustomItemListOptionDetails pageFormat = printDetailedOptions.CreateItemListOption("PageRange", "Page Range");
pageFormat.AddItem("PrintAll", "Print all");
pageFormat.AddItem("PrintSelection", "Print Selection");
pageFormat.AddItem("PrintRange", "Print Range");

添加你的自定义打印选项并分配事件处理程序。Add your custom print option and assign the event handler. 将自定义选项附加到最后,这样它会显示在选项列表的底部。The custom option is appended last so that it appears at the bottom of the list of options. 但是,你可以将其放在列表中的任意位置,自定义打印选项无需添加到最后位置。But you can put it anywhere in the list, custom print options don't need to be added last.

// Add the custom option to the option list
displayedOptions.Add("PageRange");

// Create new edit option
PrintCustomTextOptionDetails pageRangeEdit = printDetailedOptions.CreateTextOption("PageRangeEdit", "Range");

// Register the handler for the option change event
printDetailedOptions.OptionChanged += printDetailedOptions_OptionChanged;

CreateTextOption 方法会创建“范围”**** 文本框。The CreateTextOption method creates the Range text box. 用户将在此文本框中输入在他们选择“打印范围”**** 选项时希望打印的特定页面。This is where the user enters the specific pages they want to print when they select the Print Range option.

处理打印选项更改Handle print option changes

OptionChanged 事件处理程序执行两项工作。The OptionChanged event handler does two things. 第一,它根据用户选择的页面范围选项显示和隐藏页面范围的文本编辑字段。First, it shows and hides the text edit field for the page range depending on the page range option that the user selected. 第二,它将测试输入到页面范围文本框中的文本,以确保它代表文档的有效页面范围。Second, it tests the text entered into the page range text box to make sure that it represents a valid page range for the document.

此示例演示如何在 UWP 打印示例中处理打印选项更改事件。This example shows how print option change events are handled in the UWP print sample.

async void printDetailedOptions_OptionChanged(PrintTaskOptionDetails sender, PrintTaskOptionChangedEventArgs args)
{
   if (args.OptionId == null)
   {
         return;
   }

   string optionId = args.OptionId.ToString();

   // Handle change in Page Range Option
   if (optionId == "PageRange")
   {
         IPrintOptionDetails pageRange = sender.Options[optionId];
         string pageRangeValue = pageRange.Value.ToString();

         selectionMode = false;

         switch (pageRangeValue)
         {
            case "PrintRange":
               // Add PageRangeEdit custom option to the option list
               sender.DisplayedOptions.Add("PageRangeEdit");
               pageRangeEditVisible = true;
               await scenarioPage.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
               {
                     ShowContent(null);
               });
               break;
            case "PrintSelection":
               await scenarioPage.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
               {
                     Scenario4PageRange page = (Scenario4PageRange)scenarioPage;
                     PageToPrint pageContent = (PageToPrint)page.PrintFrame.Content;
                     ShowContent(pageContent.TextContentBlock.SelectedText);
               });
               RemovePageRangeEdit(sender);
               break;
            default:
               await scenarioPage.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
               {
                     ShowContent(null);
               });
               RemovePageRangeEdit(sender);
               break;
         }

         Refresh();
   }
   else if (optionId == "PageRangeEdit")
   {
         IPrintOptionDetails pageRange = sender.Options[optionId];
         // Expected range format (p1,p2...)*, (p3-p9)* ...
         if (!Regex.IsMatch(pageRange.Value.ToString(), @"^\s*\d+\s*(\-\s*\d+\s*)?(\,\s*\d+\s*(\-\s*\d+\s*)?)*$"))
         {
            pageRange.ErrorText = "Invalid Page Range (eg: 1-3, 5)";
         }
         else
         {
            pageRange.ErrorText = string.Empty;
            try
            {
               GetPagesInRange(pageRange.Value.ToString());
               Refresh();
            }
            catch (InvalidPageException ipex)
            {
               pageRange.ErrorText = ipex.Message;
            }
         }
   }
}

提示

 GetPagesInRange有关如何分析用户在 "范围" 文本框中输入的页范围的详细信息,请参阅UWP 打印示例中的方法。 See the GetPagesInRange method in the UWP print sample for details on how to parse the page range the user enters in the Range text box.

预览所选页面Preview selected pages

针对打印而设置你的应用内容的格式的方式取决于你的应用及其内容的性质。How you format your app's content for printing depends on the nature of your app and its content. UWP 打印示例中使用的打印帮助器类,用于设置要打印的内容的格式。A print helper class in used in the UWP print sample to format the content for printing.

打印部分页面时,可以通过多种方式在打印预览中显示内容。When printing a subset of pages, there are several ways to show the content in the print preview. 无论你采用哪种方法在打印预览中显示页面范围,打印输出都必须仅包含选中的页面。Regardless of the method you chose to show the page range in the print preview, the printed output must contain only the selected pages.

  • 无论是否已指定页面范围,都在打印预览中显示所有页面,这可以让用户了解实际将打印哪些页面。Show all the pages in the print preview whether a page range is specified or not, leaving the user to know which pages will actually be printed.
  • 仅在打印预览中显示用户的页面范围所选中的页面,这会在用户更改页面范围时更新显示器。Show only the pages selected by the user's page range in the print preview, updating the display whenever the user changes the page range.
  • 在打印预览中显示所有页面,但灰显未在用户所选页面范围内的页面。Show all the pages in print preview, but grey out the pages that are not in page range selected by the user.