Udostępnij za pośrednictwem


Przewodnik projektowania aplikacji do obsługi wydruku w wersji 1 i 2

Ten artykuł zawiera wskazówki i przykłady dla producentów OEM i IHV drukarek, aby stworzyć aplikację wspierającą drukowanie (PSA), która może poprawić doświadczenie drukowania użytkownika systemu Windows na kilka sposobów.

Ważny

Począwszy od wydania zestawu Windows 11 SDK (22000.1), aplikacje do obsługi wydruku (PSA) to zalecana metoda tworzenia aplikacji platformy UWP dla drukarek. Aby utworzyć aplikację do obsługi wydruku dla urządzenia do drukowania, pobierz i zainstaluj zestaw SDK systemu Windows 11 dla docelowej wersji systemu Windows.

Ważny

Ten artykuł zawiera sekcje opisujące funkcje PSA dostępne począwszy od systemu Windows 11 w wersji 22H2. Te sekcje zawierają notatkę wskazującą, że ma ona zastosowanie do tej wersji.

Aby uzyskać więcej informacji, zobacz następujące artykuły:

Temat Opis
Przewodnik projektowania interfejsu API Print App w wersji 3 Zawiera wskazówki i przykłady dla producentów OEM i dostawców sprzętu IHV w zakresie druku, którzy implementują aplikację wsparcia druku w wersji 3 (PSA) dla swoich urządzeń.
Przewodnik projektowania interfejsu API Print App w wersji 4 Zawiera wskazówki i przykłady dla OEM i IHV drukarek, które wdrażają aplikację wsparcia drukowania w wersji 4 (PSA) dla swoich urządzeń.
Specyfikacja manifestu MSIX dla wspomagania drukowania w przypadku drukarki wirtualnej Zawiera wskazówki i przykłady manifestu MSIX dla producentów OEM i IHV drukarek, które implementują Wirtualną Drukarkę Wspierającą Druk.
Wsparcie wydruku — skojarzenie aplikacji Zawiera wskazówki i przykłady dotyczące kojarzenia aplikacji do obsługi wydruku (PSA) z drukarką.

Niektóre funkcje drukarki nie są wyświetlane w oknach dialogowych drukowania wyświetlanych przez system Windows, ponieważ są to specjalne funkcje, które wymagają pomocy od aplikacji producenta do poprawnego skonfigurowania. Mogą to być również funkcje, które nie są dostępne w domyślnych możliwościach drukarki.

Funkcje specyficzne dla drukarki można grupować w sposób, który ułatwia użytkownikowi wybranie opcji i zaufanie, że wszystkie funkcje, które są zaangażowane w tym scenariuszu, są automatycznie ustawiane na prawidłowe wartości. Przykładem może być wybór między trybami oszczędzania tuszu, oszczędzania papieru i najwyższej jakości, które mogą automatycznie dostosować różne funkcje drukowania na podstawie jednego wyboru użytkownika. System Windows nie może automatycznie ich zgrupować, ponieważ wymaga to zrozumienia wszystkich niestandardowych funkcji każdego modelu drukarki.

Ta potrzeba wyświetlania niestandardowych preferencji drukowania jest rozwiązywana przez ten interfejs API z opcjonalnym kontraktem rozszerzenia UWP, który użytkownik może aktywować w wszystkich oknach dialogowych drukowania systemu Windows oraz w niestandardowych oknach dialogowych drukowania korzystających z API dostarczanego przez Windows. Producenci mogą dostosować interfejs użytkownika, aby zapewnić najlepsze doświadczenie drukowania dla określonej drukarki, którą posiada użytkownik.

Innym obszarem, w którym producenci drukarek mogą poprawić i wyróżnić, jest jakość wydruku. Producenci mogą poprawić jakość wydruku po renderowaniu, optymalizując zawartość dla określonej drukarki. Mogą również przedstawić podgląd wysokiej wierności, który lepiej reprezentuje końcowe dane wyjściowe, ponieważ może wziąć pod uwagę funkcje specyficzne dla drukarki.

Oś czasu drukowania w aplikacji wspierającej druk

Terminologia

