写しPDF作成マクロ~(2)

ファイル選択用のクラスを作る

指定した拡張子のファイルを選択させる

今回のマクロでは、写し作成元のWordファイル、ハンコ用のpngファイル、と、ファイル形式を限定して取得したい。そうすると、ファイル選択ダイアログでユーザにファイル選択を迫る際に、フィルターをかける必要がある。

クラスモジュールのコード

まず、クラスモジュールを挿入し、「オブジェクト名」を「FileNameGetter」にした。

フィールド部分
Option Explicit
'フィールド
Private gotName_ As String         '……(1)
Private isFailed_ As Boolean       '……(2)
Private isCancelled_ As Boolean    '……(3)

まずは、フィールド部分。

  • (1)は、取得したファイル名(フルパス)を入れておく変数。
  • (2)は、処理が失敗したかどうかを保持する変数。処理が失敗したらTrueが入る。
  • (3)は、キャンセルされたかどうかを保持する変数。ファイル選択がキャンセルされたらTrueが入る。
アクセサ部分
'アクセサ
Public Property Get gotName() As String
  gotName = gotName_
End Property
Public Property Get isFailed() As String
  isFailed = isFailed_
End Property
Public Property Get isCancelled() As String
  isCancelled = isCancelled_
End Property

特記事項なし。いづれも読み取り専用にしている。

コンストラクタ部分
'コンストラクタ
Private Sub Class_Initialize()
  gotName_ = ""
  isFailed_ = False
  isCancelled_ = False
End Sub

一応、初期化しているけど、全部デフォルト値を代入しているだけなので、別になくても良いと思う。

メソッド部分
'メソッド
Public Sub getFileName(ByVal prompt As String, _
                       ByVal appName As String, _
                       ByVal baseStr As String)             '……(1)
  '引数「baseStr」に3文字以外の文字列を指定すると、実行時エラーを返す。
  If Len(baseStr) <> 3 Then                                 '……(2)
    Err.Raise 10000, Description:="拡張子は必ず最初の3文字を指定せよ。" & vbCrLf & _
                                  "getFileNameメソッドの第3引数を訂正せよ。"
  End If
  isCancelled_ = False
  isFailed_ = False
  Dim str As Variant                                        '……(3)
  Dim baseName As String
  str = Application.GetOpenFilename(Title:=prompt, _
                                    FileFilter:=appName & "," & "*." & baseStr & "?") '……(4)
  If str = False Or str = "" Then                           '……(5)
    MsgBox "キャンセルされました。", vbInformation
    isCancelled_ = True
    Exit Sub
  End If
  baseName = Right(str, Len(str) - InStrRev(str, "."))      '……(6)
  If Left(baseName, 3) = baseStr Then
    gotName_ = str
  Else
    isFailed_ = True
    MsgBox "ファイルの指定が間違えています。" & vbCrLf & _
           appName & "ファイルを選択せよ。", vbCritical
  End If
End Sub

このクラスの唯一のメソッド。

3つの引数を渡して実行するようにしている。すなわち、

  • 第1引数「prompt」は、ファイル選択ダイアログのタイトル
  • 第2引数「appName」は、FileFilterで表示するアプリケーション名
  • 第3引数「baseStr」は、FileFilterで表示する拡張子
  • の3つ。

ちなみに、「FileFilter」ってのは、

f:id:akashi_keirin:20170318214721j:plain

この赤枠囲みのところね。

コード内の(1)については説明したから、(2)以降について説明。

  • (2)で、渡された引数のチェック。第3引数に必ず3文字の引数が渡されるようにしている。これは、このクラスを使用するプログラマに知らせる必要のあることなので、自作のエラーを表示させている。
  • (3)で変数「str」を準備。GetOpenFilenameメソッドの戻り値を格納するための変数なんだが、ユーザがファイル選択ダイアログで「キャンセル」を選択すると、Booleanの「False」が返るので、StringでもBooleanでも格納できるVariantにしている。
  • (4)がGetOpenFilenameメソッド。引数FileFilterを指定することにより、ダイアログに必要なファイルだけを表示させることができる。引数FileFilterの内容をよく見たら、上の画像の赤枠囲み部分と同じであることが分かるだろう。
  • (5)の条件指定だが、一つ目の「False」は、ユーザがファイル選択ダイアログで「キャンセル」を選んだ場合、二つ目は、何もファイルを選択せずに「OK」をクリックした場合。つまり、ファイルが選択されなかった場合、ということ。

