Guía de diseño de aplicaciones para compatibilidad con impresión

En este artículo se proporcionan instrucciones y ejemplos para los OEM de impresora e IHV para desarrollar una aplicación para compatibilidad con impresión (PSA) que pueda mejorar la experiencia de impresión de un usuario de Windows de varias maneras.

Importante

A partir del lanzamiento del SDK de Windows 11 (22000.1), las aplicaciones para compatibilidad con impresión (PSA) son el método recomendado para desarrollar aplicaciones UWP para impresoras. Para desarrollar una aplicación para compatibilidad con impresión para el dispositivo de impresión, descargue e instale el SDK de Windows 11 para la versión de Windows de destino.

Importante

Este tema contiene secciones que describen la funcionalidad de PSA que está disponible a partir de Windows 11, versión 22H2. Estas secciones contienen una nota que indica que se aplica a esa versión.

Algunas características de impresora no se presentan en cuadros de diálogo de impresión mostrados por Windows, ya que son características especiales que necesitan ayuda de una aplicación de fabricante para configurarse correctamente. También pueden ser características que no se proporcionan en las funcionalidades predeterminadas de la impresora.

Las características específicas de la impresora se pueden agrupar de una manera que facilita al usuario elegir una opción y confiar en que todas las características implicadas en ese escenario se establecen automáticamente en los valores correctos. Un ejemplo de ello podría ser la posibilidad de elegir entre los modos de ahorro de tinta, ahorro de papel y máxima calidad, que podrían manipular varias características de impresión automáticamente basándose en una selección del usuario. Windows no puede agruparlas automáticamente, ya que para ello es necesario conocer todas las características personalizadas de cada modelo de impresora.

Esta API aborda esta necesidad de mostrar las preferencias de impresión personalizadas con un contrato de extensión de UWP opcional que el usuario puede activar desde todos los cuadros de diálogos de impresión de Windows y cuadros de diálogo de impresión personalizados que usan la API proporcionada por Windows. Los fabricantes pueden adaptar su interfaz de usuario para proporcionar la mejor experiencia de impresión para la impresora específica que posee el usuario.

Otra área en la que los fabricantes de impresoras pueden mejorar y destacar es la calidad de impresión. Los fabricantes pueden mejorar la calidad de impresión después de la representación optimizando el contenido de la impresora específica. También pueden presentar una vista previa de alta fidelidad que represente mejor la salida final, ya que podría tener en cuenta características específicas de la impresora.

print support app print timeline

Terminología

Término Definición
APE Aplicación para compatibilidad con impresión. Una aplicación UWP que usa la API descrita en este artículo.
MPD Cuadro de diálogo de impresión moderno. Se muestra al usuario cuando una aplicación imprime con la API de windows.Graphics.Printing.
CPD Cuadro de diálogo de impresión común. Se muestra al usuario cuando la aplicación imprime con la API de Win32. Las aplicaciones que necesitan mostrar la vista previa de impresión no desencadenan este cuadro de diálogo e implementan una versión del cuadro de diálogo por sí mismas. Las aplicaciones de Office son un buen ejemplo de ello.
IPP Protocolo de impresión de Internet. Se usa desde un dispositivo cliente para interactuar con la impresora para recuperar y establecer preferencias de impresión y para enviar el documento que se va a imprimir.
Impresora asociada a la compatibilidad con impresión Impresora vinculada a PSA.
Impresora IPP Impresora que admite el protocolo IPP.
Más opciones de configuración Vínculo que abre la interfaz de usuario de la aplicación proporcionada por el asociado en MPD. El valor predeterminado es abrir la interfaz de usuario de preferencias de impresión integradas cuando no hay ninguna PSA instalada.
Interfaz de usuario de preferencias de impresora Cuadro de diálogo usado para establecer las opciones de impresora predeterminadas que se aplican en tiempo de impresión. Por ejemplo: orientación, tamaño de papel, color, impresión por ambas caras, etc.
PDL Lenguaje de descripción de datos. Formato en que se envía un documento a la impresora.
Impresora PSA asociada Impresora IPP física asociada a una aplicación PSA.
PrintDeviceCapabilities Formato de documento XML para definir funcionalidades de impresora. Para obtener más información, consulte Tecnologías de vales de impresión y capacidades de impresión.
PrintTicket Colección de varias características relacionadas con la impresión y sus valores usados para capturar la intención del usuario para un trabajo de impresión determinado.
PrintSupportExtension Tarea en segundo plano de PSA responsable de proporcionar funcionalidades de extensión de restricción de impresora.