Termin Definicja
PSA Aplikacja do obsługi drukowania. Aplikacja UWP korzystająca z interfejsu API opisanego w tym artykule.
MPD Nowoczesne okno dialogowe drukowania. Jest to wyświetlane użytkownikowi, gdy aplikacja drukuje przy użyciu interfejsu API Windows.Graphics.Printing.
CPD Typowe okno dialogowe drukowania. Jest to wyświetlane użytkownikowi podczas drukowania aplikacji przy użyciu interfejsu API Win32. Aplikacje, które muszą wyświetlać podgląd wydruku, nie wyzwalają tego okna dialogowego i implementują swoją wersję okna dialogowego. Aplikacje pakietu Office są doskonałym przykładem tego.
IPP Protokół drukowania internetowego. Używane z urządzenia klienckiego do interakcji z drukarką w celu pobierania i ustawiania preferencji drukowania oraz wysyłania dokumentu do wydrukowania.
Drukarka skojarzona z obsługą drukowania Drukarka połączona z PSA.
Drukarka IPP Drukarka obsługująca protokół IPP.
Więcej ustawień Link umożliwiający otwarcie interfejsu użytkownika aplikacji udostępnionej przez partnera w usłudze MPD. Domyślnie otwiera wbudowany interfejs preferencji drukowania, gdy nie ma zainstalowanego PSA.
Interfejs preferencji drukarki Okno dialogowe używane do ustawiania domyślnych opcji drukarki, które są stosowane w czasie drukowania. Na przykład: orientacja, rozmiar papieru, kolor, drukowanie po obu stronach itd.
PDL Język opisu strony. Format, w którym dokument jest wysyłany do drukarki.
Drukarka stowarzyszona z PSA Fizyczna drukarka IPP skojarzona z aplikacją PSA.
MożliwościUrządzeniaDrukującego Format dokumentu XML do definiowania możliwości drukarki. Aby uzyskać więcej informacji, zobacz Technologie biletu drukowania i możliwości drukowania.
PrintTicket Kolekcja różnych funkcji związanych z drukowaniem i ich wartości używanych do przechwytywania intencji użytkownika dla danego zadania drukowania.
PrintSupportExtension Zadanie w tle PSA odpowiedzialne za zapewnienie możliwości rozszerzenia ograniczeń drukarki.

Te przykłady odnoszą się do przestrzeni nazw printsupport, która jest zdefiniowana jako:

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

Gdy użytkownik ma drukować dokument, często chce ustawić pewne preferencje, za pomocą których ma być wydrukowany. Mogą na przykład wybrać drukowanie dokumentu w orientacji poziomej. Mogą również korzystać z funkcji niestandardowej obsługiwanej przez drukarkę. System Windows udostępnia domyślny interfejs użytkownika do wyświetlania preferencji niestandardowych, ale użytkownik może nie rozumieć ich, ponieważ nie ma odpowiednich ikon ani opisów. System Windows może również używać nieprawidłowej kontrolki interfejsu użytkownika, aby ją przedstawić. Taka funkcja niestandardowa jest najlepiej prezentowana przez aplikację, która całkowicie rozumie tę funkcję. Jest to motywacja do zaoferowania interfejsu API, który umożliwia producentom drukarek tworzenie aplikacji dostosowanych do różnych modeli drukarek, które tworzą.

Zostanie utworzony nowy kontrakt rozszerzenia UAP z nową kategorią o nazwie windows.printSupportSettingsUI. Aplikacje aktywowane za pomocą tego kontraktu otrzymują nowy typ ActivationKind o nazwie PrintSupportSettingsUI. Ten kontrakt nie wymaga żadnych nowych możliwości.

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

Ten kontrakt jest wywoływany, gdy użytkownik wybierze Więcej ustawień w MPD lub Preferencje w CPD. Ten kontrakt można również wywołać z Ustawienia drukowania w aplikacji Ustawienia. Po aktywowaniu kontraktu aplikacja otrzymuje obiekt PrintSupportSettingsUISession, którego można użyć do pobrania bieżących obiektów PrintTicket i PrintDevice. Obiekt PrintDevice może służyć do komunikacji z drukarką w celu odbierania atrybutów drukarki i zadania. Aplikacja może następnie wyświetlić interfejs użytkownika z odpowiednimi opcjami drukarki dla użytkownika. Gdy użytkownik dokona wyborów i wybierze OK, aplikacja może następnie zmodyfikować bilet wydruku, zweryfikować go, a następnie przesłać z powrotem przy użyciu PrintSupportPrintTicketTarget obiektu. Jeśli użytkownik zdecyduje się anulować okno preferencji, zmiany powinny zostać odrzucone, a aplikacja powinna zakończyć działanie, kończąc odroczenie pobrane z obiektu PrintSupportSettingsUISession.

Aplikacja Obsługi Wydruku powinna obsługiwać wiele równoczesnych aktywacji dla różnych zadań drukowania, dlatego taka aplikacja musi obsługiwać wiele wystąpień przy użyciu elementu SupportsMultipleInstances w pliku package.appxmanifest. Niepowodzenie tego może spowodować sytuacje, w których potwierdzenie preferencji jednego zadania drukowania może zamknąć inne okna preferencji, które mogą być otwarte. Użytkownik musi ponownie otworzyć te okna preferencji.

