Designhandbuch zur Unterstützung von Druck-Apps

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 Weise verbessern kann.

Wichtig

Ab der Veröffentlichung von Windows 11 SDK (22000.1) sind Druckunterstützungs-Apps (PSA) die empfohlene Methode zum Entwickeln von UWP-Apps für Drucker. Um eine Druckunterstützungs-App für Ihr Druckgerät zu entwickeln, laden Sie das Windows 11 SDK für die Windows-Version herunter, die Sie als Ziel verwenden.

Wichtig

Dieses Thema enthält Abschnitte, in denen die PSA-Funktionalität beschrieben wird, die ab Windows 11 Version 22H2 verfügbar ist. Diese Abschnitte enthalten einen Hinweis, der angibt, dass er für diese Version gilt.

Einige Druckerfeatures werden in druckseitigen Dialogfeldern von Windows nicht angezeigt, da es sich um spezielle Features handelt, die hilfe von einer Hersteller-App benötigen, um ordnungsgemäß konfiguriert zu werden. Sie können auch Features sein, die nicht in den Standardfunktionen des Druckers bereitgestellt werden.

Druckerspezifische Features können so gruppiert werden, dass der Benutzer einfach eine Option auswählen und darauf vertrauen kann, dass alle Features, die in diesem Szenario beteiligt sind, automatisch auf die richtigen Werte festgelegt werden. Ein Beispiel hierfür wäre die Wahl zwischen Freihandschoner, Papiersparmodus und Modi mit höchster Qualität, die verschiedene Druckfunktionen automatisch basierend auf einer Auswahl des Benutzers bearbeiten könnten. Windows kann sie nicht automatisch gruppieren, da dafür alle benutzerdefinierten Features jedes Druckermodells verstanden werden müssen.

Diese Anforderung zum Anzeigen benutzerdefinierter Druckeinstellungen wird von dieser API mit einem optionalen UWP-Erweiterungsvertrag behoben, der vom Benutzer über alle Windows-Druckdialoge und benutzerdefinierten Druckdialoge aktiviert werden kann, die die von Windows bereitgestellte API verwenden. Hersteller sind in der Lage, ihre Benutzeroberfläche so anzupassen, dass sie das beste Druckerlebnis für den spezifischen Drucker bietet, den der Benutzer besitzt.

Ein weiterer Bereich, in dem die Druckerhersteller verbessern und unterscheiden können, ist die Druckqualität. Hersteller können die Druckqualität nach dem Rendering 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 Features berücksichtigen könnte.

druckunterstützung app print Zeitleiste

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 druckt. Apps, die die Druckvorschau anzeigen müssen, lösen dieses Dialogfeld nicht aus und implementieren selbst eine Version des Dialogfelds. Office-Apps sind ein hervorragendes Beispiel dafür.
IPP Internet Printing Protocol. Wird von einem Clientgerät verwendet, um mit dem Drucker zu interagieren, um Druckeinstellungen abzurufen und festzulegen und das zu druckende Dokument zu senden.
Druckunterstützung zugeordneter Drucker Drucker, der mit PSA verknüpft ist.
IPP-Drucker Drucker, der das IPP-Protokoll unterstützt.
Weitere Einstellungen Link, der die vom Partner bereitgestellte App-Benutzeroberfläche in MPD öffnet. Standardmäßig wird die integrierte Benutzeroberfläche der Druckeinstellungen 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 Seitenbeschreibungssprache. 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 Print Ticket and Print Capabilities Technologies.
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änkungserweiterungsfunktionen zuständig ist.

Diese Beispiele verweisen auf einen printsupport-Namespace , der wie folgt definiert ist:

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

Wenn ein Benutzer ein Dokument drucken möchte, möchte er häufig einige Einstellungen festlegen, mit denen es gedruckt werden soll. Sie können sich beispielsweise dafür entscheiden, ein Dokument im Querformat zu drucken. Sie können auch eine benutzerdefinierte Funktion nutzen, die ihr Drucker unterstützt. Windows bietet eine Standard-Benutzeroberfläche zum Anzeigen benutzerdefinierter Einstellungen, aber der Benutzer versteht diese möglicherweise nicht, da es keine geeigneten Symbole oder Beschreibungen gibt. 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 druckerhersteller Apps erstellen können, die auf die verschiedenen Druckermodelle zugeschnitten sind.

