Leitfaden zum Entwurf der Druckunterstützungs-App

Dieser Artikel enthält Anleitungen und Beispiele für Drucker-OEMs und IHVs, um eine Druckunterstützungs-App (PSA) zu entwickeln, die die Druckerfahrung eines Windows-Benutzers auf verschiedene Arten verbessern kann.

Wichtig

Ab der Veröffentlichung von Windows 11 SDK (22000.1) sind Druckunterstützungs-Apps (PSA) die empfohlene Methode für die Entwicklung von UWP-Apps für Drucker. Um eine Druckunterstützungs-Apps für Ihr Druckgerät zu entwickeln, laden Sie das Windows 11 SDK für die von Ihnen gewünschte Windows-Version herunter, und installieren Sie es.

Wichtig

Dieses Thema enthält Abschnitte zur Beschreibung der PSA-Funktionalität, die ab Windows 11, Version 22H2, verfügbar ist. Diese Abschnitte enthalten eine Notiz, die angibt, dass sie für diese Version gilt.

Einige Druckerfunktionen werden in Druckdialogfeldern von Windows nicht angezeigt, da sie spezielle Funktionen sind, die Hilfe von einer Hersteller-App benötigen, um ordnungsgemäß konfiguriert zu werden. Sie können auch Funktionen sein, die nicht in den Standardfunktionen des Druckers bereitgestellt werden.

Druckerspezifische Funktionen können auf eine Weise gruppiert werden, die es dem Benutzer erleichtert, eine Option zu wählen und zu vertrauen, dass alle Funktionen, die an diesem Szenario beteiligt sind, automatisch auf die richtigen Werte festgelegt werden. Ein Beispiel hierfür könnte eine Auswahl zwischen Freihandsparmodus, Papiersparmodus und höchster Qualität sein, was verschiedene Druckfunktionen basierend auf einer Auswahl des Benutzers automatisch bearbeiten könnte. Windows kann sie nicht automatisch gruppieren, da sie alle benutzerdefinierten Funktionen jedes Druckermodells verstehen müssen.

Dieser Notwendigkeit, benutzerdefinierte Druckeinstellungen anzuzeigen, wird durch diese API mit einem optionalen UWP-Erweiterungsvertrag Rechnung getragen, der vom Benutzer in allen Windows-Druckdialogen und benutzerdefinierten Druckdialogen, die die von Windows bereitgestellte API verwenden, aktiviert werden kann. Hersteller können ihre Benutzeroberfläche anpassen, um die beste Druckerfahrung für den spezifischen Drucker zu bieten, den der Benutzer besitzt.

Ein weiterer Bereich, in dem die Druckerhersteller die Druckqualität verbessern und differenzieren können. Hersteller können die Druckqualität nach dem Rendern verbessern, indem sie den Inhalt für den jeweiligen Drucker optimieren. Sie können auch eine Vorschau mit hoher Genauigkeit darstellen, die die endgültige Ausgabe besser darstellt, da sie druckerspezifische Funktionen berücksichtigen könnte.

print support app print timeline

Begriff

