列印支援應用程式設計指南

本文提供印表機 OEM 和 IHV 的指引和範例,以開發列印支援應用程式 (PSA),以數種方式增強 Windows 使用者的印表體驗。

重要

從 Windows 11 SDK (22000.1) 版本開始,列印支援應用程式 (PSA) 是針對印表機開發 UWP 應用程式的建議方法。 若要開發列印裝置的列印支援應用程式,請下載並安裝您目標 Windows 版本的 Windows 11 SDK。

重要

本主題包含章節,說明從 Windows 11 版本 22H2 開始可用的 PSA 功能。 這些區段包含指出其適用於該版本的附註。

某些印表機功能不會顯示在 Windows 所顯示的列印對話框中,因為它們是需要製造商應用程式協助才能正確設定的特殊功能。 它們也可能是印表機預設功能中未提供的功能。

印表機特定功能可以分組,讓使用者輕鬆挑選選項,並信任該案例中涉及的所有功能會自動設定為正確的值。 其中一個範例可能是在筆墨節約器、省紙器和最高品質模式之間選擇,根據使用者的一個選取項目自動操作各種列印功能。 Windows 無法自動將它們分組,因為需要瞭解每個印表機型號的所有自定義功能。

此 API 會使用選用的 UWP 延伸模組合約來解決顯示自定義列印喜好設定的需求,該合約可由使用者從所有 Windows 列印對話框和使用 Windows 提供的 API 的自定義列印對話框啟用。 製造商能夠量身打造其UI,為用戶擁有的特定印表機提供最佳印表體驗。

印表機製造商可以改善和區分的另一個區域是列印品質。 製造商可以藉由優化特定印表機的內容,改善轉譯后的列印品質。 它們也可以呈現高逼真度預覽,更能代表最終輸出,因為它可以將印表機特定功能納入考慮。

print support app print timeline

辭彙

詞彙 定義
PSA 列印支援應用程式。 使用本文所述 API 的 UWP 應用程式。
Mpd 新式列印對話框。 當應用程式使用 Windows.Graphics.Printing API 列印時,就會向使用者顯示此專案。
Cpd 一般列印對話框。 當應用程式使用 Win32 API 列印時,這會向用戶顯示。 需要顯示列印預覽的應用程式不會觸發此對話方塊,並實作對話方塊本身的版本。 Office 應用程式 是其中的主要範例。
IPP 因特網列印通訊協定。 從用戶端裝置用來與印表機互動,以擷取和設定印表喜好設定,以及傳送要列印的檔。
列印支持相關聯的印表機 連結到PSA的印表機。
IPP 印表機 支援 IPP 通訊協定的印表機。
其他 設定 開啟合作夥伴在 MPD 中提供應用程式 UI 的連結。 預設會在未安裝 PSA 時開啟內建的列印喜好設定 UI。
印表機喜好設定UI 用來設定印表時間所套用的預設印表機選項對話框。 例如:方向、紙張大小、色彩、兩側列印等等。
Pdl 頁面描述語言。 將檔案傳送至印表機的格式。
相關聯的PSA印表機 與 PSA 應用程式相關聯的實體 IPP 印表機。
PrintDeviceCapabilities 用於定義印表機功能的 XML 檔案格式。 如需詳細資訊,請參閱 列印票證和列印功能技術
PrintTicket 各種列印相關功能及其值集合,用來擷取用戶對於指定列印作業的意圖。
PrintSupportExtension 負責提供印表機條件約束擴充功能的 PSA 背景工作。

這些範例參考 printsupport 命名空間,其定義為:

    xmlns:printsupport="http://schemas.microsoft.com/appx/manifest/printsupport/windows10"

當用戶即將列印檔時,他們通常會想要設定列印檔的一些喜好設定。 例如,他們可以選擇以橫向列印檔。 他們也可以利用印表機支援的自定義功能。 Windows 提供預設 UI 來顯示自定義喜好設定,但使用者可能無法瞭解它們,因為沒有適當的圖示或描述。 Windows 也可能使用錯誤的 UI 控制件來呈現它。 這類自定義功能最適合由完全瞭解此功能的應用程式呈現。 這是提供 API 的動機,可讓印表機製造商建立專為他們製作的各種印表機型號量身打造的應用程式。

系統會使用名為 windows.printSupport 設定 UI 的新類別來建立新的 UAP 延伸模組合約。 使用此合約啟動的應用程式會收到名為 PrintSupport 設定 UI 的新 ActivationKind。 此合約不需要任何新功能。

<Extensions>
    <printsupport:Extension Category="windows.printSupportSettingsUI" 
        EntryPoint="PsaSample.PsaSettingsUISample"/>
</Extensions>

當使用者在 MPD 中選取 [更多 設定] 或 CPD 中的 [喜好設定] 時,就會叫用此合約。 您也可以從 設定 應用程式中的列印喜好設定叫用此合約。 當合約啟動時,應用程式會收到 PrintSupport 設定 UISession 物件,可用來取得目前的 PrintTicketPrintDevice 物件。 PrintDevice 物件可用來與印表機通訊,以接收印表機和作業屬性。 然後,應用程式可以向用戶顯示具有適當印表機選項的UI。 當使用者做出選擇並選取 [確定] 時,應用程式接著可以修改列印票證、驗證它,然後使用 PrintSupportPrintTicketTarget 物件送回。 如果使用者選擇取消喜好設定視窗,應該捨棄變更,而且應用程式應該完成 PrintSupport 設定 UISession 對象的延遲結束。

列印支援應用程式應該處理不同列印作業的多個同時啟用,因此這類應用程式必須使用 package.appxmanifest 檔案中的 SupportsMultipleInstances 元素來支援多個實例。 若無法這麼做,可能會導致確認某個列印作業的喜好設定可能會關閉可能開啟的其他喜好設定視窗。 用戶必須再次開啟這些喜好設定視窗。

下列順序圖代表 設定 UI 列印票證操作的概念:

sequence diagram of settings U I print ticket manipulation

變更設定UI中的 PrintTicket

從任何列印對話框啟動時啟用 設定 UI 的 C# 範例程式代碼範例(MPD/CPD 或自定義列印對話框)或系統設定:

namespace PsaSampleApp
{
    sealed partial class App : Application
    {
        Deferral settingsDeferral;
        protected override void OnActivated(IActivatedEventArgs args)
        {
            if (args.Kind == ActivationKind.PrintSupportSettingsUI)
           {
                // Get the activation arguments
                var settingsEventArgs = args as PrintSupportSettingsActivatedEventArgs;
                PrintSupportSettingsUISession settingsSession = settingsEventArgs.Session;
                // Take deferral
                this.settingsDeferral = settingsEventArgs.GetDeferral();

                // Create root frame
                var rootFrame = new Frame();
                
        // Choose the page to be shown based upon where the application is being launched from
                switch (settingsSession.LaunchKind)
                {
                    case SettingsLaunchKind.UserDefaultPrintTicket:
                    {
                        // Show settings page when launched for default printer settings
                        rootFrame.Navigate(typeof(DefaultSettingsView), settingsSession);
                    }
                    break;
                    case SettingsLaunchKind.JobPrintTicket:
                    {
               // Show settings page when launched from printing app
                       rootFrame.Navigate(typeof(JobSettingsView), settingsSession);
                    }
                    break;
                }
                
   
                Window.Current.Content = rootFrame; 
            }
        }

        internal void ExitSettings()
        {
            settingsDeferral.Complete();
        } 
    }
}

Default 設定 View 類別的 XAML:

<Page
    x:Class="PsaSampleApp.DefaultSettingsView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:PsaSampleApp"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <StackPanel Grid.Row="0"  Orientation="Vertical" Margin="30,50,0,0">
           <ComboBox x:Name="OrientationOptions" ItemsSource="{x:Bind OrientationFeatureOptions}" SelectedItem="{x:Bind SelectedOrientationOption, Mode=TwoWay}" DisplayMemberPath="DisplayName" HorizontalAlignment="Left" Height="Auto" Width="Auto" VerticalAlignment="Top"/>
       </StackPanel>

        <StackPanel Grid.Row="1" Orientation="Horizontal">
            <Button x:Name="Ok" Content="Ok" HorizontalAlignment="Left" Margin="50,0,0,0" VerticalAlignment="Top" Click="OkClicked"/>
            <Button x:Name="Cancel" Content="Cancel" HorizontalAlignment="Left" Margin="20,0,0,0" VerticalAlignment="Top" Click="CancelClicked"/>
        </StackPanel>
    </Grid>
</Page>

顯示 UI 和變更 PrintTicket 的 C# 範例程式代碼:

namespace PsaSampleApp
{
    /// <summary>
    /// Class for showing print settings to the user and allow user to modify it
    /// </summary>
    public sealed partial class DefaultSettingsView: Page
    {
        private IppPrintDevice printer;
        private PrintSupportSettingsUISession uiSession;
        private WorkflowPrintTicket printTicket;
        private App application;
        // Bound to XAML combo box
        public ObservableCollection<PrintTicketOption> OrientationFeatureOptions { get; } = new ObservableCollection<PrintTicketOption>();
        public PrintTicketOption SelectedOrientationOption { get; set; }  

        public SettingsView()
        {
            this.InitializeComponent();
            this.application = Application.Current as App;
            this.orientationFeatureOptions = new ObservableCollection<PrintTicketOption>();
        }

        internal void OnNavigatedTo(NavigationEventArgs e)
        {
            this.uiSession = = e.Parameter as PrintSupportSettingsUISession;
            this.printer = session.SessionInfo.Printer;
            this.printTicket = session.SessionPrintTicket;
            
            PrintTicketCapabilities printTicketCapabilities = this.printTicket.GetCapabilities();

            // Read orientation feature from PrintTicket capabilities
            PrintTicketFeature feature = printTicketCapabilities.PageOrientationFeature;
            // Populate XAML combo box with orientation feature options
            this.PopulateOrientationOptionComboBox(feature.Options); 

            PrintTicketOption printTicketOrientationOption = printTicket.PageOrientationFeature.GetSelectedOption();
            // Update orientation option in XAML combo box
            this.SelectedOrientationOption = this.orientationFeatureOptions.Single((option)=> (option.Name == printTicketOrientationOption.Name && option.XmlNamespace == printTicketOrientationOption.XmlNamespace));
        }

        private async void OkClicked(object sender, RoutedEventArgs e)
        {
            // Disable Ok button while the print ticket is being submitted
            this.Ok.IsEnabled = false;

            // Set selected orientation option in the PrintTicket and submit it
            PrintTicketFeature orientationFeature = this.printTicket.PageOrientationFeature;
            orientationFeature.SetSelectedOption(this.SelectedOrientationOption);
            // Validate and submit PrintTicket
            WorkflowPrintTicketValidationResult result = await printTicket.ValidateAsync();
            if (result.Validated)
            {
                // PrintTicket validated successfully – submit and exit
                this.uiSession.UpdatePrintTicket(printTicket);
                this.application.ExitSettings();
            }
            else
            {
                this.Ok.IsEnabled = true;
                // PrintTicket is not valid – show error
                this.ShowInvalidPrintTicketError(result.ExtendedError);
            }
        }

        private void CancelClicked(object sender, RoutedEventArgs e)
        {
            this.application.ExitSettings();
        }
    }
}

從印表機裝置取得印表機屬性

從 IPP 印表機到 get-printer-attributes 查詢的 WireShark 回應:

wireshark response from an I P P printer to a get printer attributes query

從印表機取得筆跡名稱和筆跡層級的 C# 範例程式代碼:

namespace PsaSampleApp
{
    /// <summary>
    /// Class for showing print settings to the user
    /// </summary>
    public sealed partial class SettingsView : Page
    { 
       IList<string> inkNames;
       IList<int> inkLevels;
        
        private async void GetPrinterAttributes()
        {
            // Read ink names and levels, along with loaded media-sizes
            var attributes = new List<string>();
            attributes.Add("marker-names");
            attributes.Add("marker-levels");
            attributes.Add("media-col-ready");
            IDictionary<string, IppAttributeValue> printerAttributes = this.printer.GetPrinterAttributes(attributes);

            IppAttributeValue inkNamesValue = printerAttributes["marker-names"];
            CheckValueType(inkNamesValue, IppAttributeValueKind.Keyword);
            this.inkNames = inkNamesValue.GetKeywordArray();
            
            IppAttributeValue inkLevelsValue = printerAttributes["marker-levels"];
            CheckValueType(inkLevelsValue, IppAttributeValueKind.Integer);
            this.inkLevels = inkLevelsValue.GetIntegerArray();
    
            // Read loaded print media sizes
        IppAttributeValue mediaReadyCollectionsValue = printerAttributes["media-col-ready"];
            foreach (var mediaReadyCollection in mediaReadyCollectionsValue.GetCollectionArray())
            {
                IppAttributeValue mediaSizeCollection;
                if (mediaReadyCollection.TryGetValue("media-size", out mediaSizeCollection))
                {
                    var xDimensionValue = mediaSizeCollection.GetCollectionArray().First()["x-dimension"];
                    var yDimensionValue = mediaSizeCollection.GetCollectionArray().First()["y-dimension"];
                    CheckValueType(xDimensionValue, IppAttributeValueKind.Integer);
                    CheckValueType(yDimensionValue, IppAttributeValueKind.Integer);
                    int xDimension = xDimensionValue.GetIntegerArray().First();
                    int yDimension = yDimensionValue.GetIntegerArray().First();
                    this.AddMediaSize(xDimension, yDimension);
                }
            }
        }

