Поделиться через


Руководство по проектированию приложений для печати

В этой статье приведены рекомендации и примеры для изготовителей оборудования принтеров и IHV для разработки приложения поддержки печати (PSA), которое может улучшить взаимодействие с печатью пользователя Windows несколькими способами.

Важно!

Начиная с выпуска пакета SDK для Windows 11 (22000.1), приложения поддержки печати (PSA) — это рекомендуемый метод разработки приложений UWP для принтеров. Чтобы разработать приложение поддержки печати для устройства печати, скачайте и установите пакет SDK для Windows 11 для целевой версии Windows.

Важно!

В этом разделе содержатся разделы, описывающие функции PSA, доступные начиная с Windows 11 версии 22H2. Эти разделы содержат заметку, указывающую, что она применяется к этой версии.

Некоторые функции принтера не отображаются в диалоговых окнах печати Windows, так как они являются специальными функциями, которые нуждаются в правильной настройке приложения производителя. Они также могут быть функциями, которые не предоставляются в возможностях принтера по умолчанию.

Специальные функции принтера можно сгруппировать таким образом, чтобы пользователю было проще выбрать параметр и доверять, что все функции, участвующие в этом сценарии, автоматически задаются правильными значениями. Примером этого может быть выбор между рукописным сохранением, сохранением бумаги и режимами высокого качества, которые могут автоматически управлять различными функциями печати на основе одного выбора от пользователя. Windows не может автоматически группировать их так, как это требует понимания всех пользовательских функций каждой модели принтера.

Эта необходимость в отображении пользовательских настроек печати устранена этим API с необязательным контрактом расширения UWP, который можно активировать пользователем из всех диалогов печати Windows и настраиваемых диалогов печати, использующих API, предоставляемый Windows. Производители могут адаптировать свой пользовательский интерфейс, чтобы обеспечить лучший интерфейс печати для конкретного принтера, принадлежащий пользователю.

Другая область, в которой производители принтеров могут улучшить и различать качество печати. Производители могут улучшить качество печати после отрисовки, оптимизируя содержимое для конкретного принтера. Они также могут представить высокую точность предварительного просмотра, который лучше представляет окончательные выходные данные, так как он может принимать во внимание конкретные функции принтера.

print support app print timeline

Терминология

Термин Определение
PSA Приложение поддержки печати. Приложение UWP, использующее API, описанное в этой статье.
MPD Диалоговое окно "Современная печать". Это отображается пользователю при печати приложения с помощью API Windows.Graphics.Printing.
CPD Общее диалоговое окно печати. Это отображается пользователю при печати приложения с помощью API Win32. Приложения, которые должны отображать предварительный просмотр печати, не активируют этот диалог и реализуют версию диалога самостоятельно. Приложение Office являются основным примером этого.
IPP Протокол интернет-печати. Используется с клиентского устройства для взаимодействия с принтером для получения и задания параметров печати и отправки документа для печати.
Связанный принтер поддержки печати Принтер, связанный с PSA.
Принтер IPP Принтер, поддерживающий протокол IPP.
Дополнительные Параметры Ссылка, которая открывает предоставленный партнером пользовательский интерфейс приложения в MPD. По умолчанию открывается встроенный пользовательский интерфейс параметров печати при отсутствии установленной PSA.
Пользовательский интерфейс параметров принтера Диалоговое окно, используемое для задания параметров принтера по умолчанию, применяемых во время печати. Например, ориентация, размер бумаги, цвет, печать на обеих сторонах и т. д.
PDL Язык описания страницы. Формат, в котором документ отправляется принтеру.
Связанный принтер PSA Физический принтер IPP, связанный с приложением PSA.
PrintDeviceCapabilities Формат XML-документа для определения возможностей принтера. Дополнительные сведения см. в разделе "Технологии печати билетов и печати".
PrintTicket Коллекция различных функций печати и их значений, используемых для отслеживания намерения пользователя для заданного задания печати.
PrintSupportExtension Фоновая задача PSA, отвечающая за предоставление возможностей расширения ограничений принтера.

Эти примеры ссылаются на пространство имен printsupport , которое определяется как:

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

Когда пользователь собирался распечатать документ, он часто хотел бы задать некоторые настройки, с помощью которых он будет печататься. Например, они могут выбрать печать документа в альбомной ориентации. Они также могут воспользоваться пользовательскими функциями, поддерживаемыми принтером. Windows предоставляет пользовательский интерфейс по умолчанию для отображения пользовательских настроек, но пользователь может не понимать их, так как нет соответствующих значков или описаний. Windows также может использовать неправильный элемент управления пользовательского интерфейса для презентации. Такая настраиваемая функция лучше всего представлена приложением, которое полностью понимает эту функцию. Это мотивация предложения API, который позволяет производителям принтеров создавать приложения, адаптированные к различным моделям принтера, которые они делают.