Estos ejemplos hacen referencia a un espacio de nombres printsupport, que se define como:

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

Cuando un usuario se dispone a imprimir un documento, a menudo desea establecer algunas preferencias con las que imprimirlo. Por ejemplo, puede optar por imprimir un documento en orientación horizontal. También puede aprovechar una característica personalizada compatible con su impresora. Windows proporciona la interfaz de usuario predeterminada para mostrar preferencias personalizadas, pero es posible que el usuario no las entienda, ya que no hay iconos o descripciones adecuados. Windows también puede usar el control de interfaz de usuario incorrecto para presentarlo. La mejor forma de presentar una característica personalizada de este tipo es mediante una aplicación que la entienda perfectamente. Este es el motivo por el que se ofrece una API que permite a los fabricantes de impresoras crear aplicaciones adaptadas a los distintos modelos de impresoras que fabrican.

Se crea un nuevo contrato de extensión UAP con una nueva categoría llamada windows.printSupportSettingsUI. Las aplicaciones activadas con este contrato reciben un nuevo ActivationKind llamado PrintSupportSettingsUI. Este contrato no requiere ninguna nueva funcionalidad.

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

Este contrato se invoca cuando el usuario selecciona Más opciones de configuración en MPD o Preferencias en CPD. Este contrato también se puede invocar desde Preferencias de impresión en la aplicación Configuración. Cuando se activa el contrato, la aplicación recibe un objeto PrintSupportSettingsUISession que se puede usar para obtener los objetos PrintTicket y PrintDevice actuales. El objeto PrintDevice se puede usar para comunicarse con la impresora para recibir atributos de impresora y trabajo. Después, la aplicación puede mostrar la interfaz de usuario con las opciones adecuadas de la impresora al usuario. Cuando el usuario realiza las elecciones y selecciona Aceptar, la aplicación puede modificar el vale de impresión, validarlo y enviarlo de vuelta utilizando el objeto PrintSupportPrintTicketTarget. Si el usuario elige cancelar la ventana de preferencias, los cambios deben descartarse y la aplicación debe salir completando el aplazamiento tomado del objeto PrintSupportSettingsUISession.

Se espera que la aplicación para compatibilidad con impresión controle varias activaciones simultáneas para distintos trabajos de impresión, por lo que una aplicación debe admitir varias instancias mediante el elemento SupportsMultipleInstances en el archivo package.appxmanifest. Si no lo hace, podría darse el caso de que al confirmar las preferencias de un trabajo de impresión se cerraran otras ventanas de preferencias que pudieran estar abiertas. El usuario debe volver a abrir esas ventanas de preferencias.

El siguiente diagrama de secuencia representa el concepto de manipulación de vales de impresión de la interfaz de usuario Configuración:

sequence diagram of settings U I print ticket manipulation

Cambio de PrintTicket en la interfaz de usuario de configuración

Código de ejemplo de C# para la activación de la interfaz de usuario de Configuración cuando se inicia desde cualquier cuadro de diálogo de impresión (MPD/CPD o cuadro de diálogo de impresión personalizado) o desde la configuración del sistema:

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

XAML para la clase DefaultSettingsView:

<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>

Código de ejemplo de C# para mostrar la interfaz de usuario y cambiar PrintTicket:

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

Obtención de atributos de impresora del dispositivo de impresora

Respuesta WireShark de una impresora IPP a una consulta get-printer-attributes:

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

Código de ejemplo de C# para obtener nombres y niveles de tinta de la impresora:

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