        private void CheckValueType(IppAttributeValue value, IppAttributeValueKind expectedKind)
        {
            if (value.Kind != expectedKind)
            {
                throw new Exception(string.Format("Non conformant type found: {0}, expected: {1}", value.Kind, expectedKind));
            }
        }
    }
}

在印表機上設定印表機屬性

設定印表機屬性的 C# 範例程式代碼:

int defaultResolutionX = 1200;
int defaultResolutionY = 1200;
string pdlFormat = "image/pwg-raster";
private async void SetPrinterAttributes()
{
    var attributes = new Dictionary<string, IppAttributeValue>();
    attributes.Add("document-format-default", IppAttributeValue.CreateKeyword(this.pdlFormat));
    var resolution = new IppResolution(this.defaultResolutionX, this.defaultResolutionY, IppResolutionUnit.DotsPerInch);
    attributes.Add("printer-resolution-default", IppAttributeValue.CreateResolution(resolution));
            
    var result = this.printer.SetPrinterAttributes(attributes);
    if (!result.Succeeded)
    {
        foreach (var attributeError in result.AttributeErrors)
        {
            var attributeName = attributeError.Key;
            switch (attributeError.Value.Reason)
            {
            case IppAttributeErrorReason.AttributeValuesNotSupported:
                var values = attributeError.Value.GetUnsupportedValues().First();
                this.LogUnSupportedValues(attributeName, values);
                break;
            case IppAttributeErrorReason.AttributeNotSettable:
                this.LogAttributeNotSettable(attributeName);
                break;
            case IppAttributeErrorReason.AttributeNotSupported:
                this.LogAttributeNotSupported(attributeName);
                break;
            case IppAttributeErrorReason.RequestEntityTooLarge:
                this.LogAttributeNotEntityTooLarge(attributeName);
                break;
            case IppAttributeErrorReason. ConflictingAttributes:
                this.LogConflictingAttributes(attributeName);
                break;
            }
        }
    }
}

擴充印表機條件約束

列印支援應用程式支援自定義 PrintTicket 驗證,並定義預設 PrintTicket。 本節說明我們如何支持這些功能。

為了支援印表機延伸模組條件約束,已實作新的背景工作類型 PrintSupportExtension。 Package.appxmanifest 具有列印支援延伸模組的擴充性專案,如下所示:

<Extensions>
    <printsupport:Extension Category="windows.printSupportExtension" 
        EntryPoint="PsaBackgroundTasks.PrintSupportExtension"/>
</Extensions>

此服務可以在相關聯 IPP 印表機的印表作業中的任何時間點執行。 透過 Run(IBackgroundTaskInstance taskInstance) 函式啟動列印支援延伸模組時,會將 IBackgroundTaskInstance 的實例提供給 PrintSupportExtension,以提供 PrintSupportExtensionTriggerDetails 運行時間類別的存取權,該類別會在內部提供 PrintSupportExtensionSession 作為屬性。 PrintSupportExtension 背景類別接著可以使用會話對象來註冊想要提供自定義功能的事件。

  1. event Windows.Foundation.TypedEventHandler<PrintSupportExtensionSession, PrintSupportPrintTicketValidationRequestedEventArgs>; PrintTicketValidationRequested;

    如果列印支援延伸模組提供自己的 PrintTicket 驗證機制,則可以註冊此事件。 每當需要驗證 PrintTicket 時,列印系統就會引發此事件。 PrintSupportExtension 接著會取得必須在 EventArgs 內驗證的目前 PrintTicket。 PrintSupportExtension 背景類別接著可以檢查 PrintTicket 是否有效,並加以修改,以解決任何衝突。 PrintSupportExtension 背景類別應該接著使用 SetPrintTicketResult 函式來設定驗證結果,以指出 PrintTicket 是否已解決、發生衝突或無效。 此事件可以在列印作業的存留期內隨時引發。 如果 PrintSupportExtension 類別未註冊此事件,列印系統會執行自己的 PrintTicket 驗證。

  2. event Windows.Foundation.TypedEventHandler<PrintSupportExtensionSession, PrintSupportPrintDeviceCapabilitiesChangedEventArgs>; PrintDeviceCapabilitiesChanged;

    在列印系統更新相關聯 IPP 印表機的快取 PrintDeviceCapabilities 之後,就會引發 此事件。 引發此事件時,PrintSupportExtension 背景類別可以檢查已變更的 PrintDeviceCapabilities 並加以修改。

列印票證的自定義驗證

提供 PrintTicket 驗證服務的 C# 範例程式代碼:

public void Run(IBackgroundTaskInstance taskInstance)
{
    // Take task deferral
    this.taskDeferral = taskInstance.GetDeferral();
    // Associate a cancellation handler with the background task
    taskInstance.Canceled += OnTaskCanceled;

    var psaTriggerDetails = taskInstance.TriggerDetails as PrintSupportExtensionTriggerDetails;

    var serviceSession = psaTriggerDetails.Session as PrintSupportExtensionSession;

    this.ippPrintDevice = serviceSession.Printer;
    serviceSession.PrintTicketValidationRequested += this.OnPrintTicketValidationRequested;
    serviceSession.PrinterDeviceCapabilitiesChanged += this.OnPdcChanged;
    serviceSession.Start();
}

private void OnTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
{
    // Complete the deferral
    this.taskDeferral.Complete();
}

private void OnPrintTicketValidationRequested(PrintSupportExtensionSession session, PrintSupportPrintTicketValidationRequestedEventArgs args)
{
    using (args.GetDeferral())
    {
        // Get PrintTicket that needs needs to be validated and resolved   
        var printTicket = args.PrintTicket;
                
        // Validate and resolve PrintTicket
        WorkflowPrintTicketValidationStatus validationStatus = this.ValidateAndResolvePrintTicket(printTicket);
        args.SetPrintTicketValidationStatus(validationStatus);
    }
}

更新 PrintDeviceCapabilities

private void OnPdcChanged(PrintSupportExtensionSession session, PrintSupportPrintDeviceCapabilitiesChangedEventArgs args)
{
    using (args.GetDeferral())
    {
        var pdc = args.GetCurrentPrintDeviceCapabilities();

        // Check current PDC and make changes according to printer device capabilites
        XmlDocument newPdc = this.CheckAndUpdatePrintDeviceCapabilities(pdc);
        args.UpdatePrintDeviceCapabilities(newPdc);
    }
}

增強列印品質

