Visual Basic 業務アプリ構築法 第28回

印刷処理のコントロールを作る
長谷川裕行
2000/08/08

長谷川 裕行 (はせがわ ひろゆき)
有限会社 手國堂 代表取締役
http://www.hirop.com/
テクニカルライターとして活躍。プログラミングに関する著書多数、DB Magazineなどにも多くの記事を提供している。

業務アプリケーションでは印刷処理が必須です。電子帳票化が進んではいますが、やはり最終的には「紙に定着して保存する」ことになります。一覧表やちょっとした資料でも「紙に印刷されていなければ安心できない」人はまだまだ大勢います。
VBでは、印刷処理自体は非常に単純ですが、データベースのレコードを効率良く印刷し、なおかつプログラムの全体構造も保守しやすいものにしておく必要があります。テーブル内のレコードを印刷する処理を作ってみましょう。


フォームとコードの再利用

既に作ったプログラムの一部が、他のプログラムに流用できることはよくあります。特に基本構造が似通ってくる業務処理では、既存処理を再利用することで開発効率は大きく向上します。


- パターン化された処理は再利用できる -

前回は「映画データベース」を例に、アプリケーションの初期化処理を標準モジュールにまとめる方法を紹介しました。データベースを使ったアプリケーションでは、データベースの接続、テーブルのオープン、フィールドとコントロールとの連携など、初期化に必要な処理の大半がパターン化できます。  パターン化できる部分を標準モジュールとしてまとめておけば、データベース名などを書き換えるだけで他のアプリケーションにも流用できます。既存資源の有効利用という意味では、クラス化、ActiveX化などの手法もありますが、コードをそのままコピー&ペーストで利用するのは、最も簡単かつ手軽な再利用の手段です。


- フォームがあるとコピー&ペーストは難しい -

実用性の面では、再利用したいコードをクラス化してしまう方が有効でしょう。しかし、汎用性を持たせる必要があるため、設計に手間がかかります。データベースの接続など単純でほぼ決まり切った処理は、わざわざクラスを設計するよりコードをそのままコピーして書き直した方が、手っ取り早いでしょう。
コードの再利用は、クリップボードを使ってコピーし、データベース名や変数名などを書き換えれば簡単です。しかし、簡単なのはあくまで「コードのみを再利用」するためです。フォームが存在する場合は話が違ってきます。
フォームの存在する処理では、フォーム上のコントロールのオブジェクト名がコード内に至る所に登場するため、コードだけを簡単に書き換える訳にはいきません。コントロールに対応したイベントプロシージャでは、プロシージャ名自体がコントロール名に依存しています。
コピー&ペーストで再利用できるのは、フォームを持たず、なおかつ単純な構造の処理だけだと捉えておきましょう。


- ユーザーコントロールが便利 -

フォームの存在する処理を再利用するには、ユーザーコントロールを作るのが最も簡単です。今回は、「映画データベース」の印刷処理を例に、ユーザーコントロールの作り方と扱い方を紹介します。
今回使用するサンプルは、前回紹介したものと同じです。
映画データベースでは、メインフォームの[印刷]ボタンをクリックすると、画面1のように「データの一覧印刷」ダイアログボックスがオープンします。ここで[印刷]ボタンをクリックすると、プリンタの準備を促すメッセージボックスがオープンし、[OK]をクリックすれば印刷が始まります。
印刷結果は画面2のようになります。処理を簡単にするために、プリンタは「現在の標準プリンタ」に固定してあります。用紙はA4判・縦位置です。

画面1:データの一覧を印刷する
画面2:印刷結果


印刷処理をコントロール化する

印刷処理の流れを簡単に示しておきましょう。

(1) メインフォームの[印刷]ボタンをクリック
→印刷用ダイアログボックスがオープンする
(2) 印刷用ダイアログボックスの[印刷開始]ボタンをクリック
→プリンタ準備を促すメッセージボックスがオープンする
(3) メッセージボックスの[OK]ボタンをクリック
→ユーザーコントロールを介して印刷が行われる
  以下、個々の処理を、具体的に説明していきます。