Новый контракт расширения UAP создается с новой категорией с именем windows.printSupport Параметры UI. Приложения, активированные с помощью этого контракта, получают новый элемент ActivationKind с именем PrintSupport Параметры UI. Для этого контракта не требуется новая возможность.

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

Этот контракт вызывается при выборе пользователем дополнительных Параметры в MPD или настройках в CPD. Этот контракт также можно вызвать из параметров печати в приложении Параметры. При активации контракта приложение получает объект PrintSupport Параметры UISession, который можно использовать для получения текущих объектов PrintTicket и PrintDevice. Объект PrintDevice можно использовать для взаимодействия с принтером для получения атрибутов принтера и заданий. Затем приложение может отображать пользовательский интерфейс с соответствующими параметрами принтера пользователю. Когда пользователь делает выбор и выбирает ОК, приложение может изменить билет на печать, проверить его, а затем отправить обратно с помощью объекта PrintSupportPrintTicketTarget . Если пользователь решит отменить окно настроек, изменения должны быть отключены карта и приложение должно выйти, завершив отсрочку, взятую из объекта PrintSupport Параметры UISession.

Ожидается, что приложение поддержки печати обрабатывает несколько одновременных активаций для разных заданий печати, поэтому такое приложение должно поддерживать несколько экземпляров с помощью элемента SupportMultipleInstances в файле package.appxmanifest. Сбой этого может привести к ситуациям, когда подтверждение настроек одного задания печати может закрыть другие окна настроек, которые могут быть открыты. Пользователю необходимо снова открыть эти окна параметров.

На следующей схеме последовательности представлена концепция Параметры манипуляции с запросами на печать пользовательского интерфейса:

sequence diagram of settings U I print ticket manipulation

Изменение PrintTicket в пользовательском интерфейсе параметров

Пример кода C# для активации пользовательского интерфейса Параметры при запуске из любого диалогового окна печати (MPD/CPD или пользовательского диалогового окна печати) или из системных параметров:

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 для класса Default Параметры View:

<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# для отображения пользовательского интерфейса и изменения 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();
        }
    }
}

Получение атрибутов принтера с устройства принтера

Ответ WireShark от принтера IPP к запросу get-printer-attributes:

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

Пример кода C# для получения имен рукописного ввода и уровней рукописного ввода на принтере:

namespace PsaSampleApp
{
    /// <summary>
    /// Class for showing print settings to the user
    /// </summary>
    public sealed partial class SettingsView : Page
    { 
       IList<string> inkNames;
       IList<int> inkLevels;
        
        private async void GetPrinterAttributes()
        {
            // Read ink names and levels, along with loaded media-sizes
            var attributes = new List<string>();
            attributes.Add("marker-names");
            attributes.Add("marker-levels");
            attributes.Add("media-col-ready");
            IDictionary<string, IppAttributeValue> printerAttributes = this.printer.GetPrinterAttributes(attributes);

            IppAttributeValue inkNamesValue = printerAttributes["marker-names"];
            CheckValueType(inkNamesValue, IppAttributeValueKind.Keyword);
            this.inkNames = inkNamesValue.GetKeywordArray();
            
            IppAttributeValue inkLevelsValue = printerAttributes["marker-levels"];
            CheckValueType(inkLevelsValue, IppAttributeValueKind.Integer);
            this.inkLevels = inkLevelsValue.GetIntegerArray();
    
            // Read loaded print media sizes
        IppAttributeValue mediaReadyCollectionsValue = printerAttributes["media-col-ready"];
            foreach (var mediaReadyCollection in mediaReadyCollectionsValue.GetCollectionArray())
            {
                IppAttributeValue mediaSizeCollection;
                if (mediaReadyCollection.TryGetValue("media-size", out mediaSizeCollection))
                {
                    var xDimensionValue = mediaSizeCollection.GetCollectionArray().First()["x-dimension"];
                    var yDimensionValue = mediaSizeCollection.GetCollectionArray().First()["y-dimension"];
                    CheckValueType(xDimensionValue, IppAttributeValueKind.Integer);
                    CheckValueType(yDimensionValue, IppAttributeValueKind.Integer);
                    int xDimension = xDimensionValue.GetIntegerArray().First();
                    int yDimension = yDimensionValue.GetIntegerArray().First();
                    this.AddMediaSize(xDimension, yDimension);
                }
            }
        }

        private void CheckValueType(IppAttributeValue value, IppAttributeValueKind expectedKind)
        {
            if (value.Kind != expectedKind)
            {
                throw new Exception(string.Format("Non conformant type found: {0}, expected: {1}", value.Kind, expectedKind));
            }
        }
    }
}

