共用方式為


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

本文提供針對其裝置實作 v3 印表支援應用程式 (PSA) 的印表機 OEM 和 IHV 的指引和範例。

術語

術語 定義
PSA 列印支援應用程式。 本檔中使用 API 的 UWP 應用程式。
IPP 因特網列印通訊協定。 從用戶端裝置用來與印表機互動,以擷取和設定印表喜好設定,以及傳送要列印的檔。
提供列印支持的相關印表機 連結到PSA的實體 IPP 印表機。
IPP 印表機 支援 IPP 通訊協定的印表機。
PDL 頁面描述語言。 檔傳送至印表機的格式。
相關聯的PSA印表機 與 PSA 應用程式相關聯的實體 IPP 印表機。
列印設備功能 用於定義印表機功能的 XML 檔案格式。
PrintSupportExtension 負責提供印表機限制擴充功能的 PSA 背景任務。

本文包含在《列印支援應用程式設計指南》中描述的現有列印支援應用程式公用 API 的 v3 擴充功能,並涉及 Windows.Graphics.Printing.PrintSupport 命名空間。 PSA API 可讓印表機製造商開發硬體支援應用程式,以增強Windows 使用者的列印體驗,同時使用收件匣Microsoft IPP 類別驅動程式,而不需要開發自定義驅動程式。 列印元件會透過 PSA broker 程序與 PSA 應用程式通訊。

如需詳細資訊,請參閱下列文章:

主題 描述
列印支援應用程式設計指南 針對實作印表支援應用程式(PSA)的印表機 OEM 和 IHV,提供指導和範例。
列印支援應用程式 v4 API 設計指南 提供針對其裝置實作 v4 印表支援應用程式(PSA)的印表機 OEM 和 IHV 的指引和範例。
用於列印支援虛擬印表機的 MSIX 清單規格 針對正在實作印表機支援虛擬印表機的印表機 OEM 和 IHV,提供 MSIX 清單指引及範例。
列印支援應用程式關聯 提供將列印支援應用程式 (PSA) 與印表機產生關聯的指引和範例。

API 的重要擴充功能如下所示:

  • IPP 壓縮 - PSA v3 API 新增了一項功能,可以在支援這項功能的 IPP 印表機列印作業中加入 IPP 壓縮,從而增強 IPP 列印的效能。 某些PSA可能有自定義壓縮,這表示 IPP 作業具有影響效能的雙重壓縮。 為了減輕這種情況,PSA v3 API 會在 PrintWorkflowJobStartingEventArgs (PSA v1 API) 運行時間類別中引進屬性 IsIppCompressionEnabledDisableIppCompressionForJob 函式(視需要停用目前工作的壓縮)。

  • IPP 作業錯誤處理和錯誤快顯通知 - PSA v3 API 會在 PrintWorkflowJobBackgroundSession (PSA v1 API)運行時間類別中引進 JobIssueDetected 事件。 每當PSA在列印作業中發現錯誤或警告時,系統會觸發一個通知。 接下來,PSA 會負責向用戶顯示錯誤提醒。 當PSA註冊此事件 並將 skipSystemErrorToast 屬性設定為 true 時,PrintWorkflowJobIssueDetectedEventArgs,它會告訴列印系統不要顯示 Windows 打印系統快顯通知。 當使用者與快顯通知互動時,PSA v3 API 提供了一個機制,讓PSA啟動使用者界面 (UI)。

  • 自定義 IPP 通訊超時 - PSA v3 API 提供了一個接口,供 PSA 覆寫 IPP 超時設置。 此外,PrintSupportIppCommunicationConfiguration 執行階段類別會新增至 PrintSupportPrintDeviceCapabilitiesChangedEventArgs 以調整 IPP 通訊逾時。 此外,PSA v3 API 新增了一個事件,用於在 IPP 通訊出現錯誤時觸發。 導入此事件是為了讓 IHV 能夠調查故障情況,並相應調整逾時值。

  • 支援 IPPFaxOut - PSA v3 API 會將功能新增至列印系統以支援 IPPFaxOut 印表機。 為了支援傳真,PSA 支援一個用於將 XPS 轉換為 Tiff 的渲染濾鏡。 由於PSA可能會在轉換成 TIFF 之前操作 XPS 內容,因此它會在 printWorkflowPdlConversionType 中提供 XpsToTiff 列舉值,讓PSA可以存取 XPS 至 TIFF 轉換器。 此外,它會提供 IsIPPFaxOutPrinter 屬性來 IppPrintDevice 運行時間類別,讓PSA可以區分標準印表機和 IPPFaxOut 印表機。

