和風スパゲティのレシピ

日本語でコーディングするExcelVBA

セル範囲.Cells(R, C)の仕様と使い方

Worksheets("○○").Cells(,)

でおなじみのCellsプロパティですが、
実はセル範囲.Cells(行, 列)の形で実行することも可能です。

Range("B2:D4").Cells(2, 2) ' ← C3セルを取得します。

 
セル範囲.Cellsのコードはこのサンプルを見てわかる通り、
「セル範囲の先頭セルをCells(1, 1)とした位置指定」
という仕様で単独セルを取得することができます。

「Cells(2, 2)とOffset(1, 1)が同じ動き」と思ってもいいかもしれませんね。


この記述はなかなか奥が深いので、
今回はその仕様の詳細と使い方を解説していこうと思います。

セル範囲.Cells(R, C)

基本コードと便利な使い方

まずは冒頭で紹介したこちらから。

先ほどの通りセル範囲から実行したCellsプロパティは、
「セル範囲の先頭セルをCells(1, 1)とした位置指定」
という仕様で単独セルのRangeオブジェクトを取得できます。

Range("B2:D4").Cells(2, 2) ' ← C3セルを取得します。

 
この記述の最大のメリットは「シート上の位置に関わらずコードが書ける」ことで、
この画像の売上列を計算する際に、こんなコードを書くことができます。
Cellsを活用する表

Dim 表エリア As Range
Set 表エリア = Range("B4:E9")

Dim R As Long
For R = 1 To 表エリア.Rows.Count

    表エリア.Cells(R, 4) = 表エリア.Cells(R, 2) * 表エリア.Cells(R, 3)

Next

行のFor文が「For R = 4 To 最終行」のようないつもの記述ではなく、
「For R = 1 To エリアの行数」と書けているのが面白いですね。


この記述が真価を発揮するのは「同じ形の表がいくつもある場合」で、
例えばこんなシートを処理する際に整理されたコードで書くことができます。
同じ表が並んだコード

Dim 各表のアドレス
For Each 各表のアドレス In Array("B4:E9", "G4:J9", "B13:E18", "G13:J18")

    Dim 表エリア As Range
    Set 表エリア = Range(各表のアドレス)
    
    Dim R As Long
    For R = 1 To 表エリア.Rows.Count
    
        表エリア.Cells(R, 4) = 表エリア.Cells(R, 2) * 表エリア.Cells(R, 3)
    
    Next

Next

 
もし各表のアドレスも規則正しく並んでいるならそこもFor文で書けますし、
カレンダー形式になっている表などでも活躍してくれます。

セル範囲からCellsプロパティを実行する場合は、
この使い方が最も便利と思いますのでまずはこれを覚えておきましょう。

セル範囲をオーバーする記述をした場合

セル範囲.Cells(R, C)という書き方をする際、
行数Rと列数Cは元のセル範囲をオーバーしていても問題ありません。

Range("B2:D4").Cells(5, 5) ' ← F6セルを取得します。

この仕様は後述の「単独セル.Cells(R, C)」という記述に活きてくる仕様です。

単独セル.Cells(R, C)

先ほどセル範囲.Cells(R, C)は元のセル範囲をオーバーしても動くと説明しましたが、
この延長で、単独セル.Cells(R, C)という記述でも同じセルが取得できます。

Range("B2").Cells(5, 5) ' ← F6セルを取得します。

つまり、
「セル範囲.Cells(R, C)」と「セル範囲左上セル.Cells(R, C)」は同じ挙動
ということですね。


これを利用すると、先ほどの「同じ形の表を処理するループ」は、
以下のようにかなりすっきりしたコードで書くことができます。
同じ表が並んだコード

Dim 先頭セルアドレス
For Each 先頭セルアドレス In Array("B4", "G4", "B13", "G13")

    Dim 先頭セル As Range
    Set 先頭セル = Range(先頭セルアドレス)
    
    Dim R As Long
    For R = 1 To 先頭セル.Rows.Count
    
        先頭セル.Cells(R, 4) = 先頭セル.Cells(R, 2) * 先頭セル.Cells(R, 3)
    
    Next

Next

この記述ができると劇的にコードが簡略化されるマクロもありますので、
この仕様はぜひとも知っておきましょう。

セル範囲.Range

セル範囲.Cellsと同様に、セル範囲.RangeもOffsetのように使うことができます。

Range("B2:D4").Range("B2") ' ← C3セルを取得します。
Range("B2").Range("B2") ' ← C3セルを取得します。
Range("B2").Range("B2").Range("B2") ' ← D4セルを取得します。

といってもこちらはCellsと違ってめったに使うことはないですね。

どちらかというと「これでも動いてしまう」ことを知っておき、
間違って書いてしまったときに気づけるようにしておくのが大事かもしれません。

セル範囲.Cells(i)