Begriff Definition
PSA Druckunterstützungsanwendung. Eine UWP-App, die die in diesem Artikel beschriebene API verwendet.
MPD Dialogfeld Moderner Druck. Dies wird dem Benutzer angezeigt, wenn eine App mit der Windows.Graphics.Printing-API druckt.
CPD Allgemeines Dialogfeld Drucken. Dies wird dem Benutzer angezeigt, wenn die App mit der Win32-API gedruckt wird. Apps, die die Seitenansicht anzeigen müssen, lösen dieses Dialogfeld nicht aus und implementieren eine Version des Dialogfelds selbst. Office-Apps sind ein hervorragendes Beispiel dafür.
IPP Internet-Druckprotokoll. Wird von einem Clientgerät verwendet, um mit dem Drucker zu interagieren, um Druckeinstellungen abzurufen und festzulegen und das Dokument zu senden, das gedruckt werden soll.
Zugehöriger Drucker mit Druckunterstützung Drucker, der mit PSA verknüpft ist.
IPP-Drucker Drucker, der das IPP-Protokoll unterstützt.
Weitere Einstellungen Link, der die Benutzeroberfläche der vom Partner bereitgestellten App in MPD öffnet. Standardmäßig wird die integrierte Druckeinstellungs-UI geöffnet, wenn kein PSA installiert ist.
Benutzeroberfläche für Druckereinstellungen Dialogfeld zum Festlegen von Standarddruckeroptionen, die zur Druckzeit angewendet werden. Beispiel: Ausrichtung, Papierformat, Farbe, Druck auf beiden Seiten usw.
PDL Datenbeschreibungssprache. Das Format, in dem ein Dokument an den Drucker gesendet wird.
Zugeordneter PSA-Drucker Physischer IPP-Drucker, der einer PSA-Anwendung zugeordnet ist.
PrintDeviceCapabilities XML-Dokumentformat zum Definieren von Druckerfunktionen. Weitere Informationen finden Sie unter Druckticket- und Druckfähigkeitstechnologien.
PrintTicket Sammlung verschiedener druckbezogener Features und deren Werte, die verwendet werden, um die Absicht des Benutzers für einen bestimmten Druckauftrag zu erfassen.
PrintSupportExtension PSA-Hintergrundaufgabe, die für die Bereitstellung von Druckereinschränkungsfunktionen verantwortlich ist.

In diesen Beispielen wird auf einen Druckunterstützungs-Namespace verwiesen, der wie folgt definiert ist:

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

Wenn ein Benutzer in der Zeit ist, ein Dokument zu drucken, möchte er häufig einige Einstellungen festlegen, mit denen es gedruckt werden soll. Sie können z. B. ein Dokument im Querformat drucken. Sie können auch ein benutzerdefiniertes Feature nutzen, das ihr Drucker unterstützt. Windows bietet standardmäßige UIs zum Anzeigen benutzerdefinierter Einstellungen, aber der Benutzer versteht sie möglicherweise nicht, da keine geeigneten Symbole oder Beschreibungen vorhanden sind. Windows verwendet möglicherweise auch das falsche UI-Steuerelement, um es darzustellen. Ein solches benutzerdefiniertes Feature wird am besten von einer App präsentiert, die das Feature vollständig versteht. Dies ist die Motivation, eine API anzubieten, mit der die Druckerhersteller Apps erstellen können, die auf die verschiedenen druckerspezifischen Modelle zugeschnitten sind.

Ein neuer UAP-Erweiterungsvertrag wird mit einer neuen Kategorie namens windows.printSupportSettingsUI erstellt. Mit diesem Vertrag aktivierte Apps erhalten ein neues ActivationKind namens PrintSupportSettingsUI. Für diesen Vertrag sind keine neuen Funktionen erforderlich.

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

Dieser Vertrag wird aufgerufen, wenn der Benutzer in MPD Weitere Einstellungen oder in CPD Einstellungen auswählt. Dieser Vertrag kann auch über die Druckeinstellungen in der Einstellungen-App aufgerufen werden. Wenn der Vertrag aktiviert wird, empfängt die App ein PrintSupportSettingsUISession-Objekt, mit dem die aktuellen PrintTicket- und PrintDevice-Objekte abgerufen werden können. Das PrintDevice-Objekt kann für die Kommunikation mit dem Drucker verwendet werden, um Drucker- und Auftragsattribute zu erhalten. Die App kann dann die Benutzeroberfläche mit den entsprechenden Optionen des Druckers für den Benutzer anzeigen. Wenn der Benutzer die Auswahl trifft und OK auswählt, kann die Anwendung dann das Druckticket ändern, validieren und es dann mithilfe des Objekts PrintSupportPrintTicketTarget zurücksenden. Wenn der Benutzer das Einstellungsfenster abbricht, sollten Änderungen nicht Karte werden, und die Anwendung sollte beendet werden, indem die Verzögerung des PrintSupportSettingsUISession-Objekts abgeschlossen wird.

