ExcelVBA練習問題シリーズ#1「領収書PDFを出力」マクロ②解答コードです。
もちろんこれが正解という訳ではなく、いろいろなやり方あると思いますが、
ひとつの解答としてご参考ください。
※ 挑戦ポイントには挑戦していない基本コードになります。
◇ 出題ページはこちら
www.limecode.jp
◇ 解答ページはこちら
VBA問題#1「領収書PDFを出力」マクロ①解答
VBA問題#1「領収書PDFを出力」マクロ②解答【本ページ】
VBA問題#1「領収書PDFを出力」解答完成版(挑戦ポイント制覇)
ソースコード
Option Explicit ' マクロ② 領収書を一括出力 Sub 一括出力指定の全データを領収書PDFに出力する() Dim LastR販売データ As Long LastR販売データ = WS販売データ.AutoFilter.Range.Rows.Count + WS販売データ.AutoFilter.Range.Row - 1 ' 実行条件チェック If WorksheetFunction.CountIf(WS販売データ.Range(WS販売データ.Cells(5, 11) _ , WS販売データ.Cells(LastR販売データ, 11)), 1) = 0 Then MsgBox "一括出力列に「1」が立っている行がありません。" Exit Sub End If ' 出力フォルダのチェックと作成 Dim Path出力フォルダ As String: Path出力フォルダ = ThisWorkbook.Path & "\領収書" If Dir(Path出力フォルダ, vbDirectory) = "" Then MkDir Path出力フォルダ ' 一括出力列の「1」以外の値をクリア Dim R As Long For R = 5 To LastR販売データ If WS販売データ.Cells(R, 11) <> 1 _ And WS販売データ.Cells(R, 11) <> "" Then WS販売データ.Cells(R, 11) = "" End If Next ' データシート全行をループ For R = 5 To LastR販売データ ' 一括出力が「1」でかつ出力済でない行を処理 If WS販売データ.Cells(R, 11) = 1 _ And WS販売データ.Cells(R, 10) = "" Then ' 領収書への印字 WS領収書.Range("D3") = WS販売データ.Cells(R, 2) 'No WS領収書.Range("C7") = WS販売データ.Cells(R, 3) ' 購入者 WS領収書.Range("F9") = WS販売データ.Cells(R, 9) ' 売上 WS領収書.Range("F11") = WS販売データ.Cells(R, 6) ' 品物 ' 購入日 Dim y As Long, m As Long, d As Long m = WS販売データ.Cells(R, 4) d = WS販売データ.Cells(R, 5) y = WS販売データ.Range("F2") + IIf(m <= 3, 1, 0) WS領収書.Range("G3") = DateSerial(y, m, d) ' ファイル名を設定 Dim 出力ファイル名 As String 出力ファイル名 = "領収書" & Format(DateSerial(y, m, d), "yyyymmdd") _ & "(" & WS領収書.Range("C7") & ").pdf" ' PDFに出力 WS領収書.ExportAsFixedFormat xlTypePDF, Path出力フォルダ & "\" & 出力ファイル名 ' 出力日の印字 WS販売データ.Cells(R, 10) = Format(Date, "m/d") & "済" WS販売データ.Cells(R, 11) = "完了" ' 一括出力が「1」だが出力済の行 ElseIf WS販売データ.Cells(R, 11) = 1 _ And WS販売データ.Cells(R, 10) <> "" Then WS販売データ.Cells(R, 11) = "失敗" End If Next ' データシート全行をループ MsgBox "領収書PDFの一括出力を完了しました。" End Sub ' 一括出力列のクリア Sub 一括出力列をクリアする() Dim LastR販売データ As Long LastR販売データ = WS販売データ.AutoFilter.Range.Rows.Count + WS販売データ.AutoFilter.Range.Row - 1 WS販売データ.Range(WS販売データ.Cells(5, 11) _ , WS販売データ.Cells(LastR販売データ, 11)).Value = "" End Sub
解説
各コードの解説と工夫しているポイントは以下の通りです。
全体を通して
Forループ文の中にある「領収書に印字してPDFに出力」する部分は、
マクロ①と同じコードを使用しています。
こちらについての解説はマクロ①の解答ページをご参考ください。
今回のコードはマクロ①をFor文で回しているコードのため、
メインコードの解説は上記の記事を見てもらえればと思います。
本記事ではFor文の組み方、If文の組み方のみ解説します。
For文のカウンタは意味のある単語を使いましょう
今回はデータ全行をループするFor文を書きました。
まず重要なのが、「行だからRを変数名とした」点です。
Dim R As Long For R = 5 To LastR販売データ
プログラミングの文化的なお作法として、
ループのカウンタに「i」を使っているコードをよく見かけます。
しかしカウンタに「i」を使ってしまうと、
- シート番号と行番号にどちらも「i」を使うことができない。
- すでに「i」を使っているコード同士をコピペ合体できない。
- 次のカウンタを「j」にすると「i」と似ていて読みづらい。
などの問題があります。
実体のない配列を扱うプログラミング言語と違い、
ExcelVBAは「シート」「行」「列」など実物が存在するオブジェクトを扱います。
それらのカウンタは「シートNo」「R」「C」を用いておけば、
コードも読みやすく、コピペ合体などもしやすくなりますので採用してみてください。
一括出力列のクリアを単独のFor文に
今回のコードは「一括出力列が1の行を領収書に出力」するのがメインですが、
ついでに「前回のログ(完了/失敗)をクリアする処理」が必要です。
この処理はメインコードと同じループでやっても良いのですが、
本コードでは単独のFor文をもう一つ作って対応しました。
' 一括出力列の「1」以外の値をクリア Dim R As Long For R = 5 To LastR販売データ If WS販売データ.Cells(R, 11) <> 1 _ And WS販売データ.Cells(R, 11) <> "" Then WS販売データ.Cells(R, 11) = "" End If Next
こういった大したことがない処理をメインループから外すことで、
メインループをシンプルな構造に保つことができます。
一見For文を2回まわして二度手間に感じてしまいますが、
「For文自体はRを足し算しているだけ」ですので、
実は速度面でも全く影響はありません。
処理ごとにFor文を分けることで複数の処理を同時に考える必要もなくなり、
さらにはテストも別々に行うこともできます。
「ループは一本でやらなければいけない」という思い込みがあったかたは、
是非一度この手法も試してみて下さい。
出力済の行を判定するIF文
こちらはIf文の書き方の話です。
今回の処理は「出力列が1の行が対象」で、
その中で「未出力なら[実行] / 出力済なら[失敗]を印字」という分岐があります。
この分岐を表現するとき、分岐をモレなくダブりなくやるなら、
↓のようなコードになります。
If WS販売データ.Cells(R, 11) = 1 Then If WS販売データ.Cells(R, 10) = "" Then ' メインロジック Else WS販売データ.Cells(R, 11) = "失敗" End If End If
しかし見てわかる通り、今回のコードは↓のように組みました。
If WS販売データ.Cells(R, 11) = 1 _ And WS販売データ.Cells(R, 10) = "" Then ' メインロジック ElseIf WS販売データ.Cells(R, 11) = 1 _ And WS販売データ.Cells(R, 10) <> "" Then WS販売データ.Cells(R, 11) = "失敗" End If
わざわざ「一括出力が1」の判定も2回やっていますし、
「出力列が空かどうか」もElseを使わずもう一度判定しています。
こちらも一見無駄が多いように見えますが、もちろんこうしている理由があります。
まず一つ目の理由は「メインロジックのインデントを減らせる」こと。
もうひとつは「上方にあるIfを見に行かないとElseの意味がわからない」対策です。
実際のコードではメインロジックは数十行~数百行にもわたるため、
Elseとだけ書いてあってもなんだかわからないことが多々あります。
' ↑ 手掛かりははるか上方 Else ' ← なんの「それ以外」かわからない WS販売データ.Cells(R, 11) = "失敗" End If End If ' ← なにが二重の判定だったかわからない
今回のコードはそこそこ単純な分岐だったので、
この書き方でなければいけないというほどではありませんでした。
しかし、複雑な分岐を「モレなくダブりなく」と考えてしまうと、
ただでさえ複雑なロジックをより複雑にし、読みのも大変にしてしまいます。
If文は冗長に判定しても速度にはほぼ影響はありませんので、
マクロによっては読みやすさを重視して書いてみてください。
以上でマクロ②の解説を終わります。
基本コードの詰め合わせでしたが、
読みやすく書こうとすると、工夫できるポイントがたくさんありましたね。
マクロ②も作成できた方は、
是非挑戦ポイントを盛り込んだ完成版に着手してみてください。