一旦使用者透過按下列印對話方塊上的列印按鈕來認可列印之後,要列印的檔就會從正在列印的應用程式傳送至列印堆疊。 接著,本檔會進行轉換(轉譯為 PDL),使其適合目標印表機。 Windows 會根據從印表機查詢的屬性來決定要選擇的轉換。 轉換的文件接著會傳送至印表機。 雖然這適用於大多數印表機,但在某些情況下,允許合作夥伴應用程式參與轉換,可以改善列印品質。 為了方便進行這項作業,會擴充目前的列印工作流程 API,以在列印堆棧的其他點包含對應用程式的呼叫。 此 API 支援兩個新的事件,PSA 應用程式可以註冊。 這些是PSA API 介面中唯一的進入點:

  1. JobStarting

    • 當任何應用程式啟動列印作業時,就會引發此事件。 引發事件時,列印支援應用程式可以選擇略過系統轉譯,方法是呼叫 PrintWorkflowJobStartingEventArgs 上的 SetSkipSystemRendering。 如果選擇略過系統轉譯,列印系統將不會將 XPS 檔轉換成印表機所需的 PDL 格式。 相反地,列印應用程式所產生的 XPS 會直接提供給負責將 XPS 轉換為 PDL 格式的PSA。
  2. PdlModificationRequested

    • 當 Windows 開始將 XPS 資料流轉換成印表機所指示的 PDL 格式時,就會引發此事件。 運行時間類別 PrintWorkflowPdlModificationRequestedEventArgs 是以這個事件的自變數的形式提供。 這個事件類別提供 PDL 來源和目標物件,以便讀取和寫入列印作業內容。 如果 App 判斷它需要使用者輸入,則可以從 EventArgs 使用 PrintWorkflowUILauncher 啟動 UI。 此 API 使用 Tester-Doer 模式。 如果IsUILaunchEnabled函式傳回 false,PrintWorkflowUILaunchLauncher 將無法叫用 UI。 如果PSA會話是以無訊息模式執行,則此函式會傳回 false (無外設或 kiosk 模式)。 如果函式傳回 false,列印支援應用程式不應該嘗試啟動 UI。

    OutputStream 是 PrintWorkflowPdlTargetStream 函式 GetStreamTargetAsync 所傳回的一部分。 寫入目標 OutputStream 的內容會隨著文件內容一起傳遞至印表機。

PDL 修改事件的順序圖:

sequence diagram for the source stream P D L modification event

當 PSA 背景工作要求啟動 UI 時,就會啟動 PSA 前景應用程式。 PSA 可以使用前景合約來取得使用者輸入和/或向用戶顯示預覽列印預覽。

已定義新的 printSupportWorkflow 背景工作類型。 Package.appxmanifest 具有 PrintSupportWorkflow 合約的下列擴充性專案

<Extensions>
    <printsupport:Extension Category="windows.printSupportWorkflow" 
        EntryPoint="PsaBackgroundTasks.PrintSupportWorkflowSample"/>
</Extensions>

在合約啟用時,PrintWorkflowJobTriggerDetails 會指定為 IBackgroundTaskInstance-TriggerDetails>PrintWorkflowJobTriggerDetails 會在內部提供 PrintWorkflowJobBackgroundSession 做為其屬性的一部分。 應用程式可以使用 PrintWorkflowJobBackgroundSession 來註冊與列印作業工作流程中各種插入點相關的事件。 完成事件註冊之後,應用程式必須呼叫 PrintWorkflowJobBackgroundSession::Start ,列印系統才能開始引發與各種插入點相關的事件。

定義名為 PrintSupportJobUI 的新 ActivationKind。 這不需要新功能。

<Extensions>
    <printsupport:Extension Category="windows.printSupportJobUI" 
        EntryPoint="PsaSample.PrintSupportJobUISample"/>
</Extensions>

這是可從列印支援工作流程背景合約啟動的UI合約,或當用戶選取列印作業錯誤快顯通知時啟動。 啟用時, 會提供 PrintWorkflowJobActivatedEventArgs ,其中包含 PrintWorkflowJobUISession 物件。 使用 PrintWorkflowJobUISession,如果 PdlDataAvailable 事件想要存取 PDL 數據,前景應用程式應該註冊 PdlDataAvailable 事件。 如果前景應用程式想要針對作業期間可能發生的任何錯誤顯示自定義錯誤訊息,它應該註冊 JobNotification 事件。 註冊事件之後,應用程式應該呼叫 PrintWorkflowJobUISession::Start 函式,以便列印系統開始引發事件。

略過系統轉譯

namespace PsaBackground
{
    class PrintSupportWorkflowBackgroundTask : IBackgroundTask
    {
        BackgroundTaskDeferral taskDeferral;
        public void Run(IBackgroundTaskInstance taskInstance)
        {
            // Take Task Deferral            
            taskDeferral = taskInstance.GetDeferral();

            var jobTriggerDetails = taskInstance.TriggerDetails as PrintWorkflowJobTriggerDetails;

            var workflowBackgroundSession = jobTriggerDetails.PrintWorkflowJobSession as PrintWorkflowJobBackgroundSession;
            // Register for events
            workflowBackgroundSession.JobStarting += this.OnJobStarting;
            workflowBackgroundSession.PdlModificationRequested += this.OnPdlModificationRequested;
            // Start Firing events
            workflowBackgroundSession.Start();
        }
    
        private void OnJobStarting(PrintWorkflowJobBackgroundSession session, PrintWorkflowJobStartingEventArgs args)
        {
            using (args.GetDeferral())
            {
                // Call SetSkipSystemRendering to skip conversion for XPS to PDL, so that PSA can directly manipulate the XPS file.
                args.SetSkipSystemRendering();
            }
        }
     }
}

PDL 修改事件

PDL 修改事件的順序圖:

sequence diagram for the input stream P D L modification event

列印支援作業監視器讀取和寫入列印作業內容的 C# 範例程式代碼:

private void OnPdlModificationRequested(PrintWorkflowJobBackgroundSession session, PrintWorkflowPdlModificationRequestedEventArgs args)
{
    using (args.GetDeferral())
    {
        IInputStream pdlContent = args.SourceContent.GetInputStream();
        // Specify the Content type of stream that will be written to target that is passed to printer accordingly.
        PrintWorkflowPdlTargetStream streamTarget = args.CreateJobOnPrinter(args.SourceStream.ContentType);
        IOutputStream outputStream = streamTarget.GetOutputStream();

        using (var inputReader = new Windows.Storage.Streams.DataReader(pdlContent))
        {
            inputReader.InputStreamOptions = InputStreamOptions.Partial;
            using (var outputWriter = new Windows.Storage.Streams.DataWriter(outputStream))
            {
                // Write the updated Print stream from input stream to the output stream
                uint chunkSizeInBytes = 256 * 1024; // 256K chunks
                
                uint lastAllocSize = 0;
                byte[] contentData = new byte[chunkSize];
                while(this.ReadChunk(inputReader, ref contentData))
                {
                    
                    // Make any changes required to the input data
                    // ...                        
                    // Write out the modified content
                    outputWriter.WriteBytes(contentData);
                    await outputWriter.StoreAsync();
                }
            }
        }
        streamTarget.CompleteStreamSubmission(PrintWorkflowSubmittedStatus.Succeeded);
        this.taskDeferral.Complete();
        }
    }
}