停用 IPP 壓縮

IPP 壓縮會顯示在下列程式代碼範例中。

public sealed class PrintSupportWorkflowBackgroundTask : IBackgroundTask
{
    public BackgroundTaskDeferral TaskInstanceDeferral { get; set; }
    private PrintWorkflowJobBackgroundSession session;

    public void Run(IBackgroundTaskInstance taskInstance)
    {
        TaskInstanceDeferral = taskInstance.GetDeferral();

        if (taskInstance.TriggerDetails is PrintWorkflowJobTriggerDetails jobDetails)
        {
            session = jobDetails.PrintWorkflowJobSession;
            session.JobStarting += OnJobStarting;
            session.PdlModificationRequested += OnPdlModificationRequested;
            session.JobIssueDetected += OnJobIssueDetected;

            // Make sure to register all the event handlers before PrintWorkflowJobBackgroundSession.Start is called.
            session.Start();
        }
    }

    private void OnJobStarting(PrintWorkflowJobBackgroundSession sender, PrintWorkflowJobStartingEventArgs args)
    {
        using (args.GetDeferral())
        {
            // Skip system rendering.
            args.SetSkipSystemRendering();

            // If Ipp compression is enabled by the system, check to see if PSA does custom compression for the printer
            // and disable system compression if required.
            if (args.IsIppCompressionEnabled)
            {
                if (this.HasCustomCompression(args.Printer))
                {
                    args.DisableIppCompressionForJob();
                }
            }
        }
    }

    bool HasCustomCompression(IppPrintDevice device)
    {
        bool hasCustomCompression = false;
        // Check if the PSA does custom compression for the printer
        return hasCustomCompression;
    } 
} 

IPP 作業錯誤處理

此範例示範PSA應用程式如何註冊處理作業錯誤的機制、顯示作業錯誤的快顯通知,以及在用戶啟動快顯通知時啟動使用者介面(UI)。

public sealed class PrintSupportWorkflowBackgroundTask : IBackgroundTask
{
    public BackgroundTaskDeferral TaskInstanceDeferral { get; set; }
    private PrintWorkflowJobBackgroundSession session;

    public void Run(IBackgroundTaskInstance taskInstance)
    {
        TaskInstanceDeferral = taskInstance.GetDeferral();

        if (taskInstance.TriggerDetails is PrintWorkflowJobTriggerDetails jobDetails)
        {
            session = jobDetails.PrintWorkflowJobSession;
            session.JobStarting += OnJobStarting;
            session.PdlModificationRequested += OnPdlModificationRequested;
            session.JobIssueDetected += OnJobIssueDetected;

            // Make sure to register all the event handlers before PrintWorkflowJobBackgroundSession.Start is called.
            session.Start();
        }
    }

    private void OnJobIssueDetected (PrintWorkflowJobBackgroundSession sender, PrintWorkflowJobIssueDetectedEventArgs args)
    {
        // Using a deferral to exclude the background process from being suspended while PSA displays UI.
        Deferral deferral = args.GetDeferral();
        var toast = new ToastNotification(GetErrorToastXml(args.ExtendedError,
                args.JobIssueKind, args.PrinterJob, args.Configuration));
        toast.Activated += async (toastSender, e) =>
        {
            // PSA UI application 
            PrintWorkflowUICompletionStatus uiStatus = await args.UILauncher.LaunchAndCompleteUIAsync();
            // Complete deferral
            deferral.Complete();
        };
        toast.Dismissed += async (toastSender, e) => { deferral.Complete(); };
        toast.Failed += async (toastSender, e) => { deferral.Complete(); };
            
        ToastNotificationManager.CreateToastNotifier().Show(toast);
        args.SkipSystemErrorToast = true;
    }