ところで、上記コード中の(6)だが、たぶん必要ないと思う。

一応、説明しておくと、

Right関数とLen関数、InStrRev関数を組み合わせて、取得したファイル名から拡張子の部分を取り出し、引数で受け取った拡張子と比較。拡張子が違っていたら、isFailedプロパティにTrueをセットし、メッセージを表示

ということなのだが、そもそもGetOpenFilenameメソッドでファイルフィルターをかけているのだから、拡張子が食い違うことなどないと思う。なんでこんなコード書いたんだろ……?

追記

>そもそもGetOpenFilenameメソッドでファイルフィルターをかけているのだから、拡張子が食い違うことなどない
……と思っていたけど、「ファイル名」のところに表示されていないけど存在するファイル名をジカ打ちしたら拡張子が食い違うことがあり得る。すげえな、このコード書いた当時の私……w そんなことまで想定していたのか……!

FileNameGetterを使用する

標準モジュールのコード

まずは、宣言セクションで、

Public fng As FileNameGetter

Public変数としてインスタンス用の変数を宣言しておく。

Sub getStampFile()
  Set fng = New FileNameGetter          '……(1)
  With fng                              '……(2)
    .getFileName "ハンコ用のpngファイルを選べ。", "png", "png"  '……(3)
    If .isCancelled = False And .isFailed = False Then          '……(4)
      Range("ImageFilePath").Value = .gotName
    End If
  End With
  Set fng = Nothing
End Sub

コードの説明

  • (1)は、おなじみFileNameGetterクラスのインスタンス化。
  • この先、FileNameGetterクラスのインスタンス「fng」に対する操作が多くなるので、(2)のようにWithでまとめてしまう。インスタンス化した後、Withでまとめるのはよく使う手法だと思う。
  • (3)でgetFileNameメソッドを使用。引数の渡し方に注目。こんなふうに使う。
  • (4)。getFileNameメソッドを経て、isCancelledプロパティか、isFailedプロパティがTrueになっていたとしたら、ファイル名の取得に失敗しているということ。つまり、両方Falseだったら、無事にファイル名が取得できているということだ。

シートモジュールのコード

Wordファイルのフルパスを取得する処理は、セルのダブルクリックイベントをきっかけに発動するようにした。従って、シートモジュールにイベントマクロとして書いている。

Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
  If Target.Row = 2 And Target.Column = 1 Then    '……(1)
    Set fng = New FileNameGetter
    With fng
      .getFileName "写しを作成するWordファイルを指定するがよい。", _
                   "Word", "doc"                   '……(2)
      If .isCancelled = True Or .isFailed = True Then                 '……(3)
        Cancel = True
      Else
        Target.Value = .gotName
        Cancel = True
      End If
    End With
  Else
    Cancel = True
  End If
Set fng = Nothing
End Sub

コードの説明

  • (1)は、イベントマクロを起動させる条件の設定。A2セルをダブルクリックしたときだけで良いからこうなる。

ちなみに、A2セルに「DocumentPath」と名前をつけているにもかかわらず、(1)を

If Target.Name = "DocumentPath" Then

としてもうまくいかない。なんでだろ???

  • (2)は、getFileNameメソッドの呼び出し。Wordファイルの場合だと、こんな指定の仕方になる。
  • (3)。getFileNameメソッドの実行後、isCancelledプロパティか、isFailedプロパティのいづれかがTrueになっているということは、ファイル名が取得できていないということだから、何もせずにダブルクリックイベントをキャンセルする。逆に、いづれもFalseだったら、ファイル名が取得できているということだから、取得したファイル名をA2セルに書き込む。

おわりに

ファイル名を取得する、というのはよくやる処理だと思うが、クラスとして設定しておくことで、非常に使いやすくなる。クラスって、状態と振る舞いを兼ね備えているので、「FileNameGetterさん、ファイル名を取ってきて~」と、あたかも人に頼むかのようにコードを書くことができるのが強みだと思う。

今回紹介した「FileNameGetter」クラスは、そのまま他のマクロでも使えると思う。

よく使う機能をクラス単位で蓄積していったら、マクロ作りがメチャクチャはかどるようになると思う。