和風スパゲティのレシピ

日本語でコーディングするExcelVBA

VBA問題#1「領収書PDFを出力」マクロ①解答

ExcelVBA練習問題シリーズ#1「領収書PDFを出力」マクロ①解答コードです。

出力元データ
出力帳票

もちろんこれが正解という訳ではなく、いろいろなやり方あると思いますが、
ひとつの解答としてご参考ください。

※ 挑戦ポイントには対応していない基本コードになります。


◇ 出題ページはこちら
www.limecode.jp
◇ 解答ページはこちら
VBA問題#1「領収書PDFを出力」マクロ①解答【本ページ】
VBA問題#1「領収書PDFを出力」マクロ②解答
VBA問題#1「領収書PDFを出力」解答完成版(挑戦ポイント制覇)
 

ソースコード

' マクロ① 選択行から領収書を出力
Sub 選択行のデータを領収書PDFに出力する()

    ' 実行条件チェック
    If Selection.Rows.Count >= 2 Then MsgBox "1行だけを選択して実行して下さい。": Exit Sub
    
    Dim R_選択行 As Long: R_選択行 = Selection.Row
    If R_選択行 < 5 Then MsgBox "5行目以降を選択して実行してください。": Exit Sub
    
    If WS販売データ.Cells(R_選択行, 9) = 0 Then MsgBox "その行にデータがありません。": Exit Sub
    
    If WS販売データ.Cells(R_選択行, 10) <> "" Then MsgBox "この行は既に出力済です。": Exit Sub

    ' 領収書への印字
    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 Path出力フォルダ As String: Path出力フォルダ = ThisWorkbook.Path & "\領収書"
    If Dir(Path出力フォルダ, vbDirectory) = "" Then MkDir Path出力フォルダ
    
    ' ファイル名を設定
    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") & "済"

End Sub

解説

各コードの解説と工夫しているポイントは以下の通りです。

全体を通して

複数のシートを扱うマクロなので、シートの指定をしっかりしたいところです。

Cells、Rangeの親を省略してActiveSheetに実行するのは、
思わぬ事故を呼びますから避けましょう。


今回は自ブックのシートしか扱いませんので、
シートオブジェクト名」を設定して対応しました。


ワークシートには「プログラム上の名前を付ける機能」があり、

シートオブジェクト名の設定

この部分を編集することができます。

何もしないと「Sheet1」とかになってる場所ですね。


ここに画像のように「WS + 実際のシート名」と名付けておくと、
以下のようにシートを指定できるようになります。

Worksheets("領収書").Range("D3") = Worksheets("販売データ").Cells(R_選択行, 2) 'No
' ↓ シートオブジェクトに書き換え
WS領収書.Range("D3") = WS販売データ.Cells(R_選択行, 2) 'No

 

加えて「WS」を必ず頭につけるルールで全シートを命名しておけば、
Ctrl+Spaceの「コードの入力補完」機能を起動することで、

WS+Ctrl+Spaceの選択肢

こんな風に選択肢から選んで入力することができます。


この時点で既にWorksheets("○○")よりも書きやすそうというのがわかりますが、
さらにWorksheets("○○")と違って

Worksheetからのインテリセンス

この選択肢も表示されるため、以降のコードですらこちらの方が書きやすいです。


他ブックには使えないという弱点はありますが、自ブックのシートを指定する上では、
取り違えも起きづらく、読みやすく、書きやすい最強のシート指定方法です。

まだ使っていなかった方は是非とも採用してみてください。

実行チェック部分

今回のマクロは「選択してる行に対して実行」する仕様にしました。

実行条件チェックとしては、以下4つを判定しています。

  • 複数行を選択して実行
  • データ以外の場所を選択して実行
  • 空のデータに対して実行
  • 出力済のデータに対して実行

 
こういった条件判定を行うときに、

If マクロ実行条件 = False Then
    警告などの処理
Else
    メインコードここから


End If

という書き方をせず、
 

If マクロ実行条件 = False Then
    警告などの処理
    Exit Sub
End If

メインコードここから

という書き方にすると、メインコードのインデントをひとつ減らすことができます。

小さなテクニックですが覚えておいてください。

www.limecode.jp


また、この判定コードの書き方のポイントとして、
If文はElseがなければ複数行でもまとめて1行に書くことができます。