Настройка атрибутов принтера на принтере

Пример кода C# для задания атрибутов принтера:

int defaultResolutionX = 1200;
int defaultResolutionY = 1200;
string pdlFormat = "image/pwg-raster";
private async void SetPrinterAttributes()
{
    var attributes = new Dictionary<string, IppAttributeValue>();
    attributes.Add("document-format-default", IppAttributeValue.CreateKeyword(this.pdlFormat));
    var resolution = new IppResolution(this.defaultResolutionX, this.defaultResolutionY, IppResolutionUnit.DotsPerInch);
    attributes.Add("printer-resolution-default", IppAttributeValue.CreateResolution(resolution));
            
    var result = this.printer.SetPrinterAttributes(attributes);
    if (!result.Succeeded)
    {
        foreach (var attributeError in result.AttributeErrors)
        {
            var attributeName = attributeError.Key;
            switch (attributeError.Value.Reason)
            {
            case IppAttributeErrorReason.AttributeValuesNotSupported:
                var values = attributeError.Value.GetUnsupportedValues().First();
                this.LogUnSupportedValues(attributeName, values);
                break;
            case IppAttributeErrorReason.AttributeNotSettable:
                this.LogAttributeNotSettable(attributeName);
                break;
            case IppAttributeErrorReason.AttributeNotSupported:
                this.LogAttributeNotSupported(attributeName);
                break;
            case IppAttributeErrorReason.RequestEntityTooLarge:
                this.LogAttributeNotEntityTooLarge(attributeName);
                break;
            case IppAttributeErrorReason. ConflictingAttributes:
                this.LogConflictingAttributes(attributeName);
                break;
            }
        }
    }
}

Расширение ограничений принтера

Приложение поддержки печати поддерживает настраиваемую проверку PrintTicket и определение стандартного PrintTicket. В этом разделе описывается, как мы поддерживаем эти функции.

Для поддержки ограничений расширения принтера реализован новый тип фоновой задачи PrintSupportExtension. Package.appxmanifest имеет запись расширяемости для расширения поддержки печати, как показано ниже:

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

Эта служба может выполняться в любой точке задания печати для связанного принтера IPP. Так как расширение поддержки печати активируется с помощью функции Run(IBackgroundTaskInstance taskInstance), экземпляр IBackgroundTaskInstance предоставляется PrintSupportExtension для предоставления доступа к классу среды выполнения PrintSupportExtensionTriggerDetails, который внутренне предоставляет PrintSupportExtensionSession В качестве свойства. Фоновый класс PrintSupportExtension может использовать объект сеанса для регистрации событий, которые он хочет предоставить пользовательские функциональные возможности.

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

    Если расширение поддержки печати предоставляет собственный механизм проверки PrintTicket, он может зарегистрировать это событие. Всякий раз, когда требуется проверить PrintTicket, система печати вызывает это событие. Затем PrintSupportExtension получит текущий PrintTicket, который необходимо проверить в EventArgs. Фоновый класс PrintSupportExtension может проверка PrintTicket для допустимости и изменить его, чтобы устранить любые конфликты. Фоновый класс PrintSupportExtension должен задать результат проверки с помощью функции SetPrintTicketResult, чтобы указать, разрешен ли PrintTicket, имеет ли конфликт или является недопустимым. Это событие можно вызвать в любое время во время выполнения задания печати. Если класс PrintSupportExtension не регистрируется для этого события, система печати выполняет собственную проверку PrintTicket.

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

    Событие возникает после обновления системы печати кэшированных объектов PrintDeviceCapabilities связанного принтера IPP. При возникновении этого события фоновый класс PrintSupportExtension может проверить измененные выражения PrintDeviceCapabilities и изменить его.

Настраиваемая проверка билета на печать

Пример кода C# для предоставления службы проверки 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);
    }
}

Обновление 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);
    }
}

Улучшение качества печати

