Top > Programmingとか > Office > ページ番号印字スペシャル

2002年03月12日

04 スペシャルの4

前回。

本家MSJPのサイトの[XL2002]印刷されるページの総数を調べる方法のソースが影も形もなくなったところまでお話ししましたね。てゅうか、調整は入れたものの再計算のタイミングがちょっといや、てゅうところまでってのが正しいんですけど。

てゅうわけで、今回はその続きです。



まぁ引きにするほどの話ではないんですが。

自分で作ったFunctionをExcelの自動再計算のタイミングでいっしょに再計算してもらいたいなら、Application.Volatileって一文をFunctionの先頭に入れておけばいいんですね。

Function PrintPage() As Long
Dim lngRPage As Long
Dim lngCPage As Long
Dim lngRLastCell As Long
Dim lngCLastCell As Long
'--本ファンクションを自動再計算関数とする--
Application.Volatile

'--最後のセルのアドレスを取得--
If Sheet1.UsedRange.Address = "$A$1" Then
If IsEmpty(Sheet1.Range("$A$1").Value) Then
…(以下略)
なんて感じであっといぅ間に自動再計算対応。2ページ目にあたるセルにデータを入力するとぴこっと「2」といぅ計算結果にだめだ。

くそ重いっ。

1ページ目に当たるセルだろぅとどこだろぅと、とにかくどっか修正入れるたびに自動的に再計算しやがりますこんちくしょう。そのおかげでせっかく高速にだばだば入力してるってのにこのもったり感は何者っ!?ってくらいに腹が立ちます。

使えませんApplication.Volatile。却下。

てことでちょっと妥協してみることにしました。

要はプリントプレビューとかほんとの印刷の時に正しいページ数がカウントされればいいわけですから、WorkbookオブジェクトのBeforePrintイベントの中でこのFunctionを呼んでやれば。印刷直前にだけ再計算されるってことになります。

Private Sub Workbook_BeforePrint(Cancel As Boolean)
Sheet1.Range("C1").Formula = "=PrintPage()"
End Sub
…美しくないっす(T-T)。

Workbookオブジェクトに書かなきゃならないってことはファイル固定で使い回しが効かないってことですし、Sheet1.Range("C1")なんてもぅ決め打ちもいいとこ。

かといってどのシートのどのセルにこのFunctionが埋め込まれているのかを印刷のたびに全部舐めてサーチなんてムダまくりのループをかける気にもなりませんし。

まぁとりあえず。とりあえずこの手法でうまくいくところまで確認してから次の手を考えよう

と思ったら

もっととんでもないことに気がつきました。

各ページを振る方法がない。

さっきBeforePrintイベントを使いましたよね。印刷時にこのイベントの後にページごとに発生するイベントなんかないんです。

仮にあったとしたって、行タイトルに設定されている行って1行目からせいぜい数行。どんな関数を埋め込んだってページごとに振り分けて何度も実行できるような性質のもんじゃありません。

どーすんだよ。

どーすんだよここまできて。

どーすんでしょほんとにここまで話広げといて。どろどろのコード書いてまで妥協してきたのに。

まぁ。可能性としては、おとなしくヘッダに現ページ数を出しといて、それをなんとか行タイトル行に埋め込んだ関数から参照する手かと。

自分でもできるかどぅかわかんないんですけどね。やるだけやって玉砕することもよくある話で。最後までのたうち回ってみるのも漢かと。いぇどちらかといぅとそんなところで漢は発揮したくないんですけど。

まずはおとなしくヘッダに現ページ数をセットして。

020312-02.png

このページ数出しっぱなしだと行タイトルのページ番号とダブってかっこわるい(はず)ですから、まぁどこかのセルの後ろに隠してしまいましょう。セルの背景を白色に指定すれば、わからないよぅにじょうずに隠せます。
ヘッダの表示位置を下げるには、メニューバーから[ファイル(F)]-[ページ設定(U)...]で[ページ設定]ダイアログを表示させて、[余白]タブの[ヘッダー(A):]の数値を大きくしてやればいいんですね。

020312-03.png

だいたいこんな感じです。

ちょっとまだ上にはみ出てますので、もぅ少し下にえ。

020312-04.png

セル背景の白色塗りつぶしを解除して

020312-05.png

かぶっているセルのデータを消去して

020312-06.png

