この特集のトップページへ
Chapter 7:プレゼンテーション層の構築

7.4.7 顧客情報の印刷処理
●プレビュー処理

・各行の描画
 データグリッドコントロールに結び付けられているADODB.Recordsetオブジェクトの内容を読み取り,各行を処理するのは,283〜430行目のループ処理となる。

 まず,事前処理として,277行目でその時点のブックマークをoldBookMark変数に保存しておき,280行目でMoveFirstメソッドを呼び出してADODB.Recordsetオブジェクトのカレント行を先頭に移動させる。これにより,ADODB.Recordsetオブジェクトが保持するレコードを先頭から順に読み取ることができるようになる。

 MoveFirstメソッドを呼び出すまえにその時点のブックマークをoldBookMark変数に保存したのは,処理が終わったときにカレントレコードを元に戻すようにするためである。そうしないと,処理が終わったときには常にカレントレコードはレコードの先頭を指してしまう。つまり,ユーザーが[印刷]ボタンを押したあと,常にデータグリッドコントロールが先頭行を指すようになってしまうので,使い勝手が悪くなる。そこで,277行目でブックマークを保存しておき,この処理から戻る488〜451行目の部分において,保存しておいたブックマークを戻すようにしている。なお,448行目において,IsNull関数を使ってブックマークを保存しているoldBookMark変数がNullでないかどうかを確認しているのは,BookMarkプロパティにNullを格納すると実行時エラーが発生するためである。BookMarkプロパティは,カレント行の位置を示す値を返すものであるわけだが(「○[最新の状態に更新]ボタンの処理 」を参照),カレント行が有効でない場合――たとえば,レコードの先頭よりもさらにまえ(BOF)やレコードの末尾よりもさらにうしろ(EOF)――を指していた場合には,値としてNullが格納される。そこで,このように保存しておいたブックマークを戻すときには,Nullでないかどうかを判断する必要がある。

 話が前後したものの,283〜430行目のForループ処理が,1行ずつ行を取り出し,それを描画する箇所である。283行目にあるForステートメントのToの箇所を見るとわかるように,ADODB.Recordsetオブジェクトが保持している全レコード数はRecordCountプロパティに格納されている。


One Point!RecordCountプロパティは,すべてのADODB.Recordsetオブジェクトでサポートされるわけではない。たとえば,前方スクロールカーソルの場合にはサポートされない。

 290〜338行目の処理が,各ページの表の最上部にある列見出しを描画する部分となる。

 まず,297行目では,myLineプロシージャを使い,表の横線を描画している。1〜22行目に示したように,myLineプロシージャは,指定された2つの座標を結ぶ線分をLineメソッドを使って描画する。ただし,先に説明したmyPrintプロシージャと同様に,最後の引数がTrueであれば描画するが,Falseであれば描画せずにカレントポジションを移動するだけである。Lineメソッドを呼び出すと,カレントポジションは線分の終点に移動される。そこで,19行目と20行目にあるように,描画しない場合にはカレントポジションの位置を線分の終点に設定することで,Lineメソッドが呼び出されたときのカレントポジションの移動を擬似的に反映するようにしてある。

 表の横線を描画したら,見出し行を描画する。見出し行を描画する箇所は301〜337行目の処理である。

 301行目ではまず,データグリッドのColumnHeadersプロパティを参照し,データグリッドが見出し行を備えているかどうかを調べる。ColumnHeadersプロパティが見出し行を備えていればTrueを,そうでなければFalseを返す。もし見出し行を備えていなければ,列見出しを描画しないようにする。

 ちなみに,ColumnHeadersプロパティは,Visual Basicの開発環境においてデータグリッドのプロパティの[全般]ページで設定することができる(Fig.7-55)。

Fig.7-55 データグリッドのプロパティの[全般]ページ
fig7_55

 列見出しを備えていることを確認したら,次に列見出しのフォントを設定する(304〜310行目)。データグリッドの見出しフォントは,HeadFontプロパティに格納されている。HeadFontプロパティは,Visual Basicの統合環境においてデータグリッドのプロパティの[フォント]ページにて設定することができる(Fig.7-56)。