If Selection.Rows.Count >= 2 Then
    MsgBox "1行だけを選択して実行して下さい。"
    Exit Sub
End If

' ⇩書き替え

If Selection.Rows.Count >= 2 Then MsgBox "1行だけを選択して実行して下さい。": Exit Sub

 
この方法は1長1短あり、見てわかる通りちょっと読みづらくなります。

しかし、マクロ冒頭の条件判定を圧縮できることで、
コード全体を見渡す分には読みやすくなっているとも言えます。


「:」を使った複数コードの1行記述(マルチステートメント)は、

  • 使った行を少し読みづらくする
  • その分マクロ全体が読みやすくなる

こんな特徴がありますので、場面によって使ってみてください。


今回のような冒頭のマクロ実行条件チェックは、

  • 冒頭にズラズラ並ばれると邪魔
  • やってることは「判定→警告→Exit Sub」で単純
  • 先頭の判定部分さえ読めれば問題ない

こんなコードですので、マルチステートメントに適したコードと思います。

印字部分

WS領収書.Range("D3").Value = WS販売データ.Cells(R_選択行, 2) 'No

基本的な転記コードです。

シートオブジェクト名を使ってスッキリ書けていますね。


.Valueを省略するかどうか」が議論ポイントではありますが、
私は単純な転記であれば省いています。

理由は単純に「横長になり過ぎるのを嫌った」だけです。


詳しい解説を読みたい方はこちらの記事をどうぞ。
www.limecode.jp

購入日の取得

今回一番大事なポイントがこちらのコードです。

' 購入日
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").Value = DateSerial(y, m, d)

データに表示されているのは年ではなく「年度」ですので、
購入月が1~3と4~12で処理を分ける必要があります。


この処理を実装するにあたり、
「y,m,d」という変数を宣言していることに注目してください。


この3つの変数は1~2回しか使っていないため、
データの保管や使いまわす目的で言うと、変数にする必要はありません。


しかし、変数を使わないで一気にやる↓のコードと見比べてみましょう。

If WS販売データ.Cells(R_選択行, 4) <= 3 Then
    WS領収書.Range("G3").Value = DateSerial(WS販売データ.Range("F2") + 1, WS販売データ.Cells(R_選択行, 4), WS販売データ.Cells(R_選択行, 5))
Else
    WS領収書.Range("G3").Value = DateSerial(WS販売データ.Range("F2"), WS販売データ.Cells(R_選択行, 4), WS販売データ.Cells(R_選択行, 5))
End If

圧倒的に読みやすくなっていることがわかりますね。


この「コードを読みやすくする目的で値に名前を付けるために作る変数」のことを、
俗に「説明変数」と呼びます。


説明変数を宣言するのは一見手間に見えますが、
頭の整理がしやすい分バグも書きづらく、結果的にマクロも早く仕上がります。

変数にはこんな使い方もありますので、ぜひとも修得していってください。


ちなみに、月の判定にはIfステートメントではなく、

 IIf(m <= 3, 1, 0)

このIIf関数を使用しました。


仕組みは単純で「ワークシートのIF関数と同じ仕様」です。

簡単な分岐を簡潔に書けるようになりますので、
こちらも知らなかった方は是非覚えておきましょう。

PDFの出力

' 出力フォルダのチェックと作成
Dim Path出力フォルダ As String: Path出力フォルダ = ThisWorkbook.Path & "\領収書"
If Dir(Path出力フォルダ, vbDirectory) = "" Then MkDir Path出力フォルダ

' ファイル名を設定
Dim 出力ファイル名 As String
出力ファイル名 = "領収書" & Format(DateSerial(y, m, d), "yyyymmdd") _
                    & "(" & WS領収書.Range("C7").Value & ").pdf"

' PDFに出力
WS領収書.ExportAsFixedFormat xlTypePDF, Path出力フォルダ & "\" & 出力ファイル名

こちらも説明変数を駆使してコードを読みやすくしています。


フォルダパスとファイル名をベタ打ちするとズラズラ長くなりがちですが、
説明変数を使ったことでスッキリ簡潔に書けていますね。

ファイル処理関連は説明変数の使いどころですので覚えておいてください。



以上でマクロ①の解説を終わります。

基本コードの詰め合わせでしたが、
読みやすく書こうとすると、工夫できるポイントがたくさんありましたね。


参考にしながら、マクロ②の方にも取り組んでみてください。