Von der Druckunterstützungs-App wird erwartet, dass sie mehrere gleichzeitige Aktivierungen für verschiedene Druckaufträge verarbeitet. Daher muss eine solche App mehrere Instanzen mithilfe des Elements SupportsMultipleInstancest in der Datei package.appxmanifest unterstützen. Andernfalls kann dies zu Situationen führen, in denen die Bestätigung der Einstellungen eines Druckauftrags andere Einstellungsfenster schließen kann, die möglicherweise geöffnet sind. Der Benutzer muss diese Einstellungsfenster erneut öffnen.

Das folgende Sequenzdiagramm stellt das Konzept der Einstellungen Bearbeitung von Druckticket für die Benutzeroberfläche dar:

sequence diagram of settings U I print ticket manipulation

Ändern von PrintTicket in der Einstellungs-UI

C#-Beispielcode für die Aktivierung der Einstellungs-Benutzeroberfläche beim Start über einen beliebigen Druckdialog (MPD/CPD oder benutzerdefinierter Druckdialog) oder über die Systemeinstellungen:

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 für DefaultSettingsView-Klasse:

<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#-Beispielcode zum Anzeigen der Benutzeroberfläche und Ändern von 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();
        }
    }
}

Abrufen von Druckerattributen vom Druckergerät

WireShark-Antwort von einem IPP-Drucker auf eine Get-Printer-Attributes-Abfrage:

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

C#-Beispielcode zum Abrufen von Freihandnamen und Freihandebenen vom Drucker:

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

Festlegen von Druckerattributen auf dem Drucker

C#-Beispielcode zum Festlegen von Druckerattributen:

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

Erweitern von Druckereinschränkungen

Die Print Support-App unterstützt die benutzerdefinierte PrintTicket-Validierung und definieren das Standard-PrintTicket. In diesem Abschnitt wird beschrieben, wie wir diese Funktionen unterstützen.

Zur Unterstützung von Druckererweiterungseinschränkungen wurde ein neuer Hintergrundaufgabentyp PrintSupportExtension implementiert. Das Package.appxmanifest verfügt wie hier gezeigt über einen Erweiterungseintrag für die Druckunterstützungserweiterung:

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

Dieser Dienst kann an jedem Punkt in einem Druckauftrag für den zugehörigen IPP-Drucker ausgeführt werden. Da die Print Support Extension über die Funktion Run(IBackgroundTaskInstance taskInstance) aktiviert wird, wird PrintSupportExtension eine Instanz von IBackgroundTaskInstance übergeben, um Zugriff auf die Laufzeitklasse PrintSupportExtensionTriggerDetails bereitzustellen, die intern PrintSupportExtensionSession als Eigenschaft bereitstellt. Die PrintSupportExtension-Hintergrundklasse kann dann das Sitzungsobjekt zum Registrieren für Ereignisse verwenden, die benutzerdefinierte Funktionen bereitstellen möchten.

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

    Wenn die Print Support-Erweiterung einen eigenen PrintTicket-Überprüfungsmechanismus bereitstellt, kann sie sich für dieses Ereignis registrieren. Jedes Mal, wenn ein PrintTicket überprüft werden muss, löst das Drucksystem dieses Ereignis aus. PrintSupportExtension ruft dann das aktuelle PrintTicket ab, das innerhalb von EventArgs überprüft werden muss. Die PrintSupportExtension-Hintergrundklasse kann dann das PrintTicket auf Gültigkeit überprüfen und ändern, um Konflikte zu lösen. Die PrintSupportExtension-Hintergrundklasse sollte dann das Ergebnis für die Überprüfung mithilfe der Funktion SetPrintTicketResult festlegen, um anzugeben, ob das PrintTicket aufgelöst wurde, Konflikte aufweist oder ungültig ist. Dieses Ereignis kann während der Lebensdauer eines Druckauftrags jederzeit ausgelöst werden. Wenn die PrintSupportExtension-Klasse für dieses Ereignis nicht registriert wird, führt das Drucksystem eine eigene Überprüfung des PrintTicket durch.

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

    Das Ereignis wird ausgelöst, nachdem das Drucksystem die zwischengespeicherten PrintDeviceCapabilities des zugeordneten IPP-Druckers aktualisiert hat. Wenn dieses Ereignis ausgelöst wird, kann die PrintSupportExtension-Hintergrundklasse die geänderten PrintDeviceCapabilities überprüfen und ändern.