Fig.7-56 データグリッドのプロパティの[フォント]ページ
fig7_56

 厳密にいえば,わざわざ304〜310行目のようにしてHeadFontプロパティの内容を取得し,列見出しのフォントをデータグリッドのフォントと合わせるという処理はせず,適当なフォントを指定してしまってもよいと思われる。しかし,このような実装にしておくと,あとで列見出しのフォントを変更しようとしたときにプログラムを変更しなくても,Fig.7-56に示した設定だけで変更できるため,便利である。

 さて,列見出しを描画している箇所は,315〜324行目である。列見出しは,データグリッドの各列のCaptionプロパティを通じて取得できる。列見出しについては,すでに「○表示列を登録する」にて説明した。

 実際に見出し列を描画しているのは319行目である。ここでは,DrawBoxプロシージャを使って,Captionプロパティを通じて取得した列見出しの文字列を描画している。DrawBoxプロシージャ(45〜141行目)は,指定された文字列を,指定された幅に収まるように,指定された揃えで描画する,という機能を持つ。DrawBoxプロシージャは次のような書式をとる。

Private Function DrawBox(ByRef objDoc As Object, _
                         ByVal Text As String, _
                         ByVal ColumnWidth As Long, _
                         ByVal Alignment As Long, _
                         ByVal drawFlag As Boolean) As Long
     ' 指定された幅に収まるように折り返して,テキストを描画する
     ' 【引数】
     '   objDoc = 描画対象となるオブジェクト
     '              ピクチャボックスかPrinterオブジェクトのいずれか
     '   Text = 描画する文字列
     '   ColumnWidth = 描画幅
     '   Alignment = 文字揃え
     '                   dbgGeneral(標準。文字ならば左揃え,数値ならば右揃え)
     '                   dbgLeft(左揃え)
     '                   dbgRight(右揃え)
     '                   dbgCenter(中央揃え)
     '               のいずれか
     '   drawFlag = 実際に描画するかどうかのフラグ。
     '              描画するときはTrue,
     '              描画せずカレント位置を変更するだけのときにはFalse
     ' 【戻り値】
     '   描画したテキストの高さを返す

 319行目では,このDrawBoxプロシージャを次のようにして呼び出している。

ColHeight = DrawBox(objPic, _
                    g_DGrid.Columns(colIndex(col)).Caption, _
                    colWidth(col), _
                    g_DGrid.Columns(colIndex(col)).Alignment, _
                    drawFlag)

 第1引数は描画対象となるオブジェクトで,DrawPreviewプロシージャの第1引数に渡されたものをそのまま引き渡している。これについては問題ないだろう。

 第2引数は描画する文字列であり,ここではキャプションを描画したいため,データグリッドのColumnオブジェクトからCaptionプロパティを引き渡している。

 第3引数にはcolWidth配列を渡しているが,colWidth配列には先に説明した処理によって各列の幅が格納されている。つまり,DrawBoxプロシージャに対し,各列の幅に収まるように描画しろと依頼していることになる。

 第4引数は,文字の揃えを指定するものである。データグリッドコントロールの各列の揃え方は,ColumnオブジェクトのAlignmentプロパティに格納される。AlignmentプロパティはTable 7-15に示すいずれかの値をとる(GUIにおける設定についてはFig.7-42を参照)。

 第5引数は,描画するか否かを指定するもので,ここではdrawFlag変数の値を引き渡している。これにより,DrawBoxプロシージャにおいて,描画ページ範囲内であれば描画するが,そうでないときには描画しないという処理をする。