從工作流程背景啟動UI

從PSA PDL 修改要求的事件合約啟動列印支援作業 UI 的 C# 範例程式代碼:

private async void OnPdlModificationRequested(PrintWorkflowJobBackgroundSession session, PrintWorkflowPdlModificationRequestedEventArgs args)
{
    IInputStream pdlContent = args.SourceContent.GetInputStream();
    WorkflowPrintTicket printTicket = args.PrinterJob.GetJobPrintTicket();

    bool uiRequired = this.IsUIRequired(pdlContent, printTicket);
    if (!uiRequired)
    {
        // Specify the Content type of content that will be written to target that is passed to printer accordingly.
        PrintWorkflowPdlTargetStream streamTarget = args.CreateJobOnPrinter (args.SourceStream.ContentType);
        // Process content directly if UI is not required
        this.ProcessContent(pdlContent, streamTarget);
    }
    else if (args.UILauncher.IsUILaunchEnabled())
    {
        // LaunchAndCompleteUIAsync will launch the UI and wait for it to complete before returning 
        PrintWorkflowUICompletionStatus status = await args.UILauncher.LaunchAndCompleteUIAsync();
        if (status == PrintWorkflowUICompletionStatus.Completed)
        {
            PrintWorkflowPdlTargetStream streamTarget = args.CreateJobOnPrinter(args.SourceStream.ContentType);
            this.ProcessContent(pdlContent, streamTarget);
        }
        else
        {
            if (status == PrintWorkflowUICompletionStatus.UserCanceled)
            {
                // Log user cancellation and cleanup here.
                this.taskDeferral.Complete();
            }
            else
            {
                // UI launch failed, abort print job.
                args.Configuration.AbortPrintFlow(PrintWorkflowAbortReason.JobFailed);
                this.taskDeferral.Complete();
            }
        }
    }
    else
    {
        // PSA requires to show UI, but launching UI is not supported at this point because of user selection.
        args.Configuration.AbortPrintFlow(PrintWorkflowAbortReason.JobFailed);
        this.taskDeferral.Complete();
    }
}

PDLDataAvailable 事件的工作流程作業 UI 啟用

PdlDataAvailable 事件的列印作業 UI 啟用順序圖:

sequence diagram for print job U I activation for the P D L data available event

PSA 作業 UI 啟用合約的 C# 範例程式代碼:

namespace PsaSampleApp
{
    sealed partial class App : Application
    {
        protected override void OnActivated(IActivatedEventArgs args)
        {
            if (args.Kind == ActivationKind.PrintSupportJobUI)
            {
                var rootFrame = new Frame();
        
                rootFrame.Navigate(typeof(JobUIPage));
                Window.Current.Content = rootFrame;
        
                var jobUI = rootFrame.Content as JobUIPage;

                // Get the activation arguments
                var workflowJobUIEventArgs = args as PrintWorkflowJobActivatedEventArgs;

                PrintWorkflowJobUISession session = workflowJobUIEventArgs.Session;
                session.PdlDataAvailable += jobUI.OnPdlDataAvailable;
                session.JobNotification += jobUI.OnJobNotification;
                // Start firing events
                session.Start(); 
            }
        }
    }
}

namespace PsaSampleApp
{
    public sealed partial class JobUIPage : Page    
    {
        public JobUIPage()
        {
            this.InitializeComponent();
        }

        public string WorkflowHeadingLabel;

        public void OnPdlDataAvailable(PrintWorkflowJobUISession session, PrintWorkflowPdlDataAvailableEventArgs args)
        {
            using (args.GetDeferral())
            {
                string jobTitle = args.Configuration.JobTitle;
                string sourceApplicationName = args.Configuration.SourceAppDisplayName;            
                string printerName = args.Printer.PrinterName;
                this.WorkflowHeadingLabel = string.Format(this.formatHeading, jobTitle, sourceApplicationName, printerName);

                // Get pdl stream and content type
                IInputStream pdlContent = args.SourceContent.GetInputStream();
                string contentType = args.SourceContent.ContentType;
                this.ShowPrintPreview(pdlContent, contentType);
            }
        }
    }
}

取得印表機作業屬性

取得列印作業之作業屬性的 C# 範例程式代碼:

namespace PsaBackground
{
    class PrintSupportWorkflowBackgroundTask : IBackgroundTask
    {
        private async void OnPdlModificationRequested(PrintWorkflowJobBackgroundSession session, 
                             PrintWorkflowPdlModificationRequestedEventArgs args)
        {
            using (args.GetDeferral())
            {
                string colorMode = this.GetJobColorMode(args.PrinterJob);
                if (colorMode != "monochrome")
                {
                    this.SetJobColorModeToMonochrome(args.PrinterJob);
                } 
            }
        }

        private string GetJobColorMode(PrintWorkflowPrinterJob printerJob)
        {
            var attributes = new List<string>();
            attributes.Add("print-color-mode");
             // Gets the IPP attributes from the current print job
            IDictionary<string, IppAttributeValue> printerAttributes = printerJob.GetJobAttributes(attributes);

            var colorModeValue =  printerAttributes["print-color-mode"];
            this.CheckValueType(colorModeValue, IppAttributeValueKind.Keyword);

            return colorModeValue.GetKeywordArray().First();
        }
    }
} 

設定印表機作業屬性

C# 範例程式代碼,從 上述的 [取得印表機作業屬性 ] 區段繼續進行,示範如何設定作業屬性:

private async void SetJobColorModeToMonochrome(PrintWorkflowPrinterJob printerJob)
{
    var attributes = new Dictionary<string, IppAttributeValue>();
    attributes.Add("print-color-mode", IppAttributeValue.CreateKeyword("monochrome"));

    var result = PrinterJob.SetJobAttributes(attributes);
    if (!result.Succeeded)
    {
        this.LogSetAttributeError(result.AttributeErrors);
    }
}

某些 IPP 印表機不支援在建立作業之後取得/設定作業屬性。 針對這些印表機,PrintJobJobId 屬性設定為 “0”,而 GetJobAttributes SetJobAttributes/ 將會立即失敗,但例外狀況為例外。

提供 PDL 內容的記憶體檔案存取權

