@kotyのブログ

.NETとかJavaとかPythonとか勉強会のこととかを、田舎者SEがつづります。記事のライセンスは"CC BY"でお願いします。

WPFを帳票フレームワークとして使う

この記事はXAML Advent Calendar 2013XAML Advent Calendar 2013 - Adventar 15日目の記事です。昨日はahfさんのWF における xaml について簡単な説明をしてみるでした。

あまりインタネットで見ないので印刷ネタを本エントリーでは書いてみます。

簡単な印刷

webというかペーパーレス(死語)の時代になって久しいですけどもまだまだ印刷機能の需要はあります。ありますよね?

本格的に印刷機能を使うとなれば、有償無償の帳票ミドルウェアを利用することになるかと思いますが、 一人で作れる程度のちょっとしたスタンドアロンシステムにはちょっと大げさです。

本エントリーではWPFによるお手軽印刷方法を紹介しようと思います。

ソースはgithubに置いてあります。

プロジェクト構成

サンプルプロジェクトの構成は下図の通りです。WPFのMVVMフレームワークであるLivetを使っていますが、本題とは外れるので多くは説明しません。。。

f:id:kkotyy:20131210220947p:plain

画面

画面は本題とは外れるので多くは説明しません。

当サンプルプロジェクトでは、この画面で入力したものを帳票に出します。

f:id:kkotyy:20131210221331p:plain

帳票

帳票をUserControlとして定義します。

<UserControl x:Class="WpfPrintReportSample.Reports.ReportSample"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:vm="clr-namespace:WpfPrintReportSample.ViewModels"
             mc:Ignorable="d"
             d:DesignHeight="1122" d:DesignWidth="793">
    <UserControl.DataContext>
        <vm:MainWindowViewModel />
    </UserControl.DataContext>
    <Grid>
        <Label Content="{Binding Text}" FontSize="70"
               VerticalContentAlignment="Center"
               HorizontalContentAlignment="Center"/>
    </Grid>
</UserControl>

ここでミソなのが、

    <UserControl.DataContext>
        <vm:MainWindowViewModel />
    </UserControl.DataContext>

バインドするViewModelを設定しておくことです。こうすることでVisual Studioのデザイナ上でViewModelがバインドされて表示されます。

ViewModel

今回は簡単のため画面と帳票で同じViewModelをバインドしています。

using System.ComponentModel;
using System.Windows;
using Livet;
using Livet.Commands;

namespace WpfPrintReportSample.ViewModels
{
    public class MainWindowViewModel : ViewModel
    {
        public MainWindowViewModel()
        {
            if (DesignerProperties.GetIsInDesignMode(new DependencyObject()))
            {
                Text = "あいうえお";
            }
        }

        public string Text { get; set; }

        private ViewModelCommand printCommand;

        public ViewModelCommand PrintCommand
        {
            get { return this.printCommand ?? (this.printCommand = new ViewModelCommand(this.Print)); }
        }

        private void Print()
        {
            Printer.Print(this);
        }
    }
}

既に述べたVisual Studioのデザイナ上で表示されるデータは、ViewModelの引数なしコンストラクタ内で設定されたデータです。

引数なしコンストラクタが実行時にも使われる場合、デザイン時に使われるとエラーしてしまうことがあるでしょう。そういうときには、下記のように

 if (DesignerProperties.GetIsInDesignMode(new DependencyObject()))

デザイン時と実行時で処理を分けることができます。地味に便利。

出力ロジック

印刷は下記です。

using System.Collections.Generic;
using System.Windows.Controls;
using System.Printing;
using System.Windows.Xps;
using System.Windows.Documents;
using System.Windows.Markup;
using WpfPrintReportSample.ViewModels;
using WpfPrintReportSample.Reports;

namespace WpfPrintReportSample
{
    public static class Printer
    {
        public static void Print(MainWindowViewModel viewModel)
        {
            //Set up the WPF Control to be printed

            var fixedDoc = new FixedDocument();

            var objReportToPrint = new ReportSample();

            var ReportToPrint = objReportToPrint as UserControl;
            ReportToPrint.DataContext = viewModel;

            var pageContent = new PageContent();
            var fixedPage = new FixedPage();

            //Create first page of document
            fixedPage.Children.Add(ReportToPrint);
            ((IAddChild)pageContent).AddChild(fixedPage);
            fixedDoc.Pages.Add(pageContent);

            SendFixedDocumentToPrinter(fixedDoc);
        }

        private static void SendFixedDocumentToPrinter(FixedDocument fixedDocument)
        {
            XpsDocumentWriter xpsdw;

            PrintDocumentImageableArea imgArea = null;
            //こちらのオーバーロードだと、プリンタ選択ダイアログが出る。
            xpsdw = PrintQueue.CreateXpsDocumentWriter(ref imgArea);

            //var ps = new LocalPrintServer();
            //var pq = ps.DefaultPrintQueue; 
            //こちらのオーバーロードだと、プリンタ選択ダイアログを飛ばして既定のプリンタにスプールされる
            //xpsdw = PrintQueue.CreateXpsDocumentWriter(pq);
            xpsdw.Write(fixedDocument);
        }
    }
}

結果として以下のようなオブジェクト階層でFixedDocumentオブジェクトが出来上がります。

FixedDocument
└ PageContent
 └ FixedPage
  └ UserControl(DataContextをバインド)

最後にFixedDocumentをXpsDocumentWriterに渡します。

利点

追加のミドルウェアいらず

開発時、実行時ともに専用のミドルウェアは不要で.NETランタイムさえ入っていれば使うことができます。

Visual Studioのデザイナ上で帳票を作れる

ソースコード中でオブジェクトを配置するのではなく、WYSIWYGで帳票を作れます。

帳票レイアウトの柔軟度が高い

何といってもWPFですから、Advent Calendarで皆さまが書かれているようにかなり柔軟度が高いです。特にItemsControlが強力ですね。データの件数に合わせて罫線を引くなんてことも簡単です。

まとめ

駆け足ですが、WPFによるお手軽印刷を紹介しました。windowsアプリケーションでの簡単な印刷ならこれで十分かなと思います。やはり.NETは便利ですね。ほかにこんなやり方があるよ、という方はぜひ教えてください。

参考サイト

WPF 4.5入門

WPF 4.5入門