Visual Basic 初級講座 |
Visual Basic 中学校 > 初級講座 >
第35回 印刷
VBの機能だけを使った印刷について説明します。プリンタがない環境でもプレビューを使って試すことができます。本格的な印刷にはサードパーティー製のコンポーネントを使用することをお勧めしますが、VBの印刷機能でもある程度のことはできますし何より手軽さが嬉しいです。
この回の要約 ・印刷内容はPrintDocumentコントロールのPrintPageイベントで書き込む。 ・PrintPreviewDialogコントロールを使うと簡単にプレビューできる。 ・複数のページを印刷する場合はHasMoewPageプロパティを使用する。 ・テキストファイルを印刷するときは自動改行、自動改ページなどに注意する必要があって意外と大変。 |
今回は早速印刷を行うプログラムを作りましょう。印刷を行うためにはプリンタが必要ですが、プログラムを試すだけならプレビューを使用することもできます。
まず、フォームに以下のコントロールを配置してください。
名前 | 種類 | 読み方 |
btnPrint | Button | |
btnPreview | Button | |
PrintDocument1 | PrintDocument | プリントドキュメント |
PrintPreviewDialog1 | PrintPreviewDialog | プリントプレビューダイアログ |
■表1
PrintDocumentとPrintPreviewDialogはフォームに貼り付けようとしてもフォームには貼りつかないで下のコンポーネントトレイと呼ばれる領域に貼りつきます。
■画像1:フォームに配置したところ。PrintDocumentとPrintPreviewDialogは下にあるコンポーネントトレイに配置される。
今回はPrintDocumentが主役です。PrintPreviewDialogはプレビューを担当します。
コントロールを配置したらプロパティウィンドウを使ってPrintPreviewDialog1のDocumentプロパティ(読み方:Document = ドキュメント)にPrintDocument1を指定しておいてください。これによってプレビュー画面と印刷内容が関連付けられます。
用意ができたら次の通りにプログラムしてください。
Private Sub
btnPrint_Click(ByVal
sender As
System.Object, ByVal
e As
System.EventArgs) Handles
btnPrint.Click PrintDocument1.Print() End Sub |
Private
Sub
btnPreview_Click(ByVal
sender As
System.Object, ByVal
e As
System.EventArgs) Handles
btnPreview.Click
PrintPreviewDialog1.ShowDialog() End Sub |
Private
Sub
PrintDocument1_PrintPage(ByVal
sender As
System.Object, ByVal
e As
System.Drawing.Printing.PrintPageEventArgs)
Handles
PrintDocument1.PrintPage
Dim f As New Font("MS 明朝", 64, FontStyle.Bold) e.Graphics.DrawString("こんにちは", f, Brushes.Red, 10, 10) End Sub |
■リスト1:単純な印刷
フォント名を指定している「MS 明朝」の部分には注意してください。MSは全角でその後のスペースは半角で記述する必要があります。
このプログラムではbtnPrintをクリックすると印刷を実行し、btnPreviewをクリックするとプレビューを表示します。
どちらも 実行すると次のようなものが印刷されるはずです。
■画像2
上記のプログラムでは余白が考慮されていないため実際に印刷すると左側と上側が欠けて印刷されるかもしれません。余白については後で説明します。
それにしても、これだけで印刷とプレビューができてしまうのですからたいしたものです。さて、次からはこのプログラムの仕組みについて説明しもっといろいろな印刷例を紹介することにします。
補足説明 -
プリンタのない環境で印刷をテストする方法 本文中でも説明したようにプレビューを使用すればプリンタがなくても印刷プログラムを試すことができますが、特殊なソフトをインストールすると本物の印刷まで試すことができるように成ります。 Adobe社のAdobe Acrobatは特に有名で、PDF Writerという仮想プリンタを使用して印刷の結果としてPDFファイルを作成することができます。 他にはMicrosoft社のOffice System 2003にはOffice DocumentImage Writerという仮想プリンタが付属しています。Office System 2003を持っている方はプリンタの一覧にこのプリンタが表示されているか確認してみてください。もし表示されているようなら、このプリンタにを使って印刷を実行してみてください。印刷の結果としてmdiファイルが作成されます。 |
印刷する方法を一言で説明すると「PrintDocument.Printメソッドを使う」ということです。そして、何をどのように印刷するかのプログラムはPrintDocumnetのPinrtPageイベント(読み方:PrintPage = プリントページ)に記述することになります。
このときに具体的に印刷内容の書き込みに使用するのがGraphicsクラスです。Graphicsクラスは初級講座の第1回〜第3回でよく登場したクラスなのですが覚えているでしょうか?そのときは、画面にいろいろな図形や画像を描画する目的で使用しました。今回は「画面に」という部分が「紙に」に変るだけでGraphicsクラスの使い方は全く同じです。
上記の例では文字を書き込むのでDrawStringメソッド(読み方:DrawString = ドロゥストリング)を使用しています。このメソッドは初登場ですが、文字を書き込むのに使用するフォントとブラシと座標を指定する必要があります。フォントを指定するにはFontクラス(読み方:Font = フォント)を使用します。
上記の例では「MS 明朝で64ポイントで太字」というフォントを設定して、赤いブラシ(Brushes.Red)で左から10、上から10の位置に「こんにちは」と描画しています。
Graphicsクラスの使い方さえわかっていれば、画像や図形を書き込むこともできます。
次の例では、いろいろな図形を印刷します。
Private Sub
btnPrint_Click(ByVal
sender As
System.Object, ByVal
e As
System.EventArgs) Handles
btnPrint.Click PrintDocument1.Print() End Sub |
Private Sub btnPreview_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnPreview.Click PrintPreviewDialog1.ShowDialog() End Sub |
Private Sub PrintDocument1_PrintPage(ByVal sender As System.Object, ByVal e As System.Drawing.Printing.PrintPageEventArgs) Handles PrintDocument1.PrintPage Dim P1
As
New Pen(Color.Blue, 20) P1.DashStyle = Drawing2D.DashStyle.DashDotDot e.Graphics.FillRectangle(B1, 10, 10, 200, 200)
'正方形(赤いグラデーション) End Sub |
■リスト2:図形の印刷
実行結果は次のようになります。
■画像3
PrintPageイベントは印刷を制御する重要なイベントです。特に引数であるPrintPageEventArgsクラス(読み方:PrintPageEventArgsクラス)の役割は重要です。
このクラスはプリンタに描画するGraphicsクラスへのアクセスを提供するだけでなく、いくつかの重要な役割があります。このクラスの主なメンバを表にまとめてみます。
プロパティ | 読み方 | 機能 |
Cancel | キャンセル | |
Graphics | グラフィックス | |
HasMorePages | ハズモアページス | 複数ページの印刷を制御します。 |
MarginBounds | マージンバウンズ | 余白を除いた印刷可能領域を表します。 |
PageBounds | ページバウンズ | 余白を含む印刷可能領域を表します。 |
PageSettings | ページセッティングス | ページの設定を表します。 |
■表2:PrintPageEventArgsの主なメンバー
このうち最も重要なのは既に登場したGraphicsプロパティです。HasMorePagesプロパティは複数ページ印刷する場合の制御に使用します。
それではHasMorePagesプロパティを使った複数ページにに渡る印刷について説明しましょう。次の例では3ページに渡って印刷を行います。
Dim CurrentPage As Integer '現在のページ数を記録する |
Private Sub
btnPrint_Click(ByVal sender
As System.Object, ByVal
e As System.EventArgs)
Handles btnPrint.Click PrintDocument1.Print() End Sub |
Private Sub btnPreview_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnPreview.Click PrintPreviewDialog1.ShowDialog() End Sub |
Private Sub PrintDocument1_PrintPage(ByVal sender As System.Object, ByVal e As System.Drawing.Printing.PrintPageEventArgs) Handles PrintDocument1.PrintPage CurrentPage += 1 Dim f As New Font("MS 明朝", 64, FontStyle.Bold) Select
Case CurrentPage End Sub |
■リスト3:複数ページの印刷
このサンプルでは現在印刷中のページは変数CurrentPageで管理します。このようにページに関する管理は自動では行われないのですべて自分で管理する必要があります。
PrintPageイベントプロシージャ内では各ページごとに印刷するテキストが変るようになっていますが、同時にHasMorePagesプロパティにも値がセットされていることに注意してください。
1ページ目と2ページ目を印刷するときにはHasMorePagesプロパティにTrueをセットしています。このため、PrintPageイベントが終了した後でもう一度PrintPageイベントが発生します。3ページ目を印刷するときにはHasMorePagesプロパティにFalseをセットしているのでもうPrintPageイベントは発生しません。つまり、これで印刷が終了するということです。
実のところ、HasMorePageプロパティの初期値はFalseなので、何も設定しなければ自動的にそのページが最後のページとなりますからFalseの値をわざわざセットする必要はないのですが、この例のように複数ページ印刷する場合は最後のページであるという指定が人間にとって明確になるようにわざわざFalseをセットするのがわかりやすいプログラムというものです。
ところで、このような複数ページ印刷の方法を見て疑問に思われた方もいらっしゃると思いますが、どんなにたくさん印刷を命令してもページからはみ出している分はすべて無視されます。次の例では100行分の文字を描画しますが、実際に印刷されるのは紙1枚分です。
Private Sub
btnPrint_Click(ByVal
sender As
System.Object, ByVal
e As
System.EventArgs) Handles
btnPrint.Click PrintDocument1.Print() End Sub |
Private Sub btnPreview_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnPreview.Click PrintPreviewDialog1.ShowDialog() End Sub |
Private Sub PrintDocument1_PrintPage(ByVal sender As System.Object, ByVal e As System.Drawing.Printing.PrintPageEventArgs) Handles PrintDocument1.PrintPage Dim f
As
New Font("MS 明朝", 64,
FontStyle.Bold) For i = 0
To 99 End Sub |
■リスト4:はみ出した部分は印刷されない
この中で行の縦位置を計算している部分だけは役に立ちます。どんなときでも1行目は適当な場所に印刷すればよいのですが2行目はそうはいきません。1行目の下になるように印刷しなければいけませんが、「下」と言ってもフォントの大きさによって具体的な位置が変ってきます。FontクラスのGetHeightメソッド(読み方:GetHeight = ゲットヘイト)を使うとそのフォントの高さが取得できるのでこれを元に次の行の縦位置を計算できるのです。
複数ページに渡って印刷する例はもう少し後で紹介します。
次に余白です。余白と複数ページ印刷の方法さえわかれば後は工夫次第で好きなように印刷できるようになります。
といっても話は簡単で、MarginBoundsプロパティを使うだけで余白を除いた印刷可能領域を取得することができます。
次の例では印刷可能領域いっぱいに広がるように図形を描画します。
Private Sub
btnPrint_Click(ByVal
sender As
System.Object, ByVal
e As
System.EventArgs) Handles
btnPrint.Click PrintDocument1.Print() End Sub |
Private Sub btnPreview_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnPreview.Click PrintPreviewDialog1.ShowDialog() End Sub |
Private Sub PrintDocument1_PrintPage(ByVal sender As System.Object, ByVal e As System.Drawing.Printing.PrintPageEventArgs) Handles PrintDocument1.PrintPage Dim X
As
Integer =
e.MarginBounds.X e.Graphics.DrawRectangle(Pens.Black, e.MarginBounds) End Sub |
■リスト5:印刷可能領域いっぱいに広がるように図形を描画
印刷結果は次の通りです。
■画像4
印刷時には印刷する文字や図形が常にこのMarginBoundsの領域内に収まるように注意しましょう。
なお、余白を自分で設定することもできます。次の例では余白をなしにします。
Private
Sub
Button1_Click(ByVal
sender As
System.Object, ByVal
e As
System.EventArgs) Handles
Button1.Click PrintDocument1.DefaultPageSettings.Margins =
New Printing.Margins(0,
0, 0, 0) End Sub |
■リスト6
ただし、たいていのプリンタには物理的な限界があり余白をまったくなくしてしまうことはできません。いくらプログラムで指定してもプリンタの性能の限界を超えることはできませんから注意してください。
必要な知識も出そろったところで基本的なテキストファイルの印刷をプログラムしてみましょう。
テキストファイルの印刷のような地味なプログラムに興味がある方はいらっしゃらないと思いますが、ここにはVBでの印刷の基本がぎっしりとつまっていますからおろそかにできません。
難しいのは文字が紙をはみ出さないようにすることなのです。文字が途切れないようにうま改ページする必要がありますし、1行が長すぎると今度は横にはみ出してしまいますから適当なところで改行する必要もあります。そして、それらはすべて余白を考慮した長方形の中に印刷しなければなりません。
まず、改ページ・改行を一切考慮しないで単純にテキストファイルの印刷をプログラムしてみます。これには既に説明した印刷方法と前に説明したファイルの読み込みを組み合わせるだけです。やる気にあふれている方は自分でチャレンジしてみてください。
次のようになります。
Private Sub
btnPrint_Click(ByVal
sender As
System.Object, ByVal
e As
System.EventArgs) Handles
btnPrint.Click PrintDocument1.Print() End Sub |
Private Sub btnPreview_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnPreview.Click PrintPreviewDialog1.ShowDialog() End Sub |
Private Sub PrintDocument1_PrintPage(ByVal sender As System.Object, ByVal e As System.Drawing.Printing.PrintPageEventArgs) Handles PrintDocument1.PrintPage Dim Reader
As
New IO.StreamReader("C:\Program
Files\Common Files\System\Ole DB\MSDASQLreadme.txt",
System.Text.Encoding.GetEncoding("Shift-JIS")) Do
While Reader.Peek >= 0 Reader.Close() End Sub |
■リスト7:何も考えないでテキストファイルを印刷する例
対象のテキストファイルは適当なものを使用していますのでみなさんの環境にあったものに変更してください。日本語で書いてあって印刷すると2、3ページくらいになるものがちょうど良いです。適当なファイルがなければ自分で作成した方が楽かもしれません。
また、このサンプルでは、見やすいようにわざとフォントサイズを大きくしてありますが、実際にテキストを印刷する場合はフォントサイズは10前後に設定するのが普通です。フォントサイズなどの印刷に関する設定については次回に詳しく取り上げる予定です。
さて、このプログラムを実行すると次のように印刷されます。
■画像5
あきらかに行が途中で切れていますし、続きがあるはずなのに2ページ目以降は印刷されません。何も考えないで印刷を命令するとこういう結果になるのです。
DrawStringメソッドに詳しい方は描画しようとしているテキストが指定した四角形の中に納まるように指定できる別のオーバーロードを使えばうまくいくように思われるかもしれませんが、その方法だと複数ページ印刷の場合にうまく対応できません。次に複数ページ印刷について説明します。
まずはこのプログラムを改造して、複数ページ印刷されるようにしてみます。これもいままで説明した知識を組み合わせるだけですからやる気のある方はサンプルを見る前に自分でチャレンジしてみてください。
Dim Reader As IO.StreamReader |
Private Sub
btnPrint_Click(ByVal
sender As
System.Object, ByVal
e As
System.EventArgs) Handles
btnPrint.Click PrintDocument1.Print() End Sub |
Private Sub btnPreview_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnPreview.Click PrintPreviewDialog1.ShowDialog() End Sub |
Private Sub PrintDocument1_PrintPage(ByVal sender As System.Object, ByVal e As System.Drawing.Printing.PrintPageEventArgs) Handles PrintDocument1.PrintPage If
IsNothing(Reader) Then Dim
f As New Font("MS明朝",
36, FontStyle.Bold) Do While Reader.Peek >= 0 TextLine
= Reader.ReadLine
'テキストから1行読み込む
'現在のページに次の行を印刷できるだけスペースがあるか確認する。 Loop If
e.HasMorePages = False
Then End Sub |
■リスト8:改ページしながらテキストファイルを印刷する例
複数ページ対応にしただけで結構長くなってしまいました。まず、1ページにつき1回PrintPageイベントが発生しますから、PrintPageイベント内でファイルを開くのはうまくありません。そこで変数Readerをプロシージャの外に出しました。ファイルを開くのは1ページ目のときだけでファイルを閉じるのは最後のページのときだけです。
ページに続きがあるかどうかの判断は次の2つを組み合わせます。
・テキストファイルに、まだ印刷していない行がある。
・次の行を印刷すると、印刷領域をはみだしてします。
この2つが両方とも真(※1)である場合には次のページがあるということになります。
※1:真:TrueとかYesとか「はい、そうです」といったことを意味する漢字。「偽」の反対語。
テキストファイルにまだ印刷していない行があるかはStreamReaderクラスのPeekメソッドでわかります。次の行を印刷すると印刷領域をはみだすかどうかはY + LineHeight > e.MarginBounds.Y + e.MarginBounds.Heightで判断します。
ここの部分は少しわかりにくいので図を使って説明します。
■画像6
この図で赤い枠が余白の境界です。印刷は赤い枠の内側にしなくてはなりません。さて、この図は「つつつ…」の行を印刷した後の縦位置の計算を表しています。プログラムではDrawStringの次の行でY += LineHeightを実行しています。このY += LineHeightを実行した直後の状態がこの図です。
この図を見ると 次に印刷する「ててて…」の行は赤い枠からはみだしてしまいますから、「ててて…」の行は次のページの先頭に印刷しなくてはいけません。図を見れば「ててて…」の行が赤い枠からはみ出してしまうことはすぐわかりますが、プログラムではこれを式で表さなくてはいけません。
まず、「ててて…」の行の下の部分の縦位置は図にあるとおりでY + LineHeightで表すことができます。そして、赤い枠の下の部分の縦位置は、赤い枠が余白の境界であることを考えると、e.MarginBounds.Y + e.MarginBounds.Heightで表すことができます。
そこで、次の行が赤い枠をはみ出すかどうかの判断はさきほど登場したY + LineHeight > e.MarginBounds.Y + e.MarginBounds.Heightという式になるわけです。
さて、複数ページの印刷ができるようになったところで自動改行をプログラムして印刷を完全なものとしましょう。
現在の状態では、右側の余白は一切無視していますし同じ行で紙に印刷しきれない分は切り捨てられてしまっています。1行で印刷しきれない分は自動的に改行してあげるのが親切というものです。
これを実行するには常に横の幅を気にしながら印刷しなければなりません。さきほどの改ページのプログラムのところで常に縦位置を気にしながら印刷したのと似ています。
このプログラムはなれていない人には難しいと思います。次のようになります。
Dim Reader As IO.StreamReader |
Private Sub
btnPrint_Click(ByVal
sender As
System.Object, ByVal
e As
System.EventArgs) Handles
btnPrint.Click PrintDocument1.Print() End Sub |
Private Sub btnPreview_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnPreview.Click PrintPreviewDialog1.ShowDialog() End Sub |
Private Sub PrintDocument1_PrintPage(ByVal sender As System.Object, ByVal e As System.Drawing.Printing.PrintPageEventArgs) Handles PrintDocument1.PrintPage If
IsNothing(Reader) Then Dim f
As
New Font("MSP明朝",
36, FontStyle.Bold) Do TextLine =
"" '▼1印刷行を作成 'テキストファイルの次の1文字を調べる '次の文字がないならファイルの最後なので終了 '次の文字が改行の場合 '印刷するのに必要な幅を計算 '幅が印刷可能領域をオーバーする場合 Reader.Read() '現在位置を1文字すすめておく TextLine &= Convert.ToChar(iLetter) '文字を印刷行に追加する Loop If Len(TextLine) = 0
AndAlso iLetter < 0
Then '▼印刷実行 e.Graphics.DrawString(TextLine, f, Brushes.Black, X, Y) '▼改ページ判定 Y += LineHeight '縦位置を1行分次に進ませる。 '現在のページに次の行を印刷できるだけスペースがあるか確認する。 Loop If
e.HasMorePages = False
Then End Sub |
■リスト9:改行・改ページしながらテキストファイルを印刷する例
大分複雑になってしまいました。もっとシンプルなやり方を知っている方がいらっしゃいましたら教えて下さい。
ここでは、文字列の幅を計算するのにGraphicsクラスのMeasureStringメソッド(読み方:MeasureString = メジャーストリング)を使用しています。ファイルから1文字ずつ読み込んでいって幅を計算し、幅が印刷可能領域をオーバーした時点で1行分を印刷します。
オーバーした時点で印刷すると必ずオーバーしていることになりますから、実際に印刷するのはオーバーする前の状態の文字列です。
こういったことを実現するためにプログラムではStreamReaderクラスのPeekメソッドとReadメソッドを使い分けています。Peekメソッドは次の文字を調べるだけです。Readメソッドは次の文字を読み込んでカーソルを1文字分移動させます。だから、Peekで次の文字を調べておいてOKならReadし、OKでないときはReadしないという方法を使用しています。
実際にReadするまではカーソルが移動しないのでPeekは同じ値を取得し続けます。
さて、このプログラムが改ページのときの縦位置の計算に比べて複雑になるのはいくつか理由があります。1つは同じフォントでも文字によって幅が異なるという点です。ですから、実際に印刷する文字列の幅を調べないと領域内におさまるかはみでてしまうか判断がつかないのです。
もう1つは1文字ずつ読み込む必要があるため制御文字を考慮する必要がある点です。VBからみると「改行」や「タブ」は文字なのです。しかし、印刷するときは「改行」は改行ですから、「改行」記号を発見したら改行しなければなりません。
これがIf iLetter = Asc(vbCr) OrElse iLetter = Asc(vbLf) Thenの部分です。さらに、通常の改行は2バイトあって、1バイト目をキャリッジリターン、2バイト目をラインフィードとしています。この2つは現在では単なる名前にすぎないのでその意味を知る必要はありません。キャリッジリターンは定数vbCr、ラインフィードは定数vbLfで表されます。
さらに両方まとめてvbCrLfという定数も用意されていますが今回は登場しません。
vbCrを発見した時点で1行分印刷してしまえば改行したことになるのですが、その次にvbLfが待ち受けているのでここでまた印刷してしまうとテキスト上は1行改行しているだけなのに2行改行することになってしまいますからvbCrを発見した場合はその次がvbLfかどうかも調べるようにしています。
また、改行を発見したときにReadするのを忘れてはいけません。こうしないとカーソルが次にすすまないで無限ループとなってしまいます。
なんだかテキストファイル1つ印刷するだけでずいぶん大変です。そして、このプログラムでは英語の文書はうまく印刷できません。英語の文書の場合は、単語の途中で改行しないように調節しなければいけないからです。学校の英語の教科書を開いてよく見てもらえばわかると思いますが、英語の文書は単語の途中で改行しないで済むように、行ごとに文字間や単語間のスペースの幅を変えて調節しています。そして、どうしても単語の途中で改行しなければならない場合でも、単語の音節の区切りで改行されるように調節し、改行するところにハイフンを挿入することになっています。
このような処理をどのようにプログラムで実現するのか途方にくれてしまいます。
今回は最後のテキストファイルの印刷はなかなか大変ですが、単に印刷するだけならそれほど難しくないことがわかっていただけたと思います。また印刷するときに注意しなければいけない改行や改ページの問題とその解決策も示しました。
本格的な複雑な印刷を行うには、やはり市販のツールを使用するのが良いと思います。今回説明した印刷方法は単純な印刷機能を自分のアプリケーションに装備するのには向いていますが、それ以上の用途には向きません。
市販のツールにはCrystal Reports(読み方:Crystal Reports = クリスタルレポート)やActive Reports(読み方:Active Report = アクティブレポート)などさまざまなものがあります。値段が高いので個人ではなかなか変えないのですが、WEBで評価版がダウンロードできたりするので興味がある方は試してみてください。