Benutzerdefinierte Überprüfung des Drucktickets

C#-Beispielcode für die Bereitstellung des PrintTicket-Überprüfungsdiensts:

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

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

Verbesserung der Druckqualität

Sobald sich der Benutzer zum Drucken verpflichtet hat, indem er im Druckdialog auf die Schaltfläche Drucken klickt, wird das zu druckende Dokument von der druckenden App an den Druckstapel gesendet.t. Dieses Dokument wird dann transformationsfähig (Rendering in PDL), damit es für den Zieldrucker geeignet ist. Windows bestimmt, welche Transformation basierend auf den vom Drucker abgefragten Attributen ausgewählt werden soll. Das transformierte Dokument wird dann an den Drucker gesendet. Obwohl dies für die meisten Drucker gut funktioniert, gibt es Fälle, in denen die Qualität des Drucks verbessert werden könnte, indem eine Partner-App an der Transformation teilnehmen kann. Um dies zu erleichtern, wird die aktuelle Druckworkflow-API erweitert, um Aufrufe der App an zusätzlichen Punkten vom Druckstapel einzuschließen. Diese API unterstützt zwei neue Ereignisse, für die sich die PSA-App registrieren kann. Dies sind die einzigen Einstiegspunkte in die PSA-API-Oberfläche:

  1. JobStarting

    • Dieses Ereignis wird ausgelöst, wenn ein Druckauftrag von einer beliebigen Anwendung gestartet wird. Wenn das Ereignis ausgelöst wird, kann eine Druckunterstützungs-App das Systemrendering überspringen, indem SetSkipSystemRendering für PrintWorkflowJobStartingEventArgs aufgerufen wird. Wenn das Systemrendering übersprungen wird, konvertiert das Drucksystem das XPS-Dokument nicht in das PDL-Format, das vom Drucker benötigt wird. Stattdessen wird das von der Druckanwendung generierte XPS direkt an den PSA übergeben, der dann für die Konvertierung von XPS in das PDL-Format verantwortlich ist.
  2. PdlModificationRequested

    • Dieses Ereignis wird ausgelöst, wenn Windows die Konvertierung des XPS-Datenstroms in das vom Drucker angegebene PDL-Format startet. Die Laufzeitklasse PrintWorkflowPdlModificationRequestedEventArgs wird als Argument für dieses Ereignis bereitgestellt. Diese Ereignisklasse stellt PDL-Quell- und Zielobjekte zum Lesen und Schreiben des Druckauftragsinhalts bereit. Wenn die App feststellt, dass sie Benutzereingaben benötigt, kann sie die Benutzeroberfläche mithilfe von PrintWorkflowUILauncher aus den EventArgs starten. Diese API verwendet das Tester-Doer-Muster. PrintWorkflowUILauncher kann die Benutzeroberfläche nicht aufrufen, wenn die Funktion IsUILaunchEnabled "false" zurückgibt. Diese Funktion gibt "false" zurück, wenn die PSA-Sitzung im unbeaufsichtigten Modus ausgeführt wird (Kopflos oder Kioskmodus). Die Druckunterstützungs-App sollte nicht versuchen, die Benutzeroberfläche zu starten, wenn die Funktion "false" zurückgibt.

    Ein OutputStream ist als Teil von PrintWorkflowPdlTargetStream verfügbar, der von der Funktion GetStreamTargetAsync zurückgegeben wird. Der in den OutputStream-Ziel geschriebene Inhalt wird als Dokumentinhalt an den Drucker übergeben.

Sequenzdiagramm für das PDL-Änderungsereignis:

sequence diagram for the source stream P D L modification event

Die PSA-Vordergrundanwendung wird gestartet, wenn die PSA-Hintergrundaufgabe den Start der Benutzeroberfläche anfordert. Der PSA kann den Vordergrundvertrag verwenden, um Benutzereingaben zu erhalten und/oder eine Vorschau der Seitenansicht für den Benutzer anzuzeigen.

