自定义打印工作流

使用打印工作流应用创建自定义打印工作流体验。

概述

打印工作流应用是扩展Microsoft应用商店设备应用(WSDA)的功能的 UWP 应用,因此在进一步操作之前,熟悉 WSDA 将很有帮助。

与 WSDA 的情况一样,当源应用程序的用户选择打印内容并浏览打印对话框时,系统将检查工作流应用是否与该打印机相关联。 如果是,打印工作流应用将启动(主要是后台任务;下面对此进行更多介绍)。 工作流应用能够更改打印票证(为当前打印任务配置打印机设备设置的 XML 文档)和要打印的实际 XPS 内容。 它可以通过在进程中途启动 UI 来向用户公开此功能。 完成其工作后,它会将打印内容和打印票证传递到驱动程序。

由于它涉及背景和前台组件,并且由于它在功能上与其他应用(s)耦合,因此打印工作流应用可能比其他类别的 UWP 应用更复杂。 建议在阅读本指南时检查 工作流应用示例 ,以便更好地了解如何实现不同的功能。 为了简单起见,本指南中缺少一些功能,例如各种错误检查和 UI 管理。

使用入门

工作流应用必须指示其打印系统的入口点,以便可以在适当的时间启动它。 这可以通过在 UWP 项目的 package.appxmanifest 文件的元素中Application/Extensions插入以下声明来完成。

<uap:Extension Category="windows.printWorkflowBackgroundTask"  
    EntryPoint="WFBackgroundTasks.WfBackgroundTask" />

重要

在很多情况下,打印自定义不需要用户输入。 因此,默认情况下,打印工作流应用作为后台任务运行。

如果工作流应用与启动打印作业的源应用程序相关联(请参阅后面的部分,了解相关说明),则打印系统会检查其清单文件以获取后台任务入口点。

对打印票证执行后台工作

打印系统对工作流应用执行的第一件事是激活其后台任务(在本例中, WfBackgroundTask 命名空间中的 WFBackgroundTasks 类)。 在后台任务 Run 的方法中,应将任务的触发器详细信息强制转换为 PrintWorkflowTriggerDetails 实例。 这将为打印工作流后台任务提供特殊功能。 它公开 PrintWorkflowSession 属性,该属性是 PrintWorkFlowBackgroundSession实例。 打印工作流会话类(背景和前台品种)将控制打印工作流应用的顺序步骤。

然后,注册此会话类将引发的两个事件的处理程序方法。 稍后将定义这些方法。

public void Run(IBackgroundTaskInstance taskInstance) {
    // Take out a deferral here and complete once all the callbacks are done
    runDeferral = taskInstance.GetDeferral();

    // Associate a cancellation handler with the background task.
    taskInstance.Canceled += new BackgroundTaskCanceledEventHandler(OnCanceled);

    // cast the task's trigger details as PrintWorkflowTriggerDetails
    PrintWorkflowTriggerDetails workflowTriggerDetails = taskInstance.TriggerDetails as PrintWorkflowTriggerDetails;

    // Get the session manager, which is unique to this print job
    PrintWorkflowBackgroundSession sessionManager = workflowTriggerDetails.PrintWorkflowSession;

    // add the event handler callback routines
    sessionManager.SetupRequested += OnSetupRequested;
    sessionManager.Submitted += OnXpsOMPrintSubmitted;

    // Allow the event source to start
    // This call blocks until all of the workflow callbacks complete
    sessionManager.Start();
}

Start调用该方法时,会话管理器将首先引发 SetupRequested 事件。 此事件公开有关打印任务的常规信息以及打印票证。 在此阶段,可以在后台编辑打印票证。