После того как пользователь обязался печатать, нажав кнопку печати в диалоговом окне печати, документ, который будет напечатан, отправляется в стек печати из приложения, которое печатается. Затем этот документ проходит преобразование (отрисовка в PDL), чтобы сделать его подходящим для целевого принтера. Windows определит, какое преобразование следует выбрать на основе атрибутов, запрашиваемых с принтера. Затем преобразованный документ отправляется на принтер. Хотя это работает хорошо для большинства принтеров, существуют случаи, когда качество печати может быть улучшено, позволяя партнерскому приложению участвовать в преобразовании. Чтобы упростить это, текущий API рабочего процесса печати расширен для включения вызовов приложения в дополнительные точки из стека печати. Этот API поддерживает два новых события, для которых может зарегистрировать приложение PSA. Это единственные точки входа в область API PSA:

  1. JobStarting

    • Это событие возникает при запуске задания печати любым приложением. При возникновении события приложение поддержки печати может пропустить системную отрисовку, вызвав SetSkipSystemRendering в PrintWorkflowJobStartingEventArgs. Если выбрана отрисовка системы пропуска, система печати не преобразует документ XPS в формат PDL, необходимый принтером. Вместо этого XPS, созданные приложением печати, будут непосредственно переданы PSA, которая затем отвечает за преобразование XPS в формат PDL.
  2. PdlModificationRequested

    • Это событие возникает, когда Windows запускает преобразование потока XPS в формат PDL, указанный принтером. Класс Runtime PrintWorkflowPdlModificationRequestedEventArgs предоставляется в качестве аргумента для этого события. Этот класс событий предоставляет исходные и целевые объекты PDL для чтения и записи содержимого задания печати. Если приложение определяет, что ему нужны пользовательские данные, он может запустить пользовательский интерфейс с помощью PrintWorkflowUILauncher из EventArgs. Этот API использует шаблон Tester-Doer. PrintWorkflowUILauncher не сможет вызвать пользовательский интерфейс, если функция IsUILaunchEnabled возвращает значение false. Эта функция возвращает значение false, если сеанс PSA работает в автоматическом режиме (режим без головы или киоска). Приложение поддержки печати не должно пытаться запустить пользовательский интерфейс, если функция возвращает значение false.

    OutputStream доступен в составе PrintWorkflowPdlTargetStream, возвращаемой функцией GetStreamTargetAsync. Содержимое, записанное в целевой выходной поток, передается на принтер в виде содержимого документа.

Схема последовательности для события изменения PDL:

sequence diagram for the source stream P D L modification event

Приложение переднего плана PSA запускается при запросе фоновой задачи PSA к запуску пользовательского интерфейса. PSA может использовать контракт переднего плана, чтобы получить входные данные пользователя и /или показать предварительный просмотр печати пользователю.

Определен новый тип фоновой задачи printSupportWorkflow . Package.appxmanifest имеет следующую запись расширяемости для контракта PrintSupportWorkflow :

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

При активации контракта PrintWorkflowJobTriggerDetails предоставляется как IBackgroundTaskInstance-TriggerDetails>. PrintWorkflowJobTriggerDetails внутренне предоставляет PrintWorkflowJobBackgroundSession как часть его свойств. Приложение может использовать PrintWorkflowJobBackgroundSession для регистрации событий, связанных с различными точками внедрения в рабочем процессе задания печати. После завершения регистрации события приложение должно вызвать PrintWorkflowJobBackgroundSession::Start для системы печати, чтобы начать запуск событий, связанных с различными точками внедрения.

Определен новый элемент ActivationKind с именем PrintSupportJobUI . Для этого не требуется новая возможность.

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

Это контракт пользовательского интерфейса, который можно запустить из фонового контракта рабочего процесса поддержки печати или когда пользователь выбирает всплывающее сообщение об ошибке задания печати. При активации предоставляется printWorkflowJobActivatedEventArgs , который имеет объект PrintWorkflowJobUISession . Используя PrintWorkflowJobUISession, приложение переднего плана должно зарегистрировать для события PdlDataAvailable , если он хочет получить доступ к данным PDL. Если приложение переднего плана хотело бы отобразить пользовательские сообщения об ошибках для любых ошибок, которые могут возникнуть во время задания, он должен зарегистрировать событие JobNotification . После регистрации событий приложение должно вызвать функцию PrintWorkflowJobUISession::Start , чтобы система печати начала выполнения событий.

Пропуск системной отрисовки

namespace PsaBackground
{
    class PrintSupportWorkflowBackgroundTask : IBackgroundTask
    {
        BackgroundTaskDeferral taskDeferral;
        public void Run(IBackgroundTaskInstance taskInstance)
        {
            // Take Task Deferral            
            taskDeferral = taskInstance.GetDeferral();

            var jobTriggerDetails = taskInstance.TriggerDetails as PrintWorkflowJobTriggerDetails;

            var workflowBackgroundSession = jobTriggerDetails.PrintWorkflowJobSession as PrintWorkflowJobBackgroundSession;
            // Register for events
            workflowBackgroundSession.JobStarting += this.OnJobStarting;
            workflowBackgroundSession.PdlModificationRequested += this.OnPdlModificationRequested;
            // Start Firing events
            workflowBackgroundSession.Start();
        }
    
        private void OnJobStarting(PrintWorkflowJobBackgroundSession session, PrintWorkflowJobStartingEventArgs args)
        {
            using (args.GetDeferral())
            {
                // Call SetSkipSystemRendering to skip conversion for XPS to PDL, so that PSA can directly manipulate the XPS file.
                args.SetSkipSystemRendering();
            }
        }
     }
}

