印刷サポート アプリの設計ガイド

この記事では、Windows ユーザーの印刷エクスペリエンスをいくつかの方法で強化できる印刷サポート アプリ (PSA) を、プリンターの OEM と IHV が開発するためのガイダンスについて説明し、例を示します。

重要

Windows 11 SDK (22000.1) のリリース以降、プリンター用の UWP アプリ開発に推奨される方法は印刷サポート アプリ (PSA) です。 印刷デバイス用の印刷サポート アプリを開発するには、対象の Windows バージョン用の Windows 11 SDK をダウンロードしてインストールします。

重要

このトピックには、Windows 11 バージョン 22H2 以降で使用できる PSA 機能について説明するセクションが含まれています。 該当するセクションには、そのバージョンに適用されることを示すメモが含まれています。

プリンター機能の中には、Windows で表示される印刷ダイアログに表示されないものがあります。そうした機能は、正く構成するには製造元アプリの助けが必要な特殊な機能だからです。 プリンターの既定の機能では提供されない機能である場合もあります。

プリンター固有の機能は、ユーザーが簡単にオプションを選択でき、そのシナリオに関わるすべての機能が自動的に正しい値に設定されると信じられる方法でグループ化できます。 この例としては、インクセーバー、用紙セーバー、最高品質のモードの中から、ユーザーの 1 つの選択に基づいてさまざまな印刷機能を自動的に操作できるものを選ぶことが挙げられます。 Windows では、それらを自動的にグループ化することはできません。そのためには、すべてのプリンター モデルのすべてのカスタム機能を理解する必要があるからです。

このカスタム印刷設定を表示する必要性は、Windows で提供される API を使用する、Windows のすべての印刷ダイアログとカスタム印刷ダイアログからユーザーが達成できるオプションの UWP 拡張コントラクトを使用して、この API で対処されます。 製造元は、ユーザーが所有する特定のプリンターに最適な印刷エクスペリエンスを提供するように UI をカスタマイズできます。

プリンター製造元が改善して差別化できるもう 1 つの領域は、印刷品質です。 製造元は、特定のプリンターのコンテンツを最適化して、レンダリング後の印刷品質を高めることができます。 プリンター固有の機能が考慮される可能性があるため、最終的な出力をより適切に表す再現性の高いプレビューを提示することもできます。

print support app print timeline

用語

相談 定義
PSA 印刷サポート アプリケーション。 この記事で説明する API を使用する UWP アプリ。
MPD モダン印刷ダイアログ。 これは、アプリが Windows.Graphics.Printing API を使用して印刷するときにユーザーに表示されます。
CPD 一般的な印刷ダイアログ。 これは、アプリが Win32 API を使用して印刷するときにユーザーに表示されます。 印刷プレビューを表示する必要があるアプリでは、このダイアログはトリガーされず、ダイアログ自体のバージョンが実装されます。 Office アプリは、この最たる例です。
IPP インターネット印刷プロトコル。 クライアント デバイスからプリンターを操作して印刷設定を取得および設定し、印刷するドキュメントを送信するために使用します。
印刷サポートが関連付けられているプリンター PSA にリンクされているプリンター。
IPP プリンター IPP プロトコルをサポートするプリンター。
その他の設定 パートナーが提供するアプリ UI を MPD で開くリンク。 PSA がインストールされていない場合、既定では組み込みの印刷設定 UI が開きます。
プリンター設定 UI 印刷時に適用される既定のプリンター オプションを設定するために使用されるダイアログ。 たとえば、向き、用紙サイズ、色、両面印刷など。
PDL データ記述言語。 プリンターに送信される際の文書の形式。
関連付けされた PSA プリンター PSA アプリケーションに関連付けられている物理 IPP プリンター。
PrintDeviceCapabilities プリンター機能を定義するための XML ドキュメント形式。 詳細については、「印刷チケットと印刷機能のテクノロジ」を参照してください。
PrintTicket 指定された印刷ジョブに対するユーザーの意図をキャプチャするために使用される、さまざまな印刷関連機能とその値のコレクション。
PrintSupportExtension プリンター制約拡張機能を提供する PSA バックグラウンド タスク。

これらのサンプルは、次のように定義されている printsupport 名前空間を参照します。

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

ユーザーは文書を印刷しようとするとき、印刷用の設定を設定したいと思うことがよくあります。 たとえば、文書を横向きで印刷することを選択する場合があります。 プリンターでサポートされているカスタム機能を利用する場合もあります。 Windows にはカスタム設定を表示する既定の UI が用意されていますが、適切なアイコンや説明がないため、ユーザーはそれらを理解できないかもしれません。 Windows は、間違った UI コントロールを使用して表示している可能性もあります。 このようなカスタム機能を最適表示できるのは、その機能を完全に理解しているアプリです。 これは、プリンターの製造元が、自社が作成するさまざまなプリンター モデルに合わせてカスタマイズしたアプリを作成できる API を提供する動機です。