Configuración de los atributos de impresora en la impresora

Código de ejemplo de C# para configurar los atributos de la impresora:

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

Extensión de restricciones de impresora

La aplicación para compatibilidad con impresión admite la validación personalizada de PrintTicket y define el PrintTicket predeterminado. En esta sección se describe cómo admitimos estas funciones.

Para admitir restricciones de extensión de impresora, se ha implementado un nuevo tipo de tarea en segundo plano, PrintSupportExtension. Package.appxmanifest tiene una entrada de extensibilidad para la extensión de compatibilidad con impresión, como se muestra aquí:

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

Este servicio puede ejecutarse en cualquier momento en un trabajo de impresión para la impresora IPP asociada. A medida que la extensión de compatibilidad con impresión se activa a través de la función Run(IBackgroundTaskInstance taskInstance), se proporciona una instancia de IBackgroundTaskInstance a PrintSupportExtension para proporcionar acceso a la clase en tiempo de ejecución PrintSupportExtensionTriggerDetails, que proporciona internamente PrintSupportExtensionSession como una propiedad. La clase en segundo plano PrintSupportExtension puede usar el objeto de sesión para registrarse en los eventos a los que desee proporcionar una funcionalidad personalizada.

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

    Si la extensión para compatibilidad con impresión proporciona su propio mecanismo de validación de PrintTicket, puede registrarse para este evento. Siempre que se necesite validar un PrintTicket, el sistema de impresión genera este evento. PrintSupportExtension obtendrá el PrintTicket actual que debe validarse dentro de EventArgs. La clase en segundo plano PrintSupportExtension puede comprobar la validez de PrintTicket y modificarla para resolver los conflictos. A continuación, la clase en segundo plano PrintSupportExtension debe establecer el resultado para la validación mediante la función SetPrintTicketResult para indicar si se ha resuelto PrintTicket, tiene conflictos o no es válido. Este evento se puede generar en cualquier momento durante la vigencia de un trabajo de impresión. Si la clase PrintSupportExtension no se registra para este evento, el sistema de impresión realiza su propia validación de PrintTicket.

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

    El evento se genera después de que el sistema de impresión actualice PrintDeviceCapabilities almacenado en caché de la impresora IPP asociada. Cuando se genera este evento, la clase en segundo plano PrintSupportExtension puede inspeccionar los elementos PrintDeviceCapabilities modificados y modificarlos.

Validación personalizada del vale de impresión

Código de ejemplo de C# para proporcionar el servicio de validación PrintTicket:

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

Actualización de 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);
    }
}

Mejora de la calidad de impresión

Una vez que el usuario confirma la impresión pulsando el botón de imprimir en el cuadro de diálogo de impresión, el documento que se va a imprimir se envía a la pila de impresión desde la aplicación que está imprimiendo. A continuación, este documento se somete a la transformación (representación en PDL) para que sea adecuado para la impresora de destino. Windows determinará qué transformación elegir en función de los atributos consultados desde la impresora. A continuación, el documento transformado se envía a la impresora. Aunque esto funciona bien para la mayoría de las impresoras, hay casos en los que la calidad de impresión podría mejorarse al permitir que una aplicación asociada participe en la transformación. Para facilitarlo, la API de flujo de trabajo de impresión actual se extiende para incluir llamadas a la aplicación en puntos adicionales de la pila de impresión. Esta API admite dos nuevos eventos para los que la aplicación PSA puede registrarse. Estos son los únicos puntos de entrada en la superficie de la API de PSA:

  1. JobStarting

    • Este evento se genera cuando cualquier aplicación inicia un trabajo de impresión. Cuando se genera el evento, una aplicación para compatibilidad con impresión puede optar por omitir la representación del sistema llamando a SetSkipSystemRendering en PrintWorkflowJobStartingEventArgs. Si se elige omitir la representación del sistema, el sistema de impresión no convertirá el documento XPS al formato PDL requerido por la impresora. En su lugar, el XPS generado por la aplicación de impresión se proporcionará directamente a la PSA que, luego, se encargará de convertir XPS al formato PDL.
  2. PdlModificationRequested

    • Este evento se genera cuando Windows inicia la conversión de la secuencia XPS al formato PDL indicado por la impresora. La clase en tiempo de ejecución PrintWorkflowPdlModificationRequestedEventArgs se proporciona como argumento para este evento. Esta clase de evento proporciona objetos de origen y destino PDL para leer y escribir el contenido del trabajo de impresión. Si la aplicación determina que necesita entrada de usuario, puede iniciar la interfaz de usuario mediante PrintWorkflowUILauncher desde EventArgs. Esta API usa el patrón Tester-Doer. PrintWorkflowUILauncher no podrá invocar la interfaz de usuario si la función IsUILaunchEnabled devuelve false. Esta función devuelve false si la sesión de PSA se ejecuta en modo silencioso (modo de pantalla completa). La aplicación para compatibilidad con impresión no debe intentar iniciar la interfaz de usuario si la función devuelve false.

    OutputStream está disponible como parte de PrintWorkflowPdlTargetStream que devuelve la función GetStreamTargetAsync. El contenido escrito en OutputStream de destino se transfiere a la impresora como contenido del documento.