Poniższy diagram sekwencji przedstawia koncepcję zarządzania biletami drukowania w UI ustawień.

diagram sekwencji ustawień manipulowania biletami drukowania U I

Zmienianie narzędzia PrintTicket w interfejsie użytkownika ustawień

Przykładowy kod języka C# umożliwiający aktywację interfejsu użytkownika ustawień po uruchomieniu z dowolnego okna dialogowego drukowania (MPD/CPD lub niestandardowego okna dialogowego drukowania) lub z ustawień systemowych:

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 dla klasy DefaultSettingsView:

<Page
    x:Class="PsaSampleApp.DefaultSettingsView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:PsaSampleApp"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <StackPanel Grid.Row="0"  Orientation="Vertical" Margin="30,50,0,0">
           <ComboBox x:Name="OrientationOptions" ItemsSource="{x:Bind OrientationFeatureOptions}" SelectedItem="{x:Bind SelectedOrientationOption, Mode=TwoWay}" DisplayMemberPath="DisplayName" HorizontalAlignment="Left" Height="Auto" Width="Auto" VerticalAlignment="Top"/>
       </StackPanel>

        <StackPanel Grid.Row="1" Orientation="Horizontal">
            <Button x:Name="Ok" Content="Ok" HorizontalAlignment="Left" Margin="50,0,0,0" VerticalAlignment="Top" Click="OkClicked"/>
            <Button x:Name="Cancel" Content="Cancel" HorizontalAlignment="Left" Margin="20,0,0,0" VerticalAlignment="Top" Click="CancelClicked"/>
        </StackPanel>
    </Grid>
</Page>

Przykładowy kod języka C# umożliwiający wyświetlanie interfejsu użytkownika i zmienianie narzędzia 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();
        }
    }
}

Pobieranie atrybutów drukarki z urządzenia drukarki

Odpowiedź WireShark z drukarki IPP do zapytania get-printer-attributes:

odpowiedź Wireshark z drukarki IPP na zapytanie o atrybuty drukarki

Przykładowy kod języka C# do pobierania nazw atramentów i poziomów atramentu z drukarki:

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

Ustawianie atrybutów drukarki na drukarce

Przykładowy kod języka C# do ustawiania atrybutów drukarki:

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

Rozszerzanie ograniczeń drukarki

Aplikacja wspierająca druk obsługuje niestandardową walidację PrintTicket i definiowanie domyślnego PrintTicket. W tej sekcji opisano sposób obsługi tych funkcji.

Aby obsługiwać ograniczenia rozszerzenia drukarki, zaimplementowano nowy typ zadania w tle PrintSupportExtension. Plik Package.appxmanifest zawiera wpis rozszerzalności rozszerzenia obsługi wydruku, jak pokazano poniżej:

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

Ta usługa może działać w dowolnym momencie podczas realizacji zadania drukowania dla powiązanej drukarki IPP. W momencie aktywacji rozszerzenia obsługi wydruku za pomocą funkcji IBackgroundTaskInstance, wystąpienie IBackgroundTaskInstance jest przekazywane do rozszerzenia PrintSupportExtension, co umożliwia dostęp do klasy uruchomieniowej PrintSupportExtensionTriggerDetails, która wewnętrznie udostępnia właściwość PrintSupportExtensionSession. Klasa tła PrintSupportExtension może następnie używać obiektu sesji do rejestrowania zdarzeń, które chcą zapewnić funkcjonalność niestandardową.

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

    Jeśli rozszerzenie obsługi wydruku udostępnia własny mechanizm weryfikacji PrintTicket, może zarejestrować się w tym zdarzeniu. Za każdym razem, gdy PrintTicket wymaga weryfikacji, system drukowania inicjuje to zdarzenie. PrintSupportExtension uzyska bieżący PrintTicket, który musi zostać zweryfikowany w ramach EventArgs. Klasa pomocnicza PrintSupportExtension może następnie sprawdzić poprawność PrintTicket i zmodyfikować go, aby rozwiązać wszelkie konflikty. Klasa tła PrintSupportExtension powinna następnie ustawić wynik weryfikacji przy użyciu funkcji SetPrintTicketResult, aby wskazać, czy PrintTicket został rozwiązany, występują konflikty lub jest nieprawidłowy. To zdarzenie można zgłaszać w dowolnym momencie w okresie istnienia zadania drukowania. Jeśli klasa PrintSupportExtension nie rejestruje się na potrzeby tego zdarzenia, system wydruku przeprowadza własną walidację narzędzia PrintTicket.

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

    Zdarzenie jest zgłaszane po zaktualizowaniu systemu wydruku buforowanych PrintDeviceCapabilities skojarzonej drukarki IPP. Po wystąpieniu tego zdarzenia klasa PrintSupportExtension w tle może sprawdzić zmienione PrintDeviceCapabilities i zmodyfikować je.

