酢ろぐ!

カレーが嫌いなスマートフォンアプリプログラマのブログ。

Windows ストア アプリでPrintManagerを使って猫の画像を印刷する

Windows ストア アプリでは、アプリケーションからプリンターに対して印刷する機能が提供されています。プリントアウトできるのは画像(Bitmap)ではなく、UIElementになります(プログラム的なお話は後述します)。

ここでは、ブランクなPageの上に、テスト用に3枚のエレメントを用意してみました。背景は単純な赤一色のGrid、ねこの顔と体はImageです。僕の落書きですが下図のようにコントロールを重ねています。

f:id:ch3cooh393:20130108201852j:plain

Windows ストア アプリにて印刷をおこなう手順について説明します。印刷をおこなうには、まずチャームからプリンターを選択して、印刷物のプレビューを表示し、そこから実際の印刷開始の要求をおこないます。スクリーンショットを交えながら手順を紹介します。

チャームの[デバイス]を選択します。印刷に対応したアプリケーションの場合、印刷可能なプリンターの一覧が並びます。

f:id:ch3cooh393:20130111150555p:plain

印刷物を出したいプリンターを選択します。ここでは、プリンターを接続していなくても出力の確認をしやすい[Microsoft XPS Decument Writer]を選択します。(1)

f:id:ch3cooh393:20130111150614p:plain

プレビューの確認ができました。複数ページの印刷の場合は2ページ目3ページ目のプレビューを表示することが可能です。(2)

プレビューに表示される内容で問題なければ、印刷ボタンをクリックします。(3)

f:id:ch3cooh393:20130111150630p:plain

問題が発生しなければ、印刷処理は完了です。XPSを選択した場合は、ドキュメントフォルダーに成果物が生成されます。XPS リーダー等を使用して印刷物の確認をしてください。

f:id:ch3cooh393:20130111150743p:plain

以上が、Windows ストア アプリにて印刷をおこなう際の手順となります。

手順に(1)から(3)までの数字を入れています。印刷処理はメソッドを1つ実行したらプリンターから紙が出てくるわけではなく、ユーザーの操作に応じてイベントが発生し、それらのイベントにて適切な処理をおこなう必要があります。

後述するプログラム側の説明で、「(1)の動作で○○○イベントが発生します」と説明しますので、どの手順かわからなくなった場合は、前に戻って頂ければと思います。

印刷をおこなうプログラム

印刷される対象はUIElementになります。

画面には表示されていないプログラム的に生成したUIElementを印刷させることも可能ですが、ここではあらかじめ画面上に表示されている「猫の画像」を印刷したいと思います。

さきほど印刷の手順にて、説明に使用した赤背景の上に猫が立っているXMLは、以下の通りです。

<Page
    x:Class="PrinterSample.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:PrinterSample"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <Grid.RowDefinitions>
            <RowDefinition Height="280*"/>
            <RowDefinition Height="50*"/>
        </Grid.RowDefinitions>
        
        <!-- 印刷するボタン -->
        <Button x:Name="btnPrint" Content="印刷する" 
            Grid.Row="1" Click="btnPrint_Click"/>

        <!-- 猫がいる背景 -->
        <Grid x:Name="printGrid" Background="Red">
            <!-- 猫の体 -->
            <Image Source="Assets/neko.png" HorizontalAlignment="Center" 
                   VerticalAlignment="Top" Stretch="None" />
            <!-- 猫の顔 -->
            <Image Source="Assets/face.png" HorizontalAlignment="Center" 
                   VerticalAlignment="Top" Stretch="None" Margin="0,40,0,0"  />
        </Grid>
    </Grid>
</Page>

printGridと名前を付けたGridの上に、ネコの体と顔のImageコントロールを配置しています。この猫を表示しているprintGridを印刷してみましょう。

チャームの「デバイス」に印刷先のプリンターの一覧を表示させる

Windows ストア アプリにて印刷をおこなうには、印刷物があることを通知する必要があります。

画面の表示時OnNavigateToメソッドの中で、チャームの[デバイス]を選択し、印刷先を表示させるにはPrintManager.GetForCurrentView()メソッドで取得したPrintManagerオブジェクトのPrintTaskRequestedイベントのハンドラを設定します。

PrintTaskRequestedイベントは、(1)のチャームの[デバイス]を選択した瞬間に発生するイベントです。

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    var manager = PrintManager.GetForCurrentView();
    manager.PrintTaskRequested += manager_PrintTaskRequested;
}

// チャームの「デバイス」を選択した場合、または
// PrintManager.ShowPrintUIAsync()メソッドを実行した時に実行される
private void manager_PrintTaskRequested(PrintManager sender, PrintTaskRequestedEventArgs e)
{
    e.Request.CreatePrintTask("印刷物のタイトル", req => {
    });
}

これで印刷するプリンターを選択できるところまで実装できました。しかし、肝心の「何」を印刷するのかについては未設定です。

