For Eachでセルを処理する
VBAのFor文は2種類あります。
- 開始と終了のインデックスを指定する
For カウンタ変数 = 開始 To 終了
- 配列やリストの先頭~末尾までを処理する
For Each 要素の変数 In 配列やリスト
インデックスを指定する必要がなければ、記述が簡潔になるので上記2.を推奨したいところです。
そのFor Each
ループですが、ワークシート上のセルを処理する場合、知らないと「なんで??」となる挙動があるのでまとめておきます。特に他言語やジャグ配列に馴染みがある人。
…行単位や列単位でループすることのほうが多いと思うので、あんまり必要のない知識かもしれませんが。
サンプルデータ
Range("A1:C2")
に対してデータを設定しています。
サンプルプログラム
サンプルデータの範囲のセルの値をFor Each
で順番に「/」区切りで結合していき、Debug.print
で結果をイミディエイトウィンドウに出力します。
' 処理対象の指定方法によってFor Eachの順番が違う
Sub TestOrder()
Dim area As Range: Set area = Range("A1:C2")
' Rangeで読み込み
Dim str As String: str = ""
Dim cell As Variant: For Each cell In area
str = str & IIf(Len(str) > 0, "/", "") & cell.Value
Next
Debug.Print " Range: " & str
' 二次元配列で読み込み
str = ""
For Each cell In area.Value
str = str & IIf(Len(str) > 0, "/", "") & cell
Next
Debug.Print " Multidimentional: " & str
' 配列をtranspose関数で行列を入れ替えるとRangeと同じ順番に
str = ""
For Each cell In WorksheetFunction.Transpose(area.Value)
str = str & IIf(Len(str) > 0, "/", "") & cell
Next
Debug.Print "Transpose multidimentional: " & str
End Sub
実行結果
For Each
でそのままRange
で指定した場合とサンプルデータの範囲を配列に格納してから指定した場合で、処理するセルの順番が変わります。
Rangeだと行単位、配列に格納した場合だと列単位になります。最初はびっくりする。
Range: 陸/海/空/梅/竹/松
Multidimentional: 陸/梅/海/竹/空/松
Transpose multidimentional: 陸/海/空/梅/竹/松
配列に格納した場合は、(1, 1)
、(1, 2)
、(1, 3)
…ではなく、(1, 1)
、(2, 1)
、(1, 2)
…の順番で処理してるなんて、まさか夢にも思わなかったよ。
2次元配列はFor Each
で処理すると、1次元目×2次元目ではなく2次元目×1次元目なんですな…
多分3次元以上の配列も次元の大きい順で処理すると思う。確認してないけど。
配列の中身
セルをVariant型の配列に格納した場合、以下のように格納されます。
1次元目が行、2次元目が列の多次元配列です。
上記の配列に対して縦横を変換するワークシート関数のTranspose
を使うと以下のように変換されます。
2次元目×1次元目で処理するので、Rangeをそのまま指定したときと同じ順番になります。
感覚的には行×列の方がしっくりきます…
Cellsプロパティの引数もCells(行, 列)
だし、第1引数の行×第2引数の列の二重ループのが思い浮かびやすくないですか?
(他言語で)ジャグ配列を使い慣れていると、外側の小さい次元→内側の大きい次元のイメージになる気がするので、覚えとくといいかもです。
JavaでPOI使ってExcel弄るときなんか、getRowはあってもgetColumn的なのがないので、基本が行ベースの処理になりますし。(最新版は知らないけど)
VBAの多次元配列はFor Each
使わずにFor
で2重ループするほうがわかりやすいかも。