Niestandardowa walidacja biletu wydruku

Przykładowy kod języka C# służący do udostępniania usługi sprawdzania poprawności PrintTicket:

public void Run(IBackgroundTaskInstance taskInstance)
{
    // Take task deferral
    this.taskDeferral = taskInstance.GetDeferral();
    // Associate a cancellation handler with the background task
    taskInstance.Canceled += OnTaskCanceled;

    var psaTriggerDetails = taskInstance.TriggerDetails as PrintSupportExtensionTriggerDetails;

    var serviceSession = psaTriggerDetails.Session as PrintSupportExtensionSession;

    this.ippPrintDevice = serviceSession.Printer;
    serviceSession.PrintTicketValidationRequested += this.OnPrintTicketValidationRequested;
    serviceSession.PrinterDeviceCapabilitiesChanged += this.OnPdcChanged;
    serviceSession.Start();
}

private void OnTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
{
    // Complete the deferral
    this.taskDeferral.Complete();
}

private void OnPrintTicketValidationRequested(PrintSupportExtensionSession session, PrintSupportPrintTicketValidationRequestedEventArgs args)
{
    using (args.GetDeferral())
    {
        // Get PrintTicket that needs needs to be validated and resolved   
        var printTicket = args.PrintTicket;
                
        // Validate and resolve PrintTicket
        WorkflowPrintTicketValidationStatus validationStatus = this.ValidateAndResolvePrintTicket(printTicket);
        args.SetPrintTicketValidationStatus(validationStatus);
    }
}

Aktualizowanie elementu 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 capabilities
        XmlDocument newPdc = this.CheckAndUpdatePrintDeviceCapabilities(pdc);
        args.UpdatePrintDeviceCapabilities(newPdc);
    }
}

Ulepszenie jakości wydruku

Gdy użytkownik zobowiązał się do drukowania, naciskając przycisk drukowania w oknie dialogowym drukowania, dokument, który ma zostać wydrukowany, jest wysyłany do stosu wydruku z aplikacji, która drukuje. Następnie ten dokument przechodzi transformację (renderowanie do PDL), aby był odpowiedni dla drukarki docelowej. System Windows określi, jakie przekształcenia należy wybrać na podstawie atrybutów zapytanych z drukarki. Przekształcony dokument jest następnie wysyłany do drukarki. Mimo że działa to dobrze w przypadku większości drukarek, istnieją przypadki, w których jakość wydruku może zostać ulepszona, umożliwiając aplikacji partnerskiej uczestnictwo w transformacji. Aby to ułatwić, bieżący interfejs API przepływu pracy drukowania został rozszerzony w celu uwzględnienia wywołań do aplikacji w dodatkowych punktach z stosu wydruku. Ten interfejs API obsługuje dwa nowe zdarzenia, dla których aplikacja PSA może się zarejestrować. Są to jedyne punkty wejścia na powierzchnię interfejsu API PSA:

  1. RozpoczynaniePracy

    • To zdarzenie jest zgłaszane, gdy zadanie drukowania jest uruchamiane przez dowolną aplikację. Po wywołaniu zdarzenia aplikacja obsługi wydruku może pominąć renderowanie systemu, wywołując SetSkipSystemRendering w PrintWorkflowJobStartingEventArgs. Jeśli zostanie wybrane renderowanie systemu pomijania, system wydruku nie przekonwertuje dokumentu XPS na format PDL wymagany przez drukarkę. Zamiast tego, XPS wygenerowany przez aplikację drukującą zostanie bezpośrednio przekazany do PSA, który następnie odpowiada za konwertowanie XPS na format PDL.
  2. PdlModificationRequested

    • To zdarzenie jest zgłaszane, gdy system Windows uruchamia konwersję strumienia XPS do formatu PDL wskazanego przez drukarkę. Klasa środowiska uruchomieniowego PrintWorkflowPdlModificationRequestedEventArgs jest podawana jako argument dla tego zdarzenia. Ta klasa zdarzeń udostępnia obiekty źródłowe i docelowe PDL do odczytywania i zapisywania zawartości zadania drukowania. Jeśli aplikacja ustali, że potrzebuje danych wejściowych użytkownika, może uruchomić interfejs użytkownika przy użyciu PrintWorkflowUILauncher z usługi EventArgs. Ten interfejs API używa wzorca Tester-Doer. Funkcja PrintWorkflowUILauncher nie będzie mogła wywołać interfejsu użytkownika, jeśli funkcja IsUILaunchEnabled zwraca wartość false. Ta funkcja zwraca wartość false, jeśli sesja PSA jest uruchomiona w trybie dyskretnym (tryb bezgłowy lub kiosk). Aplikacja obsługa wydruku nie powinna próbować uruchamiać interfejsu użytkownika, jeśli funkcja zwraca wartość false.

    OutputStream jest dostępny w ramach PrintWorkflowPdlTargetStream zwracanego przez funkcję GetStreamTargetAsync. Zawartość zapisana w docelowym strumieniu wyjściowym jest przekazywana do drukarki jako zawartość dokumentu.

