列印支援應用程式 v3 API 設計指南
本文提供針對其裝置實作 v3 印表支援應用程式 (PSA) 的印表機 OEM 和 IHV 的指引和範例。
術語
術語 | 定義 |
---|---|
PSA | 列印支援應用程式。 本檔中使用 API 的 UWP 應用程式。 |
IPP | 因特網列印通訊協定。 從用戶端裝置用來與印表機互動,以擷取和設定印表喜好設定,以及傳送要列印的檔。 |
提供列印支持的相關印表機 | 連結到PSA的實體 IPP 印表機。 |
IPP 印表機 | 支援 IPP 通訊協定的印表機。 |
PDL | 頁面描述語言。 檔傳送至印表機的格式。 |
相關聯的PSA印表機 | 與 PSA 應用程式相關聯的實體 IPP 印表機。 |
列印設備功能 | 用於定義印表機功能的 XML 檔案格式。 |
PrintSupportExtension | 負責提供印表機限制擴充功能的 PSA 背景任務。 |
列印支援應用程式 v3 API
本文包含在《列印支援應用程式設計指南》中描述的現有列印支援應用程式公用 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) 運行時間類別中引進屬性 IsIppCompressionEnabled 和 DisableIppCompressionForJob 函式(視需要停用目前工作的壓縮)。
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 應用程式進行通訊。
相關文章
Windows 上第三方印表機驅動程式的支援計劃結束
PrintSupportIppCommunicationConfiguration
PrintSupportPrintDeviceCapabilitiesChangedEventArgs
PrintWorkflowJobBackgroundSession
PrintWorkflowJobIssueDetectedEventArgs
PrintWorkflowJobStartingEventArgs