Ein neuer UAP-Erweiterungsvertrag wird mit einer neuen Kategorie namens "windows.printSupportSettingsUI" erstellt. Apps, die mit diesem Vertrag aktiviert wurden, 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 weitere Einstellungen in MPD oder Einstellungen in CPD auswählt. Dieser Vertrag kann auch in den Druckeinstellungen in der Einstellungen-App aufgerufen werden. Wenn der Vertrag aktiviert ist, empfängt die App ein PrintSupportSettingsUISession-Objekt , das zum Abrufen der aktuellen PrintTicket - und PrintDevice-Objekte verwendet werden kann. Das PrintDevice-Objekt kann für die Kommunikation mit dem Drucker verwendet werden, um Drucker- und Auftragsattribute zu empfangen. 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 das Druckticket ändern, es überprüfen und dann mit dem PrintSupportPrintTicketTarget-Objekt zurücksenden. Wenn der Benutzer das Einstellungsfenster abbrechen möchte, sollten Änderungen verworfen werden, und die Anwendung sollte beendet werden, indem die Verzögerung aus dem PrintSupportSettingsUISession-Objekt abgeschlossen wird.

Es wird erwartet, dass die Druckunterstützungs-App mehrere gleichzeitige Aktivierungen für verschiedene Druckaufträge verarbeitet, sodass eine solche App mehrere Instanzen mit dem SupportsMultipleInstances-Element in der Datei package.appxmanifest unterstützen muss. 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 Einstellungs-UI-Druckticketbearbeitung dar:

Sequenzdiagramm der Einstellungen U I Druckticketbearbeitung

Ändern von PrintTicket in der Benutzeroberfläche für Einstellungen

C#-Beispielcode für die Aktivierung der Benutzeroberfläche "Einstellungen", wenn sie über ein beliebiges Druckdialogfeld (MPD/CPD oder benutzerdefiniertes Druckdialogfeld) oder über Systemeinstellungen gestartet wird:

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 die 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 eines IPP-Druckers auf eine Get-printer-attributes-Abfrage:

wireshark-Antwort eines IP-Druckers auf eine Abfrage zum Abrufen von Druckerattributen

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 Druckunterstützungs-App unterstützt die benutzerdefinierte PrintTicket-Überprüfung und die Definition des Standard-PrintTicket. In diesem Abschnitt wird beschrieben, wie wir diese Features unterstützen.

Zur Unterstützung von Druckererweiterungseinschränkungen wurde der neue Hintergrundaufgabentyp PrintSupportExtension implementiert. 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. Wenn die Druckunterstützungserweiterung über die Funktion Run(IBackgroundTaskInstance taskInstance) aktiviert wird, wird printSupportExtension ein instance von IBackgroundTaskInstance zugewiesen, um Zugriff auf die Laufzeitklasse PrintSupportExtensionTriggerDetails zu ermöglichen, die intern PrintSupportExtensionSession als Eigenschaft bereitstellt. Die PrintSupportExtension-Hintergrundklasse kann dann das Sitzungsobjekt verwenden, um sich für Ereignisse zu registrieren, die benutzerdefinierte Funktionen bereitstellen möchten.

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

    Wenn die Druckunterstützungserweiterung einen eigenen PrintTicket-Validierungsmechanismus bietet, kann sie sich für dieses Ereignis registrieren. Wenn ein PrintTicket überprüft werden muss, löst das Drucksystem dieses Ereignis aus. PrintSupportExtension ruft dann das aktuelle PrintTicket ab, das innerhalb der EventArgs überprüft werden muss. Die PrintSupportExtension-Hintergrundklasse kann dann das PrintTicket auf Gültigkeit überprüfen und ändern, um Konflikte zu beheben. 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 nicht für dieses Ereignis 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 zum Bereitstellen des PrintTicket-Validierungsdiensts:

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

Nachdem sich der Benutzer zum Drucken verpflichtet hat, indem er die Druckschaltfläche im Druckdialogfeld drückt, wird das zu druckende Dokument von der druckbaren App an den Druckstapel gesendet. Dieses Dokument wird dann transformationiert (Rendering in PDL), um es für den Zieldrucker geeignet zu machen. Windows bestimmt anhand von Attributen, die vom Drucker abgefragt werden, welche Transformation ausgewählt werden soll. Das transformierte Dokument wird dann an den Drucker gesendet. Dies funktioniert zwar gut für die meisten Drucker, aber es gibt Fälle, in denen die Druckqualität 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 Stellen aus dem Druckstapel aufzunehmen. 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 Print Support-App das Systemrendering überspringen, indem SetSkipSystemRendering auf PrintWorkflowJobStartingEventArgs aufgerufen wird. Wenn Systemrendering überspringen ausgewählt wird, konvertiert das Drucksystem das XPS-Dokument nicht in das vom Drucker erforderliche PDL-Format. Stattdessen wird das von der Druckanwendung generierte XPS direkt an die PSA übergeben, die 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-Streams in das vom Drucker angegebene PDL-Format startet. Die Runtime-Klasse 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 eine Benutzereingabe benötigt, kann sie die Benutzeroberfläche mithilfe von PrintWorkflowUILauncher über die 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 (headless- oder Kioskmodus) ausgeführt wird. 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. Inhalte, die in den OutputStream-Zieltext geschrieben wurden, werden als Dokumentinhalt an den Drucker übergeben.