Событие изменения PDL

Схема последовательности для события изменения PDL:

sequence diagram for the input stream P D L modification event

Пример кода C# для монитора заданий поддержки печати для чтения и записи содержимого задания печати:

private void OnPdlModificationRequested(PrintWorkflowJobBackgroundSession session, PrintWorkflowPdlModificationRequestedEventArgs args)
{
    using (args.GetDeferral())
    {
        IInputStream pdlContent = args.SourceContent.GetInputStream();
        // Specify the Content type of stream that will be written to target that is passed to printer accordingly.
        PrintWorkflowPdlTargetStream streamTarget = args.CreateJobOnPrinter(args.SourceStream.ContentType);
        IOutputStream outputStream = streamTarget.GetOutputStream();

        using (var inputReader = new Windows.Storage.Streams.DataReader(pdlContent))
        {
            inputReader.InputStreamOptions = InputStreamOptions.Partial;
            using (var outputWriter = new Windows.Storage.Streams.DataWriter(outputStream))
            {
                // Write the updated Print stream from input stream to the output stream
                uint chunkSizeInBytes = 256 * 1024; // 256K chunks
                
                uint lastAllocSize = 0;
                byte[] contentData = new byte[chunkSize];
                while(this.ReadChunk(inputReader, ref contentData))
                {
                    
                    // Make any changes required to the input data
                    // ...                        
                    // Write out the modified content
                    outputWriter.WriteBytes(contentData);
                    await outputWriter.StoreAsync();
                }
            }
        }
        streamTarget.CompleteStreamSubmission(PrintWorkflowSubmittedStatus.Succeeded);
        this.taskDeferral.Complete();
        }
    }
}

Запуск пользовательского интерфейса из фона рабочего процесса

Пример кода C# для запуска пользовательского интерфейса задания поддержки печати из PDL psA PDL запрашивается контракт события:

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

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

Активация пользовательского интерфейса задания рабочего процесса для события PDLDataAvailable

Схема последовательности для активации пользовательского интерфейса задания печати для события PdlDataAvailable :

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

Пример кода C# для контракта активации пользовательского интерфейса задания 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);
            }
        }
    }
}

Получение атрибутов задания принтера

Пример кода C# для получения атрибутов задания печати:

namespace PsaBackground
{
    class PrintSupportWorkflowBackgroundTask : IBackgroundTask
    {
        private async void OnPdlModificationRequested(PrintWorkflowJobBackgroundSession session, 
                             PrintWorkflowPdlModificationRequestedEventArgs args)
        {
            using (args.GetDeferral())
            {
                string colorMode = this.GetJobColorMode(args.PrinterJob);
                if (colorMode != "monochrome")
                {
                    this.SetJobColorModeToMonochrome(args.PrinterJob);
                } 
            }
        }

        private string GetJobColorMode(PrintWorkflowPrinterJob printerJob)
        {
            var attributes = new List<string>();
            attributes.Add("print-color-mode");
             // Gets the IPP attributes from the current print job
            IDictionary<string, IppAttributeValue> printerAttributes = printerJob.GetJobAttributes(attributes);

            var colorModeValue =  printerAttributes["print-color-mode"];
            this.CheckValueType(colorModeValue, IppAttributeValueKind.Keyword);

            return colorModeValue.GetKeywordArray().First();
        }
    }
} 

Задание атрибутов задания принтера

Пример кода C#, продолжающийся из раздела "Получение атрибутов задания принтера" выше, демонстрируя параметры атрибутов задания:

private async void SetJobColorModeToMonochrome(PrintWorkflowPrinterJob printerJob)
{
    var attributes = new Dictionary<string, IppAttributeValue>();
    attributes.Add("print-color-mode", IppAttributeValue.CreateKeyword("monochrome"));

    var result = PrinterJob.SetJobAttributes(attributes);
    if (!result.Succeeded)
    {
        this.LogSetAttributeError(result.AttributeErrors);
    }
}

Некоторые принтеры IPP не поддерживают атрибуты задания получения и настройки после создания задания. Для этих принтеров PrintJob имеет свойство JobId с значением "0", а GetJobAttributes/SetJobAttributes завершится сбоем немедленно с исключением.

Предоставление доступа к содержимому PDL в файле хранилища

Для начала обработки некоторые форматы PDL, такие как PDF, должны быть доступны полный поток. По этой причине новый метод с именем GetContentFileAsync предоставляется в классе PrintWorkflowPdlSourceContent, который возвращает служба хранилища File исходного содержимого.

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 XPS в PDF

Пример кода C# с преобразованием PDL XPS в 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();
}

Событие уведомления о задании

Схема последовательности для события уведомления о задании:

sequence diagram for the job notification event

