category name  »  page title date

ボタンの登録マクロを統一する

複数のボタンに同じマクロを登録しておいて、ボタンを押すとそれぞれ異なった処理を行う、というプログラムです。

ボタン画像などにマクロを登録して、画像をクリックするとそのマクロを実行させる、というのはよく使う処理のしかたです。とてもよく使います。
ボタンは、1個ではなく数個から10個とたくさん配置されるのが普通です。その際に当然ながら、ボタンの数だけマクロが必要で、各ボタンに登録されるマクロ名も、それぞれ異なることになります。
それで、何が都合悪いのか?

ちょっと複雑なシステムを作ったことのある人にはわかるでしょうが、それぞれのマクロは、似たような前処理と似たような後処理を含んでいることが多く、ひょっとするとそのマクロ独自の処理はほんの数行分だけ、などということもよくあります。

似たような前処理というのは、たとえば、クリックされたボタンをちょっと凹ませてチック音を出し、クリックを受け付けたことを知らせるとか、他のイベントを受け付けないようにフラグを立てておくとか、逆にフラグの状態を見て自身の実行をやめるとか、いろいろなものがありそうです。

似たような後処理というのは、たとえば、その時の状態によって特定の作業(隠しておいた他のボタンを表示する、あるいはその逆など)を実行するとか、マクロ処理が終了して次の要求をメッセージとして表示するとか、前処理で封印したイベント受付を解放するとか、これもいろいろなものがありそうです。

これらは、だいたいサブルーチン化しておいて、各個別のマクロから呼ぶ、というのが一般的でしょう。それもひとつのやり方です。

ボタンのマクロは普通こうなる

Public Sub 共通の前処理()
      '・・・・・・
End Sub
Public Sub 共通の後処理()
      '・・・・・・
End Sub

'----- 以下が個別のボタンマクロ
Public Sub ボタンA()
      共通の前処理
      'ボタンA特有の処理
      共通の後処理
End Sub
Public Sub ボタンB()
      共通の前処理
      'ボタンB特有の処理
      共通の後処理
End Sub
Public Sub ボタンC()
      共通の前処理
      'ボタンC特有の処理
      共通の後処理
End Sub
   :

これで、何も困らないという場合には、それでおしまい。

しかし、異なるボタンが10個も20個もあって、「共通の前処理」「共通の後処理」が何度も何度も出てくる、というのがどうも美しくない、と思って考えたのが、次の方法です。
ほとんどすべてのボタンに入口となるひとつのマクロを登録して、出口もひとつにしてしまう、というものです。もう、どのボタンにどのマクロを登録したのか、悩まなくてすみます。それに、やたらとたくさんの Public な関数の顔出しを見ることもなくてすみます。

こんな感じにすれば整理しやすい

Public Sub ButtonProc()
      'ボタンの属性を分析
      '共通の前処理
      'そのボタン特有の処理(A~C…)
      '共通の後処理
End Sub
Private Sub ボタンA特有の処理()
      'ボタンA特有の処理
End Sub
Private Sub ボタンB特有の処理()
      'ボタンB特有の処理
End Sub
Private Sub ボタンC特有の処理()
      'ボタンC特有の処理
End Sub
   :

すべてのボタンのマクロには「ButtonProc」が登録されています。
「ボタンの属性を分析」のところで、呼んだボタンの名称(Shape名)からどの「特有の処理」を選択するのかを取得し、「そのボタン特有の処理」では該当するサブルーチンを呼ぶ、という仕掛けです。ボタンの名称のつけかたに一定のルールを作っておけば、前処理や後処理に渡す引数なども含ませることができるので、いろいろな処置をボタン名をいじるだけで変更することができます。

たとえば、こんなルール。書いてある文字は、それぞれのボタンの名称(Shape名)です。

それぞれのボタンのマクロには、"ButtonProc" が登録されています。ボタンの名前は "○○-x" というような名前になっていますが、○○はそのボタン特有の処理を行うためのサブルーチン名の「語幹」(実際のサブルーチン名は語尾に"Proc"を追加して "○○Proc" とします)、x は 0 または 1 で、0 の場合は後処理を行わない、1 だと行う、としておきます。そうすると、ButtonProc の構成は、次のようになるでしょう。

ButtonProc

Public AcceptButtonClick As Boolean
Private Const slipGap = 2

'----- ボタンに登録するマクロ
Public Sub ButtonProc()
Dim a, buttonName, procName, doSw
      'ボタン名の解釈
      buttonName = Application.Caller
      a = Split(buttonName, "-")
      procName = a(0): doSw = a(1)
      '実行
      If Not preProc(buttonName) Then Exit Sub
      Application.Run "'" & ThisWorkbook.FullName & "'!" & procName & "Proc"
      postProc buttonName, doSw
End Sub

'----- 共通の前処理
Private Function preProc(buttonName)
      preProc = False
      If Not AcceptButtonClick Then Exit Function

      Application.EnableEvents = False
      AcceptButtonClick = False
      With ActiveSheet.Shapes(buttonName)
            .left = .left + slipGap
            .top = .top + slipGap
      End With
      
      Application.Wait [now()] + 0.02 / 86400
      ButtonChick
      preProc = True
End Function
'----- 共通の後処理
Private Sub postProc(buttonName, doSw)
      With ActiveSheet.Shapes(buttonName)
            .left = .left - slipGap
            .top = .top - slipGap
      End With
      AcceptButtonClick = True
      Application.EnableEvents = True
      
      If doSw = 0 Then Exit Sub
      ActiveSheet.Range("B2") = buttonName & " の仕事終了"
End Sub

'----- ボタンで実行するマクロの本体
Private Sub GetBookProc()
      MsgBox "GetBookProc の本体実行", vbOKOnly, ""
End Sub
Private Sub GetAiProc()
      MsgBox "GetAiProc の本体実行", vbOKOnly, ""
End Sub
Private Sub TryPrintProc()
      MsgBox "TryPrintProc の本体実行", vbOKOnly, ""
End Sub
        :
        :

それぞれの個別のマクロがすっきりするのに加えて、前処理と後処理が並記されるので、見通しがよくなります。
ここでは、共通の前処理はボタン処理を受け付けるかどうかのチェック、あらためて他のイベントの禁止措置、当該ボタン画像をちょっとずらす、ポチッ音を鳴らす、ということで、後処理はそれらを元に戻す、というだけです。ボタン名が"〇〇-1"となっていると、仕事の終了を表示します。