Cellsプロパティには引数を1つだけ渡すこともでき、
この時は「セル範囲から順番に1セルずつ取得」挙動となります。

Dim セル As Range
Dim i As Long
For i = 1 To 12
    
    Range("B2:D4").Cells(i) = i
    
Next

実行結果
Cell(i)でループしたコード

実行結果を見てわかる通り、

  • まずは→方向に向かって取得
  • 端に着いたら↓に1つ進んで再度→方向へ
  • 最大セル数を超えても同じ折り返しパターンで↓へ進んでいく

という挙動で取得ができます。


この記法を活用したコードとしては、主に以下の3つが挙げられます。

先頭(左上)セルの取得

セル範囲.Cells(1)という記述でその範囲の先頭のセルを取得できます。

Range("B2:D4").Cells(1) ' ← B2セルを取得します。

簡単で便利な記述のため、Cells(i)の中では一番利用するコードかなと思います。

最終セルの取得

セル範囲.Cells(セル範囲.Cells.Count)という記述で、
そのセル範囲の最終セルを取得できます。

Dim セル範囲  As Range
Set セル範囲 = Range("B2:D4")

セル範囲.Cells(セル範囲.Cells.Count) ' ← D4セルを取得します。

例えば「.Row」とすれば、そのセル範囲の最終行番号を取得できます。

行データ内の各セルの取得

データをループする際、行番号ではなく行ごとのセル範囲をループし、
中身のデータをCells(i)形式で指定する方法があります。

Dim 表エリア As Range
Set 表エリア = Range("B4:E9")

Dim row売上データ As Range
For Each row売上データ In 表エリア.Rows

    row売上データ.Cells(4) = row売上データ.Cells(2) * row売上データ.Cells(3)

Next

なかなか使う場面は限られると思いますが、
上手くマッチすると劇的にコードを整理できることもあります。

この書き方ができることは覚えておくとよいかもしれません。

複数エリアをもつRangeオブジェクトは注意

複数のエリアを持つRangeオブジェクトには注意が必要です。

複数エリアRangeから実行するCellsプロパティは、
第1エリアから実行したとみなされるためです。

Dim セル As Range
Dim i As Long
For i = 1 To 12
    
    Range("B2:D4,E6:G8").Cells(i) = i
    
Next

実行結果
複数エリアをCell(i)でループしたコード

実行結果の通り、
セル範囲.Cells(i)はそのセル範囲の第1エリアだけを対象に動きます。


ただしこれは「Cellsに引数を渡したとき」の挙動であり、
引数を渡さないCellsプロパティは全エリアを対象としてくれるため、
複数エリア.Cells.Countは全エリアのセル数を合計して表示してくれます。

?Range("B2:D4,E6:G8").Cells.Count ' ← 18

この仕様の違いは気を付けましょう。


これは「単独セルを複数持つRangeオブジェクト」でも同様です。

Dim セル As Range
Dim i As Long
For i = 1 To Range("B2,C3,D4,E5").Cells.Count ' ← ちゃんと4は返る
    
    Range("B2,C3,D4,E5").Cells(i) = i ' ← これはB2セルしか見られていない
    
Next

実行結果
Cells(i)の実行結果


その他、.Rowsや.Columnsも含め、
第1エリアしか見ないプロパティは数多くあります。


Rangeオブジェクトを扱う際は、エリア数に注意し、
Areasプロパティを使って各エリアごとに処理を行ってください。

おまけ:Cellsの省略とItemプロパティについて

今回のコードは、実は以下のように書いても動きます。

' すべてC3セルを取得します
Range("B2:D4").Cells(2, 2)
Range("B2:D4").Item(2, 2)
Range("B2:D4")(2, 2)

' すべてB3セルを取得します
Range("B2:B10").Cells(2)
Range("B2:B10").Item(2)
Range("B2:B10")(2)

Item(R, C)もかなり珍しい書き方だと思いますし、
Range("B2:D4")(2, 2)のように()()を繋げても動くのは、
初めて見る方もいらっしゃるかもしれません。


しかしながら、例えばB2:B10がColumnsプロパティで取得されている場合は、
以下のようにCellsの記述でないと正しく(意図する通りに)動きません。

Dim セル範囲 As Range
Set セル範囲 = Range("A2:E10").Columns(2) ' ← B2:B10

セル範囲.Cells(2) ' B3
セル範囲.Item(2) ' C2:C10
セル範囲(2) ' C2:C10

セル範囲.Cells(2, 1) ' B3
セル範囲.Item(2, 1) ' アプリケーションまたはオブジェクト定義のエラー
セル範囲(2, 1) ' アプリケーションまたはオブジェクト定義のエラー

よって、とりあえず「Cellsは省略しない」と覚えておけばOKです。

この仕様を詳しく知りたい方は、以下の記事を読んでみてください。

準備中