Пример кода C# , продолжающийся из активации пользовательского интерфейса задания рабочего процесса для PDLDataAvailable выше, чтобы отобразить ошибку при уведомлении о задании:

public sealed partial class JobUIPage : Page    
{
    public void OnJobNotification(PrintWorkflowJobUISession session, PrintWorkflowJobNotificationEventArgs args)
    {
        using (args.GetDeferral())
        {
            PrintWorkflowPrinterJobStatus jobStatus = args.PrintJob.GetJobStatus();

            switch (jobStatus)
            {
                case PrintWorkflowPrinterJobStatus::Error:
                    // Show print job error to the user
                    Frame->Navigate(JobErrorPage::typeid, this);
                break;
                case PrintWorkflowPrinterJobStatus::Abort:
                    // Show message that print job has been aborted.
                    Frame->Navigate(JobAbortPage::typeid, this);
                break;
                case PrintWorkflowPrinterJobStatus::Completed:
                    // Show job successfully completed message to the user.
                    Frame->Navigate(JobCompletedPage::typeid, this);
                break;
            }
        }
    }    
}

Создание задания с начальными атрибутами задания

В настоящее время некоторые принтеры IPP не поддерживают операцию set-attribute. Функция CreateJobOnPrinterWithAttributes и функция CreateJobOnPrinterWithAttributesBuffer в PrintWorkflowPdlDataAvailableEventArgs предоставляются для устранения этой проблемы. С помощью этих API разработчик PSA может предоставлять атрибуты задания, передаваемые принтеру при создании задания на принтере.

public sealed partial class JobUIPage : Page
{
    public async void OnPdlDataAvailable(PrintWorkflowJobUISession session, PrintWorkflowPdlDataAvailableEventArgs args)
    {
       var attributes = new Dictionary<string, IppAttributeValue>();
       attributes.Add("print-color-mode", IppAttributeValue.CreateKeyword("monochrome"));
       // Create job on printer with initial job attributes
       PrintWorkflowPdlTargetStream streamTarget = args.CreateJobOnPrinterWithAttributes(attributes, "application/pdf");
        // Write data to target stream
    }
}

Последовательная обработка XPS

Пример кода C++/Winrt для обработки XPS последовательно перед завершением spooling.

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

Локализация отображаемого имени и интеграция API сквозного руководства PDL

Важно!

В этом разделе описаны функции PSA, доступные начиная с Windows 11 версии 22H2.

В этом сценарии PSA настраивает возможности устройства печати (PDC) и предоставляет ресурсы устройства печати (PDR) для локализации строк.

PSA также задает поддерживаемые типы содержимого API PDL (форматы PDL). Если PSA не подписывается на событие или не вызывает SetSupportedPdlPassthroughContentTypes явным образом, PDL Passthrough отключен для принтеров, связанных с этим приложением 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);
    }
}

Поддержка функций на уровне страницы и атрибуты операций

Важно!

В этом разделе описаны функции PSA, доступные начиная с Windows 11 версии 22H2.

Сценарии поддержки функций уровня страницы и атрибуты операций группируются, так как они рассматриваются путем внесения изменений в то же место в примере кода.

  • Поддержка функций уровня страницы. В этом сценарии приложение PSA указывает атрибут уровня страницы, который не должен быть переопределен атрибутом IPP, проанализированным из PrintTicket.

  • Отдельная коллекция для поддержки атрибутов операций (печать ПИН-кода). В этом сценарии приложение PSA задает настраиваемые атрибуты операции IPP (например, ПИН-код).

В следующем примере кода C# показаны необходимые изменения для поддержки функций уровня страницы и отдельная коллекция для сценариев атрибутов операций.

private void OnPdlModificationRequested(PrintWorkflowJobBackgroundSession session, PrintWorkflowPdlModificationRequestedEventArgs args)
{
    using (args.GetDeferral())
    {
        IInputStream pdlContent = args.SourceContent.GetInputStream();
    
        // Custom job attributes to add to the printJob
        IDictionary<string, IppAttributeValue> jobAttributes = LocalStorageUtil.GetCustomIppJobAttributes();
        // Custom operation attributes to add to printJob
        IDictionary<string, IppAttributeValue> operationAttributes = LocalStorageUtil.GetCustomIppOperationAttributes();
        
        // PSA has an option to select preferred PDL format
        string documentFormat = GetDocumentFormat(args.PrinterJob.Printer);
    
        // Create PrintJob with specified PDL and custom attributes
        PrintWorkflowPdlTargetStream targetStream = args.CreateJobOnPrinterWithAttributes(jobAttributes, documentFormat  , operationAttributes,
           PrintWorkflowAttributesMergePolicy  .DoNotMergeWithPrintTicket /*jobAttributesMergePolicy*/, PrintWorkflowAttributesMergePolicy.MergePreferPsaOnConflict /*operationAttributesMergePolicy*/);
    
        // Adding a watermark to the output(targetStream) if source payload type is XPS
        this.ModifyPayloadIfNeeded(targetStream, args, documentFormat, deferral);
    
        // Marking the stream submission as Succeeded.
        targetStream.CompleteStreamSubmission(PrintWorkflowSubmittedStatus.Succeeded);
    
        this.taskDeferral.Complete();
    }
}

