トップ回答者
従来のPrintPageEventArgs.DrawString メソッド等を使った印刷相当のことを、WPF下で実現する方法

質問
-
アマチュアプログラマーです。
従来の、System.Drawing.Printing.PrintPageEventArgs.DrawString メソッド等を使った印刷処理に相当することを WPF アプリケーションで実現する方法が全くわかりません。
Canvas に何らかの描き方をして、XpsDocumentWriter.Write(Visual) メソッドあたりにつなげるのかなあ、という感じがしているのですが、著しく変化した印刷関連はまるで歯が立たず、具体的につないで試してみることすらわかりません。
どなたか、わかる方、宜しくお願いいたします。
回答
-
XpsDocumentWriter.Writer( ) に渡すのは,
出来上がった FixedDocument のインスタンスになります。
どっちかというと,WriteAll() の意味のようです。階層構造になっているので,例えば,ページごとにきっちりやるのなら,
FixedDocument のインスタンス
├ PageConent のインスタンス --- Page1
│ └ FixedPage のインスタンス
│ └ Canvas のインスタンス
│ └ 各内容の追加
├ PageConent のインスタンス --- Page2
│ └ FixedPage のインスタンス
│ └ Canvas のインスタンス
│ └ 各内容の追加
├ PageConent のインスタンス --- Page3
│ └ FixedPage のインスタンス
│ └ Canvas のインスタンス
│ └ 各内容の追加
のように作成した FixedDocumentのインスタンスを渡す感じになるんじゃないかと。
Programming WPF 2nd Edition という本の
第15章 Printing and XPSに詳しく書いてあるので,
早く日本語訳が出るといいんですけどね。
手元にWPFの本はたくさんあるんですが,
いまのところ(2008.1),この本しか扱っていないし,簡潔でポイント抑えているんじゃないかと。
直接プリンタに出力する場合は,
コード ブロックPrintDocumentImageableArea imgArea = null;
XpsDocumentWriter writer = PrintQueue.CreateXpsDocumentWriter(ref imgArea);
if( writer != null)
{
writer.Write( FixedDocumentのインスタンス );
}のようにして,ファイルに出力する場合は,
コード ブロックusing( XpsDocument xpsFile = new XpsDocument( 略 )) {
XpsDocumentWriter xpsDw = XpsDocument.CreateXpsDocumentWriter(xpsFile);{
writer.Write( FixedDocumentのインスタンス );
}
}のような感じです。
また,
プレビューを挟むのなら DocumentViewer (Documentプロパティにセット)を使うんでしょう。
FixedDocument でなくても,
IDocumentPaginatorSourceインターフェイスを実装していれば,
Documentプロパティにセットできます。
FixedDocument 等は,必ずしも上記のような階層でなくてもいいので,
FixedDocument や FixedPage あたりのクラスを見て
DocumentViewer に表示して,研究すれば,
なんとかなるんじゃないかと。
-
稍丼 様
Package 化の件のスレッドの方で御回答を戴き、私の中では「自分の中で、あ、これでわかった。」
と思い、実際に確認もしないで(その必要もないほどに、これでよし、と納得したので)、
この関係のすべての事後処理をすべて終えてから、最後におもむろに確認だけはしておこうと
試みたところ、プリンターが動かないので困惑してしまいました。
どう 『わかった』 かと申しますと、既に成功している PrintQueue ルートの次のコード部分
(動くまでは、中心部分以外のコードは全部省いております)
コード ブロック
LocalPrintServer ps = new LocalPrintServer();PrintQueue pq = ps.DefaultPrintQueue;
XpsDocumentWriter xpsdw
= PrintQueue.CreateXpsDocumentWriter(pq);
xpsdw.Write(fixedDocument);
を、XpsDocument ルートでの次のコードに入れ換えました。コード ブロック
XpsDocument xpsDoc = new XpsDocument( @"MyFirstXpsDoc.xps"
, FileAccess.Write);
XpsDocumentWriter xpsdw
= XpsDocument.CreateXpsDocumentWriter(xpsDoc);
xpsdw.Write(fixedDocument);私は、これでいいものと思い、ビルドし、実行しました。実行時エラーもでません。
ただ、Printer が全く動きません。
Printer は、XPS 未対応のもの一台のみ。
しかも、PrintDialog で、それをきちっと選択してから実行しております。
一体、これは何が問題で、どうすればいいのでしょうか。
もう一度、成功した 上段の PrintQueue ルートのコードに戻して試すと、ちゃんと印刷できますので、
他のコードに誤りはないと思います。
-----------------------------------------------------------------------------------------------------------------------------------------
以上の再質問を出したのですが、このスレッドの稍丼様の最初の御回答内のコードブロックをもう一度読み返しておりましたところ、私が根本的な勘違いをしていた、ということに気づきました。
但し、かなりわかりにくい構造になっています。
まず、XpsDocumentWriter クラスには、コンストラクタがありません。そのオブジェクトは、
① PrintQueue.CreateXpsDocumentWriter メソッドから取得する場合と
② XpsDocument.CreateXpsDocumentWriter メソッドから取得する場合の二通りがあります。
他方、XpsDocumentWriter.Write メソッドのところの説明では、このメソッドは、『XPS ドキュメントまたは印刷キューに・・・書き込む』、となっております。
この書き方が、私の勘違いの原因になった、と思われます。すなわち、このことから、私は①②のどちらの方から辿り着こうと、
XpsDocumentWriter オブジェクトを取得して、そのWrite メソッドを使えば Printer で印刷できるのであると思いこんでしまいました。それ故、 PrintQueue ルートと XpsDocument ルートというように分けて、二つのルートを追いかけてきました。
ところが、そもそもこれが間違いでした。そのことに先ほどの稍丼様のコードブロックを眺めていて気づきました。稍丼様は『使い分けて』おられます。わざわざ二つを並べて、XpsDocumentWriter.Write メソッドは、『使い分けなければならない』ことを示して下さっております。それが私にはわかっていませんでした。
結論はこうです。
XpsDocumentWriter.Write メソッドのところの説明は、次のようになっていないと私のような混乱を生みやすいのです。
① の PrintQueue.CreateXpsDocumentWriter メソッドから取得した XpsDocumentWriter オブジェクトの場合は、その Write メソッドは、印刷キュー(Printer)に書き出す機能になる。
② の XpsDocument.CreateXpsDocumentWriter メソッドから取得した XpsDocumentWriter オブジェクトの場合は、その Write メソッドは、XPS ドキュメントファイル(.xps)に書き出す機能になる。
従って、このスレッドで取り上げた当初の問題の位置からの、Printer での印刷を問題にする限りは、今まで私が追いかけてきた XpsDocument ルートということは、問題にはならないということです。問題にはならないことを追いかけてきてしまいました。そのことがわかりました。
このことに気づき、私は上記の XpsDocument ルート のコードを実行して、自分のプロジェクトフォルダーの ¥bin¥Debug を開いてみました。 果たして MyFirstXpsDoc.xps ファイルがそこにできておりました。これを消してもう一度実行。やはり、またできます。
すなわち、私が XpsDocumentWriter ルートと言ってきたものは、『XpsDocument.xps ファイルを書き出すコード』として正しく動いていたということになります。
以上で、全面解決です。このメッセージでは、独り相撲をとってしまいました。しかし、すっきりしました。
大変御迷惑をおかけいたしました。
-------------------------------------------------------------------------------------------------------------------------------
私が、気づいて、この解決部分を追加する作業を終えてサインアウトいたしましたら、同時だったようで、稍丼様のお答えがこの下に既に出ておりました。同じことが書かれておりました。稍丼様、どうもありがとうございました。この理解でよいことの確認になりました。この場でお礼申し上げます。
稍丼様のお答えが出た以上、私の記載は重複になるので、消してしまおうかとも考えたのですが、ライブラリの説明を補う意味があるかとも思われますので、私のような誤解をされないように、このまま残すことに致します。 -
下の XpsDocument クラスを使ったものは,
XPSファイルを出力するためのコードです。要するに,
MyFirstXpsDoc.xps というファイル名のXPSファイルを
作成するためのコードです。
実際は,@"MyFirstXpsDoc.xps" という引数は,
作成したい場所のちゃんとしたパスも含めて指定します。
ファイルにしてとっておく必要がないのなら,
最初から,PrintQueue の CreateXpsDocumnetWriter(...) から取得した
XpsDocumentWriter の Write(...) メソッドで書き込めばOKです。
つまり,
同じ XpsDocumentWriter の Write(...) メソッドなのに,
PrintQueue の CreateXpsDocumentWriter(...) から返ってきた XpsDocumentWriter(のインスタンス)と
XpsDocument の CreateXpsDocumentWriter(...) から返ってきた XpsDocumentWriter(のインスタンス)とでは,
Write(...) した時の実際の動作は異なっているということです。
同じ XpsDocumentWriter 型だけど,
プリンタに書き込むか,ファイルに書き込むかというカラクリが入っているわけです。
ファイルに書き込むほうはファイルに書き込むのだからプリンタは動かないわけです。
[追記] 解決したようなのですが,書いてしまったのでそのままにしておきます。
すべての返信
-
XpsDocumentWriter.Writer( ) に渡すのは,
出来上がった FixedDocument のインスタンスになります。
どっちかというと,WriteAll() の意味のようです。階層構造になっているので,例えば,ページごとにきっちりやるのなら,
FixedDocument のインスタンス
├ PageConent のインスタンス --- Page1
│ └ FixedPage のインスタンス
│ └ Canvas のインスタンス
│ └ 各内容の追加
├ PageConent のインスタンス --- Page2
│ └ FixedPage のインスタンス
│ └ Canvas のインスタンス
│ └ 各内容の追加
├ PageConent のインスタンス --- Page3
│ └ FixedPage のインスタンス
│ └ Canvas のインスタンス
│ └ 各内容の追加
のように作成した FixedDocumentのインスタンスを渡す感じになるんじゃないかと。
Programming WPF 2nd Edition という本の
第15章 Printing and XPSに詳しく書いてあるので,
早く日本語訳が出るといいんですけどね。
手元にWPFの本はたくさんあるんですが,
いまのところ(2008.1),この本しか扱っていないし,簡潔でポイント抑えているんじゃないかと。
直接プリンタに出力する場合は,
コード ブロックPrintDocumentImageableArea imgArea = null;
XpsDocumentWriter writer = PrintQueue.CreateXpsDocumentWriter(ref imgArea);
if( writer != null)
{
writer.Write( FixedDocumentのインスタンス );
}のようにして,ファイルに出力する場合は,
コード ブロックusing( XpsDocument xpsFile = new XpsDocument( 略 )) {
XpsDocumentWriter xpsDw = XpsDocument.CreateXpsDocumentWriter(xpsFile);{
writer.Write( FixedDocumentのインスタンス );
}
}のような感じです。
また,
プレビューを挟むのなら DocumentViewer (Documentプロパティにセット)を使うんでしょう。
FixedDocument でなくても,
IDocumentPaginatorSourceインターフェイスを実装していれば,
Documentプロパティにセットできます。
FixedDocument 等は,必ずしも上記のような階層でなくてもいいので,
FixedDocument や FixedPage あたりのクラスを見て
DocumentViewer に表示して,研究すれば,
なんとかなるんじゃないかと。
-
稍丼 / yayadon 様
丁寧な御回答をして戴き、ありがとうございました。
始めは、 FixedDocument でいくのが本筋かなと思ったのですが、そちらはかなり大がかりな仕組みのようなので、たかだか従来程度のことならもっと簡単にはいかないのかと思っていましたら、xpsDocumentWriter.Writer(Visual) メソッドがあるのに気づき、ここに Canvas のインスタンスをつなげば、1ページ分の印刷ができないのかな、ということを考えてしまいました。
ただ、そうするとxpsDocumentWriter をどこから引っ張り出してくるかという問題が生じ、うまくつながらないのですね。まだ、新しい仕組みとコードの流れの理解が不十分なので、とんでもない発想になってしまうのだと思います。普通ですと、これだけの回答を戴き解決という雰囲気なのですが、現在の私には FixedDocument の理解等を深め、より具体的なコード部分をしっかり繋げるようにしながら、御教示戴いた構造を書き上げてみないことには、プリンターからの出力を実際に確認できません。それにはかなり時間がかかりそうですので、改めてその時点で御挨拶をさせて戴くことにし、当面その方向でがんばりたいと思います。少なくとも、進む方向がつかめましたので、行き着くところまで行ってみたいと思っております。 本格的な御回答に改めて感謝申し上げます。 ----- 西方 ----
-
横着するんなら
コード ブロック
PrintDocumentImageableArea imgArea = null;XpsDocumentWriter xpsDw = PrintQueue.CreateXpsDocumentWriter(ref imgArea);
if( xpsDw != null )
{
SerializerWriterCollator swc = xpsDw.CreateVisualsCollator();
int pageCount = 0;
swc.BeginBatchWrite();
for (;;)
{
pageCount += 1;
// 略
swc.Write( ... ) // ここで一ページ分の処理となる
if( ... ) break; // なんらかの条件でループ抜け出し
}
swc.EndBatchWrite();
}
のような感じです。
-
(当初、この欄は私がまだコーディングを進めている途上で設けたものですが、プリンターが動くに至りましたので、今後御覧になられる方の便宜のために、後から内容を修正致しました。従って、内容的には次のメッセージと時間的に逆転しております。)
稍丼様の御回答のもとに、私のプリンターが始めて動いたときのコードを、御参考のために見ることができるようにしておきます。
拙いコードですが、最初はまず動きさえすればよいのですから、敢えて手を加えてありません。
以下、私のブログです。
http://hokkai53.cocolog-nifty.com/blog/ ひとりよがりの窓
この中の、カテゴリー「パソコンの窓」内の[P14]の記事になります。
その前半部分は、当スレッドの紹介ですが、後半部分から筋道の要点の記載、そしてコードになります。 -
稍丼 様
当初に示して戴いたオーソドックスな方法で、そして XpsDocument オブジェクトは作らずに、お示し戴いた PrintQueue の方から直接 XpsDocumentWriter を作り出すルートで成功致しました。Label.Content="Hello World" が、XPS非対応のプリンターから打ち出され、感動でした。どうもありがとうございました。
ただ、ここまでやったついでに XpsDocument を作り出して、そこから XpsDocumentWriter を引き出してくるルートもできるようにしておこうと思ったのですが、Package に入れる局面でいきづまり、既に御覧になられているかもしれませんが、その部分の別スレッド(下記リンク)をたてた次第です。
http://forums.microsoft.com/MSDN-JA/ShowPost.aspx?PostID=2618304&SiteID=7
というより、FixedDocument のPackage を作るという筋道そのものがおかしいのでしょうか。どこかに書いてあったのか、API を眺めていて引数の関係で勝手にそう思い込んでしまったのか、自分でもわからなくなってしまいました。
もう解決ボタンを押してもよいのですが、この点だけお尋ねしてから終わりたいと思います。宜しくお願いいたします。
なお、後からお示しいただきました便法については、そのうち勉強しておくつもりでおります。 -
稍丼様
FixedDocument のPackage 化のスレッドも含めまして、たいへんお世話になりました。
そちらのスレッドにも書いておきましたように、XpsDocument コンストラクタのパラメーターである、Package とか string には、FixedDucument のもの(XpsDocumentの原材料になるもの)を入れなければならないのだとずっと思いこんでおりました。
御回答戴いたコードを拝見し、そうではないのだということ、そして、FixedDocument のPackage 化などということを問題にすること自体が筋違いであるということがわかりました。
また、引数の意味がわかったことによって、Write する関係では簡単に(XpsDocument ファイルが現に存在しなくとも) XpsDocument のオブジェクトを作り上げ、XpsDocumentWriter オブジェクトを引き出して使うことができることもわかりました。
これで、すべて繋がります。すべてすっきり致しました。厚く御礼申し上げます。
これで、従来の自己プログラムの根幹に関わる部分は、とりあえず全部解決し、具体的な書き直し作業に着手できます。がんばります。ありがとうございました。 -
稍丼 様
Package 化の件のスレッドの方で御回答を戴き、私の中では「自分の中で、あ、これでわかった。」
と思い、実際に確認もしないで(その必要もないほどに、これでよし、と納得したので)、
この関係のすべての事後処理をすべて終えてから、最後におもむろに確認だけはしておこうと
試みたところ、プリンターが動かないので困惑してしまいました。
どう 『わかった』 かと申しますと、既に成功している PrintQueue ルートの次のコード部分
(動くまでは、中心部分以外のコードは全部省いております)
コード ブロック
LocalPrintServer ps = new LocalPrintServer();PrintQueue pq = ps.DefaultPrintQueue;
XpsDocumentWriter xpsdw
= PrintQueue.CreateXpsDocumentWriter(pq);
xpsdw.Write(fixedDocument);
を、XpsDocument ルートでの次のコードに入れ換えました。コード ブロック
XpsDocument xpsDoc = new XpsDocument( @"MyFirstXpsDoc.xps"
, FileAccess.Write);
XpsDocumentWriter xpsdw
= XpsDocument.CreateXpsDocumentWriter(xpsDoc);
xpsdw.Write(fixedDocument);私は、これでいいものと思い、ビルドし、実行しました。実行時エラーもでません。
ただ、Printer が全く動きません。
Printer は、XPS 未対応のもの一台のみ。
しかも、PrintDialog で、それをきちっと選択してから実行しております。
一体、これは何が問題で、どうすればいいのでしょうか。
もう一度、成功した 上段の PrintQueue ルートのコードに戻して試すと、ちゃんと印刷できますので、
他のコードに誤りはないと思います。
-----------------------------------------------------------------------------------------------------------------------------------------
以上の再質問を出したのですが、このスレッドの稍丼様の最初の御回答内のコードブロックをもう一度読み返しておりましたところ、私が根本的な勘違いをしていた、ということに気づきました。
但し、かなりわかりにくい構造になっています。
まず、XpsDocumentWriter クラスには、コンストラクタがありません。そのオブジェクトは、
① PrintQueue.CreateXpsDocumentWriter メソッドから取得する場合と
② XpsDocument.CreateXpsDocumentWriter メソッドから取得する場合の二通りがあります。
他方、XpsDocumentWriter.Write メソッドのところの説明では、このメソッドは、『XPS ドキュメントまたは印刷キューに・・・書き込む』、となっております。
この書き方が、私の勘違いの原因になった、と思われます。すなわち、このことから、私は①②のどちらの方から辿り着こうと、
XpsDocumentWriter オブジェクトを取得して、そのWrite メソッドを使えば Printer で印刷できるのであると思いこんでしまいました。それ故、 PrintQueue ルートと XpsDocument ルートというように分けて、二つのルートを追いかけてきました。
ところが、そもそもこれが間違いでした。そのことに先ほどの稍丼様のコードブロックを眺めていて気づきました。稍丼様は『使い分けて』おられます。わざわざ二つを並べて、XpsDocumentWriter.Write メソッドは、『使い分けなければならない』ことを示して下さっております。それが私にはわかっていませんでした。
結論はこうです。
XpsDocumentWriter.Write メソッドのところの説明は、次のようになっていないと私のような混乱を生みやすいのです。
① の PrintQueue.CreateXpsDocumentWriter メソッドから取得した XpsDocumentWriter オブジェクトの場合は、その Write メソッドは、印刷キュー(Printer)に書き出す機能になる。
② の XpsDocument.CreateXpsDocumentWriter メソッドから取得した XpsDocumentWriter オブジェクトの場合は、その Write メソッドは、XPS ドキュメントファイル(.xps)に書き出す機能になる。
従って、このスレッドで取り上げた当初の問題の位置からの、Printer での印刷を問題にする限りは、今まで私が追いかけてきた XpsDocument ルートということは、問題にはならないということです。問題にはならないことを追いかけてきてしまいました。そのことがわかりました。
このことに気づき、私は上記の XpsDocument ルート のコードを実行して、自分のプロジェクトフォルダーの ¥bin¥Debug を開いてみました。 果たして MyFirstXpsDoc.xps ファイルがそこにできておりました。これを消してもう一度実行。やはり、またできます。
すなわち、私が XpsDocumentWriter ルートと言ってきたものは、『XpsDocument.xps ファイルを書き出すコード』として正しく動いていたということになります。
以上で、全面解決です。このメッセージでは、独り相撲をとってしまいました。しかし、すっきりしました。
大変御迷惑をおかけいたしました。
-------------------------------------------------------------------------------------------------------------------------------
私が、気づいて、この解決部分を追加する作業を終えてサインアウトいたしましたら、同時だったようで、稍丼様のお答えがこの下に既に出ておりました。同じことが書かれておりました。稍丼様、どうもありがとうございました。この理解でよいことの確認になりました。この場でお礼申し上げます。
稍丼様のお答えが出た以上、私の記載は重複になるので、消してしまおうかとも考えたのですが、ライブラリの説明を補う意味があるかとも思われますので、私のような誤解をされないように、このまま残すことに致します。 -
下の XpsDocument クラスを使ったものは,
XPSファイルを出力するためのコードです。要するに,
MyFirstXpsDoc.xps というファイル名のXPSファイルを
作成するためのコードです。
実際は,@"MyFirstXpsDoc.xps" という引数は,
作成したい場所のちゃんとしたパスも含めて指定します。
ファイルにしてとっておく必要がないのなら,
最初から,PrintQueue の CreateXpsDocumnetWriter(...) から取得した
XpsDocumentWriter の Write(...) メソッドで書き込めばOKです。
つまり,
同じ XpsDocumentWriter の Write(...) メソッドなのに,
PrintQueue の CreateXpsDocumentWriter(...) から返ってきた XpsDocumentWriter(のインスタンス)と
XpsDocument の CreateXpsDocumentWriter(...) から返ってきた XpsDocumentWriter(のインスタンス)とでは,
Write(...) した時の実際の動作は異なっているということです。
同じ XpsDocumentWriter 型だけど,
プリンタに書き込むか,ファイルに書き込むかというカラクリが入っているわけです。
ファイルに書き込むほうはファイルに書き込むのだからプリンタは動かないわけです。
[追記] 解決したようなのですが,書いてしまったのでそのままにしておきます。
-
[メモ]
XPSファイルを開いて印刷 or DocumentViewer に表示するのなら,開くのに XpsDocument クラスを使って,
コード ブロックstring filePath = ... ;
using( XpsDocument doc = new XpsDocument(filePath, FileAccess.Read))
{
FixedDocumentSequence fds = doc.GetFixedDocumentSequence();
//IDocumentPaginatorSource pagenator = doc.GetFixedDocumentSequence();
// 以下略
}
FixedDocumentSequence は,FixedDocument の親玉で,かつ,
FixedDocument と同様に
IDocumentPaginatorSource インターフェイスを実装しています。
なので,
WPFウィンドウに,DocumentViewer をツールボックスからドラッグしておけば,
コード ブロックdocumentViewer1.Document = doc.GetFixedDocumentSequence();
とセットすれば,(印刷しなくても)確認できます。
ただ,仕組みはややこしくて,DocumentReference クラスが クッション で入るので
FixedDocumentSequence
└ DocmentReferenceCollection
├DocumentReference --> FixedDocument
├DocumentReference --> FixedDocument
├DocumentReference --> FixedDocument
:
となっています。なので,
FixedDocumentSequence から FixedDocument を取得するには,
FixedDocumentSequence の References プロパティ経由になります。
PrintDocumentImageableArea の プロパティの参考図