    private XmlDocument GetErrorToastXml(Exception jobError, PrintWorkflowJobIssueKind issueKind,
        PrintWorkflowPrinterJob printerJob, PrintWorkflowConfiguration workflowConfig )
    {
        var errorToastXml = new XmlDocument();
        // Generate Toast Xml based on error information from Exception and PrintWorkflowPrinterJob.
        return errorToastXml;
    }
}

設定 IPP 通訊的逾時時間

此範例示範如何設定 IPP 通訊超時設定。

public sealed class PrintSupportExtensionBackGroundTask : IBackgroundTask
{
    public BackgroundTaskDeferral TaskInstanceDeferral { get; set; }
    private PrintSupportExtensionSession session;

    public void Run(IBackgroundTaskInstance taskInstance)
    {
        taskInstance.Canceled += OnTaskInstanceCanceled;
        TaskInstanceDeferral = taskInstance.GetDeferral();

        if (taskInstance.TriggerDetails is PrintSupportExtensionTriggerDetails extensionDetails)
        {
            session = extensionDetails.Session;
            session.PrintTicketValidationRequested += OnSessionPrintTicketValidationRequested;
            session.PrintDeviceCapabilitiesChanged += OnSessionPrintDeviceCapabilitiesChanged;
            session.CommunicationErrorDetected += OnCommunicationErrorDetected;

            // Make sure to register all the event handlers before PrintSupportExtensionSession.Start is called.
            session.Start();
        }
    }

    private void OnTaskInstanceCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
    {
        session = null;
        TaskInstanceDeferral.Complete();
    }
    
    private void OnSessionPrintDeviceCapabilitiesChanged(PrintSupportExtensionSession sender, PrintSupportPrintDeviceCapabilitiesChangedEventArgs args)
    {
        // Using deferral to exclude the background process from being suspended while PSA updates the printer PDC and other configurations.
        using (args.GetDeferral())
        {
            if (args.CommunicationConfiguration.CanModifyTimeouts)
                {
                    this.UpdateAttributeTimeouts(args.CommunicationConfiguration, sender.Printer);
                    this.UpdateJobTimeouts(args.CommunicationConfiguration, sender.Printer);
                }
                // Do other operations, such as Updating PDC, PDR, and so on here.
        }
    }

    void UpdateAttributeTimeouts(PrintSupportIppCommunicationConfiguration config, IppPrintDevice device)
    {
        IppPrinterCommunicationKind communicationKind = config.CommunicationKind;
        PrintSupportIppCommunicationTimeouts currentTimeouts = config.IppAttributeTimeouts;

        // Adjust attribute timeouts based on the printer
        switch (communicationKind)
        {
            case IppPrinterCommunicationKind.Network:
                currentTimeouts.ConnectTimeout = TimeSpan.FromSeconds(10);
                currentTimeouts.SendTimeout = TimeSpan.FromSeconds(10);
                currentTimeouts.ReceiveTimeout = TimeSpan.FromSeconds(10);
                break;
            case IppPrinterCommunicationKind.UniversalPrint:
                // adjust timeout for universal printer
                break;
        }
        
    }

    void UpdateJobTimeouts(
        PrintSupportIppCommunicationConfiguration config, IppPrintDevice device)
    {
        IppPrinterCommunicationKind communicationKind = config.CommunicationKind;
        PrintSupportIppCommunicationTimeouts currentTimeouts = config.IppJobTimeouts;
        // Adjust job timeouts based on the printer and communication type
        switch (communicationKind)
        {
            case IppPrinterCommunicationKind.Network:
                currentTimeouts.ConnectTimeout = TimeSpan.FromSeconds(30);
                currentTimeouts.SendTimeout = TimeSpan.FromSeconds(30);
                currentTimeouts.ReceiveTimeout = TimeSpan.FromSeconds(30);
                break;
            case IppPrinterCommunicationKind.UniversalPrint:
                // adjust timeout for universal printer
                break;
        }
    }
}

