Excelのあれこれ ~ マクロの自動ロード

前置き

前の記事では、Excelマクロをより使いやすくする方法について書きました。本記事では、もう一歩進んでみます。

※本記事のマクロは、Excel2010で動作確認しました。

おさらい

前の記事にも書いたように、個人用マクロブックは、普通のマクロが持つ以下の問題点を解消してくれます。

  • そのブックを開くたびにセキュリティ警告が出る
  • 複数のブック間でマクロを共有しにくい
  • マクロを作用させたいブックと、マクロを保存したブックが異なる場合の操作が面倒
  • マクロをバージョン管理しにくい

新たな課題

ただ、最後の1点(バージョン管理)については微妙です。個人用マクロブックによって、マクロが1つのファイルに集約できるので、たしかに管理は楽なのですが、PERSONAL.XLSやPERSONAL.XLSBはバイナリファイルなのでバージョン間の差分が調べにくいです。

また、「個人用」というだけあって、他人と(あるいはチーム内で)共有するのには向いていません。

マクロをテキストファイルで管理し、それらをExcel起動時に自動ロードすれば、上記の課題を解決できます。

自動ロードの作戦と方針

  • マクロはテキストファイルにエクスポートした状態(foo.basとかbar.frmとか)で管理する
  • SubversionとかMercurialとかでバージョン管理すれば、他人との共有も簡単
  • マクロ(テキストファイル)の置き場は自由
  • マクロ(テキストファイル)の数や名前も自由
  • マクロのロード処理を、ThisWorkbookのWorkbook_Openに書いておき、Excel起動時に自動実行させる
  • ロードのコードは、なるべく短くする
  • マクロエディタでマクロを編集した場合、次回の自動ロードで上書きされないようにする

ThisWorkbook

マクロを書ける人なら知っていると思いますが、すべてのExcelファイルにはThisWorkbookというオブジェクトがあり、OpenやActivate、BeforeSaveなど、様々なイベントをトリガに実行されるマクロを登録することができます。

マクロの自動ロードを行うための、PERSONAL.XLS(XLSB)のThisWorkbookは、以下のようになります。

ThisWorkbook
Private Const TOP_DIR As String = "d:\etude\vba\excel2010\"          'マクロ置き場。\で終わること。
Private Const PERSONAL_BOOT_LOADER As String = "personalBootLoader"  'ブートローダモジュール。

Private Sub Workbook_Open()
  'ブートローダマクロが既存なら削除する。
  For Each vbcompo In ThisWorkbook.VBProject.VBComponents
    If vbcompo.Name = PERSONAL_BOOT_LOADER Then
      ThisWorkbook.VBProject.VBComponents.Remove vbcompo
    End If
  Next

  'ブートローダモジュールの削除が同期的に行われないケースがあるので、
  '続きは非同期で行う。
  Application.OnTime Now + TimeValue("00:00:01"), "ThisWorkbook.loadModules"
  
End Sub

Private Sub loadModules()
  ThisWorkbook.VBProject.VBComponents.Import TOP_DIR & PERSONAL_BOOT_LOADER & ".bas"
  doIt       'ここで直接personalBootを呼ぶと未定義エラーになるのでdoItを経由。
End Sub

Private Sub doIt()
  personalBoot TOP_DIR
End Sub

細かいことはコメントを参考にして下さい。以下、要点です。

  • 各自が、上記コードをマクロエディタにコピペする(あるいは添付のThisWorkbook.clsをインポートする)
  • 1行目は、マクロ(テキストファイル)の置き場で、各自が自由にカスタマイズする
  • 上記コードの本質は、personalBootLoader.basをロードして、personalBoot()を呼ぶこと
  • 既にpersonalBootLoader.basがロード済みなら上書きする
  • 自動ロードの本体は、personalBoot()にある

personalBootLoader.bas

自動ロード処理の本体のコードは、personalBootLoader.basに書きます。

personalBootLoader.bas
'ブートローダモジュール。
'personal.xlsbを開いたときに、本モジュールをロードして
'personalBoot()を呼ぶようにする。
'すると、他のモジュールもロードされる。

'ロードしたいモジュールのリスト。
Private moduleList(0 To 9) As String

Public Sub personalBoot(topDir As String)

  'ロードするモジュールのリストをセットアップ。
  moduleList(0) = "utils.bas"
  moduleList(1) = "TextBoard.frm"
  moduleList(2) = "DiffForm.frm"
  moduleList(3) = "commands.bas"

  '重複チェック。
  'もし前回のExcel起動中に手動でpersonal.xlsbを保存した場合は、
  'ここで重複が見つかるはず。
  '上書きするかキャンセルするか選ばせる。
  For Each vbcompo In ThisWorkbook.VBProject.VBComponents
    If loadingModule(vbcompo) Then
      If MsgBox("ロード予定の" & vbcompo.Name & "が既にロード済みです。" & vbCrLf & _
          "構わずに上書きロードします。", vbOKCancel, "警告") <> vbOK Then
        ThisWorkbook.Saved = True
        End
      End If
    End If
  Next

  '既存なら削除。
  For Each vbcompo In ThisWorkbook.VBProject.VBComponents
    If loadingModule(vbcompo) Then
      ThisWorkbook.VBProject.VBComponents.Remove vbcompo
    End If
  Next

  'ここで、一旦保存しておく。
  'この時点では、ブートローダモジュールだけがロードされた状態のはず。
  '基本的には、余計なものはロードしてない状態で保存しておきたい。
  ThisWorkbook.Save

  'ロードする。
  loadAll topDir

  'セーブ済み状態にしておく。
  'こうしておけば、保存を促されることなく閉じれる
  ThisWorkbook.Saved = True
End Sub

'vbcompoが、ロード予定のモジュールかどうかを返す。
Private Function loadingModule(vbcompo As Variant) As Boolean
  loadingModule = False
  For Each m In moduleList
    If m <> "" Then
      If vbcompo.Name = moduleName(CStr(m)) Then
        loadingModule = True
      End If
    End If
  Next
End Function

'全部ロードする。
Private Sub loadAll(topDir As String)
  For Each m In moduleList
    If m <> "" Then
      If Dir(topDir & m) = "" Then
        MsgBox topDir & "に、" & m & "が見つかりません。" & vbCrLf & "スキップします。"
      Else
        ThisWorkbook.VBProject.VBComponents.Import topDir & m
      End If
    End If
  Next
End Sub

'ファイル名から、モジュール名を得る。
Private Function moduleName(fname As String) As String
  moduleName = left(fname, InStrRev(fname, ".") - 1)
End Function

以下、要点です。

  • 上記の内容をpersonalBootLoader.basに保存してマクロ置き場に置いておく
  • moduleList配列に、ロードしたいマクロ(テキストファイル)のファイル名を格納する
  • 各自、moduleList配列の内容を好きなようにカスタマイズする
  • ロードしたあとでThisWorkbook.SavedをTrueにしてあるので、閉じるときに「保存しますか」ダイアログにわずらわされる心配はない
  • マクロエディタで編集&保存してからExcelを閉じた場合、次回起動時に同じ名前のマクロを2重ロードしてしまうので上書き警告を出すようにした(ここでキャンセルすれば自動ロードは行わない)
  • moduleListに登録されてないマクロに対する副作用は無い

moduleList配列の辺り(カスタマイズ方法)など、まだ改善の余地はありそうですが、ひとまずこんな感じで動いてます。

Last modified:2013/08/12 23:24:17
Keyword(s):
References:[Windowsリテラシ]
This page is frozen.