新しい UAP 拡張コントラクトは、windows.printSupportSettingsUI という名前の新しいカテゴリを使用して作成されます。 このコントラクトでアクティブ化されたアプリは、PrintSupportSettingsUI という新しい ActivationKind を受け取ります。 このコントラクトには、新しい機能は必要ありません。

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

このコントラクトは、ユーザーが MPD の [その他の設定] または CPD の[設定] を選択すると呼び出されます。 このコントラクトは、設定アプリの [印刷設定] から呼び出すこともできます。 コントラクトがアクティブ化されると、アプリは現在の PrintTicket オブジェクトと PrintDevice オブジェクトを取得するために使用できる PrintSupportSettingsUISession オブジェクトを受け取ります。 PrintDevice オブジェクトを使用してプリンターと通信し、プリンターとジョブの属性を受け取ることができます。 その後、アプリは、プリンターの適切なオプションと UI をユーザーに表示できます。 ユーザーが選択を行い、[OK] を選択すると、アプリケーションは印刷チケットを変更し検証してから、PrintSupportPrintTicketTarget オブジェクトを使用して送り返します。 ユーザーが設定ウィンドウをキャンセルすることにした場合、変更は破棄され、PrintSupportSettingsUISession オブジェクトから取得した遅延を完了してアプリケーションを終了する必要があります。

印刷サポート アプリは、異なる印刷ジョブに対して複数の同時アクティブ化を処理することが想定されているため、このようなアプリでは、package.appxmanifest ファイルの SupportsMultipleInstances 要素を使用して複数のインスタンスをサポートする必要があります。 それができなければ、1 つの印刷ジョブの設定を確認すると、開いている可能性のある他の設定ウィンドウが閉じる可能性があります。 ユーザーは、そうした設定ウィンドウをもう一度開く必要があります。

次のシーケンス図は、設定 UI の印刷チケット操作の概念を表しています。

sequence diagram of settings U I print ticket manipulation

設定 UI での PrintTicket の変更

任意の印刷ダイアログ (MPD/CPD またはカスタム印刷ダイアログ) またはシステム設定から起動したときに、設定 UI をアクティブ化するための C# サンプル コード:

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

DefaultSettingsView クラス用の XAML:

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

UI を表示して PrintTicket を変更するための C# サンプル コード:

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

プリンター デバイスからプリンター属性を取得する

IPP プリンターから get-printer-attributes クエリへの WireShark 応答:

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 は、EventArgs 内で検証する必要がある現在の PrintTicket を取得します。 そして PrintSupportExtension バックグラウンド クラスは、PrintTicket をチェックして有効性を確認し、競合があればそれを解決するように変更できます。 次に PrintSupportExtension バックグラウンド クラスは、関数 SetPrintTicketResult を使用して結果を設定して検証し、PrintTicket が解決されたか、競合しているか、無効であるかを示す必要があります。 このイベントは、印刷ジョブの有効期間中はいつでも発生させることができます。 PrintSupportExtension クラスがこのイベントに登録されていない場合、印刷システムは PrintTicket について独自の検証を実行します。

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

    印刷システムが、関連付けられている IPP プリンターのキャッシュされた PrintDeviceCapabilities を更新した後、このイベントが発生します。 このイベントが発生すると、PrintSupportExtension バックグラウンド クラスは、変更された PrintDeviceCapabilities を検査して変更できます。

印刷チケットのカスタム検証

PrintTicket 検証サービスを提供するための C# サンプル コード:

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

印刷品質の向上