Diagrama de secuencia para el evento de modificación de PDL:

sequence diagram for the source stream P D L modification event

La aplicación en primer plano de PSA se inicia cuando la tarea en segundo plano de PSA solicita la interfaz de usuario. La PSA puede usar el contrato en primer plano para obtener la entrada de usuario o para mostrar una vista previa de la impresión al usuario.

Se ha definido un nuevo tipo de tarea en segundo plano llamado printSupportWorkflow. Package.appxmanifest tiene la siguiente entrada de extensibilidad para el contrato de PrintSupportWorkflow:

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

Al activar el contrato, PrintWorkflowJobTriggerDetails se indica como IBackgroundTaskInstance->TriggerDetails. PrintWorkflowJobTriggerDetails proporciona internamente PrintWorkflowJobBackgroundSession como parte de sus propiedades. La aplicación puede usar PrintWorkflowJobBackgroundSession para registrarse para eventos relacionados con varios puntos de inyección en el flujo de trabajo de impresión. Una vez realizado el registro de eventos, la aplicación debe llamar a PrintWorkflowJobBackgroundSession::Start para que el sistema de impresión empiece a desencadenar eventos relacionados con varios puntos de inyección.

Se define un nuevo ActivationKind denominado PrintSupportJobUI. No requiere una nueva funcionalidad.

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

Se trata de un contrato de interfaz de usuario que se puede iniciar desde el contrato en segundo plano del flujo de trabajo de compatibilidad con impresión o cuando el usuario selecciona una notificación del error del trabajo de impresión. Al activarlo, se proporciona PrintWorkflowJobActivatedEventArgs, que tiene un objeto PrintWorkflowJobUISession. Con PrintWorkflowJobUISession, la aplicación en primer plano debe registrarse para el evento PdlDataAvailable si quiere acceder a los datos de PDL. Si la aplicación en primer plano desea mostrar mensajes de error personalizados para los errores que pueden producirse durante el trabajo, debe registrarse para el evento JobNotification. Una vez registrados los eventos, la aplicación debe llamar a la función PrintWorkflowJobUISession::Start para que el sistema de impresión empiece a desencadenar eventos.

Omitir la representación del sistema

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

Evento de modificación de PDL

Diagrama de secuencia para el evento de modificación de PDL:

sequence diagram for the input stream P D L modification event

Código de ejemplo de C# para la lectura y escritura del contenido del trabajo de impresión del Monitor de trabajos de compatibilidad con impresión:

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

Inicio de la interfaz de usuario desde el flujo de trabajo en segundo plano

Código de ejemplo de C# para iniciar la interfaz de usuario del trabajo de compatibilidad con impresión a partir del contrato de eventos solicitado de modificación de PDL de PSA:

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

Activación de la interfaz de usuario del trabajo de flujo de trabajo para el evento PDLDataAvailable