(1) メインフォームの[印刷]ボタンをクリック
→印刷用ダイアログボックスがオープンする

- レコードセットを受け取る -

メインフォームの[印刷]ボタン(オブジェクト名:cmdPrint)をクリックすると、リスト1のようなイベントプロシージャ“Private Sub cmdPrint_Click()”が実行されます。
“datMovie”は、メインフォームに貼り付けたDataコントロールです。これには、初期化処理でデータベース“Movie.mdb”のテーブル“映画”が接続されています。リスト2が、メインフォームの初期化処理“Form_Load”のコードです。
datMovie.DatabaseName = strDbPath
datMovie.RecordSource = TABLE_NAME
“strDbPath”がデータベースファイルのパスを示すグローバル変数、“TABLE_NAME”がテーブル名を示す記号定数です。これらは標準モジュールで定義されています。
こうしてテーブルが接続されたDataコントロール“datMovie”のレコードセットを、
Set frmPrint.rsMovie = datMovie.Recordset として印刷用フォーム“frmPrint”のレコードセットオブジェクト“rsMovie”にコピーしています。
リスト3が、印刷用フォーム“frmPrint”の宣言セクションです。ここで
Public rsMovie As Recordset
としてレコードセットオブジェクト型の変数を宣言しています。


- コントロールの重複は避ける -

複数のフォームで同じデータベースの同じテーブルをアクセスする場合、各フォームにDAOまたはADOなどのデータコントロールを貼り付ける人も多いようですが、同じ役割を持つコントロールを、わざわざ複数貼り付けるのは面倒です。また、接続の手続きなどが重複するため、間違いも入り込みやすくなります。
それより、この例のようにレコードセットを示すオブジェクト変数だけを用意し、既に接続されているデータコントロールのレコードセット――Recordsetプロパティの値(レコードセットへの参照)を代入した方が簡単で確実です。メモリの消費もわずかで済みます。
そして、最後に
frmPrint.Show 1
として、印刷用のダイアログボックス(frmPrint)をオープンします。

画面3:印刷用フォーム(frmPrint)のデザイン
リスト1:メインフォームの[印刷]ボタンがクリックされたときの処理
リスト2:メインフォームの初期化処理
リスト3:印刷用フォーム“frmPrint”の宣言セクション


(2) 印刷用ダイアログボックスの[印刷開始]ボタンをクリック
→プリンタ準備を促すメッセージボックスがオープンする

- メソッドを呼び出す -

印刷用ダイアログボックスのフォームfrmPrintで[印刷開始]ボタン(オブジェクト名“cmdPrintStart”)がクリックされると、リスト4のようなプロシージャが実行されます。
単にMsgBox関数でメッセージボックスをオープンし、ユーザーが[OK]をクリックしたらプロシージャ“PrintOut”が実行されるだけです。
実際の印刷処理は、プロシージャ“PrintOut”で行われますが、これもリスト5のように1行だけの単純なコードです。
画面3を見ればお分かりのように、フォームには印刷用のユーザーコントロールが貼り付けてあります。オブジェクト名は“usrPrint1”です。
usrPrint1.DoPrint rsMovie
の1行は、その印刷用ユーザーコントロール“usrPrint1”の“DoPrint”メソッドを、 レコードセットオブジェクト“rsMovie”を引数にして呼び出しているだけです。
つまり、ユーザーコントロールの“DoPrint”メソッドに、印刷対象とするレコードセットを引き渡していることになります。



- メモリの解放 -

リスト6は、このフォームが閉じられるときに必ず実行される“Form_Unload”プロシージャです。
レコードセットオブジェクト“reMovie”に“Nothing”を代入し、メモリを解放しています。

リスト4:印刷用フォームfrmPrintの印刷開始処理
リスト5:印刷用フォームfrmPrintの実際の印刷処理
リスト6:クローズボタンでフォームを閉じた場合に対処しておく