ヘッダの表示位置をセルの枠に合わせれば

020312-07.png

…ベスト。



最初に言っておきましたよね。

最後まで読んで怒るの禁止。
→私は逃げるし。ではっ。

2002年03月09日

03 スペシャルの3

前々回。

本家MSJPのサイトの[XL2002]印刷されるページの総数を調べる方法のソースではうまくいかない場合があるといぅところまでお話ししましたね。

てゅうわけで、今回はその続きです。



まず、Subのままだと実行→確認がめんどくさいので、Functionに書き換えてセル上で結果が確認できるよぅにします。
どぅせ最終的には行タイトル領域内のセルに埋め込みたいわけですから実害なし。むしろ先取り。てゅうほどのことではありませんが。
Function PrintPage() As Integer
Dim H_Break As Integer
Dim V_Break As Integer
Dim P_Page As Integer
Dim A_Cell As String
'--最後のセルのアドレスを取得--
A_Cell = Sheet1.UsedRange.Address
If A_Cell = "$A$1" Then
If IsEmpty(Sheet1.Range(A_Cell).Value) Then
MsgBox "印刷するデータはありません。"
Exit Function
End If
End If
'--↓の改ページ数取得--
H_Break = Sheet1.HPageBreaks.Count
'--→の改ページ数取得--
V_Break = Sheet1.VPageBreaks.Count
If V_Break = 0 Then
P_Page = H_Break + 1
Else
H_Break = H_Break + 1
V_Break = V_Break + 1
P_Page = H_Break * V_Break
End If
'--↓関数化--
PrintPage = P_Page

End Function
(赤字が追加/修正個所)
これをセルに埋め込む時には、こんな感じで。
=printpage()
PrintPageと書いてもprintpageと変わっちゃうのがなんかあれですがまぁよし。
[F2]でセル編集モードに移行してEnter押さないと再実行されないのもなんかあれですがそれもまぁよし。としましょうとりあえず。

実行結果はこんな感じです。

020309-01.png

ではこの1ページ目分をぴったり覆う罫線を引いてからもう一度実行してみます。

020309-02.png

こんな感じ。「4」になっちゃいますよね。

てことは、この時点で縦横にひとつずつ改ページができている、ってことになります。でそれにおのおの+1されているよぅですね。

…なんてこったい。

んー、このやみくもな+1を避けるには、最初の方で未入力判断に使っていたSheet1.UsedRange.Addressと、一番右下の改ページ座標との関係を調べればなんとかなりそぅな。

では。
1ページ目分をぴったり覆う罫線を引いた場合にひとつだけできる改ページはどのセル位置なのかを調べてみましょう。

とりあえずFunctionの途中でプレイクかけて、その時点での改ページオブジェクトの行/列をイミディエイトウィンドウで直に表示させてみました。

020309-03.png

A66(2ページ目になるはずの最左上セル)と出ました。なるほど。

ちょっとこのへん、いろんなパターンを試して使用領域の右下隅座標と右下隅改ページ座標との関連を考えてみましょう。

入力範囲使用セル範囲↓方向 改ページ数↓方向最終 改ページ座標→方向 改ページ数→方向最終 改ページ座標
入力なし$A$10(なし)0(なし)
A1のみ$A$10(なし)0(なし)
A列1ページ分$A$1:$A$651A660(なし)
1行1ページ分$A$1:$K$10(なし)1L1
1ページ分フル$A$1:$K$651A661L1
1ページ分フル罫線$A$1:$K$651A661L1
1ページ分フル+右1セル$A$1:$L$651A661L1
1ページ分フル+下1セル$A$1:$K$661A661L1

ちなみにこの検証用に使用したコードはこんな感じ。

Sub PrintPageTest() As Integer

