VBA マクロ作成のヒント

VBA macro 作成の簡単なヒントを作成します。例題に示したのは英文として読んでも理解できそうな表現なので取り上げました。a1 〜 e50 のセル範囲で void の語を含む全てのセルを フォントの色(赤)、斜体、中央揃え に設定します。macro を使わない方法を検索セル全部選択とデータ書式の一挙設定で扱いました。

図1

図2

例題

Set myRange = Range("a1:e50"): k = 0 
With myRange
  Set c = .Find("void", LookAt:=xlPart)
   If Not c Is Nothing Then
     first = c.Address
      Do
       k = k + 1
       c.HorizontalAlignment = xlCenter
         With c.Font
          .Color = vbRed
          .Italic = True
         End With
          Set c = .FindNext(c)
      Loop While Not c Is Nothing And c.Address <> first
   End If
End With
MsgBox k & " 個のセルを設定しました。"

変数、定数について

変数

変数には漢字なども使えます。not, is, nothing など vba で予約されている語は変数として使えません。例題では myRange, k, c, first が変数です。k = 0 は Let k = 0 ( k を 0 とせよ )の Let を省略したものです。k = k + 1 は k に 1 足した数値を k に代入せよということです。myRange は数値や文字列を格納する変数ではないので Set myRange = Range("a1:e50") のように表記します。Let と Set が相対応し Set は当然省略できません。 Evaluate メソッドを使った簡易表示で Set myRange = [a1:e50] とすることも出来ます。

(注) Set を省略して a = [a1:e50] とすれば a は次元を持った変数になってしまい、a1〜e50 のセル内容が a( , ) に代入されます。 a(3, 3) = "void" などと指定し、 [a1:e50] = a としたら c3 セルが "void" に変わります。この場合 変数 a を単独で使うことはできません。

1行目の myRange、k の変数を使わず ( つまり1行目を削除して )、With [a1:e50] 、あるいは With Range("a1:e50") から始めてもOK です。 k は当初 『 Empty 値 』 として扱われ k = k + 1 などの演算には影響されません。しかし、長いプロシージャを作成していると 1行目の記述がバグを防いだり重みをもつことが多いです。

『 検索セル全部選択と … 』 のページで扱った表部分 ( 図1 ) を選択して作業を進めたい場合は、macro 実行前に表中の任意のセルをアクティブにしておいて1行目を Set myRange = ActiveCell.CurrentRegion に変更します。 ActiveCell.CurrentRegion.Select は 通常のワークシートの表中で Ctrl + Shift + * の操作をするのと同等です。ActiveCell.CurrentRegion.Select のような簡単なステートメントの場合イミディエイトウィンドウを使うのが便利です。

プロシージャを編集するときは小文字で入力するのがよいです。自動的に大文字に変わればその変数は不適であることが分かります。反対に myRange 等の変数はわざと大文字を含めます。すると以降スペルミスをしても容易に発見できます。first は変数として使えますが second は予約されているため使えません。

定数

xlPart, xlCenter, vbRed は VBA で予約された定数で値はそれぞれ 2, -4108, 255 です。.Color = vbRed を .Color = 255 としても同じですが判読が困難になります。

入れ子構造について

例題では With … End With,  If … End If,  Do … Loop, さらに With … End With が入れ子になっているのが分かります。大きな箱の中に小さい箱があるという感じです。それぞれの箱は End ‥ ( Do では Loop、For では Next ) でふたをします。蓋が開いたままだと Error になります。他の例では Sub … End Sub,  Select Case … End Select などがあります。

条件を満たせば箱から出たい場合があります。If … Then Exit For ( For … Next の場合 ), Exit Do ( Do … Loop の場合 ), Exit Sub ( Sub … End Sub のサブルーチン ) 等を使います。

If 文は一行で記述すれば End If を付しません。次の左の例のような記述を見かけますが、2例目の方がすっきりします。あるいは右のように一行にします。長くなり見難くなる場合は 『 □_ 』( スペース + アンダースコア ) で改行したり複数のステートメントを 『 : 』 (コロン)で区切ったりします。

For i = 1 To 10      For i = 1 To 10       For i = 1 To 10
  If … Then GoTo Skip       If … Then            If … Then Else _ 
    …                   Else                  … : … : …
   …                        …                Next
    …                        … : …
Skip:                      End If
Next                     Next
   

例題について

1行目は前述の通り変数への代入です。

With