處理 IPP 通訊錯誤

此範例示範如何處理 IPP 通訊錯誤。

public sealed class PrintSupportExtensionBackGroundTask : IBackgroundTask
{
    public BackgroundTaskDeferral TaskInstanceDeferral { get; set; }
    private PrintSupportExtensionSession session;
    public void Run(IBackgroundTaskInstance taskInstance)
    {
        taskInstance.Canceled += OnTaskInstanceCanceled;
        TaskInstanceDeferral = taskInstance.GetDeferral();

        if (taskInstance.TriggerDetails is PrintSupportExtensionTriggerDetails extensionDetails)
        {
            session = extensionDetails.Session;
            session.PrintTicketValidationRequested += OnSessionPrintTicketValidationRequested;
            session.PrintDeviceCapabilitiesChanged += OnSessionPrintDeviceCapabilitiesChanged;
            session.CommunicationErrorDetected += OnCommunicationErrorDetected ;

            // Make sure to register all the event handlers before PrintSupportExtensionSession.Start is called.
            session.Start();
        }
    }

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

    private void OnCommunicationErrorDetected(PrintSupportExtensionSession sender, PrintSupportCommunicationErrorDetectedEventArgs args)
    {
        // Using deferral to exclude the background process from being suspended while PSA updates the timeouts.
        using (args.GetDeferral())
        {

            if (args.ErrorKind == IppCommunicationErrorKind.Timeout)
            {
                PrintSupportIppCommunicationConfiguration ippConfig = args.CommunicationConfiguration;
                IppPrintDevice device = sender.Printer;
                // Update timeout based on the communication error
            }
        }
    }  
}

在PSA中透過IPP傳真發送文件到相關印表機

此範例示範如何在 PSA 中列印至 IPPFaxOut 印表機。

public sealed class PrintSupportWorkflowBackgroundTask : IBackgroundTask
{
    public BackgroundTaskDeferral TaskInstanceDeferral { get; set; }
    private PrintWorkflowJobBackgroundSession session;

    public void Run(IBackgroundTaskInstance taskInstance)
    {
        TaskInstanceDeferral = taskInstance.GetDeferral();

        if (taskInstance.TriggerDetails is PrintWorkflowJobTriggerDetails jobDetails)
        {
            session = jobDetails.PrintWorkflowJobSession;
            session.JobStarting += OnJobStarting;
            session.PdlModificationRequested += OnPdlModificationRequested;
            session.JobIssueDetected += OnJobIssueDetected;

            // Make sure to register all the event handlers before PrintWorkflowJobBackgroundSession.Start is called.
            session.Start();
        }
    }