Sequenzdiagramm für das PDL-Änderungsereignis:

Sequenzdiagramm für das P D L-Änderungsereignis des Quellstreams

Die PSA-Vordergrundanwendung wird gestartet, wenn die PSA-Hintergrundaufgabe den Start der Benutzeroberfläche anfordert. Der PSA kann den Vordergrundvertrag verwenden, um Benutzereingaben abzurufen und/oder dem Benutzer eine Vorschau der Druckvorschau anzuzeigen.

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

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

Bei Aktivierung des Vertrags wird PrintWorkflowJobTriggerDetails als IBackgroundTaskInstance-TriggerDetails> angegeben. PrintWorkflowJobTriggerDetails stellt intern PrintWorkflowJobBackgroundSession als Teil seiner Eigenschaften bereit. Die App kann PrintWorkflowJobBackgroundSession verwenden, um ereignisse im Zusammenhang mit verschiedenen Einschleuspunkten 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 Einschleuspunkten 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 "Druckunterstützungsworkflow" gestartet werden kann, oder wenn der Benutzer ein Popup für einen Druckauftragsfehler ausgewählt hat. Bei der Aktivierung wird PrintWorkflowJobActivatedEventArgs bereitgestellt, die über ein PrintWorkflowJobUISession-Objekt verfügt. Mit PrintWorkflowJobUISession sollte sich die Vordergrundanwendung für das PdlDataAvailable-Ereignis registrieren, wenn sie auf die PDL-Daten zugreifen möchte. Wenn die Vordergrundanwendung benutzerdefinierte Fehlermeldungen für Fehler anzeigen möchte, die während des Auftrags auftreten können, sollte sie sich für das JobNotification-Ereignis registrieren. Sobald die Ereignisse registriert wurden, sollte die Anwendung die Funktion PrintWorkflowJobUISession::Start aufrufen, damit das Drucksystem mit dem Auslösen von Ereignissen beginnen 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:

Sequenzdiagramm für das P D L-Änderungsereignis des Eingabestroms

C#-Beispielcode für druckunterstützungsauftragsmonitor 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 Workflowhintergrund

C#-Beispielcode zum Starten der Benutzeroberfläche des Drucksupportauftrags über den angeforderten PSA-PDL-Änderungsereignisvertrag:

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 :

Sequenzdiagramm für die U I-Aktivierung des Druckauftrags für das P D L-Datenverfügbarkeitsereignis

C#-Beispielcode für den Aktivierungsvertrag für den PSA-Auftrag:

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

Festlegen von Druckerauftragsattributen

C#-Beispielcode im Abschnitt Abrufen von Druckerauftragsattributen weiter oben, um das Festlegen von Auftragsattributen zu veranschaulich:

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 hat PrintJob die JobId-Eigenschaft auf "0" festgelegt, und GetJobAttributes/SetJobAttributes schlägt sofort mit einer Ausnahme fehl.

Bereitstellen des Speicherdateizugriffs auf PDL-Inhalte

Einige PDL-Formate wie PDF benötigen einen vollständigen Stream, 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, der die PDL-Konvertierung von XPS in PDF zeigt:

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

Auftragsbenachrichtigungsereignis

Sequenzdiagramm für auftragsbenachrichtigungsereignis:

Sequenzdiagramm für das Auftragsbenachrichtigungsereignis

C#-Beispielcode, der oben im Abschnitt workflowauftragsbenutzeroberfläche für das PDLDataAvailable-Ereignis fortgesetzt wird, um fehler bei Auftragsbenachrichtigungen 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;
            }
        }
    }    
}

Erstellen eines Auftrags mit anfänglichen Auftragsattributen

Derzeit unterstützen einige IPP-Drucker keinen Set-Attribut-Vorgang. Die Funktion CreateJobOnPrinterWithAttributes und CreateJobOnPrinterWithAttributesBuffer in PrintWorkflowPdlDataAvailableEventArgs werden bereitgestellt, um dieses Problem zu beheben. Mithilfe dieser APIs kann ein PSA-Entwickler Auftragsattribute bereitstellen, die an den Drucker übergeben werden, wenn ein 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, bevor das Spooling abgeschlossen ist.

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 von Anzeigenamen und Integration der PDL-Passthrough-API

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 Druckgerätefunktionen (PDC) an und stellt Druckgeräteressourcen (PDR) für die Zeichenfolgenlokalisierung 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 die PDL-Passthrough für die Drucker deaktiviert, die dieser PSA-App zugeordnet sind.

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