某些 PDL 格式,例如 PDF 需要完整的資料流才能開始處理。 因此,PrintWorkflowPdlSourceContent 類別會提供名為 GetContentFileAsync 的新方法,該類別會傳回來源內容的 儲存體 File

public sealed partial class JobUIPage : Page
{
    public async void OnPdlDataAvailable(PrintWorkflowJobUISession session, PrintWorkflowPdlDataAvailableEventArgs args)
    {
        using (args.GetDeferral())
        {
            if (String.Equals(args.SourceContent.ContentType, "application/pdf", StringComparison.OrdinalIgnoreCase))
            {
                // Wait for all PDL data to be available
                StorageFile sourceFile == await args.SourceContent.GetContentFileAsync();
                IRandomAccessStream sourceStream = await sourceFile.OpenReadAsync();

                PdfDocument pdfDocument = await PdfDocument.LoadFromStreamAsync(sourceStream);

                for (uint i = 0; i < pdfDocument.PageCount; i++)
                {
                    PdfPage page = pdfDocument.GetPage(i);
                    var pageImage = new InMemoryRandomAccessStream();
                    await page.RenderToStreamAsync(pageImage);
                    this.AddImageToPreviewImageList(pageImage);
                }
            }
        }
    }
}    

將 XPS 轉換成 PDF 的 PDL

顯示 XPS 轉換為 PDF 的 PDL 程式代碼範例程式代碼:

private async void OnPdlModificationRequested(PrintWorkflowJobBackgroundSession session, PrintWorkflowPdlModificationRequestedEventArgs args)
{
    using (args.GetDeferral())
    {
        if (String.Equals(args.SourceContent.ContentType, "application/oxps", StringComparison.OrdinalIgnoreCase))
        {
            var xpsContent = args.SourceContent.GetInputStream();

            var printTicket = args.PrinterJob.GetJobPrintTicket();
            PrintWorkflowPdlTargetStream streamTarget = args.CreateJobOnPrinter("application/pdf");

            // Modify XPS stream here to make the needed changes 
            // for example adding a watermark

            PrintWorkflowPdlConverter pdlConverter = args.GetPdlConverter(PrintWorkflowPdlConversionType.XpsToPdf);
            await pdlConverter.ConvertPdlAsync(printTicket, xpsContent, streamTarget.GetOutputStream());

            streamTarget.CompleteStreamSubmission(PrintWorkflowSubmittedStatus.Succeeded);
        }
        else
        {
            // We except source content to be XPS in this case, abort the session if it is not XPS.
            args.Configuration.AbortPrintFlow(PrintWorkflowAbortReason.JobFailed);
        }
    }
    this.taskDeferral.Complete();
}

作業通知事件

工作通知事件的順序圖表:

sequence diagram for the job notification event

C# 範例程式代碼,從上述 PDLDataAvailable 事件區段的工作流程作業 UI 啟用繼續進行,以顯示作業通知上的錯誤:

public sealed partial class JobUIPage : Page    
{
    public void OnJobNotification(PrintWorkflowJobUISession session, PrintWorkflowJobNotificationEventArgs args)
    {
        using (args.GetDeferral())
        {
            PrintWorkflowPrinterJobStatus jobStatus = args.PrintJob.GetJobStatus();

            switch (jobStatus)
            {
                case PrintWorkflowPrinterJobStatus::Error:
                    // Show print job error to the user
                    Frame->Navigate(JobErrorPage::typeid, this);
                break;
                case PrintWorkflowPrinterJobStatus::Abort:
                    // Show message that print job has been aborted.
                    Frame->Navigate(JobAbortPage::typeid, this);
                break;
                case PrintWorkflowPrinterJobStatus::Completed:
                    // Show job successfully completed message to the user.
                    Frame->Navigate(JobCompletedPage::typeid, this);
                break;
            }
        }
    }    
}

使用初始作業屬性建立作業

目前,某些 IPP 印表機不支援 set-attribute 作業。 提供 PrintWorkflowPdlDataAvailableEventArgs 上的 CreateJobOnPrinterWithAttributesBuffer 函式和 CreateJobOnPrinterWithAttributesBuffer 函式,以減輕此問題。 使用這些 API,PSA 開發人員可以提供在印表機上建立作業時傳遞至印表機的工作屬性。

public sealed partial class JobUIPage : Page
{
    public async void OnPdlDataAvailable(PrintWorkflowJobUISession session, PrintWorkflowPdlDataAvailableEventArgs args)
    {
       var attributes = new Dictionary<string, IppAttributeValue>();
       attributes.Add("print-color-mode", IppAttributeValue.CreateKeyword("monochrome"));
       // Create job on printer with initial job attributes
       PrintWorkflowPdlTargetStream streamTarget = args.CreateJobOnPrinterWithAttributes(attributes, "application/pdf");
        // Write data to target stream
    }
}

循序 XPS 處理

C++/Winrt 範例程式代碼,用於在多任務緩衝處理完成之前循序處理 XPS。

namespace winrt
{
    struct WorkflowReceiver : public winrt::implements<WorkflowReceiver, IPrintWorkflowXpsReceiver2>
    {
        STDMETHODIMP SetDocumentSequencePrintTicket(_In_ IStream* documentSequencePrintTicket) noexcept override
        {
            // process document sequence print ticket
            return S_OK;
        }

        STDMETHODIMP SetDocumentSequenceUri(PCWSTR documentSequenceUri) noexcept override
        {
            // process document sequence URI
        }

        STDMETHODIMP AddDocumentData(UINT32 documentId, _In_ IStream* documentPrintTicket,
            PCWSTR documentUri) noexcept override
        {
            // process document URI and print ticket
            return S_OK;
        }

        STDMETHODIMP AddPage(UINT32 documentId, UINT32 pageId,
            _In_ IXpsOMPageReference* pageReference, PCWSTR pageUri)  noexcept override
        {
            // process XPS page
            return S_OK;
        }

        STDMETHODIMP Close() noexcept override
        {
            // XPS processing finished
            return S_OK;
        }

        STDMETHODIMP Failed(HRESULT XpsError) noexcept override
        {
            // XPS processing failed, log error and exit
            return S_OK;
        }
    };

    void PsaBackgroundTask::OnPdlModificationRequested(PrintWorkflowJobBackgroundSession session,
        PrintWorkflowPdlModificationRequestedEventArgs args)
    {
    auto contentType = args.SourceContent().ContentType();
        if (contentType == L"application/oxps")
        {
                    auto xpsContent = args.SourceContent().GetInputStream();
                    PrintWorkflowObjectModelSourceFileContent xpsContentObjectModel(xpsContent);
                    com_ptr<IPrintWorkflowObjectModelSourceFileContentNative> xpsContentObjectModelNative;
                    check_hresult(winrt::get_unknown(xpsContentObjectModel)->QueryInterface( 
                                                        IID_PPV_ARGS(xpsContentObjectModelNative.put())));
        
                    auto xpsreceiver = make_self<WorkflowReceiver>();
                    check_hresult(xpsContentObjectModelNative->StartXpsOMGeneration(xpsreceiver.get()));
        }
    }
}