Diagram sekwencji zdarzenia modyfikacji PDL:

diagram sekwencji dla zdarzenia modyfikacji P D L strumienia źródłowego

Aplikacja PSA na pierwszym planie jest uruchamiana, gdy zadanie w tle PSA żąda uruchomienia interfejsu użytkownika. Psa może użyć kontraktu pierwszego planu, aby uzyskać dane wejściowe użytkownika i/lub wyświetlić podgląd wydruku dla użytkownika.

Zdefiniowano nowy typ zadania w tle printSupportWorkflow. Plik Package.appxmanifest ma następujący wpis rozszerzalności dla kontraktu PrintSupportWorkflow:

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

Po aktywacji kontraktu PrintWorkflowJobTriggerDetails jest podawana jako IBackgroundTaskInstance—>TriggerDetails. PrintWorkflowJobTriggerDetails wewnętrznie udostępnia PrintWorkflowJobBackgroundSession jako część swoich właściwości. Aplikacja może używać PrintWorkflowJobBackgroundSession do rejestrowania zdarzeń związanych z różnymi punktami iniekcji w przepływie pracy zadania drukowania. Po zakończeniu rejestracji zdarzenia aplikacja musi wywołać PrintWorkflowJobBackgroundSession::Uruchom dla systemu wydruku, aby rozpocząć zdarzenia związane z różnymi punktami iniekcji.

Zdefiniowano nową ActivationKind o nazwie PrintSupportJobUI. Nie wymaga to nowej możliwości.

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

Jest to kontrakt interfejsu użytkownika, który można uruchomić z kontraktu w tle przepływu pracy obsługi wydruku lub gdy użytkownik wybierze powiadomienie o błędzie zadania drukowania. Po aktywacji dostępny jest obiekt PrintWorkflowJobActivatedEventArgs, który zawiera PrintWorkflowJobUISession. Korzystając z PrintWorkflowJobUISession, aplikacja w pierwszym planie powinna zarejestrować się do zdarzenia PdlDataAvailable, jeśli chce uzyskać dostęp do danych PDL. Jeśli aplikacja pierwszego planu chce wyświetlić niestandardowe komunikaty o błędach dla wszelkich błędów, które mogą wystąpić podczas zadania, powinna zarejestrować się dla zdarzenia JobNotification. Po zarejestrowaniu zdarzeń aplikacja powinna wywołać funkcję PrintWorkflowJobUISession::Start, aby system drukowania zaczął uruchamiać zdarzenia.

Pomijanie renderowania systemu

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

Zdarzenie modyfikacji PDL

Diagram sekwencji zdarzenia modyfikacji PDL:

diagram sekwencji dla zdarzenia modyfikacji P D L strumienia wejściowego

Przykładowy kod języka C# dla monitora zadań wsparcia drukowania do czytania i zapisywania zawartości zadania drukowania:

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

Uruchamianie interfejsu użytkownika z poziomu tła przepływu pracy

Przykładowy kod w C# do uruchamiania interfejsu użytkownika zadania drukowania po wywołaniu zdarzenia żądania modyfikacji PDL w ramach kontraktu PSA.