Featureunterstützung auf Seitenebene und Vorgangsattribute

Wichtig

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

Die Szenarien zur Unterstützung von Feature- und Vorgangsattributen auf Seitenebene sind gruppiert, da sie durch Änderungen an derselben Stelle im Beispielcode behandelt werden.

  • Featureunterstützung auf Seitenebene: In diesem Szenario gibt die PSA-Anwendung das Attribut auf Seitenebene an, das nicht von einem aus dem PrintTicket analysierten IPP-Attribut überschrieben werden soll.

  • 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 die erforderlichen Änderungen für die Featureunterstützung auf Seitenebene und die separate Sammlung für Szenarien mit Vorgangsattributen .

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 Druckdialogfelds mit der PSA-Integration Folgendes:

  • Abrufen eines Rückrufs, wenn die Auswahl im MPD auf den Drucker geändert wird, der psa zugeordnet ist

  • Eine AdaptiveCard mit Unterstützung der openUrl-Aktion anzeigen

  • Anzeigen benutzerdefinierter Features und Parameter im Druckdialogfeld

  • Ändern Sie das PrintTicket, und ändern Sie so die Auswahl für Featureoptionen, die im Dialogfeld "Drucken" angezeigt werden.

  • Rufen Sie windows.ApplicationModel.AppInfo der Druck-App ab, und öffnen Sie das Druckdialogfeld.

Im folgenden C#-Beispiel werden diese Verbesserungen des Druckdialogs 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 Verarbeitungsflags

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 aus. Dies bedeutet, dass der Host-/Druckcomputer die Drehung, Seitenreihenfolge usw. ausführt, sodass der Drucker diese Vorgänge nicht ausführen muss. Drucker-IHVs möchten jedoch möglicherweise PDL-Konvertierung ohne hostbasierte Verarbeitung, da ihr Drucker dies besser kann. Die ConvertPdlAsync-Funktion akzeptiert hostbasierte Verarbeitungsflags, um diese Anforderung zu erfüllen. Der PSA kann mithilfe dieses Flags alle hostbasierten Verarbeitungen oder einen bestimmten hostbasierten Verarbeitungsvorgang ü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
}

Festlegen der PDC-Updaterichtlinie (Print Device Capabilities, Druckgerätefunktionen)

Wichtig

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

Drucker-IHVs haben möglicherweise unterschiedliche Anforderungen, wenn druckgerätefunktionen (PDC) aktualisiert werden müssen. Um diese Anforderungen zu erfüllen, kann PrintSupportPrintDeviceCapabilitiesUpdatePolicy eine Updaterichtlinie für den PDC festlegen. PSA kann die PDC-Updaterichtlinie basierend auf der Zeit oder der Anzahl von Druckaufträgen mithilfe dieser API festlegen.

Festlegen der PDC-Updaterichtlinie basierend auf der Anzahl von Aufträgen

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

Allgemeine Entwurfsanleitung für die Druckunterstützungs-App (PSA)

Beim Entwerfen einer Druckunterstützungs-App ist es wichtig, diese Aspekte in den Entwurf einzubeziehen:

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

  • Behandeln Sie das Starten der Benutzeroberfläche für PDL-Änderungen als optionalen Schritt. Versuchen Sie, den Druckauftrag erfolgreich abzuschließen, auch wenn der Start der Benutzeroberfläche nicht zulässig war. Druckaufträge sollten nur abgebrochen werden, wenn es keine Möglichkeit gibt, sie ohne Benutzereingaben während der PDL-Änderung erfolgreich abzuschließen. Erwägen Sie, die PDL in solchen Fällen unverändert zu senden.

  • Rufen Sie beim Starten der Benutzeroberfläche für PDL-Änderungen IsUILaunchEnabled auf, bevor Sie LaunchAndCompleteUIAsync aufrufen. Dadurch soll sichergestellt werden, dass Szenarien, in denen die Benutzeroberfläche zum aktuellen Zeitpunkt nicht angezeigt werden kann, weiterhin ordnungsgemäß gedruckt werden. 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ört.

Drucken der Unterstützungs-App-Zuordnung

Windows.Devices.Printers

Windows.Graphics.Printing.PrintSupport

Windows.Graphics.Printing.Workflow

IPP-Spezifikation (Internet Printing Protocol)