    private async void OnPdlModificationRequested(PrintWorkflowJobBackgroundSession sender, PrintWorkflowPdlModificationRequestedEventArgs args)
    {
        using (args.GetDeferral())
        {
            IppPrintDevice printer = args.PrinterJob.Printer;
            IInputStream xpsContent = args.SourceContent.GetInputStream();
            WorkflowPrintTicket printTicket = args.PrinterJob.GetJobPrintTicket();

            string documentFormat = this.GetPrinterDocumentFormat(printer);
            PrintWorkflowPdlTargetStream targetStream = args.CreateJobOnPrinter(documentFormat);
            IInputStream xpsSourceContent = xpsContent;
            if (printer.IsIPPFaxOutPrinter)
            {                    
                // Add cover page to XPS source document   
                xpsSourceContent = this.AddCoverPageToXpsContent(xpsContent);
            }
            
            PrintWorkflowPdlConverter pdlConverter;
            switch (documentFormat.ToLowerInvariant())
            {
                case "image/pwg-raster":
                    pdlConverter = args.GetPdlConverter(PrintWorkflowPdlConversionType.XpsToPwgr);
                    break;
                case "application/pclm":
                    pdlConverter = args.GetPdlConverter(PrintWorkflowPdlConversionType.XpsToPclm);
                    break;
                case "application/pdf":
                    pdlConverter = args.GetPdlConverter(PrintWorkflowPdlConversionType.XpsToPdf);
                    break;
                case "image/tiff":
                    pdlConverter = args.GetPdlConverter(PrintWorkflowPdlConversionType.XpsToTiff);
                    break;
                default:
                    // This should not happen, aborting workflow if PSA does not identify the supported PDLs
                    args.Configuration.AbortPrintFlow(PrintWorkflowJobAbortReason.JobFailed);
                    return;
                }
                // Use pdlConverter to convert the source XPS stream to printer's document format and send it to the printer using targetStream.
                await pdlConverter.ConvertPdlAsync(printTicket, xpsSourceContent, targetStream.GetOutputStream());

                targetStream.CompleteStreamSubmission(PrintWorkflowSubmittedStatus.Succeeded);
            }
        }

    private IInputStream AddCoverPageToXpsContent(IInputStream xpsStream)
    {
        var coverPageXps = new InMemoryRandomAccessStream();
        // Add cover page to XPS content and write to coverPageXps stream
        return coverPageXps;
    }
}

停用 PSA 中的電話號碼傳真用戶介面

透過 IPP 傳真印表機的支援,此範例會顯示使用者介面,要求使用者在列印至傳真印表機時輸入傳真編號。 但PSA可能會想要顯示自己的UI,並提供更多資訊和選項。 因為如果使用者有兩個使用者介面(UI)用於傳真,會造成混淆,這個範例說明當 PSA 想顯示其傳真使用者介面時,如何停用系統使用者介面的選項。

下列範例示範 API 使用方式。

public sealed class PrintSupportWorkflowBackgroundTask : IBackgroundTask
{

    private void OnJobStarting(PrintWorkflowJobBackgroundSession sender, PrintWorkflowJobStartingEventArgs args)
    {
        using (args.GetDeferral())
        {
            // If the job is printing to an Ipp fax printer,
            // check whether PSA has a custom UI and disable system UI for getting the fax number.
            if (args.IsIPPFaxOutPrinter)
            {
                if (this.HasCustomUIForFax(args.Printer))
                {
                    args.SkipSystemFaxUI = true;
                }
            }
        }
    }

    bool HasCustomUIForFax(IppPrintDevice device)
    {
        bool hasCustomUIForFax = false;
        // Check if the PSA does custom UI for the given fax printer.
        return hasCustomUIForFax;
    }  
}

備註

本文中的範例是以 Print 支援應用程式設計指南中的PSA v1 和 v2 API 範例為基礎, 假設開發人員熟悉PSA API 工作流程。

本文包含在列印支援應用程式設計指南和 Windows.Graphics.Printing.PrintSupport 命名空間中所述的現有公用列印支援應用程式 API 的擴展。 PSA API 可讓印表機製造商開發 UWP 應用程式,以增強 Windows 使用者列印體驗,同時使用收件匣Microsoft IPP 類別驅動程式,而不需要開發自定義驅動程式。

列印的元件會透過 PSA 代理程式與 PSA 應用程式進行通訊。

DisableIppCompressionForJob

Windows 上第三方印表機驅動程式的支援計劃結束

IsIppCompressionEnabled

IsIPPFaxOutPrinter

IppPrintDevice

工作問題已偵測

PrintSupportIppCommunicationConfiguration

PrintSupportPrintDeviceCapabilitiesChangedEventArgs

PrintWorkflowJobBackgroundSession

PrintWorkflowJobIssueDetectedEventArgs

PrintWorkflowJobStartingEventArgs

PrintWorkflowPdlConversionType

SkipSystemErrorToast

Windows.Graphics.Printing.PrintSupport