private async void OnPdlModificationRequested(PrintWorkflowJobBackgroundSession session, PrintWorkflowPdlModificationRequestedEventArgs args)
{
    IInputStream pdlContent = args.SourceContent.GetInputStream();
    WorkflowPrintTicket printTicket = args.PrinterJob.GetJobPrintTicket();

    bool uiRequired = this.IsUIRequired(pdlContent, printTicket);
    if (!uiRequired)
    {
        // Specify the Content type of content that will be written to target that is passed to printer accordingly.
        PrintWorkflowPdlTargetStream streamTarget = args.CreateJobOnPrinter (args.SourceStream.ContentType);
        // Process content directly if UI is not required
        this.ProcessContent(pdlContent, streamTarget);
    }
    else if (args.UILauncher.IsUILaunchEnabled())
    {
        // LaunchAndCompleteUIAsync will launch the UI and wait for it to complete before returning 
        PrintWorkflowUICompletionStatus status = await args.UILauncher.LaunchAndCompleteUIAsync();
        if (status == PrintWorkflowUICompletionStatus.Completed)
        {
            PrintWorkflowPdlTargetStream streamTarget = args.CreateJobOnPrinter(args.SourceStream.ContentType);
            this.ProcessContent(pdlContent, streamTarget);
        }
        else
        {
            if (status == PrintWorkflowUICompletionStatus.UserCanceled)
            {
                // Log user cancellation and cleanup here.
                this.taskDeferral.Complete();
            }
            else
            {
                // UI launch failed, abort print job.
                args.Configuration.AbortPrintFlow(PrintWorkflowAbortReason.JobFailed);
                this.taskDeferral.Complete();
            }
        }
    }
    else
    {
        // PSA requires to show UI, but launching UI is not supported at this point because of user selection.
        args.Configuration.AbortPrintFlow(PrintWorkflowAbortReason.JobFailed);
        this.taskDeferral.Complete();
    }
}

Aktywowanie interfejsu użytkownika dla przepływu pracy przy zdarzeniu PDLDataAvailable.

Diagram sekwencji aktywacji interfejsu użytkownika dla zdarzenia związanego z zadaniem drukowania PdlDataAvailable:

diagram sekwencji aktywacji UI dla zdarzenia dostępności danych PDL w zadaniu drukowania

Przykładowy kod języka C# dla kontraktu aktywacji interfejsu użytkownika zadania PSA:

namespace PsaSampleApp
{
    sealed partial class App : Application
    {
        protected override void OnActivated(IActivatedEventArgs args)
        {
            if (args.Kind == ActivationKind.PrintSupportJobUI)
            {
                var rootFrame = new Frame();
        
                rootFrame.Navigate(typeof(JobUIPage));
                Window.Current.Content = rootFrame;
        
                var jobUI = rootFrame.Content as JobUIPage;

                // Get the activation arguments
                var workflowJobUIEventArgs = args as PrintWorkflowJobActivatedEventArgs;

                PrintWorkflowJobUISession session = workflowJobUIEventArgs.Session;
                session.PdlDataAvailable += jobUI.OnPdlDataAvailable;
                session.JobNotification += jobUI.OnJobNotification;
                // Start firing events
                session.Start(); 
            }
        }
    }
}

namespace PsaSampleApp
{
    public sealed partial class JobUIPage : Page    
    {
        public JobUIPage()
        {
            this.InitializeComponent();
        }

        public string WorkflowHeadingLabel;

        public void OnPdlDataAvailable(PrintWorkflowJobUISession session, PrintWorkflowPdlDataAvailableEventArgs args)
        {
            using (args.GetDeferral())
            {
                string jobTitle = args.Configuration.JobTitle;
                string sourceApplicationName = args.Configuration.SourceAppDisplayName;            
                string printerName = args.Printer.PrinterName;
                this.WorkflowHeadingLabel = string.Format(this.formatHeading, jobTitle, sourceApplicationName, printerName);

                // Get pdl stream and content type
                IInputStream pdlContent = args.SourceContent.GetInputStream();
                string contentType = args.SourceContent.ContentType;
                this.ShowPrintPreview(pdlContent, contentType);
            }
        }
    }
}

Pobieranie atrybutów zadania drukarki

Przykładowy kod języka C# do pobierania atrybutów zadania drukowania:

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

Ustawianie atrybutów zadania drukarki

Przykładowy kod w C#, kontynuując od sekcji Pobierz atrybuty zadania drukarki powyżej, pokazujący, jak ustawić atrybuty zadania.

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

Niektóre drukarki IPP nie obsługują funkcji pobierania i ustawiania atrybutów zadań po ich utworzeniu. W przypadku tych drukarek PrintJob ma właściwość JobId ustawioną na "0", i GetJobAttributes/SetJobAttributes zakończy się natychmiast niepowodzeniem z powodu wyjątku.

Zapewnianie dostępu do plików magazynujących zawartość PDL

Niektóre formaty PDL, takie jak PDF, wymagają pełnego strumienia, aby rozpocząć przetwarzanie. Z tego powodu nowa metoda o nazwie GetContentFileAsync jest dostarczana w klasie PrintWorkflowPdlSourceContent, która zwraca StorageFile dla zawartości źródłowej.

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