顯示名稱當地語系化和 PDL 傳遞 API 整合

重要

本節說明從 Windows 11 版本 22H2 開始可用的 PSA 功能。

在此案例中,PSA 會自定義列印裝置功能 (PDC),並提供列印裝置資源 (PDR) 進行字串當地語系化。

PSA 也會設定支援的 PDL 傳遞 API 內容類型 (PDL 格式)。 如果 PSA 未訂閱事件,或未明確呼叫 SetSupportedPdlPassthroughContentTypes ,則會針對與此 PSA 應用程式相關聯的印表機停用 PDL Passthrough。

// Event handler called every time PrintSystem updates PDC or BindPrinter is called
 private void OnPdcChanged(PrintSupportExtensionSession session, PrintSupportPrintDeviceCapabilitiesChangedEventArgs args)
{
    using (args.GetDeferral())
    {
        XmlDocument pdc = args.GetCurrentPrintDeviceCapabilities();
        XmlDocument pdr = args.GetCurrentPrintDeviceResources();
        
        // Check current PDC and make changes according to printer device capabilities 
        XmlDocument newPdc = this.CheckAndUpdatePrintDeviceCapabilities(pdc);
        // Get updated printer devices resources, corresponding to the new PDC 
        XmlDocument newPdr = this.GetPrintDeviceResourcesInfo(newPdc, pdr, args.ResourceLanguage);

        // Update supported PDL formats 
        args.SetSupportedPdlPassthroughContentTypes(GetSupportedPdlContentTypes());
        
        args.UpdatePrintDeviceCapabilities(newPdc);
        args.UpdatePrintDeviceResources(newPdr);
    }
}

頁面層級功能支援和作業屬性

重要

本節說明從 Windows 11 版本 22H2 開始可用的 PSA 功能。

頁面層級功能支援和作業屬性案例會分組,因為它們會藉由在範例程序代碼中的相同位置進行變更來解決。

  • 頁面層級功能支援: 在此案例中,PSA 應用程式會指定頁面層級屬性,不應該由從 PrintTicket 剖析的 IPP 屬性覆寫。

  • 作業屬性支援的不同集合(PIN 列印): 在此案例中,PSA 應用程式會指定自定義 IPP 作業屬性(例如 PIN)。

下列 C# 範例程式代碼顯示頁面層級功能支援的必要變更,以及作業屬性案例的個別集合。

private void OnPdlModificationRequested(PrintWorkflowJobBackgroundSession session, PrintWorkflowPdlModificationRequestedEventArgs args)
{
    using (args.GetDeferral())
    {
        IInputStream pdlContent = args.SourceContent.GetInputStream();
    
        // Custom job attributes to add to the printJob
        IDictionary<string, IppAttributeValue> jobAttributes = LocalStorageUtil.GetCustomIppJobAttributes();
        // Custom operation attributes to add to printJob
        IDictionary<string, IppAttributeValue> operationAttributes = LocalStorageUtil.GetCustomIppOperationAttributes();
        
        // PSA has an option to select preferred PDL format
        string documentFormat = GetDocumentFormat(args.PrinterJob.Printer);
    
        // Create PrintJob with specified PDL and custom attributes
        PrintWorkflowPdlTargetStream targetStream = args.CreateJobOnPrinterWithAttributes(jobAttributes, documentFormat  , operationAttributes,
           PrintWorkflowAttributesMergePolicy  .DoNotMergeWithPrintTicket /*jobAttributesMergePolicy*/, PrintWorkflowAttributesMergePolicy.MergePreferPsaOnConflict /*operationAttributesMergePolicy*/);
    
        // Adding a watermark to the output(targetStream) if source payload type is XPS
        this.ModifyPayloadIfNeeded(targetStream, args, documentFormat, deferral);
    
        // Marking the stream submission as Succeeded.
        targetStream.CompleteStreamSubmission(PrintWorkflowSubmittedStatus.Succeeded);
    
        this.taskDeferral.Complete();
    }
}

使用 PSA 增強列印對話框

重要

本節說明從 Windows 11 版本 22H2 開始可用的 PSA 功能。

在此案例中,使用列印對話框與 PSA 整合可啟用下列動作:

  • 當 MPD 中的選取範圍變更為與 PSA 相關聯的印表機時,取得回呼

  • 使用 openUrl 動作的支持顯示一個 AdaptiveCard

  • 在列印對話框中顯示自定義功能和參數

  • 修改 PrintTicket,因此變更列印對話框中所顯示之功能選項的選取範圍

  • 取得列印應用程式的 Windows.ApplicationModel.AppInfo,開啟列印對話方塊

下列 C# 範例說明這些列印對話框增強功能:

public BackgroundTaskDeferral TaskInstanceDeferral { get; set; }

public void Run(IBackgroundTaskInstance taskInstance)
{
    // Take task deferral 
    TaskInstanceDeferral   = taskInstance.GetDeferral();
    // Associate a cancellation handler with the background task 
    taskInstance.Canceled += OnTaskCanceled;

    if (taskInstance.TriggerDetails is PrintSupportExtensionTriggerDetails extensionDetails)
    {
         PrintSupportExtensionSession session = extensionDetails.Session;
         session.PrintTicketValidationRequested += OnSessionPrintTicketValidationRequested;
         session.PrintDeviceCapabilitiesChanged += OnSessionPrintDeviceCapabilitiesChanged;
         session.PrinterSelected += this.OnPrinterSelected;
    }
}

private void OnTaskInstanceCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
{
    TaskInstanceDeferral.Complete();
}

// Event handler called when the PSA Associated printer is selected in Print Dialog
private void OnPrinterSelected(PrintSupportExtensionSession session, PrintSupportPrinterSelectedEventArgs args)
{
    using (args.GetDeferral())
    {
        // Show adaptive card in the Print Dialog (generated based on Printer and Printing App) 
        args.SetAdaptiveCard  (GetCustomAdaptiveCard(session.Printer, args.SourceAppInfo));

        // Request to show Features and Parameters in the Print Dialog if not shown already
        const string xmlNamespace = "\"http://schemas.microsoft.com/windows/2003/08/printing/printschemakeywords\"";
        var additionalFeatures= new List<PrintSupportPrintTicketElement> { new PrintSupportPrintTicketElement { LocalName = "PageMediaType", NamespaceUri = xmlNamespace } };                  
        var additionalParameters = new List<PrintSupportPrintTicketElement> { new PrintSupportPrintTicketElement { LocalName = "JobCopiesAllDocuments", NamespaceUri = xmlNamespace } };

        if ((featuresToShow.Count + parametersToShow.Count) <= args.AllowedCustomFeaturesAndParametersCount)
        {
            args.SetAdditionalFeatures(additionalFeatures);
            args.SetAdditionalParameter(additionalParameters);
        }
        else
        {
            // Cannot show that many additional features and parameters, consider reducing the number
            // of additional features and parameters by selecting only the most important ones
        }
    }
}