With を使わないで3行目を Set c = myRange.Find("void" … とし 13行目を Set c = myRange.FindNext(c) としてもかまいません。With を使うと処理速度というよりも記述の簡素化( 緑色部分が省略できること )がメリットです。9行目で With を使わないと c.Font.Color = vbRed, c.Font.Italic = True のように表記します。

Find, FindNext

Set c = .Find("void", LookAt:=xlPart) は Excel シート上で Ctrl + F の操作で 検索を行う場面に相当します。( 図2参照 ) 検索でヒットすると当該セルが選択されます。『 次を検索(F) 』 を繰り返すと最初に見つかったセルに戻ります。これはいつまでも続きます。 LookAt:=xlPart は 図2の 『 オプション(T) 』 をクリックして 『 セル内容が完全に同一であるものを検索する(O) 』 にチェックが入っていない状態で検索しています。これにチェックが入った状態で検索するには LookAt:=xlWhole に変えます。

最初にセルが見つからなければ 変数 c に Nothing が代入されます。If Not c Is Nothing ( c が Nothing でなければ … 見つかれば ) 最初のセル番地、例えば c4 セルなら first に 『 $C$4 』 が代入されます。

Find メソッドの書式は次の通りです。

expression.Find(What, After, LookIn, LookAt, SearchOrder, SearchDirection, MatchCase, MatchByte, SearchFormat)

このうち What 以外の引数は全て省略可能です。例題の場合 LookAt の部分だけなので Set c = .Find("void", , , xlPart) あるいは Set c = .Find("void", , , 2) と記述することもできますが判読しづらくなります。このように必要な引数を指定する場合は LookAt := xlPart のように 『 := 』 ( コロン、イコール ) 表記します。引数を指定する場合は順は不同で Set c = .Find(Lookat:=xlPart, what:="void") としてもかまいません。何れも 『 , 』( コンマ ) で区切ります。引数名 what などは小文字でも OK ですが定数の xlwhole 等とは異なり自動的に大文字化はされません。

FindNext(c) は Find で見つかったセル c の次のセルを検索条件を変えずに探します。ここで指定した検索条件は保存され、macro を終了してシート上で検索する場合にも反映されます。

検索 ( Find ) と対になるのは 置換 ( Replace ) ですが2つの方法があります。セルの値( c.Value ) や 文字列 sを c.Value = Replace(c.Value, "void", "余白") のようにして関数で置換する方法、もう1つは myRange などの範囲に対して myRange.Replace What:="void", Replacement:="余白" のようにして範囲内のすべてが置換されます( Replace メソッド)。

一方、Word では事情は全く異なり、実務上 VBA を使うより、文書編集画面で Ctrl + H (『 置換 』)ダイアログで 「 オプション 」 → 「 ワイルドカードを使用する(U) 」 にチェックすることにより実に多様な置換を実現することができます。(詳細略)

Do, Loop While

Find メソッドでセルが見つかれば Do … Loop 間を実行します。前述した通り検索は最初に見つかったセルに戻り延々と続きます。従って最初のセルアドレスになれば終わるように While Not c Is Nothing And c.Address <> first ( セル c が見つかり、最初のセルアドレスにならない限り ) と条件を付けています。 Do … Loop を使うときは無限ループに入らないように注意する必要があります。無限ループに陥った場合は Ctrl + Break でストップさせます。

Do … Loop には、While 以外に Until を使い、Do Until condition のように先頭に記載する、あるいは condition を使わない ( 余録参照 ) など5様の使い方があります。注意したいのは終わりには終わりが含まれているのか、あるいは重複していないかなどの検討です。例題では変数 k を使い実際の個数を確かめています。

余録

VBA 画面の表示法などはmacro およびイミディエイトに記しました。

For … Next、Do … Loop の使い方の例を示します。

For Each c In myRange
   … 
Next

上の例は myRange に含まれるセル c それぞれに対して実行されます。この場合、変数 c に対して Set で指定する必要がありません。例題でも扱ったように変数 c はセルとしてよく使われますが決まっているわけではありません。MsgBox c とすればセルの値が表示されますが c は数値でも文字列でもありません。値を指定するときは c.Value を使うのが無難です。

上の例では For Each c In myRange.Cells また例題の2行目は With myRange.Cells の .Cells が省略されていると解釈します。Cells は複数形で書かれており Cell のコレクションです。 myRange 範囲のうち3行目のセル全体を対象にしたい場合に For Each c In myRange.Rows(3) としても c はセルと認識されません。For Each c In myRange.Rows(3).Cells とします。Rows もコレクションで () 内のインデックスが存在しない場合は Error になります。

複数のワークシートがありシート2を開くには Worksheets("Sheet2").Activate などと表記します。Worksheets(2).Activate とも書けますが、シートの削除や名前設定をしたりするとどのシートか分からなくなります。名前がついていれば Worksheets("3月").Activate とし、最初のシート名 Sheet2 と同じように引用符で示します。範囲を指定する Range も範囲名があれば Range("得点") 等とします。
 Cells と Range の違いは次の例を参照してください。いずれもアクティブセルの 9×9 の範囲に九九表を作ります。

For y = 1 To 9: For x = 1 To 9
 ActiveCell.Cells(y, x) = x * y
Next: Next

For Each c In ActiveCell.Range("a1:i9")
 c.Value = (c.Column - ActiveCell.Column + 1) * (c.Row - ActiveCell.Row + 1)
Next

(注)上の ActiveCell.Range("a1:i9") では気になることがあります。i 列が9番目の列であることには何の論理性もないことです。Set a = ActiveCell および Set b = ActiveCell.Cells(9, 9) のように始点と終点のセルを指定して Range(a, b) と表示すると ActiveCell.Range("a1:i9") と同じになります。a, b の変数を使わずに Range(ActiveCell, ActiveCell.Cells(9, 9)) としても構いません。なお a, b の始点、終点は単一のセルでなく範囲を指定してもOKです。

n = 10
For i = 1 to n - 2
 For j = i + 1 to n - 1
  For k = j + 1 to n
   … …
   MsgBox i & "," & j & "," & k 
  Next k
 Next j
Next i

Next k などの k は省略した方がよいです。順を間違えると入れ子が崩れ Error になります。上の例は10個のデータが配列変数に格納されていて、3個ずつをすべての組み合わせで取り出したい場合の一方法です。プロシージャの冒頭で Dim a(10) と宣言したうえで 上記の … … の部分で a1 = a(i) : a2 = a(j) : a3 = a(k) 等と代入し、以下 a1, a2, a3 を比較検討するステートメントを記述します。上記の場合 MsgBox は何回表示されるでしょうか

次の例は 100 点満点のテストの順位を求める一方法です。ワークシート上ならば rank 関数を使って容易に求めることができます。1行目で配列変数に余裕をもって 10 で宣言しています。p(1)〜p(5) は 1〜5 の人の得点、配列変数は 0 も使えるので p(0) に満点 100 を使い、rnk(0) を順位の変数に当てています。rnk(1)〜rnk(5) にはそれぞれの人の順位が求まります。

n は人数、k は順位の決まった人数を数えます。

Dim p(10), rnk(10)

p(1) =78:p(2) =89:p(3) =76:p(4) = 89:p(5) = 98  '得点の入力
p(0) =100: n = 5: rnk(0) = 1: k = 0      '初期設定
Do
For i = 1 To n
 If p(i) = p(0) Then rnk(i) = rnk(0): k = k + 1
Next
 If k = n Then Exit Do
  p(0) = p(0) - 1         '最初100点の人を探し、次に99点を探す。
    rnk(0) = k + 1     '同点の人もいるので順位の変数を更新します。
 j = j + 1: If j > 100 Then Exit Do          '無限ループを回避
Loop

得点の入力は面倒なので p = Array(100, 78, 89, 76, 89, 98) とする方がすっきりしますが Word では使えません。終わりから2行目は誤入力による無限ループを回避するためのものです。誤入力で 125点とした人は順位の対象外となりループから出ても順位は付されません。

b = Split(" 高橋 佐藤 羽生 谷川 三浦") などと名前のデータを追加し、 例えば i = 2 と指定した上で 『 MsgBox b(i) & "さんの得点は " & p(i) & " 点で、順位は " & rnk(i) & " 位です。" 』 と記載すれば 佐藤さんの得点と順位が表示されます。コンマ区切りのデータの場合は b = Split(",高橋,佐藤,羽生,谷川,三浦", ",") とします。

この例のように、セル範囲を指定しない記述は Word でも有効となります。順位の結果を Word の表中はもちろんのこと、段落中に反映させることもできます。データを VBA 表記で入力せずに word の表中、段落中からも取り込めます。

(参考) Function について

Sub … End Sub を使ってサブルーチンを作っても差し支えありませんが Function … End Function を使うと メインルーチンなどで MsgBox quad1(1, 2, 1) などとして直接関数を呼び出し結果を得ることができます。次の例は 二次方程式 ( a x2 + b x + c = 0 ) の根 を求める関数で Word で使うこともできます。

Function quad1(a, b, c)
D = b ^ 2 - 4 * a * c
If D >= 0 Then quad1 = (-b + Sqr(D)) / (2 * a) _
 Else x = -b / (2 * a): y = Sqr(-D) / (2 * a): _
 quad1 = x & " + " & y & " i"
End Function

Function quad2(a, b, c)
D = b ^ 2 - 4 * a * c
If D >= 0 Then quad2 = (-b - Sqr(D)) / (2 * a) _
 Else x = -b / (2 * a): y = Sqr(-D) / (2 * a): _
 quad2 = x & " - " & y & " i"
End Function

二次方程式は 2 つの根を持つので2種類 … ここでは quad1(a, b, c) と quad2(a, b, c) … を作る必要があります。Sub … End Sub のサブルーチンだと1つで済み記述も簡素化できます。上記では省略していますが a がゼロの場合 Error になるのでその処理も必要です。

Function を設定すると、Excel ならば ワークシート上で一般の SUM 関数などと同様に使うことができます。セル上で 『 =quad2(1,1,1) 』 と入力すると x2 + x + 1 = 0 の2番目の根 -0.5 - 0.866025403784439 i が表示されます。

Function 名として quad1, quad2 など意味不明な語句を採用しました。x1(a, b, c), x2(a, b, c) を使っても問題ありませんが変数名と紛らわしく、またワークシート上で関数として使うとセル番地と競合し Error になるなど不適切です。簡単なようで意外と悩まされるのが変数名などの命名の仕方です。

最近 VBA を使って数独の解き方, スリザーリンク解法, タイ語等タイプ入力の拙作を作りました。

このサイトで扱った項目