システム側では何を印刷して良いのか分からない状態ですので、もしプリンターを選択しても「印刷するコンテンツがアプリにはありませんでした。」と警告が表示されてしまいます。

印刷物のプレビューを表示する

PrintDocumentクラスのオブジェクトを生成して、印刷の開始時、プレビュー表示時、印刷時に発生するイベントのハンドラを設定します。

printDocumentから取り出したドキュメントのソースをインスタンス変数に保存しておき、チャームの「デバイス」を選択した時(もしくは、PrintManager.ShowPrintUIAsync()メソッドを実行した時)に実行されるPrintTaskRequestedイベントハンドラで印刷物のタイトルとドキュメントソースを設定します。

// 印刷ドキュメントオブジェクト
PrintDocument printDocument = null;

// 印刷するドキュメントのソース
IPrintDocumentSource printDocumentSource = null;

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    // 印刷ドキュメントオブジェクトを生成する
    printDocument = new PrintDocument();

    // ドキュメントのソースを保存します
    printDocumentSource = printDocument.DocumentSource;

    // 印刷が要求させると発生するイベントにハンドラを設定する
    printDocument.Paginate += printDocument_Paginate;

    // 印刷プレビューで表示されると表示するイベントにハンドラを設定する
    printDocument.GetPreviewPage += printDocument_GetPreviewPage;

    // 印刷を要求するイベントにハンドラを設定する
    printDocument.AddPages += printDocument_AddPages;

    var manager = PrintManager.GetForCurrentView();
    manager.PrintTaskRequested += manager_PrintTaskRequested;
}

// チャームの「デバイス」を選択した場合、
// またはPrintManager.ShowPrintUIAsync()メソッドを実行した時に実行される
private void manager_PrintTaskRequested(PrintManager sender, PrintTaskRequestedEventArgs e)
{
    e.Request.CreatePrintTask("印刷物のタイトル", req => {
        req.SetSource(printDocumentSource);
    });
}

手順の(2)にてプリンターを選択すると、PaginateイベントのハンドラprintDocument_Paginateメソッドが実行されます。このイベントハンドラでは、印刷物の内容を作るいわば印刷の準備をおこないます。

どんな実装方法でも構いませんが、ここではページ別に印刷するUIElementのリストを作り、そのリストにprintGridを追加しました。

// 印刷したいUIElementをページ別にリスト
List<UIElement> printPages = new List<UIElement>();

private void printDocument_Paginate(object sender, PaginateEventArgs e)
{
    // UIElement(猫が表示されているGrid)を追加します
    printPages.Clear();
    printPages.Add(printGrid);
}

さて、印刷物の準備が完了したので、印刷物のプレビューがおこなわれます。

プレビューを表示する直前にGetPreviewPageイベントのハンドラprintDocument_GetPreviewPageメソッドが実行されます。PrintDocument型のsenderに対して、プレビューさせるUIElementを渡します。。

private void printDocument_GetPreviewPage(object sender, GetPreviewPageEventArgs e)
{
    var document = (PrintDocument)sender;

    // 配列からプレビュー表示するUIElementを取り出す
    var element = printPages[e.PageNumber - 1];
    // プレビューページに表示するオブジェクトを設定する
    document.SetPreviewPage(e.PageNumber, element);
}

複数ページ存在している場合は、ページを変更する度にこのイベントハンドラが実行されます。ここまでの処理が実装できていれば、正しくプレビューが表示されることが確認できます。

印刷をおこなう

さて、手順(3)での[印刷]のボタンをクリックするとAddPagesイベントのハンドラprintDocument_AddPagesメソッドが実行されます。AddPageメソッドを使って、プリンターで印刷させるUIElementを追加します。

最後に印刷物の追加が完了したことをAddPagesCompleteメソッドを使って通知します。

private void printDocument_AddPages(object sender, AddPagesEventArgs e)
{
    var document = (PrintDocument)sender;

    // プリンタに送信する印刷ページを追加する
    for (int i = 0; i < printPages.Count; i++)
    {
        printDocument.AddPage(printPages[i]);
    }

    // アプリケーションでの印刷ページの追加が完了したことを通知する
    document.AddPagesComplete();
}

ここまで問題なく処理が実装できていれば、プリンターから紙が出てくる(XPSの場合はドキュメントフォルダにファイルが生成されている)かと思います。以上で印刷をおこなう方法については終わりです。

印刷機能をチャーム以外から呼びだす

また、印刷機能を呼び出すのにチャームを使う以外にも、プログラム側から表示することも可能です。

    private async void btnPrint_Click(object sender, RoutedEventArgs e)
    {
        if (Windows.UI.ViewManagement.ApplicationView.Value 
            != Windows.UI.ViewManagement.ApplicationViewState.Snapped)
        {
            await Windows.Graphics.Printing.PrintManager.ShowPrintUIAsync();
        }
    }