Dim strBf As String
strBf = ""
strBf = strBf & "使用セル範囲:" & Sheet1.UsedRange.Address
strBf = strBf & vbCrLf & "↓改ページ数:" & Sheet1.HPageBreaks.Count
strBf = strBf & vbCrLf & "↓改ページ座標:"
If (Sheet1.HPageBreaks.Count > 0) Then
strBf = strBf & vbCrLf _
& Sheet1.HPageBreaks(Sheet1.HPageBreaks.Count).Location.Column & "/" _
& Sheet1.HPageBreaks(Sheet1.HPageBreaks.Count).Location.Row
End If
strBf = strBf & vbCrLf & "→改ページ数:" & Sheet1.VPageBreaks.Count
strBf = strBf & vbCrLf & "→改ページ座標:"
If (Sheet1.VPageBreaks.Count > 0) Then
strBf = strBf & vbCrLf _
& Sheet1.VPageBreaks(Sheet1.VPageBreaks.Count).Location.Column & "/" _
& Sheet1.VPageBreaks(Sheet1.VPageBreaks.Count).Location.Row
End If
MsgBox strBf
End Function
ここから推測できるルールは、
改ページの発生は、縦方向・横方向に関わらず共通。
セルの使用形態がデータ・罫線に関わらず共通。
ページの途中までしかデータがない場合は、その下(右)に改ページは発生しない。
ページぎりぎりまでデータを入れた場合は、その直下(直右)に改ページが発生する。
くらいでしょうか。ならば。

使用セル範囲右下隅が最下(最右)改ページの
1つ上(左)ならばちょっきりと見なす。

よし、これでいこう。

いこう。ってのは簡単ですが。使用セル範囲の最右下隅座標をどう取得するかも割と大変です。Sheet1.UsedRange.Addressじゃぁ文字列で返ってくるのでセル座標数値に変換するのも大変ですし。

Sheet1.UsedRange.Rows.Countを使ってみましょうか。これならば使用行数が取得できます。
ただし、1行目にデータがない場合はその分勘定されません(2~3行にのみデータがある場合、このプロパティは「2」となります)ので、Sheet1.UsedRange.Rowで使用最上行が取得できますからこれと合わせます。
かといってただ足すと必ず1多くなりますので、そこから1引いてやる、と。

Row/Rowsの代わりにColumn/Columnsを使ってやると、同様に最右列も取得できます。

以上から、実装するロジックは

  1. 使用セル範囲の最右下隅座標がA1、かつA1にデータがなければそのシートは白紙なので0ページとする。

  2. 縦(横)方向の改ページ数が0であれば1ページあるとする。

  3. 使用セル範囲の最右下隅座標が一番下(右)の改ページの1つ上(左)であれば、その改ページ数をそのままページ数とする。

  4. でなければ改ページ位置より下(右)にまだデータがあると見なし、その改ページ数に1を足した数をページ数とする。

  5. 最終的なページ数は、上記で求めた縦ページ数×横ページ数で取得できる。
ああぁぁぁ、しんどい。

で、これを実装したコードはこぅだっ!

Function PrintPage() As Long
Dim lngRPage As Long
Dim lngCPage As Long
Dim lngRLastCell As Long
Dim lngCLastCell As Long
'--最後のセルのアドレスを取得--
If Sheet1.UsedRange.Address = "$A$1" Then
If IsEmpty(Sheet1.Range("$A$1").Value) Then
lngCPage = 0
Exit Function
End If
End If
'--縦方向のページ数を算出--
lngRLastCell = Sheet1.UsedRange.Rows.Count + Sheet1.UsedRange.Row - 1
lngRPage = Sheet1.HPageBreaks.Count
If (lngRPage = 0) Then
lngRPage = 1
ElseIf (lngRLastCell <> Sheet1.HPageBreaks(lngRPage).Location.Row - 1) Then
lngRPage = lngRPage + 1
End If
'--横方向のページ数を算出--
lngCLastCell = Sheet1.UsedRange.Columns.Count + Sheet1.UsedRange.Column - 1
lngCPage = Sheet1.VPageBreaks.Count
If (lngCPage = 0) Then
lngCPage = 1
ElseIf (lngCLastCell <> Sheet1.VPageBreaks(lngCPage).Location.Column - 1) Then
lngCPage = lngCPage + 1
End If
'--総ページ数を返す--
PrintPage = lngRPage * lngCPage
End Function
…もぅMicrosoftの元コードの影も形もなし。

とりあえずこれで総ページ数を表示させることはできるよぅになりました。
ただ、プレビューや印刷のタイミングではなく、この関数を埋め込んだセルを編集しないと再計算されないってのがちょっとやりにくいですね。そこで、

→さらに続くし。

2002年03月08日

02 スペシャルの2

前回。

本家MSJPのサイトに[XL2002]印刷されるページの総数を調べる方法を見つけたところまでお話ししましたね。