Ein neuer printSupportWorkflow-Hintergrundaufgabentyp wurde definiert. Der Package.appxmanifest verfügt über den folgenden Erweiterungseintrag für den PrintSupportWorkflow-Vertrag :

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

Bei der Aktivierung des Vertrags wird PrintWorkflowJobTriggerDetails als IBackgroundTaskInstance-TriggerDetails> angegeben. PrintWorkflowJobTriggerDetails stellt PrintWorkflowJobBackgroundSession intern als Teil seiner Eigenschaften bereit. Die App kann PrintWorkflowJobBackgroundSession verwenden, um Ereignisse im Zusammenhang mit verschiedenen Einfügepunkten im Druckauftragsworkflow zu registrieren. Nachdem die Ereignisregistrierung abgeschlossen ist, muss die App PrintWorkflowJobBackgroundSession::Start aufrufen, damit das Drucksystem mit dem Auslösen von Ereignissen im Zusammenhang mit verschiedenen Injektionspunkten beginnt.

Ein neues ActivationKind namens PrintSupportJobUI ist definiert. Dies erfordert keine neue Funktion.

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

Dies ist ein Benutzeroberflächenvertrag, der entweder über den Hintergrundvertrag für den Druckunterstützungsworkflow gestartet werden kann oder wenn der Benutzer ein Druckauftragsfehler-Popup auswählt. Bei der Aktivierung wird PrintWorkflowJobActivatedEventArgs bereitgestellt, das über ein PrintWorkflowJobUISession-Objekt verfügt. Bei Verwendung von PrintWorkflowJobUISession sollte die Vordergrundanwendung für das PdlDataAvailable-Ereignis registriert werden, wenn sie auf die PDL-Daten zugreifen möchte. Wenn die Vordergrundanwendung benutzerdefinierte Fehlermeldungen für alle Fehler anzeigen möchte, die während des Auftrags auftreten können, sollte sie für das JobNotification-Ereignis registriert werden. Sobald die Ereignisse registriert wurden, sollte die Anwendung die PrintWorkflowJobUISession::Start-Funktion aufrufen, damit das Drucksystem das Auslösen von Ereignissen starten kann.

Überspringen des Systemrenderings

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

Sequenzdiagramm für das PDL-Änderungsereignis:

sequence diagram for the input stream P D L modification event

C#-Beispielcode für Druckunterstützungsauftragsüberwachung: Lesen und Schreiben von Druckauftragsinhalten:

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

Starten der Benutzeroberfläche aus dem Workflowhintergrund

C#-Beispielcode zum Starten der Druckunterstützungsauftragsbenutzeroberfläche von PSA PDL-Änderung angeforderter Ereignisvertrag:

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

Aktivierung der Workflowauftrags-UI für das PDLDataAvailable-Ereignis

Sequenzdiagramm für die Aktivierung der Druckauftrags-UI für das PdlDataAvailable-Ereignis :

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

C#-Beispielcode für den PSA-Job-UI-Aktivierungsvertrag:

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

Abrufen von Druckerauftragsattributen

C#-Beispielcode zum Abrufen von Auftragsattributen für einen Druckauftrag:

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

Druckerauftragsattribute festlegen

C#-Beispielcode, Fortsetzung des Abschnitts Druckerauftragsattribute abrufen oben, der das Festlegen von Auftragsattributen demonstriert:

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

Einige IPP-Drucker unterstützen das Abrufen/Festlegen von Auftragsattributen nach dem Erstellen des Auftrags nicht. Für diese Drucker ist bei PrintJob die Eigenschaft JobId auf „0“ gesetzt und GetJobAttributes/SetJobAttributes schlägt sofort mit einer Ausnahme fehl.

Bereitstellen des Dateizugriffs auf PDL-Inhalte

Einige PDL-Formate wie PDF benötigen einen vollständigen Datenstrom, um mit der Verarbeitung zu beginnen. Aus diesem Grund wird eine neue Methode mit dem Namen GetContentFileAsync für die PrintWorkflowPdlSourceContent-Klasse bereitgestellt, die eine StorageFile des Quellinhalts zurückgibt.

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