(3) メッセージボックスの[OK]ボタンをクリック
→ユーザーコントロールを介して印刷が行われる

- プリンタの設定変更と復元 -

ユーザーコントロール“usrPrint”のフォームデザインは、画面4のようになっています。

画面4:印刷コントロール“usrPrint”のフォームデザイン
  このコントロールのフォームでは、単に印刷に使用するプリンタ名をラベルに表示するだけです。
印刷処理は、実際にはPrinterオブジェクトのPrintメソッドで標準プリンタに印刷用のデータを送ればよいだけなのですが、その前後にいくつかの処理が必要になります。なぜなら、アプリケーションによってプリンタの設定が変わるためです。
アプリケーション内でプリンタの設定を変更した場合、次に別のアプリケーションが同じプリンタを使用するときに、設定が変更されたままとなっています。そのため、印刷処理を終えたら元の設定に戻す必要があります。



- 共有プリンタでは特に重要 -

実際には、アプリケーションごとに、印刷の都度ユーザーにプリンタ設定の確認を促すため、このことにはあまり神経質になる必要はありません。が、例えばネットワークの共有プリンタで

AさんがアプリXでWebページを印刷した
Bさんが映画データベースで一覧を印刷した
---★
Aさんが再びアプリXで別のWebページを印刷した

というような場合、この映画データベースがプリンタの設定を変更して元に戻していないとすると、Aさんは★の箇所でプリンタの設定が変更されたことを知らないまま、2回目の印刷を行う可能性があります。直前に印刷を行っていると、プリンタの設定を確認しないまま[OK]ボタンをクリックすることはよくあります。
このときアプリXの印刷機能が、常に「標準的なプリンタ設定」であることを前提として設計されていたら、Aさんは1回目と2回目で異なる結果を得ることになるかもしれません。
そのような事態を防ぐため、プリンタは使ったら元に戻しておくようにします。


- 設定保存と復元の手順 -

プリンタを元の状態に戻すには、以下のような手順を採ります。

(1)現在の設定を保存する
(2)設定を変更する
(3)印刷する
(4)(1)で保存した設定に戻す

プリンタの状態は、Printerオブジェクトのプロパティから取得できます。表1に主なプロパティを掲げておきます。印刷は画面表示同様グラフィックスデータとしてプリンタドライバに送られるため、グラフィックス描画のためのプロパティが使用できます。
Printerオブジェクトのプロパティに関する詳細やメソッドについては、VBのヘルプまたはMSDNライブラリで確認してください。

表1:Printerオブジェクトの主なプロパティ
プロパティ 意味
Copies 印刷枚数
CurrentX 現在の印字位置X座標
CurrentY 現在の印字位置Y座標
DeviceName プリンタドライバの示す機種名
DriverName プリンタドライバ名
FillColor 塗りつぶし色。RGB値またはカラー定数で指定
FillStyle 塗りつぶし形式。VbFS定数で指定
Font 文字のフォント
Orientation 用紙方向。VbPRORPortrait(1):縦長、VbPRORLandscape(2):横長
Page 現在のページ番号
PageSize 用紙サイズ。VbPRP定数で指定
PrintQuality プリンタ解像度。VbPRPQ定数で指定
ScaleMode 座標系の単位。Vb定数で指定


- ユーザー定義型で保存する -

処理前に保存するプロパティは、ユーザーコントロールの中で設定を変更するものだけで構いません。個別に変数を用意すると混乱するので、構造体(ユーザー定義型)を用意します。
リスト7が、プリンタの状態を保存するための構造体“PrinterDefault”の定義です。

リスト7:プリンタの設定を保存する構造体の定義


- 設定の保存 -