てゅうわけで、今回はその続きです。



さてこのMicrosoft提供のサンプルコードを眺めてみると。

まず、データを入力している一番右下のセルをコード側から取得できるんですね。で、それが「$A$1」(シートの一番左上)でしかもそこが空っぽなら、"印刷するデータはありません。"ってことでひとつ。ってチェックロジックが入って。

ここまではおっけー。

それから、今度は縦方向と横方向の改ページ数をカウント。なるほど、シートオブジェクトの中にその情報をきちんと持っている、と。ただし、上記サンプルを実行する前に、改ページ位置のセルを画面に表示して、改ページ位置を認識 させる必要がありますなんて注意書きがあるところを見ると、画面表示かZoomOut→ZoomInのアクションの時にセットされる情報っぽいですね。

ExcelXPでVBAコードウィンドウに貼り付けて実際にやってみると、特にそんなことに気をつけなくともちゃんと動作しました。
たまたまそれっぽい操作をやっていたのか、XPではアクショントリガが変わったのかは把握していませんが、まぁとりあえずはいいかと。

ではページいっぱいを罫線で囲んで縁取りして、行タイトル指定してあるセルにこの関数を突っ込んでだめだ。

縦横に1ページずつ多い。

1ページ分しか作っていないのに、4ページと表示されてしまいます。ためしに2ページ分の罫線を引いてみると6ページと表示、縦横に2ページ分ずつ(つまり正解は4ページ)になるように罫線を引いてみると、9ページとの表示になってしまいました。

なぜだ。

ってんでもぅ一度サンプルコートを眺め直してみると。

縦横の改ページ数に、無条件に1足してます。

ページのぎりぎりいっぱいまでセルを使わないのがまぁメジャーケースですので、最後の改ページ位置のさらに下側(右側)にまだデータがあるだろぅと。
だから単純に取得した改ページ数に1足しちゃえと。

誰だこんなやっつけコード書いたの。
Microsoftか!?

いゃほんとにMicrosoftなんですけどね。

どぅもページちょっきり下隅とか右隅とかにデータとか罫線とかがあると、その下側(右側)に改ページができちゃうみたいなんですよ。

のでこのサンプルコードは、アイデアとしてはおっけーだけれども正常に動作しない場合があるといぅことで。よって却下。

ではどぅするかといぅと、

→また続くし。

2002年03月07日

01 スペシャルの1

今回もExcelのお話。ちょっと長くなりそぅなので、何回かに分けてお話ししてみよぅかと。

最初に言っておきます。

最後まで読んで怒るの禁止。

Excelではヘッダ・フッタ設定ができるよぅになっているわけですが、無造作に1行とか2行とかが頭とお尻にくっつく形にしかなりません。 一応左-真ん中-右なんて印字位置の振り分けはできますが、そこまで。罫線も領域内センタリングもできないので、いまいちどぅもあれって感じになりがちです。

聞いた話では、欧米人はさほど出力帳票のスタイルにさほどこだわらない国民性といぅか地域性といぅか、らしいんです。
これはヘッダといぅよりむしろ作表全般に対する感覚で。

このへんツッコんでくとなんかまた深入りしていきそぅなので、これ以上は避けますが。

メニューバーから[ファイル(F)]-[ページ設定(U)...]で表示される[ページ設定]ダイアログの、[ヘッダー/フッター]タグでの設定ではやはりいまいちだとお嘆きのあなた。
同じく[ページ設定]ダイアログの[シート]タグ-[印刷タイトル]-[行のタイトル(R):]ではいかがでしょうか。

ここで設定したセル領域は、印刷/プレビュー時に各ページに印字/表示されます。ので、どんなにビジュアライズされたヘッダもおっけー!
設計書のヘッダ(システム名とか作成日とか入るアレです)もこれでかっちょよく。客先に提示しても恥ずかしくない出来にだめだ

この手法だとページ振れません。

しかし複数行にわたるレイアウトを丸ごとヘッダにできるこの機能は捨てがたいところです。なんとか行タイトル中に現在ページ/総ページを表記する方法はないものか。



てことであちこち捜してみました。…あったあった。本家MSJPのサイトに、[XL2002] 印刷されるページの総数を調べる方法ってのが。

まずはこのへんを足がかりに、総ページ数を表示するところから調べていきましょう。