Повышение диалогового окна печати с помощью PSA

Важно!

В этом разделе описаны функции PSA, доступные начиная с Windows 11 версии 22H2.

В этом сценарии использование диалогового окна печати с интеграцией PSA позволяет выполнять следующие действия:

  • Получение обратного вызова при изменении выделения в MPD на принтер, связанный с PSA

  • Показать один Адаптивный знак с поддержкой действия openUrl

  • Отображение настраиваемых функций и параметров в диалоговом окне печати

  • Измените printTicket, изменив выделение параметров компонентов, отображаемых в диалоговом окне печати

  • Получите Windows.ApplicationModel.AppInfo приложения печати, открыв диалоговое окно печати

В следующем примере C# показаны следующие улучшения диалогового окна печати:

public BackgroundTaskDeferral TaskInstanceDeferral { get; set; }

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

    if (taskInstance.TriggerDetails is PrintSupportExtensionTriggerDetails extensionDetails)
    {
         PrintSupportExtensionSession session = extensionDetails.Session;
         session.PrintTicketValidationRequested += OnSessionPrintTicketValidationRequested;
         session.PrintDeviceCapabilitiesChanged += OnSessionPrintDeviceCapabilitiesChanged;
         session.PrinterSelected += this.OnPrinterSelected;
    }
}

private void OnTaskInstanceCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
{
    TaskInstanceDeferral.Complete();
}

// Event handler called when the PSA Associated printer is selected in Print Dialog
private void OnPrinterSelected(PrintSupportExtensionSession session, PrintSupportPrinterSelectedEventArgs args)
{
    using (args.GetDeferral())
    {
        // Show adaptive card in the Print Dialog (generated based on Printer and Printing App) 
        args.SetAdaptiveCard  (GetCustomAdaptiveCard(session.Printer, args.SourceAppInfo));

        // Request to show Features and Parameters in the Print Dialog if not shown already
        const string xmlNamespace = "\"http://schemas.microsoft.com/windows/2003/08/printing/printschemakeywords\"";
        var additionalFeatures= new List<PrintSupportPrintTicketElement> { new PrintSupportPrintTicketElement { LocalName = "PageMediaType", NamespaceUri = xmlNamespace } };                  
        var additionalParameters = new List<PrintSupportPrintTicketElement> { new PrintSupportPrintTicketElement { LocalName = "JobCopiesAllDocuments", NamespaceUri = xmlNamespace } };

        if ((featuresToShow.Count + parametersToShow.Count) <= args.AllowedCustomFeaturesAndParametersCount)
        {
            args.SetAdditionalFeatures(additionalFeatures);
            args.SetAdditionalParameter(additionalParameters);
        }
        else
        {
            // Cannot show that many additional features and parameters, consider reducing the number
            // of additional features and parameters by selecting only the most important ones
        }
    }
}