Table 7-15 データグリッドコントロールのAlignmentプロパティ
解説
dbgGeneralデフォルト値。文字ならば左揃え,数値ならば右揃え
dbgLeft左揃え
dbgRight右揃え
dbgCenterセンタリング

 ここで,DrawBoxプロシージャの実装について簡単に説明する。まず,64行目と65行目にあるOffsetX定数とOffsetY定数には,罫線から文字までの距離を指定するものとする。罫線と文字を同じ場所に描画すると,文字が非常に見づらくなるため,ここでは罫線と文字とのあいだに約1ミリメートル(56twip)の空白を設けることにした(ただし,罫線はDrawBoxプロシージャ内では描画しない。この設定は,カレントポジションからどれだけ離れた位置に文字を描画するのかという設定にすぎない)。

 文字列を描画しているのは,106〜135行目のDoループ内である。まず,108〜112行目のWhileループで,描画しようとしている文字列を1文字ずつ取り出し,第3引数に指定された幅を超えないかどうかをチェックする。108〜112行目のループでは,1文字ずつ取り出して,幅を超えたときにWhileループを抜けるようにしてある。そして,ループを抜けたら,120〜130行目の処理で指定された文字揃えの位置に応じて描画位置を変更したあと,132行目にあるmyPrintプロシージャを呼び出して,その文字列を描画する。もし,幅に入りきらない文字列があった場合には,1行改行してから再度同じ処理を実行する。そして,すべての文字列を描画し終えるまで106〜135行目をループし続けることにより,複数行に渡って折り返し描画するという処理を実現している。

 なお,DrawBoxプロシージャは描画した文字列の高さ(折り返しの必要が生じ複数行に渡ったときは,その全体の高さ)を返すようにしてある(140行目)。これにより,DrawBoxプロシージャを呼び出した側は,どれだけの高さが実際に描画されたのかを戻り値を通じて取得できる。

 では,DrawBoxプロシージャの説明は終わりにして,列見出しの描画処理に話を戻す。319行目でDrawBoxプロシージャを呼び出し,列幅に合うように描画するというところまで説明したのであった。

 DrawBoxプロシージャでは,描画した高さが戻り値として戻ってくることになる。具体的には,描画した全列のうち,最も高い高さをColMaxHeight変数に保存するという処理を実装してある(320〜332行目)。これにより,行の高さを決定することができる。

 行の高さが決まったら,326〜330行目の処理で縦線を描画する。そののち,335行目と336行目にあるように,カレントポジションのX座標を左余白位置に戻し,Y座標に対して描画した行の高さを加えることで,次の行に移動する処理に備える。

 見出し行の描画が終わったら,次に実際にADODB.Recordsetオブジェクトが保持している行を描画するという処理に入る。その描画処理は,340〜377行目の部分になる。まず,340〜345行目においては,横線を描画する。これについては問題ないだろう。

 次に350〜357行目において,描画するフォントの設定をする。データグリッドコントロールの各セルの表示フォントは,Fontプロパティに格納されている。FontプロパティもHeadFontプロパティと同様に,Visual Basicの統合環境において,データグリッドのプロパティの[フォント]ページで設定することができる(Fig.7-56を参照)。このセルのフォントも,列見出しのフォント設定と同様に,あえて設定する必要はなく,適当なフォントで描画してしまってもかまわない。しかし。データグリッドのフォント設定と合わせるようしておけば,あとでフォントを変更したいときにデータグリッドのフォント設定だけを変更すればすむため,便利になる。

 各列の内容を描画するのが,360〜369行目にあるForループである。この処理は,すでに説明した列見出しの描画処理(315〜324行目)とよく似ている。実際に,ADODB.Recordsetオブジェクトが保持するデータを描画している箇所は364行目であり,次のようになっている。

ColHeight = DrawBox(objPic, _
              g_DGrid.Columns(colIndex(col)).CellText(BookMark), _
              colWidth(col), _
              g_DGrid.Columns(colIndex(col)).Alignment, _
              drawFlag)

 すでに説明したとおり,DrawBoxプロシージャでは描画したい文字列を第2引数で指定するようにしてあるわけだが,ここでは,“g_DGrid.Columns(colIndex(col)).CellText(BookMark)”のように,ColumnオブジェクトのCellTextプロパティを通じて取得している点に注目したい。

 この部分は,ColumnオブジェクトのCellTextプロパティではなく,ADODB.RecordsetオブジェクトのFieldオブジェクトのValueプロパティを使うようにしてもよい。しかし,CellTextプロパティを使うと,データグリッドの表示書式で指定された文字列をそのまま取得できるというメリットがある。

 データグリッドの表示書式は,データグリッドのプロパティの[形式]ページで設定できる(Fig.7-57)。

Fig.7-57 データグリッドのプロパティの[形式]ページ
fig7_57

 たとえば,あるフィールドが日付型であり,データが“2000-06-24”であったとしよう。このとき,Fig.7-57のように“ggge年m月d日”という書式設定がされていた場合,データグリッドには,“平成12年6月24日”と表示される。印刷する場合にも,“2000-06-24”と印刷するのではなく,データグリッドコントロールに表示されているのと同じ書式に合わせ,“平成12年6月24日”と印刷したほうがよいだろう。そのような場合に,CellTextプロパティを使うのである。

 データグリッドコントロールのCellTextプロパティを使えば,データグリッドコントロールのセルに表示されている文字列をそのまま取得することができる。一方,データグリッドコントロールに結び付けられているADODB.RecordsetオブジェクトのFieldオブジェクトからValueプロパティの値を取得した場合には,日付型の生データしか取得できず,文字列に直すのは開発者の仕事になる。そのため364行目では,CellTextプロパティを使ったのである。

 ところでCellTextプロパティは,引数にブックマークをとり,そのブックマークの位置にあるフィールドの値を取得する。そこで,ループ処理に入るまえに描画対象となっている行のブックマーク位置をBookMark変数に格納しておき(359行目),その値をCellTextプロパティの引数として渡すようにした。