Diagrama de secuencia para la activación de la interfaz de usuario del trabajo de impresión para el evento PdlDataAvailable:

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

Código de ejemplo de C# para el contrato de activación de la interfaz de usuario del trabajo de PSA:

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

Obtención de atributos de trabajo de impresora

Código de ejemplo de C# para obtener atributos de trabajo para un trabajo de impresión:

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

Configuración de atributos de trabajo de impresora

Código de ejemplo de C#, continuando desde la sección anterior Obtención de atributos de trabajos de impresora, que muestra la configuración de los atributos de trabajos:

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

Algunas impresoras IPP no admiten atributos de trabajo de obtención/configuración después de crear el trabajo. Para esas impresoras, PrintJob tiene la propiedad JobId establecida en "0" y GetJobAttributes/SetJobAttributes fallará inmediatamente con una excepción.

Aprovisionamiento de acceso a archivos de almacenamiento al contenido PDL

Algunos formatos PDL, como PDF, necesitan una secuencia completa para que esté disponible para iniciar el procesamiento. Por ese motivo, se proporciona un nuevo método denominado GetContentFileAsync en la clase PrintWorkflowPdlSourceContent que devuelve un StorageFile del contenido de origen.

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

Conversión de PDL de XPS a PDF

Código de ejemplo de C# que muestra la conversión de PDL de XPS a PDF:

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

Evento de notificación de trabajo

Diagrama de secuencia para el evento de notificación del trabajo:

sequence diagram for the job notification event

Código de ejemplo de C#, que continúa desde la activación de la interfaz de usuario del trabajo de flujo de trabajo para la sección de eventos PDLDataAvailable anterior, para mostrar el error en la notificación del trabajo:

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

Creación de un trabajo con atributos de trabajo iniciales

Actualmente, algunas impresoras IPP no admiten la operación set-attribute. Para mitigar este problema, se proporciona la función CreateJobOnPrinterWithAttributes y la función CreateJobOnPrinterWithAttributesBuffer en PrintWorkflowPdlDataAvailableEventArgs. Con estas API, un desarrollador de PSA puede proporcionar atributos de trabajo que se transfieren a la impresora cuando se crea el trabajo en la impresora.

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
    }
}

Procesamiento de XPS secuencial

Código de ejemplo de C++/Winrt para procesar XPS secuencialmente antes de completar la cola.

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

Localización de nombres para mostrar e integración de la API de paso de PDL

Importante

En esta sección se describe la funcionalidad de PSA disponible a partir de Windows 11, versión 22H2.

En este escenario, la PSA personaliza las funcionalidades del dispositivo de impresión (PDC) y proporciona recursos de dispositivo de impresión (PDR) para la localización de cadenas.

La PSA también establece los tipos de contenido de la API de paso de PDL admitidos (formatos PDL). Si la PSA no se suscribe al evento o no llama explícitamente a SetSupportedPdlPassthroughContentTypes, el paso de PDL se deshabilitará para las impresoras asociadas a esta aplicación PSA.

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

Atributos de operación y compatibilidad de características de nivel de página

Importante

En esta sección se describe la funcionalidad de PSA disponible a partir de Windows 11, versión 22H2.

Los escenarios de atributos de operación y compatibilidad de características de nivel de página se agrupan porque se abordan mediante la realización de cambios en el mismo lugar en el código de ejemplo.

  • Compatibilidad con características de nivel de página: en este escenario, la aplicación PSA especifica el atributo de nivel de página, que no se debe anular mediante un atributo IPP analizado desde PrintTicket.

  • Colección independiente para compatibilidad con atributos de operación (impresión con PIN): en este escenario, la aplicación PSA especifica atributos de operación IPP personalizados (por ejemplo, PIN).

En el siguiente código de ejemplo de C# se muestran los cambios necesarios para escenarios de compatibilidad de características de nivel de página y recopilación independiente para atributos de operación.

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

Mejora del cuadro de diálogo de impresión con PSA

Importante