// Create simple AdaptiveCard to show in MPD
public IAdaptiveCard GetCustomAdaptiveCard(IppPrintDevice ippPrinter, AppInfo appInfo)
{
    return AdaptiveCardBuilder.CreateAdaptiveCardFromJson($@"
        {{""body"": [
                {{ 
                    ""type"": ""TextBlock"",
                    ""text"": ""Hello {appInfo.DisplayInfo.DisplayName} from {ippPrinter.PrinterName}!""
                }}
              ],
              ""$schema"": ""http://adaptivecards.io/schemas/adaptive-card.json"",
            ""type"": ""AdaptiveCard"",
            ""version"": ""1.0""
        }}");
}

Преобразование PDL с флагами обработки на основе узла

Важно!

В этом разделе описаны функции PSA, доступные начиная с Windows 11 версии 22H2.

Текущий API преобразования PDL, PrintWorkflowPdlConverter.ConvertPdlAsync, выполняет обработку на основе узлов по умолчанию. Это означает, что компьютер узла или печати выполняет поворот, порядок страниц и т. д., чтобы принтер не выполнял эти операции. Однако IHV принтера может потребовать преобразования PDL без обработки на основе узла, так как их принтер может сделать это лучше. Функция ConvertPdlAsync принимает флаги обработки на основе узла, чтобы устранить это требование. PSA может пропустить всю обработку на основе узла или определенную операцию обработки на основе узла с помощью этого флага.

class HostBaseProcessingRequirements
{
    public bool CopiesNeedsHostBasedProcessing = false;
    public bool PageOrderingNeedsHostBasedProcessing = false;
    public bool PageRotationNeedsHostBasedProcessing = false;
    public bool BlankPageInsertionNeedsHostBasedProcessing = false;
}

private async void OnPdlModificationRequested(PrintWorkflowJobBackgroundSession sender, PrintWorkflowPdlModificationRequestedEventArgs args)
{
    using (args.GetDeferral())
    {
        var targetStream = args.CreateJobOnPrinter("application/pdf");
        var pdlConverter = args.GetPdlConverter(PrintWorkflowPdlConversionType.XpsToPdf);

        var hostBasedRequirements = this.ReadHostBasedProcessingRequirements(args.PrinterJob.Printer);
            
        PdlConversionHostBasedProcessingOperations hostBasedProcessing = PdlConversionHostBasedProcessingOperations.None;
        if (hostBasedRequirements.CopiesNeedsHostBasedProcessing)
        {
            hostBasedProcessing |= PdlConversionHostBasedProcessingOperations.Copies;
        }

        if (hostBasedRequirements.PageOrderingNeedsHostBasedProcessing)
        {
            hostBasedProcessing |= PdlConversionHostBasedProcessingOperations.PageOrdering;
        }

        if (hostBasedRequirements.PageRotationNeedsHostBasedProcessing)
        {
            hostBasedProcessing |= PdlConversionHostBasedProcessingOperations.PageRotation;
        }

        if (hostBasedRequirements.BlankPageInsertionNeedsHostBasedProcessing)
        {
            hostBasedProcessing |= PdlConversionHostBasedProcessingOperations.BlankPageInsertion;
        }

        await pdlConverter.ConvertPdlAsync(args.PrinterJob.GetJobPrintTicket(), args.SourceContent.GetInputStream(), targetStream.GetOutputStream(), hostBasedProcessing);
    }
}

private HostBaseProcessingRequirements ReadHostBasedProcessingRequirements(IppPrintDevice printDevice)
{
    // Read Host based processing requirements for the printer
}

Настройка политики обновления возможностей устройства печати (PDC)

Важно!

В этом разделе описаны функции PSA, доступные начиная с Windows 11 версии 22H2.

IHV принтера могут иметь разные требования при обновлении возможностей устройства печати (PDC). Чтобы устранить эти требования, PrintSupportPrintDeviceCapabilitiesUpdatePolicy может задать политику обновления для PDC. PSA может задать политику обновления PDC в зависимости от времени или количества заданий печати с помощью этого API.

Настройка политики обновления PDC на основе количества заданий

// Event handler called every time PrintSystem updates PDC
private void OnPdcChanged(PrintSupportExtensionSession session, PrintSupportPrintDeviceCapabilitiesChangedEventArgs args)
{
    using (args.GetDeferral())
    {
        // Set update policy to update the PDC on bind printer of every print job.
        var updatePolicy = PrintSupportPrintDeviceCapabilitiesUpdatePolicy.CreatePrintJobRefresh(1);
        args.SetPrintDeviceCapabilitiesUpdatePolicy(updatePolicy);      
    }
}

Настройка политики обновления PDC на основе 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);      
    }
}

Руководство по проектированию приложения поддержки печати (PSA)

При разработке приложения поддержки печати важно включить в проект следующие аспекты:

  • Контракты переднего плана и фона должны быть помечены как поддерживающие несколько экземпляров, например SupportsMultipleInstance , должны присутствовать в манифесте пакета. Это позволяет обеспечить надежное управление временем существования контрактов для нескольких одновременных заданий.

  • Рассматривайте пользовательский интерфейс запуска для изменения PDL как необязательный шаг. Сделайте все возможное, чтобы выполнить задание печати успешно, даже если запуск пользовательского интерфейса не разрешен. Задания печати должны быть прерваны только в том случае, если их невозможно успешно завершить без ввода пользователем во время изменения PDL. Рассмотрите возможность отправки PDL, не измененного в таких случаях.

  • При запуске пользовательского интерфейса для изменения PDL вызовите IsUILaunchEnabled перед вызовом LaunchAndCompleteUIAsync. Это гарантирует, что сценарии, которые не могут отображать пользовательский интерфейс в текущее время, продолжают печатать правильно. Эти сценарии могут быть на безголовом устройстве или устройстве, которое в настоящее время находится в режиме киоска или не беспокоить режим.

Сопоставление приложений поддержки печати

Windows.Devices.Printers

Windows.Graphics.Printing.PrintSupport

Windows.Graphics.Printing.Workflow

Спецификация протокола интернет-печати (IPP)