打印支持应用设计指南

本文为打印机 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 打印时,会向用户显示此项。 需要显示打印预览的应用不会触发此对话框并实现对话版本本身。 办公室应用是其中的主要示例。
IPP Internet 打印协议。 从客户端设备用于与打印机交互,以检索和设置打印首选项,以及发送要打印的文档。
打印支持关联的打印机 链接到 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.printSupportSettingsUI 的新类别创建了一个新的 UAP 扩展协定。 使用此协定激活的应用会收到名为 PrintSupportSettingsUI 的新 ActivationKind。 此协定不需要任何新功能。

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

当用户在 MPD 中选择“更多设置”或在 CPD 中选择“首选项”时,将调用此协定。 还可以从“设置”应用中的“打印首选项”调用此协定。 激活协定后,应用会收到 PrintSupportSettingsUISession 对象,该对象可用于获取当前的 PrintTicketPrintDevice 对象。 PrintDevice 对象可用于与打印机通信以接收打印机和作业属性。 然后,应用可以向用户显示具有相应打印机选项的 UI。 当用户做出选择并选择“确定”时,应用程序可以修改打印票证,对其进行验证,然后使用 PrintSupportPrintTicketTarget 对象提交回打印票证。 如果用户选择取消首选项窗口,则应丢弃更改,并通过完成从 PrintSupportSettingsUISession 对象中获取的延迟退出应用程序。

打印支持应用应处理不同打印作业的多个同时激活,因此此类应用必须使用 package.appxmanifest 文件中的 SupportsMultipleInstances 元素支持多个实例。 如果不这样做,可能会导致在确认一个打印作业的首选项时关闭可能打开的其他首选项窗口。 用户需要再次打开这些首选项窗口。

以下序列图表示设置 UI 打印票证操作的概念:

sequence diagram of settings U I print ticket manipulation

在设置 UI 中更改 PrintTicket

用于在从任何打印对话框(MPD/CPD 或自定义打印对话框)或系统设置启动时激活设置 UI 的 C# 示例代码:

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();
        } 
    }
}

DefaultSettingsView 类的 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) 激活,因此向 PrintSupportExtension 提供了一个 IBackgroundTaskInstance 实例,以提供对 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 将直接提供给 PSA,然后由其负责将 XPS 转换为 PDL 格式。
  2. PdlModificationRequested

    • 当 Windows 开始将 XPS 流转换为打印机指示的 PDL 格式时,将引发此事件。 运行时类 PrintWorkflowPdlModificationRequestedEventArgs 作为此事件的参数提供。 此事件类提供 PDL 源和目标对象,用于读取和写入打印作业内容。 如果应用确定它需要用户输入,则可以使用 EventArgs 中的 PrintWorkflowUILauncher 启动 UI。 此 API 使用 Tester-Doer 模式。 如果函数 IsUILaunchEnabled 返回 false,PrintWorkflowUILaunchLauncher 将无法调用 UI。 如果 PSA 会话以无提示模式(无外设模式或展台模式)运行,则此函数返回 false。 如果函数返回 false,则打印支持应用不应尝试启动 UI。

    OutputStream 作为由函数 GetStreamTargetAsync 返回的 PrintWorkflowPdlTargetStream 的一部分提供。 写入目标 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 协定,可以从“打印支持工作流”后台协定启动,或者当用户选择打印作业错误 Toast 时启动。 在激活时,会提供 PrintWorkflowJobActivatedEventArgs,其中包含 PrintWorkflowJobUISession 对象。 如果使用 PrintWorkflowJobUISession,前台应用程序应在想要访问 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 的新方法,该类返回源内容的 StorageFile

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 转换的 C# 示例代码:

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 上的 CreateJobOnPrinterWithAttributes 函数和 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 直通。

// 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 功能。

打印机 IHV 对何时需要更新打印设备功能 (PDC) 可能有不同的要求。 为了满足这些要求,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。

  • 启动 UI 进行 PDL 修改时,请先调用 IsUILaunchEnabled,然后再调用 LaunchAndCompleteUIAsync。 这是为了确保当前无法显示 UI 的方案继续正确打印。 这些方案可能位于无外设设备或当前处于展台模式或请勿打扰模式的设备上。

打印支持应用关联

Windows.Devices.Printers

Windows.Graphics.Printing.PrintSupport

Windows.Graphics.Printing.Workflow

Internet 打印协议 (IPP) 规范