BookMark = g_DGrid.GetBookmark(row)

 GetBookmarkメソッドは,カレント行の位置から引数の位置まで移動した位置のブックマーク値を返すメソッドである。すでに280行目において,MoveFirstメソッドを呼び出してカレント行を先頭に移動しているので,これは先頭行からrowだけ進んだレコード,つまり,先頭からrow番目のレコードを示すブックマークということになる。row変数とは,283行目にあるように,Forループのループ変数を指す。つまり,Forループ処理内では,row変数の内容が「0」から「レコードの総数−1」まで順に変化するので,359行目の文はループのたびに,0行目のブックマーク,1行目のブックマーク,……という具合に,先頭のレコードから順に示すブックマークということになる。

 さて,各行の描画が終わったならば,371〜377行目にあるように縦線を引く。

 そして,380行目において,1ページの描画範囲を超えていないかどうかを判断する。1ページの描画範囲を超えたかどうかについては,カレントポジションのY座標が描画領域の下辺(用紙高さから下余白を引いたもの)を越えたかどうかによって判断している。


One Point! この改ページの判断基準は,かなり曖昧である。実際に指定された余白以上の行を描画してしまうことも否定できない。今回のサンプルでは,1レコードが複数行に渡ることもあるわけだが,もし一番下の行が折り返されてかなり高い高さを持つ場合,余白をはみ出て印刷されるおそれがある。なぜなら,折り返し処理中は改ページを判断せず,1レコード分を描画したあとで判断しているためである。よって,本サンプルでは,下余白の設定はかなり曖昧であると考えてほしい。本来ならば,DrawBoxプロシージャ内で折り返したときにも,下の余白をはみ出していないかどうかをチェックすべきである。しかし,そうすると処理が煩雑になるため,今回はそこまで踏み込んで実装していない。多少,下余白をはみ出て印刷されても,プリンタの印刷可能範囲を超えさえしなければ,帳票が途中で切れてしまうといったことはないので,実用上はこの処理でも問題ないだろう。

 もし,カレントポジションのY座標が描画領域の下辺を越えていた場合には,381〜417行目の処理を実行し,改ページの処理をする。384〜387行目の処理では,改ページしたときに描画ページ範囲を超えているならば,Forループを抜けるという処理をする。もし,まだ後続のページを処理する必要があれば,391〜397行目にあるように横線を描画する。そして,もし描画の対象がプリンタであれば,399〜403行目にあるようにNewPageメソッドを呼び出し,プリンタに対する改ページ処理をする。

objPic.NewPage

 NewPageメソッドは,プリンタで改ページするためのメソッドである。このメソッドが呼び出されると,プリンタは改ページする。

 次の399〜417行目は改ページ後の処理である。ここでは,ページ番号を増やし,カレントポジションを描画範囲の左上に移動している。この部分は,211〜220行目の処理とほぼ同じである。

 さて,283〜419行目の長いループを終えれば,ようやく描画の完了となる。描画が完了したときには,一番下に横線を描画する(423〜428行目)

 次に,430〜433行目の処理である。ここでは,描画対象がプリンタであった場合には,EndDocメソッドを呼び出し,印刷処理を終了させる。

objPic.EndDoc

 EndDocメソッドは,プリンタへの出力を終了するメソッドである。これにより,プリンタスプーラは閉ざされ,印刷が完了する。もし,EndDocメソッドを呼び忘れると,印刷は完了しないので注意してほしい。

 最後に,435行目で描画したページ数をDrawPreviewプロシージャの戻り値として設定し,437〜440行目においてカレント行を元に戻す処理をして,DrawPewviewプロシージャの処理は完了となる。

 なお,447行目以降は,エラーハンドラである。すでに201行目でOn Error Gotoステートメントを使い,もし実行時エラーが発生したときには,ErrHandleラベル以降に飛ぶように設定してある。

 基本的に,DrawPreviewプロシージャ内の処理ではあまり実行時エラーが発生する箇所はない。とはいえ,プリンタに出力するときには,時折実行時エラーが発生することがある。たとえば,ハードディスクの空き容量が足らなくて,プリンタスプーラの書き込みに失敗したようなケースである。

 実行時エラーが発生した場合,もしプリンタへの出力であったならば。448〜451行目にあるように,KillDocメソッドを呼び出して強制的に印刷を中止するようにした。

objPic.KillDoc

 KillDocメソッドは,プリンタへの出力を強制終了する働きを持つ。このメソッドが呼び出されると,プリンタスプーラからそのデータは削除される。

Prev 51/134 Next