En esta sección se describe la funcionalidad de PSA disponible a partir de Windows 11, versión 22H2.

En este escenario, el uso del cuadro de diálogo de impresión con la integración de PSA permite las siguientes acciones:

  • Obtener una devolución de llamada cuando se cambia la selección en el MPD a la impresora asociada con PSA

  • Mostrar una AdaptiveCard con compatibilidad con la acción openUrl

  • Mostrar características y parámetros personalizados en el cuadro de diálogo de impresión

  • Modificar PrintTicket, cambiando así la selección de las opciones de características que se muestran en el cuadro de diálogo de impresión

  • Obtener Windows.ApplicationModel.AppInfo de la aplicación de impresión y abrir el cuadro de diálogo de impresión.

En el ejemplo de C# siguiente se muestran estas mejoras en el cuadro de diálogo de impresión:

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

Conversión de PDL con marcas de procesamiento basadas en host

Importante

En esta sección se describe la funcionalidad de PSA disponible a partir de Windows 11, versión 22H2.

La API de conversión de PDL actual, PrintWorkflowPdlConverter.ConvertPdlAsync, realiza el procesamiento basado en host de forma predeterminada. Esto significa que el equipo host/de impresión realiza la rotación, el orden de página, etc., para que la impresora no necesite realizar estas operaciones. Sin embargo, los IHV de impresoras pueden querer la conversión PDL sin procesamiento basado en host, ya que su impresora puede hacerlo mejor. La función ConvertPdlAsync toma las marcas de procesamiento basadas en host para satisfacer este requisito. La PSA puede omitir todo el procesamiento basado en host o una operación de procesamiento basada en host determinada mediante esta marca.

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
}

Configuración de la directiva de actualización de funcionalidades de dispositivo de impresión (PDC)

Importante

En esta sección se describe la funcionalidad de PSA disponible a partir de Windows 11, versión 22H2.

Los IHV de impresoras pueden tener requisitos diferentes sobre cuándo se deben actualizar las funcionalidades del dispositivo de impresión (PDC). Para abordar estos requisitos, PrintSupportPrintDeviceCapabilitiesUpdatePolicy puede establecer una directiva de actualización para el PDC. La PSA puede establecer la directiva de actualización de PDC en función del tiempo o el número de trabajos de impresión mediante esta API.

Configuración de la directiva de actualización de PDC en función del número de trabajos

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

Configuración de la directiva de actualización de PDC basada en TimeOut

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

Guía de diseño general de aplicaciones para compatibilidad con impresión (PSA)

Al diseñar una aplicación para compatibilidad con impresión, es importante incluir estos aspectos en el diseño:

  • Los contratos en primer plano y en segundo plano deben marcarse como compatibles con varias instancias, por ejemplo, SupportsMultipleInstance debe estar presente en el manifiesto del paquete. Esto es para garantizar que la duración de los contratos se pueda administrar de forma fiable para varios trabajos simultáneos.

  • Trate el inicio de la interfaz de usuario para la modificación de PDL como un paso opcional. Haga todo lo posible para completar el trabajo de impresión correctamente, incluso si el lanzamiento de la interfaz de usuario no estaba permitido. Los trabajos de impresión solo deben cancelarse si no hay forma de completarlos correctamente éxito sin la entrada del usuario durante la modificación de PDL. Considere la posibilidad de enviar el PDL sin modificar en tales casos.

  • Al iniciar la interfaz de usuario para la modificación de PDL, llame a IsUILaunchEnabled antes de llamar a LaunchAndCompleteUIAsync. Esto es para asegurarse de que los escenarios que no pueden mostrar la interfaz de usuario en el momento actual continúan imprimiendo correctamente. Estos escenarios podrían estar en un dispositivo periférico o en un dispositivo que actualmente se encuentra en modo de pantalla completa o no molestar.

Asociación de aplicaciones para compatibilidad con impresión

Windows.Devices.Printers

Windows.Graphics.Printing.PrintSupport

Windows.Graphics.Printing.Workflow

Especificación del Protocolo de impresión en Internet (IPP)