ユーザーが印刷ダイアログの印刷ボタンを押して印刷をコミットすると、印刷するドキュメントが印刷中のアプリから印刷スタックに送信されます。 その後、このドキュメントでは変換 (PDL へのレンダリング) が行われ、ターゲット プリンターに適したドキュメントに変わります。 Windows は、プリンターからクエリが実行された属性に基づいて、選択する変換を決定します。 その後、変換されたドキュメントがプリンターに送信されます。 これはほとんどのプリンターで適切に機能しますが、変換の際にパートナー アプリも利用できるようにすると、印刷の品質が高まる可能性が場合によってはあります。 これを簡単に行えるように、現在の印刷ワークフロー API は、印刷スタックからの追加のポイントにおけるアプリへの呼び出しを含めるように拡張されています。 この API は、PSA アプリを登録できる 2 つの新しいイベントをサポートします。 PS API サーフェスへの唯一のエントリ ポイントは次のとおりです。

  1. JobStarting

    • このイベントは、任意のアプリケーションによって印刷ジョブが開始されたときに発生します。 イベントが発生すると、印刷サポート アプリは PrintWorkflowJobStartingEventArgs で SetSkipSystemRendering を呼び出して、システム レンダリングをスキップすることを選択できます。 システム レンダリングのスキップを選択した場合、印刷システムでは、XPS ドキュメントはプリンターで必要な PDL 形式に変換されません。 代わりに、印刷アプリケーションによって生成された XPS は、XPS から PDL 形式への変換を行う PSA に直接渡されます。
  2. PdlModificationRequested

    • このイベントは、Windows がプリンターで示される PDL 形式への XPS ストリームの変換を開始すると発生します。 ランタイム クラス PrintWorkflowPdlModificationRequestedEventArgs は、このイベントの引数として提供されます。 このイベント クラスは、印刷ジョブの内容を読み書きするために PDL のソース オブジェクトとターゲット オブジェクトを提供します。 ユーザーによる入力が必要であると判断した場合、アプリは EventArgs から PrintWorkflowUILauncher を使用して UI を起動できます。 この API では、Tester-Doer パターンが使用されます。 PrintWorkflowUILauncher では、関数 IsUILaunchEnabled が false を返した場合、UI を呼び出すことはできません。 この関数は、PSA セッションがサイレント モード (ヘッドレス モードまたはキオスク モード) で実行されている場合に false を返します。 関数が false を返した場合、印刷サポート アプリは UI の起動を試みません。

    OutputStream は、関数 GetStreamTargetAsync によって返される PrintWorkflowPdlTargetStream の一部として使用できます。 ターゲット OutputStream に書き込まれたコンテンツは、ドキュメントのコンテンツとしてプリンターに渡されます。

PDL 変更イベントのシーケンス図:

sequence diagram for the source stream P D L modification event

PSA フォアグラウンド アプリケーションは、PS バックグラウンド タスクが UI の起動を要求したときに起動します。 PSA は、フォアグラウンド コントラクトを使用してユーザーによる入力を取得したり、プレビュー印刷プレビューをユーザーに表示したりできます。

新しい printSupportWorkflow バックグラウンド タスクの種類が定義されました。 Package.appxmanifest には、PrintSupportWorkflow コントラクト用の次の拡張エントリがあります。

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

コントラクトのアクティブ化の際に、PrintWorkflowJobTriggerDetailsIBackgroundTaskInstance->TriggerDetails として指定されます。 PrintWorkflowJobTriggerDetails は、プロパティの一部として PrintWorkflowJobBackgroundSession を内部で提供します。 アプリでは、PrintWorkflowJobBackgroundSession を使用して、印刷ジョブ ワークフロー内のさまざまな挿入ポイントに関連するイベントに登録できます。 イベントの登録が完了した後、アプリでは PrintWorkflowJobBackgroundSession::Start を呼び出して、印刷システムがさまざまな挿入ポイントに関連するイベントの発生を開始する必要があります。

PrintSupportJobUI という新しい ActivationKind が定義されています。 これには新しい機能は必要ありません。

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

これは、印刷サポート ワークフローのバックグラウンド コントラクトから起動できる、またはユーザーが印刷ジョブ エラー トーストを選択したときに起動できる UI コントラクトです。 アクティブ化すると、PrintWorkflowJobUISession オブジェクトが含まれている PrintWorkflowJobActivatedEventArgs が提供されます。 フォアグラウンド アプリケーションは PDL データにアクセスする場合でも、PrintWorkflowJobUISession を使用して、PdlDataAvailable イベントに登録する必要があります。 フォアグラウンド アプリケーションで、ジョブ中に発生する可能性があるエラーのカスタム エラー メッセージを表示する場合は、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();
        }
    }
}

ワークフロー バックグラウンドから UI を起動する

PSPDL 変更要求イベント コントラクトから印刷サポート ジョブ UI を起動するための C# サンプル コード:

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 イベントのワークフロー ジョブ UI のアクティブ化

PdlDataAvailable イベントの印刷ジョブ UI のアクティブ化に関するシーケンス図:

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

PSA ジョブ UI のアクティブ化コントラクトの C# サンプル コード:

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 コンテンツへのストレージ ファイル アクセスの提供

PDF など一部の PDL 形式では、処理を開始するために完全なストリームが使用できる必要があります。 そのため、ソース コンテンツの StorageFile を返す PrintWorkflowPdlSourceContent クラスに GetContentFileAsync という名前の新しいメソッドが提供されます。

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

XPS から PDF への PDL 変換

XPS から PDF への PDL 変換を示す C# サンプル コード:

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

