从应用打印

本主题介绍如何从通用 Windows 应用打印。

有关更高级的功能,请参阅 自定义打印预览 UI

重要的 API

提示

 本主题中的大多数示例都基于通用 Windows 平台 (UWP) 打印示例,该示例是 GitHub 上的通用 Windows 平台 (UWP) 应用示例存储库的一部分。

注册打印

向应用添加打印的第一步是注册打印合同。 你的应用必须在希望用户能够打印的每个屏幕上执行此操作。 只有向用户显示的屏幕才能注册打印。 如果应用的一个屏幕已注册打印,则必须在退出时取消注册打印。 如果它被另一个屏幕替换,则下一个屏幕必须在打开时注册新的打印合同。

提示

 如果需要支持从应用中的多个页面进行打印,可以将此打印代码放在一个常见的帮助程序类中,并使应用页面重复使用它。 有关如何执行此操作的示例,请参阅 PrintHelper UWP 打印示例中类。

首先,声明 PrintManagerPrintDocument PrintManager 类型位于 Windows.Graphics.Printing 命名空间中,以及支持其他 Windows 打印功能的类型。 PrintDocument 类型位于 Windows.UI.Xaml.Printing 命名空间中,以及支持准备 XAML 内容进行打印的其他类型。 通过向页面添加以下 usingImports 语句,可以更轻松地编写打印代码。

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

PrintDocument 类用于处理应用与 PrintManager 之间的大部分交互,但它公开了其自身的多个回调。 在注册期间,创建 PrintManagerPrintDocument实例,并为其打印事件注册处理程序。

UWP 打印示例中,注册由 RegisterForPrinting 该方法执行。

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 启动注册。

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

在此示例中,事件处理程序未在 UnregisterForPrinting 方法中注册。

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

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

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

当用户离开支持打印的页时,将在 OnNavigatedFrom 方法中注销事件处理程序。

注意

如果你有一个多页应用并且未断开打印连接,则当用户离开页然后返回到该页时会引发异常。

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

创建打印按钮

将打印按钮添加到应用屏幕中,你希望显示该按钮。 请确保它不会干扰要打印的内容。

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

接下来,将事件处理程序添加到应用的代码以处理单击事件。 使用 ShowPrintUIAsync 方法开始从应用打印。 ShowPrintUIAsync 是一种异步方法,用于显示相应的打印窗口。 我们建议先调用 IsSupported 方法,以检查应用是否在支持打印的设备上运行(并处理不打印的情况)。 如果当时无法出于任何其他原因执行打印, ShowPrintUIAsync 将引发异常。 我们建议捕获这些异常,并让用户知道打印何时无法继续。

在此示例中,按钮单击的事件处理程序中会显示打印窗口。 如果方法引发异常(因为当时无法执行打印), ContentDialog 控件会通知用户情况。

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

设置应用内容的格式

调用 ShowPrintUIAsync将引发 PrintTaskRequested 事件。 此步骤中显示的 PrintTaskRequested 事件处理程序通过调用 PrintTaskRequest.CreatePrintTask 方法创建 PrintTask,并传递打印页的标题和 PrintTaskSourceRequestedHandler 委托的名称。 请注意,在此示例中, PrintTaskSourceRequestedHandler 是内联定义的。 PrintTaskSourceRequestedHandler 提供用于打印的格式内容,稍后将进行说明。

在此示例中,还定义了一个完成处理程序来捕获错误。 处理完成事件是个好主意,因为应用可以让用户知道是否发生了错误并提供可能的解决方案。 同样,你的应用可以使用完成事件来指示用户在打印作业成功后要执行的后续步骤。

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 会通过引发分页事件请求打印页面集合以在打印预览 UI 中显示。 这对应于 IPrintPreviewPageCollection 接口的 Paginate 方法。 此时将调用注册期间创建的事件处理程序。

重要

 如果用户更改打印设置,将再次调用分页事件处理程序,以便重新排列内容。 为获得最佳用户体验,建议在重新排列内容之前检查设置,避免在不需要时重新初始化分页内容。

Paginate 事件处理程序(CreatePrintPreviewPagesUWP 打印示例中的方法),创建页面以在打印预览 UI 中显示并发送到打印机。 用于准备应用内容以供打印的代码特定于你的应用和打印的内容。 请参阅 UWP 打印示例 源代码,了解它如何设置其打印内容的格式。

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 事件。 这对应于 IPrintPreviewPageCollection 接口的 MakePage 方法。 此时将调用注册期间创建的事件处理程序。

GetPreviewPage 事件处理程序(GetPrintPreviewPageUWP 打印示例中的方法)中,在打印文档中设置相应的页面。

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

最后,用户单击打印按钮后,PrintManager 会通过调用 IDocumentPageSource 接口的 MakeDocument 方法请求要发送到打印机的页面的最终集合。 在 XAML 中,这会引发 AddPages 事件。 此时将调用注册期间创建的事件处理程序。

AddPages 事件处理程序(AddPrintPagesUWP 打印示例中的方法)中,将页面集合中的页面添加到要发送到打印机的 PrintDocument 对象。 如果用户指定要打印的特定页面或页面范围,请使用此处的信息仅添加实际发送到打印机的页面。

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

准备打印选项

接下来准备打印选项。 例如,本部分将介绍如何设置页面范围选项以允许打印特定页面。 有关更高级的选项,请参阅 自定义打印预览 UI

此步骤将创建新的打印选项,定义该选项支持的值列表,然后将该选项添加到打印预览 UI。 页面范围选项具有以下设置:

选项名称 操作
全部打印 打印文档中的所有页面。
打印选择 仅打印用户选择的内容。
打印范围 显示一个编辑控件,用户可以在其中输入要打印的页面。

首先,修改 PrintTaskRequested 事件处理程序以添加代码以获取 PrintTaskOptionDetails 对象。

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

清除打印预览 UI 中显示的选项列表,并添加当用户想要从应用打印时要显示的选项。

注意

 这些选项以追加的相同顺序显示在打印预览 UI 中,其中第一个选项显示在窗口顶部。

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 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 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 方法创建 Range 文本框。 这是用户在选择“ 打印范围 ”选项时输入要打印的特定页面的位置。

处理打印选项更改

OptionChanged 事件处理程序执行两项操作。 首先,它根据用户选择的页面范围选项显示和隐藏页面范围的文本编辑字段。 其次,它会测试在页面范围文本框中输入的文本,以确保它表示文档的有效页面范围。

此示例演示了如何在 UWP 打印示例中处理打印选项更改事件。

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有关如何分析用户在 Range 文本框中输入的页面范围的详细信息,请参阅 UWP 打印示例中的方法

预览所选页面

设置应用内容打印格式的方式取决于应用的性质及其内容。 UWP 打印示例中使用的打印帮助程序类,用于设置打印内容的格式。

在打印这些页的子集时,可采用多种方法在打印预览中显示内容。 无论选择哪种方法在打印预览中显示页面范围,打印输出都必须仅包含所选页面。

  • 在打印预览中显示是否指定了页面范围的所有页面,使用户知道实际打印哪些页面。
  • 在打印预览中仅显示用户的页面范围选择的页面,每当用户更改页面范围时更新显示。
  • 显示打印预览中的所有页面,但将不在用户选择的页面范围内的页面灰显。