// Create simple AdaptiveCard to show in MPD
public IAdaptiveCard GetCustomAdaptiveCard(IppPrintDevice ippPrinter, AppInfo appInfo)
{
    return AdaptiveCardBuilder.CreateAdaptiveCardFromJson($@"
        {{""body"": [
                {{ 
                    ""type"": ""TextBlock"",
                    ""text"": ""Hello {appInfo.DisplayInfo.DisplayName} from {ippPrinter.PrinterName}!""
                }}
              ],
              ""$schema"": ""http://adaptivecards.io/schemas/adaptive-card.json"",
            ""type"": ""AdaptiveCard"",
            ""version"": ""1.0""
        }}");
}

使用主機型處理旗標進行 PDL 轉換

重要

本節說明從 Windows 11 版本 22H2 開始可用的 PSA 功能。

目前的 PDL 轉換 API PrintWorkflowPdlConverter.ConvertPdlAsync 預設會執行主機型處理。 這表示主計算機/列印計算機會執行旋轉、分頁順序等等,因此印表機不需要執行這些作業。 不過,印表機 IHV 可能會想要 PDL 轉換,而不需要主機型處理,因為他們的印表機可以做得更好。 ConvertPdlAsync 函式會採用主機型處理旗標,以解決這項需求。 PSA 可以使用這個旗標略過所有主機型處理或特定主機型處理作業。

class HostBaseProcessingRequirements
{
    public bool CopiesNeedsHostBasedProcessing = false;
    public bool PageOrderingNeedsHostBasedProcessing = false;
    public bool PageRotationNeedsHostBasedProcessing = false;
    public bool BlankPageInsertionNeedsHostBasedProcessing = false;
}

private async void OnPdlModificationRequested(PrintWorkflowJobBackgroundSession sender, PrintWorkflowPdlModificationRequestedEventArgs args)
{
    using (args.GetDeferral())
    {
        var targetStream = args.CreateJobOnPrinter("application/pdf");
        var pdlConverter = args.GetPdlConverter(PrintWorkflowPdlConversionType.XpsToPdf);

        var hostBasedRequirements = this.ReadHostBasedProcessingRequirements(args.PrinterJob.Printer);
            
        PdlConversionHostBasedProcessingOperations hostBasedProcessing = PdlConversionHostBasedProcessingOperations.None;
        if (hostBasedRequirements.CopiesNeedsHostBasedProcessing)
        {
            hostBasedProcessing |= PdlConversionHostBasedProcessingOperations.Copies;
        }

        if (hostBasedRequirements.PageOrderingNeedsHostBasedProcessing)
        {
            hostBasedProcessing |= PdlConversionHostBasedProcessingOperations.PageOrdering;
        }

        if (hostBasedRequirements.PageRotationNeedsHostBasedProcessing)
        {
            hostBasedProcessing |= PdlConversionHostBasedProcessingOperations.PageRotation;
        }

        if (hostBasedRequirements.BlankPageInsertionNeedsHostBasedProcessing)
        {
            hostBasedProcessing |= PdlConversionHostBasedProcessingOperations.BlankPageInsertion;
        }

        await pdlConverter.ConvertPdlAsync(args.PrinterJob.GetJobPrintTicket(), args.SourceContent.GetInputStream(), targetStream.GetOutputStream(), hostBasedProcessing);
    }
}

private HostBaseProcessingRequirements ReadHostBasedProcessingRequirements(IppPrintDevice printDevice)
{
    // Read Host based processing requirements for the printer
}

設定列印裝置功能 (PDC) 更新原則

重要

本節說明從 Windows 11 版本 22H2 開始可用的 PSA 功能。

印表裝置功能 (PDC) 需要更新時,印表機 IHV 可能會有不同的需求。 為了解決這些需求, PrintSupportPrintDeviceCapabilitiesUpdatePolicy 可以設定 PDC 的更新原則。 PSA 可以根據使用此 API 的時間或列印作業數目來設定 PDC 更新原則。

根據作業數目設定 PDC 更新原則

// Event handler called every time PrintSystem updates PDC
private void OnPdcChanged(PrintSupportExtensionSession session, PrintSupportPrintDeviceCapabilitiesChangedEventArgs args)
{
    using (args.GetDeferral())
    {
        // Set update policy to update the PDC on bind printer of every print job.
        var updatePolicy = PrintSupportPrintDeviceCapabilitiesUpdatePolicy.CreatePrintJobRefresh(1);
        args.SetPrintDeviceCapabilitiesUpdatePolicy(updatePolicy);      
    }
}

根據 TimeOut 設定 PDC 更新原則

// Event handler called every time PrintSystem updates PDC
private void OnPdcChanged(PrintSupportExtensionSession session, PrintSupportPrintDeviceCapabilitiesChangedEventArgs args)
{
    using (args.GetDeferral())
    {
        // Set update policy to update the PDC on bind printer of every print job.
        var updatePolicy = PrintSupportPrintDeviceCapabilitiesUpdatePolicy.CreatePrintJobRefresh(1);
        args.SetPrintDeviceCapabilitiesUpdatePolicy(updatePolicy);      
    }
}

一般列印支援應用程式 (PSA) 設計指引

設計列印支援應用程式時,請務必在設計中包含下列層面:

  • 前景和背景合約都應該標示為支援多個實例,例如 ,SupportsMultipleInstance 應該出現在套件指令清單中。 這是為了確保合約的存留期可以可靠地管理多個同時作業。

  • 將啟動UI以進行 PDL 修改視為選擇性步驟。 即使不允許啟動UI,也盡最大努力完成列印作業。 只有在 PDL 修改期間無法順利完成列印作業時,才應該中止列印作業。 請考慮在這類情況下傳送未修改的 PDL。

  • 啟動 PDL 修改的 UI 時,請先呼叫 IsUILaunchEnabled ,再呼叫 LaunchAndCompleteUIAsync。 這是為了確保目前無法顯示UI的案例會繼續正確列印。 這些案例可能位於無頭裝置或目前處於 Kiosk 模式或未打擾模式的裝置上。

列印支援應用程式關聯

Windows.Devices.Printers

Windows.Graphics.Printing.PrintSupport

Windows.Graphics.Printing.Workflow

因特網列印通訊協定 (IPP) 規格