2010年9月16日木曜日

Silverlightで複数ページを印刷する PrintDocument

Silverlightで単一ページを印刷するのは至極簡単でPrintDocumentを使用する。コード例は下記になる。

var printDoc = new PrintDocument();
printDoc.PrintPage += (s, eArgs) =>
{
 eArgs.PageVisual = this;
};
printDoc.Print("Print page");

PrintPageイベント引数のPageVisualプロパティにVisual要素を渡すとその内容をそのまま印刷してくれる。ここで一点注意が必要なのだが、Print関数はユーザのアクション(ボタンクリックなど)をハンドルした関数内で呼び出さないとSecurityExceptionがThrowされる。

ついで複数ページを印刷する方法を解説する。元ネタは下記から。
Silverlight Business Apps: Module 6.2 - Multi Page Printing

var printDoc = new PrintDocument();
bool firstPage = true;
var index = 0;
var itemsSource = new List<Item>{ 項目多数 };

printDoc.PrintPage += (s, eArgs) =>
{
 var itemHost = new StackPanel { Width = eArgs.PrintableArea.Width };
 if (firstPage)
 {
    itemHost.Children.Add(new HeaderControl());
    firstPage = false;
 }

 while (index < itemsSource.Count)
 {
    var item = itemsSource[index];
    var control = new ResultControl { Text = item.Text };
    control.Measure(new Size(eArgs.PrintableArea.Width, double.PositiveInfinity));

    var desiredHeight = control.DesiredSize.Height;
    var insideStack = new StackPanel { Height = desiredHeight };
    insideStack.Children.Add(control);
    itemHost.Children.Add(insideStack);

    itemHost.Measure(new Size(eArgs.PrintableArea.Width, double.PositiveInfinity));
    if (itemHost.DesiredSize.Height > eArgs.PrintableArea.Height)
    {
        itemHost.Children.Remove(insideStack);
        eArgs.HasMorePages = true;
        break;
    }
    index++;
 }
 eArgs.PageVisual = itemHost;
};
printDoc.Print("Print Page");

PrintPageイベント引数にPrintableAreaに印刷可能範囲の縦横サイズが入っている。このサイズより外にあるものは印刷されないので、こちら側で制御してやる必要があり、二枚目以降の印刷が必要な場合は再度PrintPageイベントが発生しないといけないのでHasMorePagesにtrueを設定しフレームワークに印刷がすべて終わっていないことを伝えておく必要がある。

Measure関数で実際に描画されるコントロールのサイズを計測する。Measure関数を呼び出しておくとDesiredSizeにコントロールに必要なサイズが設定されるのでそれを参照して、印刷可能範囲との比較を行っていく。

一点はまった点としてinsideStackの存在理由を説明しておく。一見無駄なコーディングに見えるが、これがないと実際に印刷されるサイズとDesiredSizeで取得されるサイズに差異が生じる場合があり、予期せぬ印刷結果につながることがある。

というのも、ResultControlは内部的にGridを保持し、さらにRowDefinitionで3行保持している。ただ、ある特定の場合は最後の1行にデータが設定されないので上2行分のHeight40ピクセルほどで事足りる。しかしDesiredSizeでは毎回3行分のHeight60ピクセルが返却され、さらに始末の悪いことに描画は40ピクセル分で行われる。そのためプログラム上はHeight60ピクセルで計算している部分が実はHeight40ピクセルが正しく、その20ピクセル分の差異がitemsSourceの項目が多くなるほど積もり積もって大きくなり、正しい印刷制御が行えなくなってしまう。それを防ぐためにinsideStackを使用している。insideStackのHeightにDesiredHeightで取得したHeightを設定し、必ずその高さを保証することにより、実際に印刷されるものと、プログラム上で取得できるサイズの差異が発生しないようにしている。

0 件のコメント:

コメントを投稿