Konwersja PDL XPS na PDF

Przykładowy kod języka C# przedstawiający konwersję formatu PDL z XPS na 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();
}

Zdarzenie związane z powiadomieniem o zadaniu

Diagram sekwencji zdarzenia powiadomienia o zadaniu:

diagram sekwencji dla zdarzenia powiadomienia zadania

Przykładowy kod języka C#, kontynuujący aktywację interfejsu użytkownika zadania przepływu pracy dla sekcji zdarzenia PDLDataAvailable powyżej, w celu pokazania błędu w powiadomieniu o zadaniu.

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

Tworzenie zadania z atrybutami zadania początkowego

Obecnie niektóre drukarki IPP nie obsługują operacji ustawiania atrybutów. Funkcja createJobOnPrinterWithAttributes i CreateJobOnPrinterWithAttributesBuffer funkcji PrintWorkflowPdlDataAvailableEventArgs są udostępniane w celu rozwiązania tego problemu. Korzystając z tych interfejsów API, deweloper PSA może dostarczyć atrybuty zadania, które są przekazywane do drukarki, gdy zadanie jest tworzone na drukarce.

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

Sekwencyjne przetwarzanie XPS

Przykładowy kod C++/Winrt na potrzeby sekwencyjnego przetwarzania plików XPS przed zakończeniem buforowania.

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

Lokalizacja nazwy wyświetlanej i integracja interfejsu API przekazywania PDL

Ważny

W tej sekcji opisano funkcje PSA dostępne począwszy od systemu Windows 11 w wersji 22H2.

W tym scenariuszu PSA dostosowuje Możliwości Urządzenia Drukującego (PDC) i udostępnia Zasoby Urządzenia Drukującego (PDR) na potrzeby lokalizacji ciągów.

PSA ustawia również obsługiwane typy zawartości interfejsu API przekazywania PDL (formaty PDL). Jeśli PSA nie subskrybuje zdarzenia lub nie wywołuje jawnie SetSupportedPdlPassthroughContentTypes, przekazywanie PDL jest wyłączone dla drukarek powiązanych z tą aplikacją PSA.

// Event handler called every time PrintSystem updates PDC or BindPrinter is called
 private void OnPdcChanged(PrintSupportExtensionSession session, PrintSupportPrintDeviceCapabilitiesChangedEventArgs args)
{
    using (args.GetDeferral())
    {
        XmlDocument pdc = args.GetCurrentPrintDeviceCapabilities();
        XmlDocument pdr = args.GetCurrentPrintDeviceResources();
        
        // Check current PDC and make changes according to printer device capabilities 
        XmlDocument newPdc = this.CheckAndUpdatePrintDeviceCapabilities(pdc);
        // Get updated printer devices resources, corresponding to the new PDC 
        XmlDocument newPdr = this.GetPrintDeviceResourcesInfo(newPdc, pdr, args.ResourceLanguage);

        // Update supported PDL formats 
        args.SetSupportedPdlPassthroughContentTypes(GetSupportedPdlContentTypes());
        
        args.UpdatePrintDeviceCapabilities(newPdc);
        args.UpdatePrintDeviceResources(newPdr);
    }
}

Obsługa funkcji na poziomie strony i atrybuty operacji

Ważny

W tej sekcji opisano funkcje PSA dostępne począwszy od systemu Windows 11 w wersji 22H2.

Scenariusze obsługi funkcji na poziomie strony i atrybutów operacji są grupowane, ponieważ zostały one rozwiązane przez wprowadzenie zmian w tym samym miejscu w przykładowym kodzie.

  • obsługa funkcji na poziomie strony: W tym scenariuszu aplikacja PSA określa atrybut poziomu strony, który nie powinien być zastępowany przez atrybut IPP wywodzący się z PrintTicket.

  • Oddzielna kolekcja na potrzeby obsługi atrybutów operacji (drukowanie numeru PIN): W tym scenariuszu aplikacja PSA określa niestandardowe atrybuty operacji IPP (na przykład numer PIN).

Poniższy przykładowy kod w języku C# pokazuje wymagane zmiany dotyczące obsługi funkcjonalności scenariuszy na poziomie strony oraz oddzielnej kolekcji dla atrybutów operacji.

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

Ulepszanie okna dialogowego drukowania z PSA

Ważny

W tej sekcji opisano funkcje PSA dostępne począwszy od systemu Windows 11 w wersji 22H2.