PDL-Konvertierung von XPS in PDF

C#-Beispielcode mit PDL-Konvertierung von XPS in 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();
}

Jobbenachrichtigungsereignis

Sequenzdiagramm für das Auftragsbenachrichtigungsereignis:

sequence diagram for the job notification event

C#-Beispielcode, Fortsetzung der Workflow-Job-UI-Aktivierung für den Ereignisabschnitt PDLDataAvailable oben, um einen Fehler bei der Jobbenachrichtigung anzuzeigen:

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

Auftrag mit Anfangsauftragsattributen erstellen

Derzeit unterstützen einige IPP-Drucker keinen Set-Attribut-Vorgang. Die CreateJobOnPrinterWithAttributes-Funktion und die CreateJobOnPrinterWithAttributesBuffer-Funktion für PrintWorkflowPdlDataAvailableEventArgs werden bereitgestellt, um dieses Problem zu beheben. Mithilfe dieser APIs kann ein PSA-Entwickler Auftragsattribute bereitstellen, die an Drucker übergeben werden, wenn der Auftrag auf dem Drucker erstellt wird.

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

Sequenzielle XPS-Verarbeitung

C++/Winrt-Beispielcode für die sequenzielle Verarbeitung von XPS vor Abschluss des Spoolings.

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

Lokalisierung des Anzeigenamens und PDL-Passthrough-API-Integration

Wichtig

In diesem Abschnitt werden die PSA-Funktionen beschrieben, die ab Windows 11, Version 22H2, verfügbar sind.

In diesem Szenario passt der PSA die Print Device Capabilities (PDC) an und stellt Print Device Resources (PDR) für die String-Lokalisierung bereit.

Der PSA legt auch die unterstützten PDL Passthrough-API-Inhaltstypen (PDL-Formate) fest. Wenn der PSA das Ereignis nicht abonniert oder SetSupportedPdlPassthroughContentTypes nicht explizit aufruft, ist der PDL-Passthrough für die mit dieser PSA-App verknüpften Drucker deaktiviert.

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

Unterstützungs- und Vorgangsattribute auf Seitenebene

Wichtig

In diesem Abschnitt werden die PSA-Funktionen beschrieben, die ab Windows 11, Version 22H2, verfügbar sind.

Die Szenarien für die Unterstützung der Seitenebene und die Vorgangsattribute werden gruppiert, da sie durch Änderungen an derselben Stelle im Beispielcode behoben werden.

  • Unterstützung der Seitenebene: In diesem Szenario gibt die PSA-Anwendung das Seitenebenenattribut an, das nicht durch ein vom PrintTicket analysiertes IPP-Attribut überschrieben werden sollte.

  • Separate Auflistung für die Unterstützung von Vorgangsattributen (PIN-Druck): In diesem Szenario gibt die PSA-Anwendung benutzerdefinierte IPP-Vorgangsattribute an (z. B. PIN).

Der folgende C#-Beispielcode zeigt erforderliche Änderungen für die Unterstützung der Seitenebene undseparate Auflistung für Vorgangsattributeszenarien .

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

Verbessern des Druckdialogfelds mit PSA

Wichtig

In diesem Abschnitt werden die PSA-Funktionen beschrieben, die ab Windows 11, Version 22H2, verfügbar sind.

In diesem Szenario ermöglicht die Verwendung des Dialogfelds Drucken mit der PSA-Integration die folgenden Aktionen:

  • Erhalten Sie einen Rückruf, wenn die Auswahl im MPD auf den mit PSA verknüpften Drucker geändert wird

  • Anzeigen einer AdaptiveCard mit Unterstützung der openUrl-Aktion

  • Anzeigen von benutzerdefinierten Features und Parametern im Druckdialogfeld

  • Ändern Sie das PrintTicket und ändern Sie so die Auswahl der im Druckdialog angezeigten Funktionsoptionen

  • Abrufen der Windows.ApplicationModel.AppInfo der Druck-App, Öffnen des Druckdialogfelds