上記の「PDLDataAvailable イベントのワークフロー ジョブ UI のアクティブ化」セクションからの続きで、ジョブ通知にエラーを表示するための C# サンプル コード:

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 操作がサポートされていません。 この問題を軽減するために、PrintWorkflowPdlDataAvailableEventArgsCreateJobOnPrinterWithAttributes 関数と CreateJobOnPrinterWithAttributesBuffer 関数が用意されています。 PSA 開発者はこれらの API を使用して、プリンターでジョブが作成されるときにプリンターに渡されるジョブ属性を提供できます。

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

スプールが完了する前に XPS を順次処理するための C++/Winrt サンプル コード。

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

表示名のローカライズと PDL パススルー API の統合

重要

このセクションでは、Windows 11 バージョン 22H2 以降で使用できる PSA 機能について説明します。

このシナリオでは、PSA は印刷デバイス機能 (PDC) をカスタマイズし、文字列ローカライズ用の印刷デバイス リソース (PDR) を提供します。

サポートされている PDL パススルー API コンテンツの種類 (PDL 形式) も設定します。 PSA がイベントをサブスクライブしない場合、または SetSupportedPdlPassthroughContentTypes を明示的に呼び出さない場合、この PSA アプリに関連付けられているプリンターに対して PDL パススルーは無効になります。

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

ページ レベル機能のサポートと操作属性

重要

このセクションでは、Windows 11 バージョン 22H2 以降で使用できる PSA 機能について説明します。

ページ レベル機能サポートと操作属性のシナリオは、サンプル コード内の同じ場所に変更を加えることで対処されるため、グループ化されます。

  • ページ レベル機能サポート: このシナリオでは、PSA アプリケーションでページ レベル属性を指定します。これは、PrintTicket から解析された IPP 属性によってオーバーライドされるべきではありません。

  • 操作属性サポート (PIN 印刷) の個別のコレクション: このシナリオでは、PSA アプリケーションでカスタム IPP 操作属性 (PIN など) を指定します。

次の 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 による印刷ダイアログの拡張

重要

このセクションでは、Windows 11 バージョン 22H2 以降で使用できる PSA 機能について説明します。

このシナリオでは、印刷ダイアログと PSA 統合を使用すると、次のアクションが可能になります。

  • MPD の選択が PSA に関連付けられているプリンターに変更されたら、コールバックを取得する

  • openUrl アクションをサポートする 1 つの AdaptiveCard を表示する

  • 印刷ダイアログにカスタム機能とパラメーターを表示する

  • 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 変換

重要

このセクションでは、Windows 11 バージョン 22H2 以降で使用できる PSA 機能について説明します。

現在の PDL 変換 API である 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) 更新ポリシーの設定

重要

このセクションでは、Windows 11 バージョン 22H2 以降で使用できる PSA 機能について説明します。

プリンターの IHV は、印刷デバイス機能 (PDC) を更新する必要がある場合、異なる要件を設定することがあります。 これらの要件に対処するために、PrintSupportPrintDeviceCapabilitiesUpdatePolicy で PDC 用の更新ポリシーを設定できます。 PSA では、この API を使用して、時間または印刷ジョブの数に基づいて PDC 用の更新ポリシーを設定できます。

ジョブの数に基づいて 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);      
    }
}

TimeOut に基づいて 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);      
    }
}

一般的な印刷サポート アプリ (PSA) 設計ガイダンス

印刷サポート アプリを設計するときは、次の側面を設計に含めることが重要です。

  • フォアグラウンド コントラクトとバックグラウンド コントラクトの両方を、複数のインスタンスをサポートするものとしてマークする必要があります。たとえば、SupportsMultipleInstance がパッケージ マニフェストに存在している必要があります。 これは、複数の同時ジョブについてコントラクトの有効期間を確実に管理できるようにするためです。

  • PDL 変更の起動 UI を省略可能な手順として扱います。 UI の起動が許可されていない場合でも、印刷ジョブを正常に完了するように最善を尽くしてください。 印刷ジョブを中止する必要があるのは、PDL の変更中にユーザーによる入力なしで正常に完了する方法がない場合のみです。 このような場合は、変更されていない PDL を送信することを検討してください。

  • PDL 変更のために UI を起動する場合は、LaunchAndCompleteUIAsync を呼び出す前に IsUILaunchEnabled を呼び出します。 これは、現時点で UI を表示できないシナリオが引き続き正しく印刷されるようにするためです。 これらのシナリオは、ヘッドレス デバイス、あるいは現在キオスク モードまたは応答不可モードになっているデバイスで考えられます。

印刷サポート アプリの関連付け

Windows.Devices.Printers

Windows.Graphics.Printing.PrintSupport

Windows.Graphics.Printing.Workflow

インターネット印刷プロトコル (IPP) の仕様