W tym scenariuszu użycie okna dialogowego drukowania z integracją PSA umożliwia wykonanie następujących akcji:

  • Otrzymuj wywołanie zwrotne po zmianie zaznaczenia w MPD na drukarkę skojarzoną z PSA

  • Pokaż jedną kartę Adaptacyjną z obsługą akcji openUrl

  • Wyświetlanie niestandardowych funkcji i parametrów w oknie dialogowym drukowania

  • Zmodyfikuj obiekt PrintTicket, zmieniając więc wybór opcji funkcji wyświetlanych w oknie dialogowym drukowania

  • Pobierz informacje o aplikacji Windows.ApplicationModel.AppInfo do drukowania, otwierając okno dialogowe drukowania.

Poniższy przykład w języku C# ilustruje następujące ulepszenia okna dialogowego drukowania:

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

Konwersja PDL z flagami przetwarzania opartego na hoście

Ważny

W tej sekcji opisano funkcje PSA dostępne począwszy od systemu Windows 11 w wersji 22H2.

Bieżący interfejs API konwersji PDL, PrintWorkflowPdlConverter.ConvertPdlAsync, domyślnie wykonuje przetwarzanie oparte na hoście. Oznacza to, że host/komputer drukujący wykonuje rotację, kolejność stron itd., dzięki czemu drukarka nie musi wykonywać tych operacji. Jednak niezależni producenci sprzętu drukarek mogą chcieć konwersji PDL bez przetwarzania opartego na hoście, ponieważ ich drukarki mogą to zrobić lepiej. Funkcja ConvertPdlAsync przyjmuje flagi przetwarzania oparte na hoście w celu spełnienia tego wymagania. P.S.A. może za pomocą tej flagi pominąć wszystkie operacje przetwarzania oparte na hoście lub określoną operację takiego przetwarzania.

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
}

Ustawianie zasad aktualizacji funkcji urządzenia drukowania (PDC)

Ważny

W tej sekcji opisano funkcje PSA dostępne począwszy od systemu Windows 11 w wersji 22H2.

Dostawcy sprzętu drukarek mogą mieć różne wymagania dotyczące aktualizacji zaawansowanych możliwości drukarki (PDC). Aby spełnić te wymagania, można ustawić zasady aktualizacji dla kontrolera PDC za pomocą PrintSupportPrintDeviceCapabilitiesUpdatePolicy. PSA może ustawić politykę aktualizacji PDC na podstawie czasu lub liczby zadań drukowania przy użyciu tego interfejsu API.

Ustaw politykę aktualizacji PDC na podstawie liczby zadań

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

Ustaw zasady aktualizacji PDC w zależności od czasu limitu

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

Ogólne wskazówki dotyczące projektowania aplikacji do obsługi wydruku (PSA)

Podczas projektowania aplikacji do obsługi wydruku ważne jest, aby uwzględnić te aspekty w projekcie:

  • Oba kontrakty pierwszego planu i w tle powinny być oznaczone jako obsługujące wiele wystąpień, na przykład SupportsMultipleInstance powinny być obecne w manifeście pakietu. Ma to na celu zapewnienie niezawodnego zarządzania okresem istnienia kontraktów w przypadku wielu równoczesnych zadań.

  • Traktuj uruchamianie interfejsu użytkownika dla modyfikacji PDL jako krok opcjonalny. Wykonaj najlepsze wysiłki, aby pomyślnie wykonać zadanie drukowania, nawet jeśli uruchomienie interfejsu użytkownika nie było dozwolone. Zadania drukowania powinny zostać przerwane tylko wtedy, gdy nie ma możliwości ich pomyślnego ukończenia bez wprowadzania danych przez użytkownika podczas modyfikacji języka PDL. Rozważ wysłanie pliku PDL niezmodyfikowanego w takich przypadkach.

  • Podczas uruchamiania PDL do modyfikacji interfejsu użytkownika, wywołaj IsUILaunchEnabled przed wywołaniem LaunchAndCompleteUIAsync. Ma to na celu upewnienie się, że scenariusze, w których nie można wyświetlić interfejsu użytkownika w bieżącym czasie, będą nadal prawidłowo drukować. Te scenariusze mogą występować na urządzeniu bez interfejsu lub na urządzeniu, które jest obecnie w trybie kiosku albo w trybie nie przeszkadzania.

koniec planu obsługi sterowników drukarek innych firm w systemie Windows

specyfikacja protokołu IPP (Internet Printing Protocol)

Print — skojarzenie aplikacji

Windows.Urządzenia.Drukarki

Windows.Graphics.Printing.PrintSupport

Windows.Graphics.Printing.Workflow