共用方式為


自訂列印工作流程

透過使用列印工作流程應用程式建立自定義列印工作流程體驗。

概觀

列印工作流程應用程式是 UWP 應用程式,它擴展了 Microsoft Store 裝置應用程式(WSDAs)的功能,因此在進一步了解之前,對 WSDAs 有所熟悉會很有幫助。

就像 WSDA 的情況一樣,當來源應用程式的使用者選擇列印某個專案並瀏覽印表對話框時,系統會檢查工作流程應用程式是否與該印表機相關聯。 如果是,列印工作流程應用程式會啟動(主要是作為背景工作,稍後介紹)。 工作流程應用程式能夠改變列印票證(用於設定目前列印工作的印表機裝置設置的 XML 文件),以及要列印的實際 XPS 內容。 它可以選擇性地透過程式中途啟動UI,向用戶公開這項功能。 執行完工作後,它會將列印內容和列印票證傳遞給驅動程式。

因為它牽涉到背景和前景元件,而且因為它的功能與其他應用程式結合,所以列印工作流程應用程式可以比其他 UWP app 類別更複雜。 建議您在閱讀本指南時檢查 工作流程應用程式範例,以進一步瞭解如何實作不同的功能。 為了簡單起見,本指南中缺少一些功能,例如各種錯誤檢查和UI管理。

入門指南

工作流程應用程式必須指出其進入點至列印系統,才能在適當的時間啟動。 這可藉由在 UWP 專案 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 檔案的 元素必須具有下列幾行。 將 EntryPoint 的值替換成前景應用程式的名稱。

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

接下來,印刷系統會針對指定的應用程式入口點呼叫 OnActivated 方法。 在其 App.xaml.cs 檔案的 OnActivated 方法中,工作流程應用程式應檢查啟用種類,以確認這是工作流程啟用。 如果是,工作流程應用程式可以將啟用自變數轉換成 PrintWorkflowUIActivatedEventArgs 物件,此物件會將 PrintWorkflowForegroundSession 對象作為屬性公開。 這個物件,就像上一節的背景對應項目一樣,包含列印系統所引發的事件,而且您可以將處理程式指派給這些事件。 在此情況下,事件處理功能將會在稱為 WorkflowPage的個別類別中實作。

首先,在 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 實例提供了一個選項,可以取消列印作業,或是指出作業成功但不需要執行輸出列印作業。 做法是透過將 Complete 方法與 PrintWorkflowSubmittedStatus 值一同呼叫來完成。

備註

如果工作流程應用程式取消列印作業,強烈建議它提供快顯通知,以指出作業取消的原因。

對列印內容執行最終背景工作

一旦 UI 在 PrintTaskXpsDataAvailable 事件中完成延遲(或略過 UI 步驟),列印系統就會為背景任務觸發 提交 事件。 在此事件的處理程式中,工作流程應用程式可以存取 XpsDataAvailable 事件所提供的所有相同數據。 不過,與先前任何事件不同,送出 也會 透過 PrintWorkflowTarget 實例,提供最終列印作業內容的寫入 存取權。

用來卷軸處理最終列印資料的物件,取決於源資料是以原始位元組串流存取或在 XPS 物件模型中存取。 當工作流程應用程式透過位元組資料流存取源資料時,會提供輸出位元組數據流來寫入最終作業數據。 當工作流程應用程式透過物件模型存取源數據時,會提供檔寫入器以將物件寫入輸出作業。 不論是哪一種情況,工作流程應用程式都應該讀取所有源數據、修改所需的任何數據,並將修改過的數據寫入輸出目標。

當背景工作完成寫入數據時,它應該在對應的 PrintWorkflowSubmittedOperation 物件上呼叫 Complete。 工作流程應用程式完成此步驟並結束 送出 事件處理程序之後,工作流程會話就會關閉,而且使用者可以透過標準列印對話框監視最終列印作業的狀態。

最終步驟

向印表機註冊列印工作流程應用程式

您的工作流程應用程式會使用與 WSDA 相同的元數據檔案提交類型與印表機相關聯。 事實上,單一 UWP 應用程式可以同時作為工作流程應用程式和 WSDA,以提供列印工作設定功能。 請遵循對應的 WSDA 步驟來建立元數據關聯

差別在於,雖然 WSDA 會自動為使用者啟用(當該使用者在相關聯的裝置上列印時,應用程式會自動啟動),但工作流程應用程式則不會自動啟用。 它們有必須設定的個別原則。

設定工作流程應用程式的原則

工作流程應用程式原則是由要執行工作流程應用程式的裝置上的Powershell命令所設定。 Set-Printer、Add-Printer(現有埠)和 Add-Printer (新的 WSD 埠) 命令將會修改為允許設定工作流程原則。

  • Disabled:工作流程應用程式將不會啟動。
  • Uninitialized:如果工作流程 DCA 安裝在系統中,工作流程應用程式將會啟動。 如果未安裝應用程式,列印仍會繼續進行。
  • Enabled:如果工作流程 DCA 安裝在系統中,則會啟動工作流程合約。 如果未安裝應用程式,列印將會失敗。

下列命令會使工作流程應用程式成為指定印表機上的必需品。

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

本機用戶可以在本機印表機上執行此原則,或針對企業實作,印表機管理員可以在列印伺服器上執行此原則。 政策將同步到所有用戶端連線。 每當新增印表機時,印表機系統管理員可以使用此原則。

另請參閱

工作流程應用程式範例

Windows.Graphics.Printing.Workflow 命名空間