private void OnSetupRequested(PrintWorkflowBackgroundSession sessionManager, PrintWorkflowBackgroundSetupRequestedEventArgs printTaskSetupArgs) {
    // Take out a deferral here and complete once all the callbacks are done
    Deferral setupRequestedDeferral = printTaskSetupArgs.GetDeferral();

    // Get general information about the source application, print job title, and session ID
    string sourceApplicationName = printTaskSetupArgs.Configuration.SourceAppDisplayName;
    string jobTitle = printTaskSetupArgs.Configuration.JobTitle;
    string sessionId = printTaskSetupArgs.Configuration.SessionId;

    // edit the print ticket
    WorkflowPrintTicket printTicket = printTaskSetupArgs.GetUserPrintTicketAsync();

    // ...

重要的是,在处理 SetupRequested 时,应用将确定是否启动前台组件。 这可能取决于以前保存到本地存储的设置,或者打印票证编辑期间发生的事件,也可能是特定应用的静态设置。

// ...

if (UIrequested) {
    printTaskSetupArgs.SetRequiresUI();

    // Any data that is to be passed to the foreground task must be stored the app's local storage.
    // It should be prefixed with the sourceApplicationName string and the SessionId string, so that
    // it can be identified as pertaining to this workflow app session.
}

// Complete the deferral taken out at the start of OnSetupRequested
setupRequestedDeferral.Complete();

对打印作业执行前台工作(可选)

如果调用了 SetRequiresUI 方法,则打印系统将检查入口点到前台应用程序的清单文件。 Application/Extensions package.appxmanifest 文件的元素必须具有以下行。 将值 EntryPoint 替换为前台应用的名称。

<uap:Extension Category="windows.printWorkflowForegroundTask"  
    EntryPoint="MyWorkFlowForegroundApp.App" />

接下来,打印系统为给定的应用入口点调用 OnActivated 方法。 在App.xaml.cs文件的 OnActivated 方法中,工作流应用应检查激活类型以验证它是否为工作流激活。 如果是这样,工作流应用可以将激活参数强制转换为 PrintWorkflowUIActivatedEventArgs 对象,该对象将 PrintWorkflowForegroundSession 对象公开为属性。 此对象与上一节中的后台对应对象一样,包含由打印系统引发的事件,你可以为这些对象分配处理程序。 在这种情况下,事件处理功能将在名为 <a0/> 的单独类中实现。

首先,在 App.xaml.cs 文件中:

protected override void OnActivated(IActivatedEventArgs args){

    if (args.Kind == ActivationKind.PrintWorkflowForegroundTask) {

        // the app should instantiate a new UI view so that it can properly handle the case when
        // several print jobs are active at the same time.
        Frame rootFrame = new Frame();
        if (null == Window.Current.Content)
        {
            rootFrame.Navigate(typeof(WorkflowPage));
            Window.Current.Content = rootFrame;
        }

        // Get the main page
        WorkflowPage workflowPage = (WorkflowPage)rootFrame.Content;

        // Make sure the page knows it's handling a foreground task activation
        workflowPage.LaunchType = WorkflowPage.WorkflowPageLaunchType.ForegroundTask;

        // Get the activation arguments
        PrintWorkflowUIActivatedEventArgs printTaskUIEventArgs = args as PrintWorkflowUIActivatedEventArgs;

        // Get the session manager
        PrintWorkflowForegroundSession taskSessionManager = printTaskUIEventArgs.PrintWorkflowSession;

        // Add the callback handlers - these methods are in the workflowPage class
        taskSessionManager.SetupRequested += workflowPage.OnSetupRequested;
        taskSessionManager.XpsDataAvailable += workflowPage.OnXpsDataAvailable;

        // start raising the print workflow events
        taskSessionManager.Start();
    }
}

UI 附加事件处理程序并退出 OnActivated 方法后,打印系统将触发 SetupRequested 事件,以便 UI 进行处理。 此事件提供后台任务设置事件提供的相同数据,包括打印作业信息和打印票证文档,但无法请求启动其他 UI。 在 WorkflowPage.xaml.cs 文件中:

internal void OnSetupRequested(PrintWorkflowForegroundSession sessionManager, PrintWorkflowForegroundSetupRequestedEventArgs printTaskSetupArgs) {
    // If anything asynchronous is going to be done, you need to take out a deferral here,
    // since otherwise the next callback happens once this one exits, which may be premature
    Deferral setupRequestedDeferral = printTaskSetupArgs.GetDeferral();

    // Get information about the source application, print job title, and session ID
    string sourceApplicationName = printTaskSetupArgs.Configuration.SourceAppDisplayName;
    string jobTitle = printTaskSetupArgs.Configuration.JobTitle;
    string sessionId = printTaskSetupArgs.Configuration.SessionId;
    // the following string should be used when storing data that pertains to this workflow session
    // (such as user input data that is meant to change the print content later on)
    string localStorageVariablePrefix = string.Format("{0}::{1}::", sourceApplicationName, sessionID);

    try
    {
        // receive and store user input
        // ...
    }
    catch (Exception ex)
    {
        string errorMessage = ex.Message;
        Debug.WriteLine(errorMessage);
    }
    finally
    {
        // Complete the deferral taken out at the start of OnSetupRequested
        setupRequestedDeferral.Complete();
    }
}

接下来,打印系统将为 UI 引发 XpsDataAvailable 事件。 在此事件的处理程序中,工作流应用可以访问可用于设置事件的所有数据,还可以直接读取 XPS 数据(作为原始字节流或对象模型)。 访问 XPS 数据允许 UI 提供打印预览服务,并向用户提供有关工作流应用将对数据执行的操作的其他信息。

作为此事件处理程序的一部分,如果工作流应用将继续与用户交互,则必须获取延迟对象。 如果没有延迟,打印系统将考虑在 XpsDataAvailable 事件处理程序退出或调用异步方法时 UI 任务完成。 当应用从用户与 UI 的交互中收集所有必需信息时,它应完成延迟,以便打印系统可以继续。

internal async void OnXpsDataAvailable(PrintWorkflowForegroundSession sessionManager, PrintWorkflowXpsDataAvailableEventArgs printTaskXpsAvailableEventArgs)
{
    // Take out a deferral
    Deferral xpsDataAvailableDeferral = printTaskXpsAvailableEventArgs.GetDeferral();

    SpoolStreamContent xpsStream = printTaskXpsAvailableEventArgs.Operation.XpsContent.GetSourceSpoolDataAsStreamContent();

    IInputStream inputStream = xpsStream.GetInputSpoolStream();

    using (var inputReader = new Windows.Storage.Streams.DataReader(inputStream))
    {
        // Read the XPS data from input stream
        byte[] xpsData = new byte[inputReader.UnconsumedBufferLength];
        while (inputReader.UnconsumedBufferLength > 0)
        {
            inputReader.ReadBytes(xpsData);
            // Do something with the XPS data, e.g. preview
            // ...
        }
    }

    // Complete the deferral taken out at the start of this method
    xpsDataAvailableDeferral.Complete();
}

此外, 事件参数公开的 PrintWorkflowSubmittedOperation 实例提供了取消打印作业或指示作业成功但不需要输出打印作业的选项。 为此,请使用 PrintWorkflowSubmittedStatus 值调用 Complete 方法

注意

如果工作流应用取消打印作业,强烈建议它提供 Toast 通知,指示作业取消的原因。

对打印内容执行最终背景工作

UI 完成 PrintTaskXpsDataAvailable 事件中的延迟(或者如果绕过 UI 步骤),打印系统将触发后台任务的提交事件。 在此事件的处理程序中,工作流应用可以访问 XpsDataAvailable 事件提供的所有相同数据。 但是,与上述任何事件不同,Submitted 还通过 PrintWorkflowTarget 实例提供对最终打印作业内容的写入访问权限。

用于后台处理最终打印数据的对象取决于源数据是作为原始字节流访问还是作为 XPS 对象模型访问。 当工作流应用通过字节流访问源数据时,会提供输出字节流以将最终作业数据写入到该流。 当工作流应用通过对象模型访问源数据时,会提供文档编写器以将对象写入输出作业。 在任一情况下,工作流应用都应读取所有源数据、修改所需的任何数据,并将修改后的数据写入输出目标。

当后台任务完成写入数据时,它应对相应的 PrintWorkflowSubmittedOperation 对象调用 Complete。 工作流应用完成此步骤并 退出提交 事件处理程序后,工作流会话将关闭,用户可以通过标准打印对话框监视最终打印作业的状态。

最终步骤

将打印工作流应用注册到打印机

工作流应用与使用与 WSDA 相同的元数据文件提交类型的打印机相关联。 事实上,单个 UWP 应用程序可以充当工作流应用和提供打印任务设置功能的 WSDA。 按照相应的 WSDA 步骤创建元数据关联

区别在于,当 WSDA 为用户自动激活时(当用户在关联设备上打印时,该应用将始终启动),则工作流应用不会。 它们具有必须设置的单独策略。

设置工作流应用的策略

工作流应用策略由要运行工作流应用的设备上的 Powershell 命令设置。 将修改 Set-Printer、Add-Printer(现有端口)和“添加打印机”(新的 WSD 端口)命令,以允许设置工作流策略。

  • Disabled:不会激活工作流应用。
  • Uninitialized:如果工作流 DCA 安装在系统中,将激活工作流应用。 如果未安装应用,打印将继续。
  • Enabled:如果在系统中安装了工作流 DCA,则会激活工作流协定。 如果未安装应用,打印将失败。

以下命令使工作流应用在指定的打印机上是必需的。

Set-Printer –Name "Microsoft XPS Document Writer" -WorkflowPolicy Enabled

本地用户可以在本地打印机上运行此策略,或者,对于企业实现,打印机管理员可以在打印服务器上运行此策略。 然后,策略将同步到所有客户端连接。 每当添加新打印机时,打印机管理员可以使用此策略。

另请参阅

工作流应用示例

Windows.Graphics.Printing.Workflow 命名空间