宣言セクションでこの構造体型の変数を用意し、ユーザーコントロールの初期化時に変数にプリンタの現在の設定を保存します。
リスト8が宣言セクションのコードです。
Private pdOldPrinterSet As PrinterDefault
として、Privateなグローバル変数“ pdOldPrinterSet”を宣言しています。この変数に、プリンタの設定が保存されます。
その下のCONSTステートメントの連続は、実際の印刷処理で用いる印字位置(座標値)の記号定数です。

リスト8:グローバル変数の宣言と記号定数の定義


- 初期化処理で保存する -

リスト9が、このユーザーコントロールの初期化を行うInitializeイベントのプロシージャです。
“BackupPrinterSet”を呼び出してプリンタの現在の設定を保存し、ScaleMode、Fontの各プロパティを変更しています。
“BackupPrinterSet”のコードはリスト10のようになっています。構造体の各メンバ(構成要素)にPrinterオブジェクトのプロパティを代入するだけです。
印刷処理を終えたらリスト11の“RestorePrinterSet”が呼び出されます。このプロシージャは“BackupPrinterSet”の逆で、構造体のメンバの値をPrinterオブジェクトのプロパティに代入し、元の状態に戻します。

リスト9:コントロールの初期化時に現在の設定を保存する
リスト10:プリンタの現在の設定を保存する
リスト11:プリンタの設定を復元する


実際の印刷処理

実際の印刷処理についても、簡単に説明しておきましょう。


- 下請けプロシージャを呼び出す -

実際に印刷を行うのは、Publicキーワード付きで宣言したプロシージャ“DoPrint”です。Public付きのプロシージャはユーザーコントロールのメソッドとなります。引数にはレコードセットオブジェクト(への参照)を取ります。
コードを見れば分かるように、単に“PrintList”に引数を渡すだけです。

リスト12:ユーザーコントロールの印刷メソッド


- さらに下請けに委ねる -

“PrintList”では、リスト13のように用紙サイズを変更し、“A4ListPrint”を呼び出して印刷を行っています。その後“RestorePrinterSet”を呼び出して、プリンタの設定を復元しています。
ここでも、実際の印刷処理は“A4ListPrint”に送られています。このような二段構えにした理由は、用紙サイズの変更などを可能にするためです。ここではA4判に固定していますが、この段階で異なる用紙サイズの指定を受け入れ、それに合わせて異なる印刷処理を実行させることもできます。

リスト13:用紙サイズを設定して一覧印刷する


- 二重ループで印刷する -

リスト14に“A4ListPrint”のコードを掲げておきます。ここでは、Whileループでレコード件数分処理を繰り返し、さらにForループでリスト15の1ページ分のレコードを印刷するFunctionプロシージャ“PrintOnePage”を呼び出しています。
“PrintOnePage”は、規定の行数印刷すれば、Falseを返します。すると、A4ListPrintの内側のForループが終了し、次のページの印刷に移ります。
この他にもいくつかの下請けプロシージャが用意されていますが、サンプルのコードを読めばお分かりいただけるでしょう。具体的な処理の説明は省略します。

リスト14:A4判の用紙に印刷する処理
リスト15:1ページ分の印刷処理


実際の印刷処理

印刷処理に用いるその他の下請けプロシージャの概要を、簡単に紹介しておきます。

Sub PrintField(Data As Field)
1フィールド分のデータを印刷します。フィールドにはNull値が入っている場合があるため、それを空白(スペース)に変換しています。
Sub A4PrintTitle()
ページの先頭にタイトルを印刷します。
Sub A4PrintListHeader(iPage As Integer)
ページの先頭にヘッダ(項目見出し)とページ番号を印刷します。


レコードのフィールド数が多く印刷処理自体が複雑なために、コードが長くなっています。そのためユーザーコントロールの全体像が分かりにくくなっていますが、ここでのポイントは
プリンタの制御をユーザーコントロールとする
プリンタの現在の設定を保存し、最後に復元する
ユーザーコントロールにはレコードセットだけを渡す
の3点です。



DownloadVBプロジェクトファイルのダウンロード
(LZH形式 40.9KB)
Copyright © MESCIUS inc. All rights reserved.