Im folgenden C#-Beispiel werden diese Verbesserungen des Druckdialogfelds veranschaulicht:

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-Konvertierung mit hostbasierten Verarbeitungskennzeichnungen

Wichtig

In diesem Abschnitt werden die PSA-Funktionen beschrieben, die ab Windows 11, Version 22H2, verfügbar sind.

Die aktuelle PDL-Konvertierungs-API, PrintWorkflowPdlConverter.ConvertPdlAsync, führt standardmäßig hostbasierte Verarbeitung durch. Dies bedeutet, dass der Host-/Druckcomputer die Drehung, die Seitenreihenfolge usw. ausführt, sodass dieser Drucker diese Vorgänge nicht ausführen muss. Drucker-IHVs können jedoch PDL-Konvertierungen ohne hostbasierte Verarbeitung wünschen, da ihr Drucker dies besser tun kann. Die ConvertPdlAsync-Funktion verwendet hostbasierte Verarbeitungskennzeichnungen, um diese Anforderung zu erfüllen. Der PSA kann alle hostbasierten Verarbeitungen oder einen bestimmten hostbasierten Verarbeitungsvorgang mithilfe dieses Flags überspringen.

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
}

Legen Sie die Aktualisierungsrichtlinie für die Druckgerätefunktionen (PDC) fest

Wichtig

In diesem Abschnitt werden die PSA-Funktionen beschrieben, die ab Windows 11, Version 22H2, verfügbar sind.

Drucker-IHVs können unterschiedliche Anforderungen erfüllen, wenn Die Druckgerätefunktionen (Print Device Capabilities, PDC) aktualisiert werden müssen. Um diese Anforderungen zu erfüllen, kann PrintSupportPrintDeviceCapabilitiesUpdatePolicy eine Updaterichtlinie für die PDC festlegen. PSA kann die PDC-Updaterichtlinie basierend auf der Zeit oder der Anzahl der Druckaufträge mit dieser API festlegen.

Festlegen der PDC-Updaterichtlinie basierend auf der Anzahl der Aufträge

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

Festlegen der PDC-Updaterichtlinie basierend auf 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);      
    }
}

Designleitfaden für allgemeine Druckunterstützungs-App (PSA)

Beim Entwerfen einer Druckunterstützungs-App ist es wichtig, diese Aspekte in das Design einzuschließen:

  • Vordergrund- und Hintergrundverträge sollten als Unterstützung mehrerer Instanzen gekennzeichnet werden, z. B. SupportsMultipleInstance im Paketmanifest. Dadurch soll sichergestellt werden, dass die Lebensdauer der Verträge für mehrere gleichzeitige Aufträge zuverlässig verwaltet werden kann.

  • Behandeln Sie den Start der Benutzeroberfläche für PDL-Änderungen als optionalen Schritt. Bemühen Sie sich am besten, den Druckauftrag erfolgreich abzuschließen, auch wenn der Start der Benutzeroberfläche nicht zulässig war. Druckaufträge sollten nur abgebrochen werden, wenn sie während der PDL-Änderung ohne Benutzereingabe nicht erfolgreich abgeschlossen werden können. Erwägen Sie, die PDL in solchen Fällen unverändert zu senden.

  • Wenn Sie die Benutzeroberfläche für PDL-Änderungen starten, rufen Sie IsUILaunchEnabled auf, bevor Sie LaunchAndCompleteUIAsync aufrufen. Dadurch wird sichergestellt, dass Szenarien, in denen die Benutzeroberfläche zur aktuellen Zeit nicht angezeigt werden kann, weiterhin ordnungsgemäß gedruckt werden kann. Diese Szenarien können sich auf einem kopflosen Gerät oder einem Gerät befinden, das sich derzeit im Kioskmodus befindet oder den Modus nicht stören.

Zuordnung der Druckunterstützungs-App

Windows.Devices.Printers

Windows.Graphics.Printing.PrintSupport

Windows.Graphics.Printing.Workflow

IPP-Spezifikation (Internet Printing Protocol)